(未完,持续更新)

1.功能实现
1.1前端部分权限管理
1.1.1什么是权限管理

登录的人的角色可能是超级管理员、管理员、以及普通用户或者有更多的层级角色,拥有不同权限的用户登录系统之后看到的界面是不一样的。

若依系统中的权限分为以下几类:

1 菜单权限:用户登录系统之后能看到哪些菜单
2 按钮权限:用户在一个页面上能看到哪些按钮,比如新增、删除等按钮
3 接口权限:用户带着认证信息请求后端接口,是否有权限访问,该接口和前端页面上的按钮一一对应
4 数据权限:用户有权限访问后端某个接口,但是不同的用户相同的接口相同的入参,根据权限大小不同,返回的结果应当不一样——权限大的能够看到的数据更多。
1.1.2菜单权限

(1)获取用户角色、权限存入Vuex

permission.js文件中设置了导航守卫,每次路由发生变化的时候就会触发router.beforeEach的回调函数。当用户登录时,会调用代码如下:

//permission.js
isRelogin.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.user
            const avatar = (user.avatar == "" || user.avatar == null) ? defAva : user.avatar;

            if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
              this.roles = res.roles
              this.permissions = res.permissions
            } else {
              this.roles = ['ROLE_DEFAULT']
            }
            this.name = user.userName
            this.avatar = avatar;
            resolve(res)
          }).catch(error => {
            reject(error)
          })
        })
      },

(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 = false
          usePermissionStore().generateRoutes().then(accessRoutes => {
            // 根据roles权限生成可访问的路由表
            accessRoutes.forEach(route => {
              if (!isHttp(route.path)) {
                router.addRoute(route) // 动态添加可访问路由表
              }
            })
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
          })
//router/index.js
const 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

// 单个
<el-button v-hasPermi="['system:user:add']">存在权限字符串才能看到</el-button>
// 多个
<el-button v-hasPermi="['system:user:add', 'system:user:edit']">包含权限字符串才能看到</el-button>

(2)使用角色字符串 v-hasRole

// 单个
<el-button v-hasRole="['admin']">管理员才能看到</el-button>
// 多个
<el-button v-hasRole="['role1', 'role2']">包含角色才能看到</el-button>
提示
在某些情况下,它是不适合使用v-hasPermi,如元素标签组件,只能通过手动设置v-if。 可以使用全局权限判断函数,用法和指令 v-hasPermi 类似。
<template>
  <div class="app-container">
        <el-tabs type="border-card">
          <el-tab-pane v-if="checkPermi(['system:user:add'])" label="用户管理" name="user">用户管理</el-tab-pane>
          <el-tab-pane v-if="checkPermi(['system:user:add', 'system:user:edit'])" label="角色增改" name="menu">角色增改</el-tab-pane>
          <el-tab-pane v-if="checkPermi(['system:config:list'])" label="参数管理" name="menu1">参数管理</el-tab-pane>
          <el-tab-pane v-if="checkRole(['admin'])" label="角色管理" name="role">角色管理</el-tab-pane>
          <el-tab-pane v-if="checkRole(['admin','common'])" label="定时任务" name="job">定时任务</el-tab-pane>
        </el-tabs>
  </div>
</template>

<script>
import { checkPermi, checkRole } from "@/utils/permission"; // 权限判断函数
export default({
   methods: {
    checkPermi,
    checkRole
  }
})
</script>
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' }
}
//路由对应的view  system/config/index
export 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 文档的 <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.js    
state: () => ({
      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 
<el-menu
        :default-active="activeMenu"
        :collapse="isCollapse"
        :background-color="sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground"
        :text-color="sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor"
        mode="vertical"
      >
<script setup>
import variables from '@/assets/styles/variables.module.scss'
import useSettingsStore from '@/store/modules/settings'
const settingsStore = useSettingsStore()
</script>
1.4.3 新增样式

页面的样式和组件是一个道理,全局的 @assets/styles 放置全局公用的样式,每一个页面的样式就写在当前 views下面,请记住加上scoped 就只会作用在当前组件内了,避免造成全局的样式污染。

1.5 使用图标
1.5.1 使用方式
<!-- icon-class 为 icon 的名字; class-name 为 icon 自定义 class-->
<svg-icon icon-class="password"  class-name='custom-class' />
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: '<id>',
  label: '<label>',
  children: [
    {
      id: '<child id>',
      label: '<child 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: `<label1>`, visible: true },
  { key: 1, label: `<label2>`, visible: true },
],

Array

-

-

search

是否需要显示/隐藏搜索栏

Boolean

true/false

false

gutter

组件样式配置

Number

-

10

2.2.3 事件

事件

说明

update:showSearch

点击隐藏搜索/显示搜索按钮,返回Boolean值

queryTable

点击刷新按钮

3.前端打包部署服务器

(未完)

Logo

快速构建 Web 应用程序

更多推荐