在使用这个vue-admin-template的时候,发现其官方文档的推荐是使用动静结合的方法去配置动态路由
其官方大致过程就是
//此处路由数组为通用跳转路径,意思是不需要权限认证即可通过
export const constantRoutes = [{path:'/login'} ...]
//此路由数组为所有需要进行认证的后才能决定可不可以跳转
export const asyncRoutes = [{path:'user'}...]
随后将用户的角色数组交给vuex进行分配路由:
generateRoutes({ commit }, roles) {
... 进行构造路由 具体代码可看官网,大致是遍历树的子节点做判断是否push到constantRoutes中
}
这就意味这,你需要在前端和后端,同时做一个数据校验,即role表和permission表或者menu表与前端的关系要一一对应
{
path: '/permission',
component: Layout,
redirect: '/permission/page',
alwaysShow: true, // will always show the root menu
name: 'Permission',
meta: {
title: 'Permission',
icon: 'lock',
roles: ['admin', 'editor'] // 你需要在这里进行与数据库对比
},
children: [
{
path: 'page',
component: () => import('@/views/permission/page'),
name: 'PagePermission',
meta: {
title: 'Page Permission',
roles: ['admin'] //你需要在这里进行对比
}
},
这对快速开发来说,是比较快而且方便的,但是如果要减少这一对比的过程,我们就需要动态的来加载新的route数组
那么我们现在需要实现的功能就是:
可以通过改数据库上的数据来实时修改到前端的侧边栏和路由
经典权限五张表,关系表user_role,role_menu已省略
表一
用户表
名 | 类型 | 描述 |
---|---|---|
user_id | integer | 用户id |
user_name | string | 用户名 |
user_password | string | 用户密码 |
表二
角色表
名 | 类型 | 描述 |
---|---|---|
role_id | integer | 角色id |
role_name | string | 角色名 |
role_key | string | 角色标识符 |
表三
权限菜单表
名 | 类型 | 描述 |
---|---|---|
menu_id | integer | 资源id |
menu_name | string | 资源名 |
menu_url | string | 资源路径 |
menu_perms | string | 对应组件路径 |
menu_perms_name | string | 组件名称 |
menu_parent_id | string | 父资源id |
注意,后端使用任意语言即可
主要需要实现以下请求接口:
名字任意
参数 | 返回值 | 前端例子 |
---|---|---|
角色标识符 | 该角色下的所有权限菜单 | getMenusByRoleKey(roleKey) |
新建文件src/store/permission
const getDefaultState = () => {
return {
// 当前角色名,若为#TEST则为混合模式
roleName: '#TEST',
routes: [],
addRoutes: []
}
}
const state = getDefaultState()
const mutations = {
SET_PERMISSION: (state, role) => {
state.roleName = role
},
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
createRoute({ commit }, rolesNames) {
return new Promise(resolve => {
commit('SET_PERMISSION', rolesNames)
//此处使用的是我的接口
getMenuByRoleNames(rolesNames).then(resp => {
const menus = resp.data
menus.forEach(menu => {
menu['children'] = []
})
// console.log(filterRoute(menus))
commit('SET_ROUTES', filterRoute(menus))
resolve(filterRoute(menus))
})
})
}
}
export function filterRoute(menus) {
const result = []
const test = []
for (let i = 0; i < menus.length; i++) {
//也就是说一个最基本的路由,需要你满足以下几个属性才能让他在侧边栏上发挥作用
test.push({
path: '/' + menus[i].permsName,
name: menus[i].permsName,
component: (resolve) => require(['@/views/' + menus[i].perms + '/index'], resolve),
children: [],
meta: { title: menus[i].menuName, 'icon': menus[i].icon }
})
}
// 寻找子节点并装入子节点数组 此处逻辑需要自行根据项目进行构建
for (let i = 0; i < menus.length; i++) {
for (let j = 0; j < menus.length; j++) {
if (menus[i].parentId === menus[j].menuId) {
// console.log(test[j])
test[j].children.push(test[i])
menus[j].children.push(menus[i])
}
}
if (menus[i].parentId === 0) {
test[i].component = Layout
if (menus[i].children.length === 0) {
test[i].children.push(
{
path: '/'+menus[i].permsName+'/index',
name: menus[i].permsName,
//这里使用懒加载组件时,需要用require拼接
component: (resolve) => require(['@/views/' + menus[i].perms + '/index'], resolve),
meta: { title: menus[i].menuName, icon: menus[i].icon }
}
)
}
// test[i]['redirect'] = test[i].path + test[i].children[0].path
result.push(test[i])
}
}
return result
}
export default {
namespaced: true,
state,
mutations,
actions
}
注意⚠️:path属性,无论是子路由还是父路由,都不能有一点重复
比如
{
path: '/test'
...
children:{
path: '/test' //这个地方不能使用test了,原因是侧边栏使用了v-for 设置了:key为path
...
}
}
getter中添加
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token,
avatar: state => state.user.avatar,
cachedViews: state => state.tagsView.cachedViews,
name: state => state.user.name,
roles: state => state.user.roles,
roleName: state => state.permission.roleName,
//添加此处,用于从vuex中取出拼接好的路由
permission_routes: state => state.permission.routes
}
在src/permission 中添加
if (hasToken) {
if (to.path === '/login') {
// 如果有token值了访问到login可以直接到/路径
next({ path: '/' })
NProgress.done()
} else {
const hasGetUserInfo = store.getters.name
if (hasGetUserInfo) {
next()
} else {
// 没有用户信息
try {
// get user info
await store.dispatch('user/getInfo')
const roleNames = []
store.state.user.roles.forEach(role => {
roleNames.push(role.roleName)
})
// 这里这里这里这里,下面两行添加上去就好了
const accessRoutes = await store.dispatch('permission/createRoute', roleNames)
router.addRoutes(accessRoutes)
// console.log(accessRoutes)
next()
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
随后修改src/layout/components/Sidebar/index.vue
...
<sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
el-menu>
el-scrollbar>
div>
template>
<script>
import { mapGetters } from 'vuex'
import Logo from './Logo'
import SidebarItem from './SidebarItem'
import variables from '@/styles/variables.scss'
export default {
components: { SidebarItem, Logo },
computed: {
//此处修改。。。
...mapGetters([
'sidebar',
'permission_routes'
]),
routes() {
return this.$router.options.routes
},
然后启动页面查看
修改数据库,改变其结构
可以看到确实改变了
那么可能就有人问了,你这个能实现三级菜单吗,能实现路由嵌套吗
可以是可以,但是你必须在你写的组件下去写route-view啊
这种的话,建议直接使用上一级的路由,在数据库中修改父id为0就好了,三级也是可以的,直接去修改数据库中的嵌套关系就好了,笔者只写出修改的思路,其中的具体实现过程,大伙可以灵活应用