github: https://github.com/heyongsheng/hevue3-admin 码云: https://gitee.com/ihope_top/hevue3-admin 线上体验地址 https://ihope_top.gitee.io/hevue3-admin
本章知识点:
基础部分讲完了,这一章开始就不会再讲那么详细了,因为代码量很多,一点一点写估计要写好久,也没人愿意看,所以只挑重点讲。
登录页面其实没什么好说的,内容都比较简单,我也不怎么会设计,我就是用主题色简单做了几个色块,右上角加入了切换暗黑主题的按钮,个人感觉还可以,给大家看一下成品图看一下。
白天
晚上
这里背景色取的事主题色,所以你如果修改主题色的话,这里也会跟着变
界面没啥难的,这里就说几个小细节点吧。第一个是浏览器填充账号密码输入框默认背景颜色的问题,就像下面这样
这里我用的办法是给这个背景颜色变化加一个延迟,和动画过渡,只要时间设置的足够久,就相当于没有变。
input:-internal-autofill-selected {
background-color: transparent !important;
background-image: none !important;
color: rgb(255, 255, 255) !important;
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
transition-delay: 500000s;
transition: background-color 50000s ease-out;
-webkit-transition-delay: 50000s;
-webkit-transition: background-color 50000s ease-out;
}
还有一个问题就是如何禁止浏览器填充密码,比如我们在修改密码的时候,就不想让浏览器给自动填充,因为用户要修改密码,肯定是要修改不一样的,自动填充的话还得删掉重新输入。网上说的方法有设置一个隐藏的输入框之类的,我这里采取的方式是给password框添加一个readonly
属性,等用户输入完验证码之后再移除该属性,就可以成功的阻止浏览器填充密码了,当然你也可以搞个定时器移除该属性。
本套系统的登录流程其实和大多数都后台管理系统一样
另外就是本套系统的权限关联关系其实也是常规方案,就是用户关联角色,角色关联菜单。
还有就是本套系统暂未设计多级菜单,菜单层级就只有 菜单>页面>按钮 三级。
登录之后进行判断的步骤我们通常利用路由守卫来进行,我们先在根目录创建一个permission.ts
(你也可以在其他目录创建)。
import router from '@/router'
import { useStaffStore } from '@/stores/staff'
import { usePermissionStore } from '@/stores/permission'
const whiteList = ['/login', '/404'] // no redirect whitelist
// 路由前置守卫
router.beforeEach(async (to, _from, next) => {
const store = useStaffStore()
// 获取token
const token = store.token
// 如果token存在
if (token) {
// 如果是登录页
if (to.path === '/login') {
// 跳转到首页
next('/')
} else {
if (!store.staff) {
try {
await store.getStaffInfo()
} catch (error) {
store.logOut()
next('/login')
}
}
const permissionStore = usePermissionStore()
if (!permissionStore.routes || permissionStore.routes.length == 0) {
const accessRoutes = await permissionStore.getAccessRoutes()
accessRoutes.forEach((route) => {
router.addRoute(route)
})
next({ path: to.fullPath, replace: true, query: to.query })
} else {
next()
}
}
} else {
// 如果是白名单
if (whiteList.indexOf(to.path) !== -1) {
// 正常跳转
next()
} else {
// 否则跳转到登录页
next('/login')
}
}
})
一个很简单的路由跳转判断,我们这里设置了访问路由白名单,并引入了pinia的员工实例useStaffStore
用来判断用户是否存在,以及执行获取用户信息和退出登录的操作,还引入了pinia的权限实例usePermissionStore
来获取权限内的菜单,并添加到动态路由。
获取用户信息什么的就不说了,这里我们来看一下获取权限菜单的相关操作。这一部分我都放到了pinia中来处理。
首先我们来看一下后端返回的数据
这里附上我的菜单表的字段
// 类型
// 1:菜单 2:页面 3:按钮
@prop()
menuType: '1' | '2' | '3';
// 菜单名称
@prop()
title: string;
// 访问的路径
@prop()
path: string;
// 模板地址
@prop()
component: string;
// 路由名称
@prop()
name: string;
// 图标
@prop()
icon: string;
// 父级id
@prop()
parentId: string;
// 排序
@prop()
sort: string;
// 是否隐藏 0:不隐藏 1:隐藏
@prop()
hidden: '1' | '0';
// 权限标识(唯一)
@prop({ unique: true })
permission: string;
// 是否缓存 0:不缓存 1:缓存
@prop()
cache: string;
// 固定标签栏 0:不固定 1:固定
@prop()
affix: string;
// 常显菜单 0:否 1:是
@prop()
alwaysShow: string;
这里我在后端已经做好了分类,menus里返回的是菜单及页面,permissions里返回的是按钮权限列表。所以我们这里循环menus只用判断是菜单还是页面就可以了,当然,还需要转化为树形结构,因为我们后面要用来生成菜单用。其他的就是添加一些自定义属性,比如是否缓存啊,是否常显啊之类的,后面都会讲到。
import { defineStore } from 'pinia'
import { publicRouters } from '@/router'
import { getRolePermission } from '@/api/role'
import type { RouteRecordRaw } from 'vue-router'
import { arrToTree } from '@/utils/util'
import Layout from '@/layout/index.vue'
import { menuHideDic, menuCacheDic } from '@/dictionary/menu'
// 给RouteRecordRaw添加_id属性
//双星号是递归解释器遍历文件和文件夹的占位符或指令。它是一个简单的递归通配符,而只有一个星号表示全部没有递归
const modules = import.meta.glob('../views/**/**.vue')
export const usePermissionStore = defineStore('permission', {
state: () => ({
routes: [],
permissions: []
}),
actions: {
async getAccessRoutes() {
let result = (await getRolePermission()).data
let { menus, permissions } = result
//
menus.map((item: any) => {
if (!item.parentId) {
item.component = Layout
} else {
item.component = modules[`../views${item.component}.vue`]
}
item.meta = {
title: item.title,
icon: item.icon,
sort: item.sort,
cache: item.cache === menuCacheDic.trueValue,
affix: item.menuType === '2' && item.affix === menuHideDic.trueValue,
hidden: item.hidden === menuHideDic.trueValue,
alwaysShow:
item.menuType === '1' && item.alwaysShow === menuHideDic.trueValue
}
})
// 递归处理后台返回的路由数据
const routes: RouteRecordRaw[] = arrToTree({
list: menus,
id: '_id',
pid: 'parentId',
children: 'children'
})
this.routes = publicRouters.concat(routes)
this.permissions = permissions
return routes
}
}
})
这里大家可能还注意到了有一些带Dic的字段,这是我写的在前端的字典😂,后面也会讲到,这个仅供参考,毕竟大部分字典都是写在后端的,如果感兴趣,也可以先看后面关于前端字典的部分。
这里处理好了之后把权限内的菜单返回到我们刚写的路由守卫那里,然后通过addRoute
添加到路由列表就好了。
当然,我们还需要在main.ts
中引入这个文件
// main.ts
import './permission'
页面级权限我们通过动态路由来进行管理,按钮级权限我们一般都是通过v-if
或者封装一个公共方法来判断,这里我用的是自定义指令,你也可以选择你喜欢的方式。
我们来新建一个directives
目录来存放我们的自定义指令,因为我们后期也可能会开发其他的自定义指令,所以我们这里创建一个index.ts
来自动遍历我们目录内的指令文件并注册
// directives/index/ts
const directives: any = import.meta.glob('./module/*.ts', { eager: true })
export default {
install(app: any) {
Object.keys(directives).forEach((key) => {
const name = key.replace(/\.\/module\/(.*)\.ts/, '$1')
app.directive(name, directives[key].default)
})
}
}
然后我们还需要在main.ts
中进行引入
import directives from './directives'
app.use(directives)
之后我们来创建directives/modules/permission.ts
来编写我们的权限指令。
import { usePermissionStore } from '@/stores/permission'
import { useStaffStore } from '@/stores/staff'
import { superAdminRole } from '@/dictionary/staff'
export default {
mounted(el: any, binding: any) {
const permissionStore = usePermissionStore()
const staffStore = useStaffStore()
if (staffStore.staff?.role_code === superAdminRole) {
return
}
const hasPermission = permissionStore.permissions.includes(binding.value)
if (!hasPermission) {
el.remove()
}
}
}
vue3中,自定义指令的生命周期发生了变化,具体可查看官网。我们这里接收一个权限标识,首先会判断用户是否是管理员,如果是的话不做处理,如果不是的话则判断用户权限列表里是否存在该权限标识,如果不存在,则移除该按钮。这里我本来是想做成如果不存在,这不渲染该按钮的,但没找到方法,如果有会的大佬可以指教一下。
之后我们就可以在页面中使用了。
文章里有些代码我没有说是因为我也不是太精通,但都是我四处查询资料查到的用法,如果你也是小白的话,照着抄就行了,基本不会出错。
权限相关的就这些了,下一章我们来讲主页面开发,通过路由生成侧边栏菜单,以及标签栏开发。