引言
相信大家都已经对Vue掌握的比较透彻了,在接下来的几篇文章中,我们将用Vue且使用一些插件如
Element-Plus、Pinia等去实现一个较为常用的后台管理系统,在做项目的同时会带大家回顾一些重要的知识点。在这篇文章中,我们将介绍五星路由和如何使用 Vue 和 ElementPlus 构建一个后台管理系统的登录界面。我们将重点介绍 ElementPlus 的表单组件及其在登录表单中的应用。
初始化项目
首先,我们需要创建一个新的 Vue 项目并安装 ElementPlus 和相关依赖:
npm init vite
npm install element-plus
npm install @element-plus/icons-vue
配置ElementPlus
在 main.js 中引入并配置 ElementPlus:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
// 按需引入 Element Plus 的组件
import {
ElButton,
ElForm,
ElFormItem,
ElInput,
ElCheckbox,
ElLink,
ElIcon,
ElAvatar,
ElDropdown,
ElDropdownMenu,
ElDropdownItem,
ElMenu,
ElMenuItem,
ElSubMenu
} from 'element-plus'
import 'element-plus/dist/index.css' // 引入 Element Plus 的样式
import * as ElementPlusIconsVue from '@element-plus/icons-vue' // 引入 Element Plus 图标库
// 创建 Vue 应用实例
const app = createApp(App)
// 注册按需引入的组件
app.use(createPinia())
app.use(router)
// 注册组件
app
.use(ElButton)
.use(ElForm)
.use(ElFormItem)
.use(ElInput)
.use(ElCheckbox)
.use(ElLink)
.use(ElIcon)
.use(ElAvatar)
.use(ElDropdown)
.use(ElDropdownMenu)
.use(ElDropdownItem)
.use(ElMenu)
.use(ElMenuItem)
.use(ElSubMenu)
// 注册 Element Plus 图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
// 挂载应用
app.mount('#app')
这里需要注意的是,我们不是引入整个ElementPlus库,而是按需引入 Element Plus 的组件,以避免引入整个库,从而减少打包体积,如果不按需引入,整个 Element Plus 库可能会非常庞大,这会导致页面加载速度变慢。且通过仅引入使用到的组件和图标,可以避免引入不必要的代码,从而提高应用的性能和响应速度。更多 ElementPluss 的使用可以参考一个 Vue 3 UI 框架 | Element Plus (element-plus.org)
配置路由
首先分析,该选择哪种路由,hash 兼容好但形式不好,url中带有#,一般在(用户端 + PC 产品);history 兼容性差但是和后端路由一致 ,结合项目分析,
这是一个后台管理系统,是给公司的内部员工和老板使用的,所以可以放心使用history路由。
在 router 文件夹下创建 index.js 文件,内容如下:
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { usePermissStore } from '../store/permiss'
const routes = [
{
path: '/',
redirect: '/dashboard'
},
{
path: '/',
name: 'Home',
component: Home,
children: [
{
path: '/dashboard',
name: 'dashboard',
meta: {
title: '系统首页',
permiss: '11'
},
component: () => import('../views/dashboard.vue')
},
{
path: '/system-user',
name: 'system-user',
meta: {
title: '用户管理',
permiss: '11'
},
component: () => import('../views/system-user.vue')
},
{
path: '/system-role',
name: 'system-role',
meta: {
title: '角色管理',
permiss: '12'
},
component: () => import('../views/system-role.vue')
}
]
},
{
path: '/login',
meta: {
title: '登录',
noAuth: true
},
component: () => import('../views/login.vue')
},
{
path: '/403',
meta: {
title: '403',
noAuth: true
},
component: () => import('../views/403.vue')
},
{
path: '/404',
name: '404',
meta: {
title: '404',
noAuth: true
},
component: () => import('../views/404.vue')
},
{
path: '/:path(.*)',
redirect: '/404'
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, from, next) => {
NProgress.start()
document.title = to.meta.title
const role = localStorage.getItem('ms_name')
const permissStore = usePermissStore()
if (!role && !to.meta.noAuth) {
next('/login')
} else if (typeof to.meta.permiss == 'string' && !permissStore.key.includes(to.meta.permiss)) {
next('/403')
} else {
next()
}
})
router.afterEach(() => {
NProgress.done()
})
export default router
主要路由配置
-
根路径
/被重定向到/dashboard。 -
Home组件下的子路由包括/dashboard,/system-user, 和/system-role,使用了路由懒加载来优化性能。 -
登录、403 和 404 页面路径都被配置了
meta属性以便于后续的路由守卫判断。 -
:path(.*)用于匹配所有未定义的路径,并重定向到 404 页面。
这里有些亮点:
-
路由懒加载:
- 使用动态
import()函数来实现懒加载,只有在实际访问路由时才加载对应的组件,减少初始加载时间。
- 使用动态
-
二级路由:
- 子路由配置在
Home组件下,通过嵌套路由实现页面的嵌套结构。
- 子路由配置在
-
meta 属性:
- title: 用于设置页面标题。
- noAuth: 标记无需认证的路由。
- permiss: 权限标识,用于权限控制。
-
NProgress 进度条:
- 安装依赖:
npm i NProgress - router.beforeEach: 开始显示进度条。
- router.afterEach: 隐藏进度条,路由切换完成后执行。
- 安装依赖:
这里我们设置了全局前置守卫
-
NProgress.start():开始显示进度条,提升用户体验,提示用户路由切换的进度。 -
document.title = to.meta.title:更新页面标题,基于目标路由的meta信息。 -
const role = localStorage.getItem('ms_name'):从本地存储中获取用户角色信息。通常用于检查用户是否已登录。 -
const permissStore = usePermissStore():获取权限存储,用于权限控制。 -
if (!role && !to.meta.noAuth):- 如果用户未登录且目标路由没有标记
noAuth(表示需要认证),则重定向到登录页面。
- 如果用户未登录且目标路由没有标记
-
else if (typeof to.meta.permiss === 'string' && !permissStore.key.includes(to.meta.permiss)):- 如果用户权限不足(未包含目标路由所需的
permiss),则重定向到 403 页面。
- 如果用户权限不足(未包含目标路由所需的
-
else { next() }:- 其他情况,允许路由切换。
且使用了后置全局守卫 NProgress.done() :结束进度条,表示路由切换完成。用于恢复 UI 状态,提供良好的用户体验。
权限存储
Pinia
这个项目的数据流管理我们使用的是Pinia,Pinia 是 Vue 3 的一个状态管理库,用于替代 Vuex(不了解Vuex的可以看看这篇文章浅析Vuex——快速入门复杂项目的数据流管理 - 掘金 (juejin.cn))。它比 Vuex 更加现代化和易用,设计简洁,提供了更好的 TypeScript 支持和更简单的 API。Pinia 允许你在 Vue 应用中管理全局状态,并通过 store 来集中管理应用的状态。
使用
-
defineStore用于定义 store。 -
state用于定义状态。 -
actions用于定义修改状态的方法。
想更深入了解可以查阅Pinia | The intuitive store for Vue.js (vuejs.org)
回到项目中
权限管理
在main.js中
import {createPinia} from 'pinia'
import { usePermissStore } from './store/permiss'
const permissStore = usePermissStore();
在 store 文件夹下创建 permiss.js 文件,内容如下:
import { defineStore } from 'pinia';
export const usePermissStore = defineStore('permiss', {
state: () => {
const defaultList = {
// 代表某个菜单下面的一级或二级菜单
admin: [
'0',
'1',
'11',
'12',
'13',
'2',
'21',
'22',
'23',
'24',
'25',
'26',
'27',
'28',
'29',
'291',
'292',
'3',
'31',
'32',
'33',
'34',
'4',
'41',
'42',
'5',
'7',
'6',
'61',
'62',
'63',
'64',
'65',
'66',
],
user: ['0', '1', '11', '12', '13'],
};
const username = localStorage.getItem('ms_name');
return {
key: (username == 'admin' ? defaultList.admin : defaultList.user),
defaultList
}
},
actions: {
handleSet(val) {
this.key = val
}
}
})
这里 state 定义了默认权限列表 defaultList 和当前用户的权限 key。
actions 定义了 handleSet 方法,用于设置用户权限。相比于Vuex,Pinia不用使用mutations去修改状态,而是直接通过actions简洁了许多!
创建登录界面
在 views 文件夹下创建 Login.vue 文件,内容如下:
<template>
<div class="login-bg">
<div class="login-container">
<header class="login-header">
<img src="../assets/images/logo.svg" alt="" class="logo mr10">
<div class="login-title">后台管理系统</div>
</header>
<el-form
size="large"
:model="param"
:rules="rules"
ref="login"
>
<el-form-item label="用户名" prop="username">
<el-input
v-model="param.username"
placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="param.password"
type="password"
placeholder="请输入密码"></el-input>
</el-form-item>
<div class="pwd-tips">
<el-checkbox class="pwd-checkbox"
v-model="checked"
label="记住密码"
/>
<el-link type="primary">忘记密码</el-link>
</div>
<el-button
class="login-btn"
type="primary"
size="large"
@click="submitForm(login)"
>登入</el-button>
<p class="login-tips">Tips:用户名admin 密码123456</p>
<p class="login-text">
没有账号? <el-link type="primary">立即注册</el-link>
</p>
</el-form>
</div>
</div>
</template>
<script setup>
import { ElMessage } from 'element-plus';
import { reactive,ref } from 'vue';
import { useRouter } from 'vue-router';
import { usePermissStore } from '../store/permiss';
const router = useRouter();
const permissStore = usePermissStore();
const login = ref(null) // 绑定form 元素
const param = reactive({
username:'',
password:''
})
const rules = {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur'
}
],
password:[
{
required: true,
message: '请输入密码',
trigger: 'blur'
},
{
min:6,
messages: '密码长度不低于6位',
trigger: 'blur'
}
]
}
const checked = ref(false)
const submitForm = (formEl) => {
console.log(formEl);
formEl.validate((valid) =>{
if(valid){
ElMessage.success('登入成功')
localStorage.setItem('ms_name',param.username)
const keys = permissStore.defaultList[param.username];
permissStore.handleSet(keys);
router.push('/');
// console.log('表单验证成功')
}else{
ElMessage.error('登入失败')
// console.log('表单验证失败');
}
})
}
</script>
<style scoped>
.login-bg {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
}
.login-header {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40px;
}
.logo {
width: 35px;
}
.login-title {
font-size: 22px;
color: #333;
font-weight: bold;
}
.login-container {
width: 450px;
border-radius: 5px;
background: #fff;
padding: 40px 50px 50px;
box-sizing: border-box;
}
.pwd-tips {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
margin: -10px 0 10px;
color: #787878;
}
.pwd-checkbox {
height: auto;
}
.login-btn {
display: block;
width: 100%;
}
.login-tips {
font-size: 12px;
color: #999;
}
.login-text {
display: flex;
align-items: center;
margin-top: 20px;
font-size: 14px;
color: #787878;
}
</style>
从 <template> 开始讲解。我们用了:model="param" 绑定表单数据模型,:rules="rules" 绑定表单验证规则,ref="login" 绑定 Form 实例用于后续操作。v-model="param.username" 和 v-model="param.password" :双向绑定表单输入值到 param 对象。
<el-button> :提交按钮,点击时调用 submitForm(login) 方法。
<script> 部分。用了
const login = ref(null):用于绑定表单实例,以便后续调用validate方法。const param = reactive({ username: '', password: '' }):响应式对象,用于绑定表单数据。const rules:表单验证规则。username和password有不同的验证要求,比如必填项、密码长度不低于6位。且使用了blur鼠标离开输入框就会发生校验。formEl.validate:调用 Element Plus 表单实例的validate方法来进行表单验证。ElMessage.success和ElMessage.error:根据验证结果显示成功或失败的消息。localStorage.setItem('ms_name', param.username):将用户名存储在localStorage中,以便在后续的请求中使用。const keys = permissStore.defaultList[param.username]:获取用户的权限列表。permissStore.handleSet(keys):更新 Pinia store 中的权限列表。router.push('/'):登录成功后,重定向到首页。
由此登入界面便完成了。
总结
通过以上步骤,我们使用Vue、ElementPlus、Pinia 创建了一个基础的后台管理系统,包含登录界面,权限管理等。实现后续功能的文章还在更新中,如果觉得对你有帮助的话可以点个赞哦。