(未完,持续更新)
1.功能实现
1.1前端部分权限管理
1.1.1什么是权限管理
登录的人的角色可能是超级管理员、管理员、以及普通用户或者有更多的层级角色,拥有不同权限的用户登录系统之后看到的界面是不一样的。
若依系统中的权限分为以下几类:
1 菜单权限:用户登录系统之后能看到哪些菜单
2 按钮权限:用户在一个页面上能看到哪些按钮,比如新增、删除等按钮
3 接口权限:用户带着认证信息请求后端接口,是否有权限访问,该接口和前端页面上的按钮一一对应
4 数据权限:用户有权限访问后端某个接口,但是不同的用户相同的接口相同的入参,根据权限大小不同,返回的结果应当不一样——权限大的能够看到的数据更多。
1.1.2菜单权限
(1)获取用户角色、权限存入Vuex
permission.js文件中设置了导航守卫,每次路由发生变化的时候就会触发router.beforeEach的回调函数。当用户登录时,会调用代码如下:
//permission.jsisRelogin.show = true// 判断当前用户是否已拉取完user_info信息useUserStore().getInfo().then(() => {//...}).catch(err => {useUserStore().logOut().then(() => {ElMessage.error(err)next({ path: '/' })})})
将用户的权限字符存入数据库,当用户登录后根据用户的登录信息获取用户的所有信息(包括用户的权限信息),也就是调用Vuex里user模块的actions:getInfo(),将角色信息roles、权限标识存入user模块的states。
//user.js(store)// 获取用户信息getInfo() {return new Promise((resolve, reject) => {getInfo().then(res => {console.log(res)const user = res.userconst avatar = (user.avatar == "" || user.avatar == null) " />(2)获取路由数据,动态路由遍历,验证是否具备权限后端请求可以访问的路由数据,遍历后台传来的路由字符串,转换为组件对象,根据roles权限生成可访问的路由表。
动态路由遍历,验证是否具备权限,如果具备权限也加入路由表。
//permission.js(store)generateRoutes(roles) {return new Promise(resolve => {// 向后端请求路由数据getRouters().then(res => { //...// 遍历后台传来的路由字符串,转换为组件对象const rewriteRoutes = filterAsyncRouter(rdata, false, true) //... resolve(rewriteRoutes)})})}
(3)获取路由实例,调用addRoute动态添加可访问路由表
添加路由配置具体可见:https://router.vuejs.org/zh/guide/advanced/dynamic-routing.html
// 判断当前用户是否已拉取完user_info信息之后useUserStore().getInfo().then(() => {isRelogin.show = falseusePermissionStore().generateRoutes().then(accessRoutes => {// 根据roles权限生成可访问的路由表accessRoutes.forEach(route => {if (!isHttp(route.path)) {router.addRoute(route) // 动态添加可访问路由表}})next({ ...to, replace: true }) // hack方法 确保addRoutes已完成})//router/index.jsconst router = createRouter
此时如果访问没有权限的路由地址,相当于没有和已经定义好的路由匹配的,就会重定向到404page,pathMatch的作用就是使用正则进行路径匹配,当匹配的结果没有一个和已经定义好的路由相同的话,就会进行重定向。
{path: "/:pathMatch(.*)*",component: () => import('@/views/error/404'),hidden: true},
(4)菜单组件
1.1.2(2)步骤同时也将可访问的路由存入state中 的sidebarRouters,在菜单栏组件调用实现,没有权限的自然也不会显示,并且只会显示hidden: false的组件,其他例如/401、/404则不会显示。
1.1.3 按钮权限
封装了一个指令权限,能简单快速的实现按钮级别的权限判断。v-permission
(1)使用权限字符串 v-hasPermi
// 单个存在权限字符串才能看到// 多个包含权限字符串才能看到
(2)使用角色字符串 v-hasRole
// 单个管理员才能看到// 多个包含角色才能看到
提示
在某些情况下,它是不适合使用v-hasPermi,如元素标签组件,只能通过手动设置v-if。 可以使用全局权限判断函数,用法和指令 v-hasPermi 类似。
用户管理角色增改参数管理角色管理定时任务import { checkPermi, checkRole } from "@/utils/permission"; // 权限判断函数export default({ methods: {checkPermi,checkRole}})
1.1.4 接口权限
前端鉴权只能保证可以隐藏或禁用菜单,并不能保证菜单关联的后端接口请求不被非法调用,若依支持在后端接口方法使用角色或权限字符声明权限:
以下代码表示必须拥有system:user:add和system:user:update权限才可访问
@RequiresPermissions({"system:user:add", "system:user:update"})public AjaxResult save(...){return AjaxResult.success(...);}
以下代码表示必须拥有admin角色才可访问
@RequiresRoles("admin")public AjaxResult save(...){return AjaxResult.success(...);}
若依没有为后端接口专门设计权限管理模块,它认为后端接口和菜单具有对应关系,可以直接使用菜单的角色或权限字符用于后端接口的权限声明。
1.1.5 数据权限
略
1.1.6 流程图
1.2 页签缓存
1.2.1 简介
由于目前 keep-alive 和 router-view 是强耦合的,而且查看文档和源码不难发现 keep-alive 的 include 默认是优先匹配组件的 name ,所以在编写路由 router 和路由对应的 view component 的时候一定要确保 两者的 name 是完全一致的。(切记 name 命名时候尽量保证唯一性 切记不要和某些组件的命名重复了,不然会递归引用最后内存溢出等问题
1.2.2 demo
//router 路由声明{path: 'config',component: ()=>import('@/views/system/config/index'),name: 'Config',meta: { title: '参数设置', icon: 'edit' }}
//路由对应的viewsystem/config/indexexport default {name: 'Config'}
一定要保证两者的名字相同,切记写重或者写错。默认如果不写 name 就不会被缓存。
提示
在系统管理-菜单管理-可以配置菜单页签是否缓存,默认为缓存
1.3 异常处理
1.3.1 实现
request.js 配置了vue的axios参数,对入参和出参进行判断、封装和处理,对于一些共通错误进行统一抛出,不管什么接口方法都需要用到 request.js ,之后如果对接口入参和出参有调整都需要去修改该文件。
axios配置:https://blog.csdn.net/JiangZhengyang7/article/details/129260624?spm=1001.2014.3001.5502
1.3.2 响应拦截的几种情况
登录过期,提示需要重新登录,利用vuex清除token数据
服务器报错500,提示对应异常、
接口正常执行,但返回非200状态码,提示对应异常
正常执行正常返回数据
1.4 全局样式
1.4.1 组件使用主题颜色样式实现
(1)定义一套主题色 css 变量,每种主题色对应不同的透明度
// 变浅颜色值export function getLightColor(color, level) {let rgb = hexToRgb(color)for (let i = 0; i < 3; i++) {rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i])}return rgbToHex(rgb[0], rgb[1], rgb[2])}// 变深颜色值export function getDarkColor(color, level) {let rgb = hexToRgb(color)for (let i = 0; i < 3; i++) {rgb[i] = Math.floor(rgb[i] * (1 - level))}return rgbToHex(rgb[0], rgb[1], rgb[2])}
(2)自定义html中的样式
声明一个自定义属性,属性名需要以两个减号(--)开始,属性值则可以是任何有效的 CSS 值。
//utils/theme.js// 处理主题样式export function handleThemeStyle(theme) {document.documentElement.style.setProperty('--el-color-primary', theme)for (let i = 1; i <= 9; i++) {document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, `${getLightColor(theme, i / 10)}`)}for (let i = 1; i <= 9; i++) {document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, `${getDarkColor(theme, i / 10)}`)}}
document.documentElement 是一个会返回文档对象(document)的根元素的只读属性(如 HTML 文档的 元素)。
document.documentElement.style 属性定义了 当前浏览器支持的 所有 css 属性.
以document.documentElement.style.setProperty方式设置CSS变量会添加到html中。这样就可以在 HTML 文档的任何地方访问到它了
(3)组件color、background使用自定义属性
例子:修改TreeSelect组件中的样式
:deep(.el-tree-node__content:focus) {color:var(--el-color-primary)}
备注: 自定义属性名是大小写敏感的,--my-color 和 --My-color 会被认为是两个不同的自定义属性。
使用一个局部变量时用 var() 函数包裹以表示一个合法的属性值:
js中获取:
document.documentElement.style.getPropertyValue('--el-color-primary')//#409EFF
1.4.2 主题样式自定义存储实现
(1)Vuex中定义了默认的主题样式
一开始进入后台,本地存储中不会存储有主题样式,此时会使用store定义的默认样式。
//store/setting.jsstate: () => ({title: '',theme: storageSetting.theme || '#409EFF',sideTheme: storageSetting.sideTheme || sideTheme,showSettings: showSettings, //...}),
(2)用户修改主题风格、样式存储,保存后存入本地存储localStorage中
经过对Vuex的学习我们可以知道,网页刷新时state中的数据也会丢失,因此当用户在系统中修改主题样式并保存时,就会将样式保存至本地也就是localStorage中。
function saveSetting() {proxy.$modal.loading("正在保存到本地,请稍候...");let layoutSetting = {"topNav": storeSettings.value.topNav,"tagsView": storeSettings.value.tagsView,//...};localStorage.setItem("layout-setting", JSON.stringify(layoutSetting));setTimeout(proxy.$modal.closeLoading(), 1000)}
(3)在公共组件中读取Vuex中存储的主题样式,判断
公共组件例如sidebar、TopNav,对Vuex中存储的主题样式进行判断,比如判断是否需要隐藏Navbar,或者是否是theme-dark风格之后,在全局样式variables.module.scss中获取对应不同风格的样式。当然,对于主题颜色的设置和1.4.1中的方法不一样。
需要注意的是,在 vite 创建的项目中,如果你想在 js 里引用 scss 文件,需要在后缀前加上 .module,如variables.module.scss
//sidebar/index.vue import variables from '@/assets/styles/variables.module.scss'import useSettingsStore from '@/store/modules/settings'const settingsStore = useSettingsStore()
1.4.3 新增样式
页面的样式和组件是一个道理,全局的 @assets/styles 放置全局公用的样式,每一个页面的样式就写在当前 views下面,请记住加上scoped 就只会作用在当前组件内了,避免造成全局的样式污染。
1.5 使用图标
1.5.1 使用方式
1.5.2 改变颜色
svg-icon 默认会读取其父级的 color fill: currentColor;
你可以改变父级的color或者直接改变fill的颜色即可。
提示
如果你是从 iconfont (opens new window)下载的图标,记得使用如 Sketch 等工具规范一下图标的大小问题,不然可能会造成项目中的图标大小尺寸不统一的问题。 本项目中使用的图标都是 128*128 大小规格的
2.二次封装组件
2.1 树形选择组件TreeSelect
2.1.1 示例
组件是直接使用,无需引入。
2.1.2 属性
属性名
说明
类型
可选值
默认值
objMap
配置项,对应了options中的字段名,子级字段名以及显示名称
Object
-
value: 'id', label: 'label',
children: 'children'
options
传入要的分支节点以及叶节点
[{id: '',label: '
Array
-
[]
accordion
是否自动收起
Boolean
true/false
false
value
当前双向绑定的值
String, Number
-
''
placeholder
输入框内部的文字
String
-
''
2.1.3 事件
事件
说明
update:value
树形选择组件所选值更新,返回所选id
2.2 工具栏右侧组件RightToolbar
2.2.1 示例
分别有搜索栏隐藏或显示、刷新搜索栏、table显隐列显示配置的功能。
2.2.2 属性
属性名
说明
类型
可选值
默认值
showSearch
双向绑定一个Boolean属性,点击工具栏第一个按钮则改变,将其v-show绑定搜索栏
Boolean
true/false
true
columns
传入表格数据显隐列配置,将columns[].visible绑定表格列。
[{ key: 0, label: ``, visible: true },{ key: 1, label: ``, visible: true },],
Array
-
-
search
是否需要显示/隐藏搜索栏
Boolean
true/false
false
gutter
组件样式配置
Number
-
10
2.2.3 事件
事件
说明
update:showSearch
点击隐藏搜索/显示搜索按钮,返回Boolean值
queryTable
点击刷新按钮
3.前端打包部署服务器
(未完)