(未完,持续更新)

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.前端打包部署服务器

(未完)