路由权限(动态路由)

355 阅读11分钟

我们这个路由权限是根据RABC设计的,RABC也就是基于角色的权限分配解决法案,相当于传统法案,RABC提供了中间层Role(角色),其权限模式如下:

image-20220424114302674-16507717835361.png RABC实现用户和权限的分离,想给用户设置权限,只需要给这个用户设置角色就好了,而该角色拥有了对应权限,这样一来,权限的分配和设计就做到了很简单,高效。想要回收用户的权限时,取消对应的角色就好了。

总结起来:

简单来说就是角色就好像是社会里面的职业,这个职业给我们带来了权限。而不是我们本身拥有这些权限,这样就可以实现权限和角色的分开了。不用给社会每个人设置就是权限,给他们相当于的职业就好了

实现步骤:

1、权限页面

01、顶部布局实现

顶部布局需求分析

顶部是有一个添加一级权限的,我们之前封装了一个组件。因为左右两边但是通过具名插槽进行html的传入的,

每一个页面用到的按钮是不一样的,所以需要具名插槽

<PageTools :show-before="false"> //因为这里页面不需要这个文字,所以传入false。通过v-if不渲染出来

<template #after>

<el-button type="primary">主要按钮</el-button>

</template>

</PageTools>

0.1.png 一进去权限页面发起axios请求得到后台返回的权限数据

created() {

this.getPermissionList()

},

methods: {

async getPermissionList() {

const arr = await getPermissionList()

console.log(arr)

}

}

1655643064525.png

表格布局需求分析

这里需要用到表格,而且还有二级权限。我们这个项目是用到了饿了么开发。饿了么里面有Table(表格)有树形数据与懒加载 这个模板, 可以实现树形结构,但是 所需要数据 children ,后台返回数据是没有的。就需要我们进行数据的转换

3.png

后台告诉我们一级权限的pid是 '0', 它的子权限是上一级权限的ID来的,所以我们先定义一个函数。遍历后台传过来的数组,筛选出来一级权限(也就是pid是 '0')。

listToTreeData(arr, id) {

const list = []

arr.map(element => {

if (element.pid === id) {

list.push(element)

}

})

return list

}

4.png 这里就可以得到一级权限,之后我们要找到这些权限的子权限了。添加一级权限之前追加子权限到一级权限的children里面,我们直接调用这个函数传入这个数组以及一级权限的id就可以实现了

listToTreeData(arr, id) {

const list = []

arr.map(element => {

if (element.pid === id) {

这里用到递归就一句话

element.children = this.listToTreeData(arr, element.id)

list.push(element)

}

})

return list

}

我先把一级权限筛选出来在去,在去寻找它的子权限最后面通过追加进去之前添加子权限。让函数自己调用自己并没有一开始就想使用递归函数的

后面就是把这个过滤的数组保存起来,存储到data里面。

02、表格布局实现

饿了么组件里面表格使用

1、el-table标签是表格体 data 是要渲染的数组

2、el-table-colum 是表格里面的每一行

3、label每一行的头部名字 align 是 对齐方式 prop是要渲染数据对应对象名

4、el-table-colum里面是可以使用插槽的就可以实现表格里面添加按钮

<el-table :data="list">

<el-table-column align="center" label="名称" prop="name" />

<el-table-column align="center" label="标识" prop="code" />

<el-table-column align="center" label="描述" prop="description" />

<el-table-column align="center" label="操作">

<template>

<el-button type="text">添加</el-button>

<el-button type="text">编辑</el-button>

<el-button type="text">删除</el-button>

</template>

</el-table-column>

</el-table>

5.png 就可以实现表格一级权限的布局完成示。 当row包含children字段时,被视为树形数据。渲染树形数据时,必须要指定row-key (唯一标识)就是子权限的ID

<el-table :data="list" row-key="id"> 添加这个就可以实现表格渲染子权限了

6.png

03、删除功能的实现

7.png 需要点击删除之后出现弹框,确认是否删除?提升用户体验,这里用到的是饿了么里面的MessageBox 弹框

可以直接在JS里面触发不需要添加HTML结构的,简单弹框。

this.$confirm('此操作将永久删除该文件, 是否继续?')就可以实现上面的弹框操作 但是我们删除这个权限需要这个权限的ID就可以用的作用域插槽了

作用域插槽就需要我们在 template 设置一个变量接收插槽传过来的值。 这个变量是我们随便定义的,这个是一个对象,但是对象里面的我们需要的值是不能随便找的。需要看文档,如果找不到文档就可以打印这个值出来看看就知道是那个了。

<el-button type="text" @click="btnCancel(scoped)">删除</el-button>

btnCancel(e) {

console.log(e)

}

通过这个就可以知道是row里面id就是我们需要的值了,一般都是row返回值的

8.png 因为弹框是异步操作所以需要等待这个确认才能进行下面的删除操作。可以通过 .then 进行操作,也可以使用 async await把这个同步操作变成异步的

async btnCancel(id) {

await this.$confirm('此操作将永久删除该文件, 是否继续?')

await delPermission(id)

this.$message.success('删除成功')

this.getPermissionList()

}

然后删除成功发起请求重新获取数据,已经用户提示。到这里删除功能就可以实现了

04、添加功能的实现

9.png 我们这里用饿了么的Dialog 对话框

Dialog 对话框和MessageBox 弹框区别?

1、Dialog 对话框需要HTML的结构里面可以添加更加多的内容需要通过visible的布尔值控制显示或者隐藏

2、而MessageBox 弹框不用添加怎么多的内容、简单JS里面添加一句话就可以实现。

实现步骤:

1、设置Dialog 对话框,el-dialog 设置标题title。通过visible后面绑定布尔值就可以控制显示或者说隐藏了

对话框里面有一个事件(close)是关闭对话框之前调用的事件,通过这个可以去除form表单检验结果,已经清空form表单。

2、然后设置对话框里面的表单el-form,如果想标题和input在一行label-width就可以实现。rules是检验构造,

model是需要检验的整体

3、el-form-item 是表单,每一行label的标题。prop是绑定检验里面的对应属性

4、el-input通过v-model双向绑定

5、Switch 开关可以设置active-valueinactive-value属性,接受Boolean, StringNumber类型的值。

我们这里设置 1 是开启,0 是关闭

HTML

<el-dialog :title="formData.id? '修改':'添加'" :visible="isShowDialog" @close="btnCancel">

<!-- 表单 -->

<el-form ref="perForm" :model="formData" :rules="rules" label-width="120px">

<el-form-item label="权限名称" prop="name">

<el-input v-model="formData.name" style="width:90%" />

</el-form-item>

<el-form-item label="权限标识" prop="code">

<el-input v-model="formData.code" style="width:90%" />

</el-form-item>

<el-form-item label="权限描述">

<el-input v-model="formData.description" style="width:90%" />

</el-form-item>

<el-form-item label="开启">

<el-switch

v-model="formData.enVisible"

active-value="1"

inactive-value="0"

/>

</el-form-item>

</el-form>

<el-row slot="footer" type="flex" justify="center">

<el-col :span="6">

<el-button size="small" type="primary" @click="btnOK">确定</el-button>

<el-button size="small" @click="btnCancel">取消</el-button>

</el-col>

</el-row>

</el-dialog>

JS

formData: {

name: '', // 名称

code: '', // 标识

description: '', // 描述

type: '', // 类型 该类型 不需要显示 因为点击添加的时候已经知道类型了

pid: '', // 因为做的是树 需要知道添加到哪个节点下了

enVisible: '0' // 开启

},

rules: {

name: [{ required: true, message: '权限名称不能为空', trigger: 'blur' }],

code: [{ required: true, message: '权限标识不能为空', trigger: 'blur' }]

},

isShowDialog: false

最终效果

这里头部是蓝色是因为我们设置了,css样式

// 弹窗信息

.el-dialog__header {

background: #66b1ff;

.el-icon-close:before {

color: #fff;

}

.el-dialog__title {

color: #fff;

}

10.png

功能实现步骤:

1、功能实现分析:看接口文档

enVisiblestring非必须
namestring非必须权限名字
codestring非必须权限标识
descriptionstring非必须权限描述
typenumber非必须一级权限
pidstring非必须根据这个判断所有那个一级权限的子权限

11.png 2、代码实现:

2.1、这个表单是需要pid(决定是那个权限的子权限)以及type(代表着是子权限是2,一级权限是1)的。我们在调用这个接口函数时候就可以存进去。

2.2、我们需要检验这个表单,所以饿了么的form表单有一个方法。可以检验整一个表单(validate)也可以检验部分表单( validateField ),需要绑定这个form这里就需要用到vue的ref属性绑定这个标签。通过this.$refs.perForm.validate()就可以调用标签里面的方法,又因为这个是Promise的方法(也是因为我们要等着用户输入完了在进行检验,所以是一个异步的操作)就可以 .then也可以用Promise的语法糖 async 和 await

2.3、发起添加权限的请求,把对象传过去。

2.4、添加成功之后重新发起获取数据请求,已经提示用户的文本信息,把对话框的布尔值变成了false

HTML

这里是添加一级权限 1是(type)一级权限意思,后面的是pid 是一级权限的字符串0

<el-button type="primary" @click="addPermission(1,'0')">主要按钮</el-button>

HTML

这里是添加一级权限的子权限 2是(type)二级权限意思,后面的是pid 是一级权限的id

<el-button type="text" @click="addPermission(2,,scoped.row.id)">添加</el-button>

JS

addPermission(type, pid) {

this.isShowDialog = true

this.formData.pid = pid

this.formData.type = type

},

async btnOK() {

await this.$refs.perForm.validate()

await addPermission(this.formData)

this.getPermissionList()

this.$message.success('操作成功')

this.isShowDialog = false

},

我们这个系统只设置了两级的权限,子权限就不能在添加了。所以我们要通过 v-if 进行判断让二级权限的添加功能没有。为什么不用 v-show 呢?因为 v-show 是通过 display: none; 控制显示和隐藏的。可以在HTML上看到这个功能存在,就可以添加权限了。 v-if 是不会渲染到HTML的

这里判断是一级权限就渲染就可以了

<el-button v-if="scoped.row.type===1" type="text" @click="addPermission(2,scoped.row.id)">添加</el-button>

05、编辑功能:

12.png 用户操作:是点击这个编辑,打开对话框。进行编辑然后确认,之后编辑成功。

我们要点击之后发起请求获取这个权限的全部信息,赋值给那个对话框,在确认编辑之后在发起修改请求

HTML

<el-button type="text" @click="showEditDialog(scope.row.id)">编辑</el-button>

JS

async showEditDialog(id) {

this.formData = await getPermissionDetail(id)

this.isShowDialog = true

},

我们用的是同一个对话框,所以不用在绑定点击事件了。编辑和添加的区别就是在有没有ID的情况,有就发起编辑的请求。没有就发起添加的请求,就可以了因为其他的功能我们在添加时候就做完了。

async btnOK() {

this.$refs.perForm.validate()

if (this.formData.id) {

await updatePermission(this.formData)

} else {

await addPermission(this.formData)

}

this.$message.success('操作成功')

this.getPermissionList()

this.isShowDialog = false

},

但是这里有问题就是,我们点击编辑之后。在点击添加就会发现还有数据在哪里,我们需要的是清除这些数据,在饿了么对话框有一个 close 事件 作用是 Dialog 关闭的回调 。我们在关闭时候进行表格检验的清空以及数据的初始化就可以了

<el-dialog :title="formData.id? '修改':'添加'" :visible="isShowDialog" @close="btnCancel">

btnCancel() {

this.isShowDialog = false

this.$refs.perForm.resetFields()

this.formData = {

name: '', // 名称

code: '', // 标识

description: '', // 描述

type: '', // 类型 该类型 不需要显示 因为点击添加的时候已经知道类型了

pid: '', // 因为做的是树 需要知道添加到哪个节点下了

enVisible: '0' // 开启

}

},

并且在添加完成之后,调用这个方法就可以了

async btnOK() {

await this.$refs.perForm.validate()

if (this.formData.id) {

await updatePermission(this.formData)

} else {

await addPermission(this.formData)

}

this.getPermissionList()

this.$message.success('操作成功')

this.isShowDialog = false

this.btnCancel()

},

2、员工页面

员工页面大致会权限页面是差不多的,这里就不说了。我们说最关键的权限哪里吧

13.png

2.1员工分配角色

14.png 这里需要一个对话框,还需要多选。饿了么有一个 Dialog 对话框 以及 Checkbox 多选框。我们这里员工页面写太多东西了,所以需要搞一个组件封装起来

先做网页结构 el-dialog绑定布尔值,获取数据再看看,怎么使用 Checkbox 多选框。我们注册完了之后就可以发起请求了

\
<template>

<el-dialog :visible="isShowassignRole" @close="btnCancel">

<el-checkbox-group v-model="checkList">

<el-checkbox label="复选框 A" />

<el-checkbox label="复选框 B" />

<el-checkbox label="复选框 C" />

<el-checkbox label="禁用" disabled />

<el-checkbox label="选中且禁用" disabled />

</el-checkbox-group>

<template #footer>

<el-button type="primary" @click="btnOK">确定</el-button>

<el-button @click="btnCancel">取消</el-button>

</template>

</el-dialog>

</template>

<script>

export default {

props: {

isShowassignRole: {

type: Boolean,

default: false

}

},

data() {

return {

checkList: ['选中且禁用', '复选框 A']

}

},

methods: {

btnCancel() {

this.$parent.isShowassignRole = false

}

}

}

</script>

1、我们要获取全部的角色列表,并且还要获取这个员工绑定了什么角色。这些接口我们之前做时候封装过了,所以找到直接使用就好了。

async created() {

const data = await getTableData({ page: 1, pagesize: 9999 })

this.list = data.rows

},

2、一开始就调用获取全部角色的接口,获取全部的角色信息。员工拥有多少角色,这个就需要封装一个函数了,因为我们不知道什么时候用户调用点击角色编辑角色信息。


> <el-button type="text" size="small" @click="showRole(scope.row.id)">角色</el-button>
>
> showRole(id) {
>
> this.isShowassignRole = true
>
> 通过绑定子组件就可以调用子组件的方法,并且传ID进去获取员工个人信息,里面有这个员工绑定的角色
>
> this.$refs.assignRole.showRole(id)
>
> },

\

3、所以我们就需要封装函数,点击这个角色在调用这个函数发起请求获取信息就好了

子组件

> async showRole(id) {
>
> const data = await getUserDetailById(id)
>
> this.checkList = data.roleIds
>
> }

饿了么的多选框:

el-checkbox-group作为容器包裹el-checkbox ,el-checkbox-group的 v-model 绑定的是多选框中选中的数组

el-checkbox 我们拿到的是数组,进行 v-for 的遍历就可以渲染出来里面的数据了 label 和 el-checkbox-group的v-model数组里面1的一一对应就可以让多选框被选中了。多选框的文字通过插值表达式就可以显示了。

 <el-checkbox-group v-model="checkList">
      <el-checkbox v-for="v in list" :key="v.id" :label="v.id">{{ v.name }}</el-checkbox>
    </el-checkbox-group>

1656143549196.png

2.2 编辑角色功能实现

点击确定发起修改员工角色请求

Body

名称类型是否必须默认值备注其他信息
idstring非必须
roleIdsstring []非必须item 类型: string

看接口文档要什么我们就传什么,成功之后提示用户,并且调用清除函数

   async btnOK() {
      await assignRole({ id: this.id, roleIds: this.checkList })
      this.$message.success('操作成功')
      this.btnCancel()
    }
    
     btnCancel() {
     我们这个组件是父组件的直接子元素使用,可以直接调用父组件修改里面的值的。清空选中的数组
      this.$parent.isShowassignRole = false
      this.checkList = []
    },

3、 公司设置

这里和上面的权限没有太多区别所以直接,这里直接说角色分配权限

1656144775186.png

3.1 角色分配权限

我们点击就要获取全部的权限,根据ID发起请求员工详情就可以得到这个员工得到了什么权限。

但是这里权限是有一级的权限,二级的权限。后台返回数据并没有进行嵌套就需要我们进行数据转换成为我们需要的格式。我们就把权限页面用到的回调函数封装起来

1656145629435.png

哪里都可以用,放在一个文件里面。导出一个函数,哪里要用就找到这个文件调用这个函数就可以了

export function listToTreeData(arr, id) {
  const newArr = []
  arr.forEach(v => {
    if (v.pid === id) {
    JS里面是没有this的,所以我们这里是不用this了
      v.children = listToTreeData(arr, v.id)
      newArr.push(v)
    }
  })
  return newArr
}

import { listToTreeData } from '@/utils/index'

 async created() {
    this.companyInfo = await getCompanyInfo(this.$store.getters.companyId)
    this.loadPage()
    这个接口返回的是一个数组,我们直接传进去这个数组就可以了,在传入一个值用来判断是不是一级权限就可以了。
    这里一级权限是字符串0
    this.permlist = listToTreeData(await getPermissionList(), '0')
  },

1656145944106.png 转换之后就可以使用饿了么的树形 el-tree 了 ,

1、data是绑定要遍历的数组。里面要有 children 就可以渲染树形了。

2、props 是 指定节点标签为节点对象的某个属性值 就是要渲染出来的显示的文字

3、show-checkbox显示多选框

4、 default-checked-keys 默认勾选的节点的 key 的数组

5、默认勾选和 遍历数组之间的联系( 就是通过这个属性名判断默认勾选的数组有没有,有就选中)

6、check-change 节点选中状态发生变化时的回调 (选中就会触发这个事件)

7、树形我没有看到,点击之后可以自动添加到默认勾选数组里面的。所以需要这个事件触发之后添加进去

 <el-dialog :visible="isShowPerm" title="编辑权限" @close="btnCancelPerm">
        <el-tree
          :data="permlist"
          :props="props"
          show-checkbox
          :default-checked-keys="perm.permIds"
          node-key="id"
          @check-change="checkChange"
        />
        <template #footer>
          <el-button type="primary" @click="btnOKPerm">确定</el-button>
          <el-button @close="btnCancelPerm">取消</el-button>
        </template>
      </el-dialog>
      
        checkChange(data) {
      // 这里是要判断,数组里面有就不用添加了
      if (this.perm.permIds.indexOf(data.id) === -1) {
        this.perm.permIds.push(data.id)
      }
      if (data.children) {
        data.children.forEach(v => {
          if (this.perm.permIds.indexOf(v.id) === -1) {
            this.perm.permIds.push(v.id)
          }
        })
      }
    },

就可以发起编辑的请求

 async  btnOKPerm() {
      console.log(this.perm)
      await assignPerm(this.perm)
      用户提示
      this.$message.success('操作成功')
      调用关闭对话框的函数
      this.btnCancelPerm()
    },
    
    btnCancelPerm() {
      this.isShowPerm = false
      this.perm = {
        id: '',
        permIds: []
      }
    },

4、权限受控的主体思路

我们已经把给用户分配了角色, 给角色分配了权限,那么在用户登录获取资料的时候,会自动查出该用户拥有哪些权限,这个权限需要和我们的菜单还有路由有效结合起来 。

1656155696326.png后台返回数据

后台返回的数据是根据员工拥有多少角色,角色在拥有多少权限。这样员工间接拥有这些权限了

就是通过后台传过来的数据,在动态路由进行筛选。然后在返回动态路由和静态路由结合一起返回。通过addRoutes追加到路由里面。

我们这个是花裤衩( 这是一个极简的 vue admin 管理后台。 )项目,我们是根据这个二次开发的。在静态路由添加了多少个页面侧边栏就会根据这个静态路由进行渲染出来。而且这个是多个页面使用的,让我想到了vuex是可以管理多个页面的,所以我们就所以vuex进行多个页面的数据管理。

1656158897206.png

步骤:
4.1、设置一个权限vuex里面数据可以设置成菜单
vuex的使用步骤:
设置一个permission.js文件
// 1、vuex存储数据的地方
const state = {
  routes: []
}
// 2、vuex改变数据的唯一地方,同步方法
const mutations = {

}
// 3、vuex异步方法,只能调用同步方法改变数据
const actions = {

}
// 最后面把这三个放到一个对象里面导出
export default {
  // 这个设置true就是作用域
  namespaced: true,
  state,
  mutations,
  actions
}

在到index.js,所以的vuex都是在这里一起引入注册的
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import app from './modules/app'
import settings from './modules/settings'
import user from './modules/user'
// 5、在这里引入
import permission from './modules/permission'
Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    app,
    settings,
    user,
    // 6、注册
    permission
  },
  getters
})

export default store
4.2、获取后台数据筛选

不同用户登录,之后跳转路由都会经过路由守卫。我们之前就在路由守卫哪里判断是否有数据,没有就发起个人信息请求获取数据。不同账号的权限是不一样的,所以说我们就可以在哪里得到数据进行筛选。我们都设置了这个权限的vuex就可以直接在上面进行筛选就好了

1656155696326.png

4.2.1获取个人数据打印出来

      const menus = store.state.user.user.roles.menus
      console.log(menus)

4.2.2设置静态路由 和 动态路由数组

   动态路由数组
export const moveRoutes = [
  approvalsRouter,
  departmentsRouter,
  employeesRouter,
  permissionRouter,
  attendancesRouter,
  salarysRouter,
  settingRouter,
  socialRouter
]
静态路由就是一开始设置好的路由
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: 'Dashboard', icon: 'el-icon-coffee' }
    }]
  },
    { path: '*', redirect: '/404', hidden: true }
]

6.2.3设置权限vuex里面的方法


// 2、vuex改变数据的唯一地方,同步方法
const mutations = {
  setRoutes(state, routes) {
    state.routes = [
      // 因为静态路由不用筛选,直接和筛选完了的动态路由一起添加到我们定义到数组里面
    //   因为静态路由和动态路由都是数组所以需要解构
      ...constantRoutes,
      ...routes
    ]
  }
}

这是打印的动态路由

1656161289947.png

// 3、vuex异步方法,只能调用同步方法改变数据
const actions = {
  filterRoutes(store, menus) {
    // 返回的权限标识是数组,动态路由也是数组。这样就可能需要两次遍历
    // 会非常麻烦可以使用indexOf方法进行判断有没有这个字符串,没有就返回-1
    // 我们需要返回一个集合(动态路由的集合),需要一个数组。filter就是返回一个数组
    const routes = moveRoutes.filter(v => menus.indexOf(v.path.split('/')[1]) !== -1)
    store.commit('setRoutes', routes)
    return routes
  }
}

路由守卫里面
  if (Object.keys(store.state.user.user).length === 0) {
      await store.dispatch('user/getUserInfo')
      const menus = store.state.user.user.roles.menus
      // 因为这个vuex方法是开启了作用域所以需要这个名字/这个方法才能调用
      // store.dispatch('permission/filterRoutes', menus)
      // 我们在vuex设置了返回值所以这里可以进行接收,又因为我们用的是异步方法
      // 这个异步方法通过Promise封装可以通过  await得到获取返回值
      const routes = await store.dispatch('permission/filterRoutes', menus)
      // addRoutes是一个方法,里面传入一个数组  我们返回是一个数组所以需要解构
      router.addRoutes([
        ...routes
      ])
    }
    next()

我们已经筛选完了就可以获取这个vuex里面的路由进行遍历渲染到侧边栏了

  routes() {
      // return this.$router.options.routes
      return this.$store.state.permission.routes
    },

5、优化

5.1、修复bug

当我们点击动态路由时候刷新页面就会跳转到404页面的

1656389608579.png

回顾

我们可以看看我们是怎么做的动态路由?先在路由守卫里面判断有没有个人信息?

有就放进去,没有就发起请求获取之后在进去。

个人信息里面是有我们要的路由标识的!这个路由标识才是我们判断的依据,所以我们在路由守卫里面进行动态路由的筛选。

因为登录进去之后就要显示动态路由了,放在哪里合适?感觉在哪里都不合适,但是路由守卫呢?不管是那个页面进去都会进行一次路由守卫的。在路由守卫里面进行筛选最好的。

造成这个问题的原因?

我们点击动态路由,但是页面刷新是没有数据回来这么快的。因为页面刷新就会清空数据,所有的数据就会初始化了,最先加载的是静态路由进去。静态路由是没有这个页面的,当所以的页面都不匹配就会进去404页面。这就是404页面出现的原因了。

import router from './router'
import store from './store'
router.beforeEach(async(to, form, next) => {
  if (store.state.user.token) {
    if (to.path === '/login') return next('/')
    if (Object.keys(store.state.user.user).length === 0) {
      await store.dispatch('user/getUserInfo')
      const menus = store.state.user.user.roles.menus
      // 因为这个vuex方法是开启了作用域所以需要这个名字/这个方法才能调用
      // store.dispatch('permission/filterRoutes', menus)
      // 我们在vuex设置了返回值所以这里可以进行接收,又因为我们用的是异步方法
      // 这个异步方法通过Promise封装可以通过  await得到获取返回值
      const routes = await store.dispatch('permission/filterRoutes', menus)
      // addRoutes是一个方法,里面传入一个数组  我们返回是一个数组所以需要解构
      router.addRoutes([
        ...routes
      ])
    }
    next()
  } else {
    const arr = ['/login', '/text', '/404']
    if (arr.indexOf(to.path) > -1) return next()
    next('/login')
  }
})

解决办法

我们可以把404放到动态路由里面,就不会出现404页面了。

export const moveRoutes = [
  approvalsRouter,
  departmentsRouter,
  employeesRouter,
  permissionRouter,
  attendancesRouter,
  salarysRouter,
  settingRouter,
  socialRouter,
  { path: '*', redirect: '/404', hidden: true }
]

这样404问题就会解决但是又一个问题是出现了空白页面

空白页面原因

因为页面刷新是没有数据回来这么快的。因为页面刷新就会清空数据,所有的数据就会初始化了,最先加载的是静态路由进去。静态路由是没有这个页面的,当所以的页面都不匹配就会进去404页面。 1656395026549.png 404都没有了,只能是空白页面了。404就是为了解决空白页面存在的。

router.beforeEach(async(to, form, next) => {
  if (store.state.user.token) {
    if (to.path === '/login') return next('/')
    if (Object.keys(store.state.user.user).length === 0) {
      // 虽然我们用了 async  await  这个语法糖可以等着结果回来但是
      // JavaScript的执行机制是,先执行同步的,检测到异步的就会放到任务队列里面等着同步任务执行完了才执行任务队列里面的异步任务
      // 所以说这里是先不执行,
      await store.dispatch('user/getUserInfo')

    }
    // 直接执行在里面的放行操作所以就会出现空白页面以及404页面了
    next()
  }

解决办法就是执行完了之后在跳转回动态页面就可以了,

但是你怎么知道动态页面呢?在路由守卫里面有三个参数 to (去哪里), form(从哪里来), next(是否放行)

在里面的to是一个对象里面path就是要去的目的地,这里就会有我们要去的动态路由的地址

         const routes = await store.dispatch('permission/filterRoutes', menus)
      // addRoutes是一个方法,里面传入一个数组  我们返回是一个数组所以需要解构
      router.addRoutes([
        ...routes
      ])
这里直接加这一句就可以解决路由的问题了
      next(to.path)

就是这么简单