文章目录
一.登录页的静态布局和切换
1.静态布局
先下载icon组件:pnpm i @element-plus/icons-vue
粘贴静态页面代码并按需引入:import { User, Lock } from '@element-plus/icons-vue'
<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
const isRegister = ref(true)//改成false就变成登录页了
</script>
<template>
<el-row class="login-page">
<!-- grid布局:一行分成24份,左边的图片独占12份,登录页占6份并居中 -->
<!-- el-row+el-col -->
<!-- 左:图片占12份,通过背景图片插入bg图和logo图 -->
<el-col :span="12" class="bg">
</el-col>
<!-- 右:登录页占6份并居中,offset是间隙 -->
<el-col :span="6" :offset="3" class="form">
<!-- 登录和注册的互相切换:isRegister -->
<!-- ***这里是注册页部分*** -->
<el-form ref="form" size="large" autocomplete="off" v-if="isRegister">
<!-- el-form-item:表单的一行(一个表单域==互不影响的独立盒子) -->
<el-form-item>
<h1>注册</h1>
</el-form-item>
<el-form-item>
<!-- input:表单元素--配置图标prefix-icon -->
<el-input :prefix-icon="User" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item>
<el-input
:prefix-icon="Lock"
type="password"
placeholder="请输入密码"
></el-input>
</el-form-item>
<el-form-item>
<el-input
:prefix-icon="Lock"
type="password"
placeholder="请输入再次密码"
></el-input>
</el-form-item>
<el-form-item>
<el-button class="button" type="primary" auto-insert-space>
注册
</el-button>
</el-form-item>
<el-form-item class="flex">
<el-link type="info" :underline="false" @click="isRegister = false">
← 返回
</el-link>
</el-form-item>
</el-form>
<!-- ***这里是登录页部分*** -->
<el-form ref="form" size="large" autocomplete="off" v-else>
<el-form-item>
<h1>登录</h1>
</el-form-item>
<el-form-item>
<el-input :prefix-icon="User" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item>
<el-input
name="password"
:prefix-icon="Lock"
type="password"
placeholder="请输入密码"
></el-input>
</el-form-item>
<el-form-item class="flex">
<div class="flex">
<el-checkbox>记住我</el-checkbox>
<el-link type="primary" :underline="false">忘记密码?</el-link>
</div>
</el-form-item>
<el-form-item>
<el-button class="button" type="primary" auto-insert-space
>登录</el-button
>
</el-form-item>
<el-form-item class="flex">
<el-link type="info" :underline="false" @click="isRegister = true">
注册 →
</el-link>
</el-form-item>
</el-form>
</el-col>
</el-row>
</template>
<style lang="scss" scoped>
.login-page {
height: 100vh;
background-color: #fff;
.bg {
background: url('@/assets/logo2.png') no-repeat 60% center / 240px auto,
url('@/assets/logo_bg.jpg') no-repeat center / cover;
border-radius: 0 20px 20px 0;
}
.form {
display: flex;
flex-direction: column;
justify-content: center;
user-select: none;
.title {
margin: 0 auto;
}
.button {
width: 100%;
}
.flex {
width: 100%;
display: flex;
justify-content: space-between;
}
}
}
</style>
2.登录和注册的切换
const isRegister = ref(true)//改成false就变成登录页了
<!-- ***这里是注册页部分*** -->
<el-form ref="form" size="large" autocomplete="off" v-if="isRegister"></el-form>
<!-- ***这里是登录页部分*** -->
<el-form ref="form" size="large" autocomplete="off" v-else></el-form>
二.注册功能的实现:校验+注册
1.添加校验
常见的校验规则:非空,长度,正则,自定义
1.1.el-form语法介绍
el-form:用于构建表单的容器组件,支持对整个表单进行校验和布局
常用属性:
:model:绑定表单的数据对象
:rules:定义表单的校验规则
label-width:设置所有表单项的标签宽度
常用方法:
validate(callback):对整个表单进行校验
el-form-item:是el-form的子组件,包裹具体的表单项,可以单独定义校验规则,支持嵌套在el-col和el-row中
常见属性:
prop:指定表单项对应的数据字段名(需要和:model中绑定的对象保持一致)
el-row:栅格系统组件,定义一行布局el-col:略
1.2.添加校验的步骤:以username为例
// step1:准备formModel对象--代表整个用于提交的form数据对象
const formModel = ref({//提供三个和注册相关的字段(查看文档>注册>请求参数)
username: '',
password: '',
repassword: ''
})
// step2:再准备校验规则rules对象--可以对formModel的每个属性设置校验,校验规则写成数组,随时添加
const rules = {
username: [
{ required: true, message: "请输入用户名", trigger: 'blur' },//失焦时校验也可改成change
{ min: 5, max: 10, message: "用户名长度在5到10个字符", trigger: 'blur' }//长度校验
]
}
......
<!-- ***这里是注册页部分*** -->
<!-- step3:在el-form中绑定formModel和rules -->
<el-form :model="formModel" :rules="rules" ref="form" size="large" autocomplete="off" v-if="isRegister">
<!-- el-form-item:表单的一行(一个表单域==互不影响的独立盒子) -->
<el-form-item>
<h1>注册</h1>
</el-form-item>
<!-- step4:配置prop来绑定具体的校验规则,以示生效的是哪条规则 -->
<el-form-item prop="username">
<!-- input:表单元素--配置图标prefix-icon -->
<!-- step5:在与username相关的el-input中v-model绑定formModel的子属性username -->
<el-input v-model='formModel.username' :prefix-icon="User" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item>
<el-input :prefix-icon="Lock" type="password" placeholder="请输入密码"></el-input>
</el-form-item>
<el-form-item>
<el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码"></el-input>
</el-form-item>
<el-form-item>
<el-button class="button" type="primary" auto-insert-space>
注册
</el-button>
</el-form-item>
<el-form-item class="flex">
<el-link type="info" :underline="false" @click="isRegister = false">
← 返回
</el-link>
</el-form-item>
</el-form>
1.3.对password(非空+正则)和repassword(非空+正则+自定义)添加校验
password: [
{ required: true, message: "请输入密码", trigger: 'blur' },//非空
{ pattern: /^.{6,15}$/, message: "密码必须是6-15位的非空字符", trigger: 'blur' }//正则
],
repassword: [
{ required: true, message: "请再次输入密码", trigger: 'blur' },
{ pattern: /^.{6,15}$/, message: "密码必须是6-15位的非空字符", trigger: 'blur' },//正则
{
validator: (rule, value, callback) => {//自定义校验规则
if (value !== formModel.value.password) {
callback(new Error('两次输入密码不一致!'))
} else {
callback()
}//回调函数,校验通过就什么都不做(callback())
}, trigger: 'blur'
}
]
<!-- 密码 -->
<el-form-item prop="password">
<el-input v-model="formModel.password" :prefix-icon="Lock" type="password" placeholder="请输入密码"></el-input>
</el-form-item>
<!-- 确认密码 -->
<el-form-item prop="repassword">
<el-input v-model="formModel.repassword" :prefix-icon="Lock" type="password" placeholder="请输入再次密码"></el-input>
</el-form-item>
踩坑:遇到了正则校验不通过的问题–语法错误(引号包裹问题)
当正则表达式被写成字符串形式 '/^.{6,15}$/' 时,实际会被解析为字符串而非正则对象。
在 JavaScript 中,引号包裹的正则表达式会被当作普通字符串,导致匹配逻辑完全失效
2.注册预校验
之前的校验是每个单独的el-form-item的校验,
但是我们要求,点击注册按钮的时候也要对上面用户输入的内容进行再次校验
只有通过了注册之前的预校验,才会发起注册请求
拿到当前的form组件的实例
知识点:模板引用–ref标识的两个作用:ref写在标签上,拿到DOM对象;ref写在子组件占位符上,拿到子组件实例对象
const form = ref(null)//表单ref 用于表单校验
<el-form ref='form'></el-form>
给注册按钮绑定点击事件
<el-button @click='register' class="button" type="primary" auto-insert-space>
注册
</el-button>
const register = async () => {
// 1.校验表单
await form.value.validate()//校验通过后才执行下一步,校验不通过就阻止提交
// 2.发送请求
console.log('注册成功,下一步发送请求...')
}
3.发起注册请求
封装api

//新建api/user.js
import request from '@/utils/request'
// 注册接口
export const userRegisterService = ({ username, password, repassword }) => {
return request.post('/api/reg', { username, password, repassword })
}
页面调用
import { userRegisterService } from '@/api/user'
import { ElMessage } from 'element-plus'
const register = async () => {
// 1.校验表单
await form.value.validate()//校验通过后才执行下一步,校验不通过就阻止提交
// 2.发送请求
await userRegisterService(formModel.value)//发送请求:调用注册接口并传入formModel.value作为参数
ElMessage.success('注册成功')
isRegister.value = false//注册成功后自动切换到登录页
}
优化:在eslint.config.js中声明全局变量名,就不用对常用的element-plus组件按需导入了
//eslint.config.js
globals: {
ElMessage: 'readonly',
ElMessageBox: 'readonly',
ElLoading: 'readonly'
}
三.登录功能的实现:校验+登录+提交+pinia中存token
1.登录校验
直接复用注册校验的规则
<!-- ***这里是登录页部分*** -->
<el-form :model="formModel" :rules="rules" ref="form" size="large" autocomplete="off" v-else>
......
<el-form-item prop="username">
<el-input v-model="formModel.username" :prefix-icon="User" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="formModel.password" name="password" :prefix-icon="Lock" type="password"
placeholder="请输入密码"></el-input>
</el-form-item>
......
</el-form>
优化:在切换注册/登录的过程中,用户输入的信息不应该保留
思路:在切换的时候(isRegister值更新),重置输入框
import { watch } from 'vue'
// 监听isRegister,切换时要重置表单
watch(() => isRegister.value, () => {
formModel.value = {}//重置表单数据对象
})
2.登录前预校验
拿到当前的form组件的实例
const form = ref(null)//已存在
//下面是登录的el-form
<el-form ref='form'></el-form>
给登录按钮绑定点击事件
// 登录点击事件login
const login = async () => {
// 1.校验表单
await form.value.validate()
alert('登录成功')
// 2.发送请求
console.log("封装登录接口...");
}
3.提交登录
封装api接口

//api/user.js
// 登录接口
export const userLoginService = ({ username, password }) => {
return request.post('/api/login', { username, password })
}
页面调用
const login = async () => {
// 1.校验表单
await form.value.validate()
// 2.发送请求
const res = await userLoginService(formModel.value)
console.log(res.data.token);//查看是否成功传token
ElMessage.success('登录成功')
}
4.存token
存token的方法已存在,直接调用stores/modules/user.js中的setToken()方法即可
import { userRegisterService, userLoginService } from '@/api/user'
import { useUserStore } from '@/stores'
import { useRouter } from 'vue-router'
const router = useRouter()
const userStore = useUserStore()
const login = async () => {
// 1.校验表单:登录前预校验
await form.value.validate()
// 2.发送请求
const res = await userLoginService(formModel.value)
console.log(res.data.token);//查看是否成功传token
// 3.存储token:存到pinia并自动本地化
userStore.setToken(res.data.token)
// 4.跳转到首页(路由)
router.push('/')
ElMessage.success('登录成功')
}
四.首页的静态布局
1.了解el-menu并搭起首页架子
1. el-container--架子组件列表:看成更语义化的div
el-container 是一个基础的布局容器,用于将页面划分为不同的区域。它可以嵌套使用,并且必须包含子组件如
el-header、el-aside、el-footer 和 el-main 中的一个或多个
2.el-menu --整个菜单组件
el-menu 是一个导航菜单组件,支持水平和垂直两种模式。
通过设置 mode 属性为 horizontal 或 vertical 来指定菜单的方向。default-active 属性用于指定默认激活的菜单项
active-text-color:激活时文字颜色
:default-active:'$route.path'(重要)
配置默认的菜单项(可以理解为router-link的to)
router选项开启,el-menu-item的index就是点击跳转的路径
2.静态页面布局
<script setup>
import {
Management,
Promotion,
UserFilled,
User,
Crop,
EditPen,
SwitchButton,
CaretBottom
} from '@element-plus/icons-vue'
</script>
<template>
<el-container class="layout-container">
<el-aside width="200px">
<div class="el-aside__logo"></div>
<el-menu active-text-color="#ffd04b" background-color="#232323" :default-active="$route.path" text-color="#fff"
router>
<el-menu-item index="/article/channel">
<el-icon>
<Management />
</el-icon>
<span>文章分类</span>
</el-menu-item>
<el-menu-item index="/article/manage">
<el-icon>
<Promotion />
</el-icon>
<span>文章管理</span>
</el-menu-item>
<el-sub-menu index="/user">
<!--.....多级菜单的标签,本质上是一个具名插槽title -->
<!-- 配置el-sub-menu的大标题 -->
<template #title>
<el-icon>
<UserFilled />
</el-icon>
<span>个人中心</span>
</template>
<!-- ....展开的内容,本质上是一个默认插槽.... -->
<el-menu-item index="/user/profile">
<el-icon>
<User />
</el-icon>
<span>基本资料</span>
</el-menu-item>
<el-menu-item index="/user/avatar">
<el-icon>
<Crop />
</el-icon>
<span>更换头像</span>
</el-menu-item>
<el-menu-item index="/user/password">
<el-icon>
<EditPen />
</el-icon>
<span>重置密码</span>
</el-menu-item>
</el-sub-menu>
</el-menu>
</el-aside>
<el-container>
<el-header>
<div>前端开发:<strong>张三</strong></div>
<el-dropdown placement="bottom-end">
<span class="el-dropdown__box">
<el-avatar
src="https://img2.baidu.com/it/u=2527990145,2068681281&fm=253&fmt=auto&app=138&f=PNG?w=500&h=500" />
<el-icon>
<CaretBottom />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="profile" :icon="User">基本资料</el-dropdown-item>
<el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item>
<el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item>
<el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-header>
<el-main>
<router-view></router-view>
</el-main>
<el-footer>© 2025 @前端OnTheRun. 版权所有</el-footer>
</el-container>
</el-container>
</template>
<style lang="scss" scoped>
.layout-container {
height: 100vh;
.el-aside {
background-color: #232323;
&__logo {
height: 120px;
background: url('https://img2.baidu.com/it/u=2527990145,2068681281&fm=253&fmt=auto&app=138&f=PNG?w=500&h=500') no-repeat center / 120px auto;
}
.el-menu {
border-right: none;
}
}
.el-header {
background-color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
.el-dropdown__box {
display: flex;
align-items: center;
.el-icon {
color: #999;
margin-left: 10px;
}
&:active,
&:focus {
outline: none;
}
}
}
.el-footer {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #666;
}
}
</style>
五.首页的登录访问拦截
当前没有登录,没有任何token,但也能访问到首页数据,这不合理,应该做一个拦截跳转到登录页
1.语法介绍
//router/index.js
//官网位置:VueRouter4>导航守卫>全局前置守卫
//添加访问拦截:
router.beforeEach((to,from)=>{})
放行还是拦截,是根据返回值来决定的,返回值分为几种情况:
- undefined或true:直接放行
- false:返回from的地址页面(打哪来回哪去)
- 具体路径
'/login’或路径对象{name:'login'}:拦截到对应地址
2.具体实现
//router/index.js
// 添加访问拦截
router.beforeEach((to) => {
const userStore = useUserStore()
// 如果没有token且访问非登录页,就拦截并跳转登录页
if (!userStore.token && to.path !== '/login') {
return '/login'
}
})
六.用户基本信息的获取和渲染
最终效果如下:

封装api接口

// 获取用户基本信息
export const userGetInfoService = () => {
return request.get('/my/userinfo')
}
存仓库
//stores/modules/user.js
export const useUserStore = defineStore('big-user', () => {
......
// 获取用户数据的方法
const user = ref({})//把用户信息存入user对象中
const getUser = async () => {
const res = await userGetInfoService()//请求获取用户信息
// console.log("获取到了什么:", res.data.data);
user.value = res.data.data
}
......
}
页面调用
//LayoutContainer.vue
import {useUserStore} from "@/stores"
import { onMounted } from 'vue'
const userStore=useUserStore()
//在钩子中调用
onMounted(()=>{
userStore.getUser()
})
动态渲染
<div>前端开发:<strong>{{ userStore.user.username || userStore.user.nickname }}</strong></div>
<el-dropdown placement="bottom-end">
<span class="el-dropdown__box">
<el-avatar
:src="userStore.user.user_pic || 'https://img2.baidu.com/it/u=2527990145,2068681281&fm=253&fmt=auto&app=138&f=PNG?w=500&h=500'" />
<el-icon>
<CaretBottom />
</el-icon>
</span>
七.退出功能的实现

了解el-dropdown下拉菜单组件
el-dropdown 是 Element-UI 提供的一个下拉菜单组件,用于实现点击或悬停触发的下拉菜单交互
子组件
el-dropdown-menu:用于包裹下拉菜单的内容。el-dropdown-item:表示下拉菜单中的单个选项。
如何在el-dropdown中绑定点击事件?
el-dropdown 提供了 @command 事件,当用户点击某个 el-dropdown-item 时会触发该事件,并将 command 属性值传递给事件处理函数
使用示例:
handleCommand(command) {
console.log('选择了:', command); // 输出:选项一 或 选项二
}
<el-dropdown @command="handleCommand">
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="option1">选项一</el-dropdown-item>
<el-dropdown-item command="option2">选项二</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
在首页中使用下拉菜单组件
<el-dropdown placement="bottom-end">
<span class="el-dropdown__box">
<el-avatar
:src="userStore.user.user_pic || 'https://img2.baidu.com/it/u=2527990145,2068681281&fm=253&fmt=auto&app=138&f=PNG?w=500&h=500'" />
<el-icon>
<CaretBottom />
</el-icon>
</span>
<!-- 这是折叠的下拉部分 -->
<template #dropdown>
<!-- el-dropdown-menu用于包裹下拉菜单的内容 -->
<el-dropdown-menu>
<!-- el-dropdown-item是折叠部分的选项 -->
<el-dropdown-item command="profile" :icon="User">基本资料</el-dropdown-item>
<el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item>
<el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item>
<el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
实现退出登录
- 提供退出登录后清空用户数据的方法
//stores/modules/user.js
// 退出登录,清空用户数据
const setUser = (obj) => {
user.value = obj
}
return {
setUser
}
- 使用在
@command实现在el-dropdown中绑定点击事件
<el-dropdown placement="bottom-end" @command='handleCommand'>
......
</el-dropdown>
- 在点击事件中实现退出登录的相关操作
import { useUserStore } from '@/stores'
const userStore = useUserStore()
import { useRouter } from 'vue-router'
const router = useRouter()
const handleCommand = (command) => {
if (command === 'logout') {//用户点击的是设置了command='logout'的那个item标签
userStore.removeToken()//清空token
userStore.setUser({})//清空用户信息
router.push('/login')//跳转登录页
} else {//用户点击的是文章分类,文章管理,更换头像
router.push(`/user/${command}`)//跳转到该二级路由
}
}
优化:用ElMessageBox优化退出确认框
const handleCommand = (command) => {
if (command === 'logout') {//用户点击的是设置了command='logout'的那个item标签
// 弹出确认框
ElMessageBox.confirm('确认要退出登录吗?', "温馨提示", {
type: 'warning',
comfirmButtonText: '确认',
cancalBUttonText: '取消'
})
userStore.removeToken()//清空token
userStore.setUser({})//清空用户信息
router.push('/login')//跳转登录页
} else {//用户点击的是文章分类,文章管理,更换头像
router.push(`/user/${command}`)//跳转到该二级路由
}
}
1670

被折叠的 条评论
为什么被折叠?



