# Vue3 实现动态路由(权限菜单)
# 步骤一:分析数据
假设后端发来的数据是这样的
type ===1 时是二级菜单(也就是可以展开的菜单,里面有各个子菜单)
type === 2 时是一级菜单(不可展开)
# 步骤二:创建组件和路由
先将需要的所有路由都创建出来(后续再根据什么用户权限对应注册哪些路由,菜单也是根据后端数据动态生成的)
只有一级菜单才创建,创建的路径跟传过来的 url 一致,因为 url 对应的其实就是 path
例如这里的 url 是 /main/system/role 所以我就在 views/main/system/role 下面创建 role.vue
同时在 router/main/system/role 下面创建 role.ts
其内容为路由的注册信息
这里因为要创建太多的组件和路由,所以用一个工具来快速创建 (coderwhy 提供的)
npm i coderwhy -g |
安装成功后敲命令,它就会创建出相应路径的 role.vue 和 role.ts
// coderwhy add3page(如果是vue2就用addpage) 组件名 -d 组件存放路径 | |
// 例如 | |
coderwhy add3page role -d src/views/main/system/role | |
coderwhy add3page user -d src/views/main/system/user | |
... |
注意:自己一个个创建组件和路由都可以,路径也自己可以顶,弄这么长的路径其实只是为了利用这个工具而已
命令分析:它会在创建完组件后,会根据 - d 后面的路径替换 views 为 router 创建它的路由规则,而且路径都是一一对应的,相当方便
# 步骤三:根据菜单权限获取路由规则数组
创建完所有组件和路由规则后
在 src/utils 下创建 map-menus.ts 文件,该文件是根据用户菜单权限拿到对应的路由规则
import { RouteRecordRaw } from 'vue-router' | |
export function mapMenusToRoutes(userMenus: any[]): RouteRecordRaw[] { | |
const routes: RouteRecordRaw[] = [] | |
// 1. 先去加载默认所有的 routes | |
const allRoutes: RouteRecordRaw[] = [] | |
// 这里的三个参数分别为①加载文件路径②是否递归加载,如果为 false,只会加载路径下文件,如果 true | |
// 路径里面文件夹的文件也会加载③正则表达式(加载什么文件) | |
const routeFiles = require.context('@/router/main', true, /\.ts/) | |
routeFiles.keys().forEach((key) => { | |
const route = require('@/router/main' + key.slice(1)) | |
allRoutes.push(route.default) | |
}) | |
// 2. 根据菜单获取需要添加的 routes | |
// userMenus: | |
// type === 1 -> children -> type === 1 | |
// type === 2 -> route | |
const _recurseGetRoute = (menus: any[]) => { | |
for (const menu of menus) { | |
if (menu.type === 2) { | |
const route = allRoutes.find((route) => route.path === menu.url) | |
if (route) routes.push(route) | |
} else { | |
_recurseGetRoute(menu.children) | |
} | |
} | |
} | |
_recurseGetRoute(userMenus) | |
return routes | |
} |
# 步骤四:注册路由
大概实现思路就是这样,不同的情况可以适当修改,思路是一致的,
最后使用的话只需要导入该文件(map-menus.ts),在获取到用户菜单权限后调用该文件导出的函数 mapMenusToRoutes(传入的菜单数据)就可以得到相应的路由规则,然后遍历注册就可以了
我这里是在登录成功后获取数据并将数据存入 vuex 里面,所以我在存的时候顺便注册路由(vuex 存的操作就不说了,直接调用就好了)
当然,vuex 的特点就是刷新页面丢失,因为我是在登录成功才会进行存 vuex 的操作,而注册动态路由也放在这个操作里面,所以刷新 vuex 没了,动态路由的注册也没了,所以我们在登录成功后保存用户菜单信息到 vuex 之前,先将它存到本地,这样的话在登录成功后,vuex 和 localStorage 里面都有我们的菜单信息了,然后再定义一个函数(该函数作用是获取 localStorage 的菜单信息,然后将他存入 vuex,也就是再次调用存 vuex 的操作,这样也就间接的又注册了动态路由),在每次页面刷新都调用该函数
// 这里是 vuex 里面的 actions 函数, | |
actions: { | |
// 从本地缓存取数据保存入 vuex | |
loadLocalLogin(context) { | |
const token = localStorage.getItem('token') | |
if (token) { | |
context.commit('changeToken', token) | |
} | |
const userInfo = localStorage.getItem('userInfo') | |
if (userInfo) { | |
context.commit('changeUserInfo', userInfo) | |
} | |
const userMenus = localStorage.getItem('userMenus') | |
if (userMenus) { | |
context.commit('changeUserMenusInfo', userMenus) | |
} | |
}, | |
} |
该函数定义在哪里随意,最后在 main.js 里面调用就好了
// 在每次页面重新加载时(刷新或第一次打开时),将本地缓存中vuex需要的数据存入vuex
export function setupStore() {
// 从本地缓存获取Login模块需要的vuex数据
store.dispatch('login/loadLocalLogin')
}
这里注意该函数的调用一定要在 app.use (router) 之前,因为在 app.use (router) 后,页面路径就会去匹配路由,这时动态路由还没注册,这时页面路径是 router 里面的还好,可以正常显示,如果是动态路由的路径,那就 gg,找不到页面...
可以放上面试一试,刷新一下页面,会发现页面空白~
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { setupStore } from './store'
const app = createApp(App)
// 在每次页面重新加载时(刷新或第一次打开时),将本地缓存中vuex需要的数据存入vuex
// 该函数要放在app.use(router)之前,因为在函数里面注册了动态路由,如果放后面
// 路径是动态路由的路径的话,刷新会页面丢失,因为这时动态路由还没注册,匹配不到路径
setupStore()
app.use(router)
app.use(store)
app.mount('#app')