项目需求:后端返回权限数组,前端通过js操作,生成一棵权限树,并将这棵树动态加入路由route中,从而实现动态生成后台管理系统左侧菜单栏
后端返回的数据:
[
{
"parentId":"0",
"resCode":"city",
"resId":"07fe2c8b976047e186bb6bcb8f4d6544",
"resLevel":2,
"resName":"城运微平台",
"resOrder":0,
"resType":1
},
{
"parentId":"0",
"resCode":"nbh",
"resId":"07fe2c8c976047e186bb6bcb8f4d6545",
"resLevel":2,
"resName":"居委微平台",
"resOrder":0,
"resType":2
},
{
"parentId":"0",
"resCode":"street",
"resId":"07fe2e8c976047e186bb6bcb8f4d6547",
"resLevel":2,
"resName":"街镇微平台",
"resOrder":0,
"resType":3
},
{
"parentId":"07fe2c8c976047e186bb6bcb8f4d6545",
"resCode":"JWHT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6557",
"resLevel":3,
"resName":"后台管理",
"resType":2,
"sysUrl":""
},
{
"parentId":"07fe2c8c976047e186bb6bcb8f4d6545",
"resCode":"JWQT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6558",
"resLevel":3,
"resName":"前台管理",
"resType":2,
"sysUrl":""
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6547",
"resCode":"JZHT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6559",
"resLevel":3,
"resName":"后台管理",
"resType":3,
"sysUrl":""
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6547",
"resCode":"JZQT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6560",
"resLevel":3,
"resName":"前台管理",
"resType":3,
"sysUrl":""
},
{
"parentId":"07fe2c8b976047e186bb6bcb8f4d6544",
"resCode":"CYHT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6561",
"resLevel":3,
"resName":"后台管理",
"resType":1
},
{
"parentId":"07fe2c8b976047e186bb6bcb8f4d6544",
"resCode":"CYQT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6562",
"resLevel":3,
"resName":"前台管理",
"resType":1
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6561",
"resCode":"CYTZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6563",
"resLevel":4,
"resName":"通知公告设置",
"resType":1,
"sysUrl":"/sys/city/inform"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6557",
"resCode":"JWTZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6564",
"resLevel":4,
"resName":"通知公告设置",
"resType":2,
"sysUrl":"/sys/nbh/inform"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6559",
"resCode":"JZTZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6565",
"resLevel":4,
"resName":"通知公告设置",
"resType":3,
"sysUrl":"/sys/street/inform"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6561",
"resCode":"CYJS",
"resId":"07fe2e8c976047e186bb6bcb8f4d6566",
"resLevel":4,
"resName":"角色管理",
"resType":1,
"sysUrl":"/sys/city/role"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6557",
"resCode":"JWJS",
"resId":"07fe2e8c976047e186bb6bcb8f4d6567",
"resLevel":4,
"resName":"角色管理",
"resType":2,
"sysUrl":"/sys/nbh/role"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6559",
"resCode":"JZJS",
"resId":"07fe2e8c976047e186bb6bcb8f4d6568",
"resLevel":4,
"resName":"角色管理",
"resType":3,
"sysUrl":"/sys/street/role"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6561",
"resCode":"CYYH",
"resId":"07fe2e8c976047e186bb6bcb8f4d6569",
"resLevel":4,
"resName":"用户管理",
"resType":1,
"sysUrl":"/sys/city/user"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6557",
"resCode":"JWYH",
"resId":"07fe2e8c976047e186bb6bcb8f4d6570",
"resLevel":4,
"resName":"用户管理",
"resType":2,
"sysUrl":"/sys/nbh/user"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6559",
"resCode":"JZYH",
"resId":"07fe2e8c976047e186bb6bcb8f4d6571",
"resLevel":4,
"resName":"用户管理",
"resType":3,
"sysUrl":"/sys/street/user"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6562",
"resCode":"CYZXT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6572",
"resLevel":4,
"resName":"城运子系统列表",
"resType":1,
"sysUrl":"/sys/city/subsys"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6558",
"resCode":"JWZXT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6573",
"resLevel":4,
"resName":"居委子系统列表",
"resType":2,
"sysUrl":"/sys/nbh/subsys"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6560",
"resCode":"JZZXT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6574",
"resLevel":4,
"resName":"街道子系统列表",
"resType":3,
"sysUrl":"/sys/street/subsys"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6561",
"resCode":"CYZXTSZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6575",
"resLevel":4,
"resName":"子系统设置",
"resType":1,
"sysUrl":"/sys/city/subset"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6557",
"resCode":"JWZXTSZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6576",
"resLevel":4,
"resName":"子系统设置",
"resType":2,
"sysUrl":"/sys/nbh/subset"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6559",
"resCode":"JZZXTSZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6577",
"resLevel":4,
"resName":"子系统设置",
"resType":3,
"sysUrl":"/sys/street/subset"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6561",
"resCode":"CYZBSZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6578",
"resLevel":4,
"resName":"值班列表设置",
"resType":1,
"resUrl":"",
"sysUrl":"/sys/city/duty"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6557",
"resCode":"JWZBSZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6579",
"resLevel":4,
"resName":"值班列表设置",
"resType":2,
"resUrl":"",
"sysUrl":"/sys/nbh/duty"
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6559",
"resCode":"JZZBSZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6580",
"resLevel":4,
"resName":"值班列表设置",
"resType":3,
"resUrl":"",
"sysUrl":"/sys/street/duty"
}
]
第一步:将后端返回的列表数据转换成一棵树tree
实现代码:
// 生成权限树
const createTree = (arr) => {
const map = {}
arr.map(item => {
//此处设置meta,增加title,icon和sysUrl,为后续添加route做准备
item.meta = { title: item.resName, icon: 'el-icon-s-help', sysUrl: item.sysUrl }
map[item.resId] = item
})
const routeTree = []
arr.forEach(child => {
//利用parentId和resId的关系,生成tree
const mapItem = map[child.parentId]
if (mapItem) {
(mapItem.children || (mapItem.children = [])).push(child)
} else {
routeTree.push(child)
}
})
return routeTree
}
生成的权限树:
[
{
"parentId":"0",
"resCode":"city",
"resId":"07fe2c8b976047e186bb6bcb8f4d6544",
"resLevel":2,
"resName":"城运微平台",
"resOrder":0,
"resType":1,
"meta":{
"title":"城运微平台",
"icon":"el-icon-s-help"
},
"children":[
{
"parentId":"07fe2c8b976047e186bb6bcb8f4d6544",
"resCode":"CYHT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6561",
"resLevel":3,
"resName":"后台管理",
"resType":1,
"meta":{
"title":"后台管理",
"icon":"el-icon-s-help"
},
"children":[
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6561",
"resCode":"CYTZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6563",
"resLevel":4,
"resName":"通知公告设置",
"resType":1,
"sysUrl":"/sys/city/inform",
"meta":{
"title":"通知公告设置",
"icon":"el-icon-s-help",
"sysUrl":"/sys/city/inform"
}
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6561",
"resCode":"CYJS",
"resId":"07fe2e8c976047e186bb6bcb8f4d6566",
"resLevel":4,
"resName":"角色管理",
"resType":1,
"sysUrl":"/sys/city/role",
"meta":{
"title":"角色管理",
"icon":"el-icon-s-help",
"sysUrl":"/sys/city/role"
}
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6561",
"resCode":"CYYH",
"resId":"07fe2e8c976047e186bb6bcb8f4d6569",
"resLevel":4,
"resName":"用户管理",
"resType":1,
"sysUrl":"/sys/city/user",
"meta":{
"title":"用户管理",
"icon":"el-icon-s-help",
"sysUrl":"/sys/city/user"
}
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6561",
"resCode":"CYZXTSZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6575",
"resLevel":4,
"resName":"子系统设置",
"resType":1,
"sysUrl":"/sys/city/subset",
"meta":{
"title":"子系统设置",
"icon":"el-icon-s-help",
"sysUrl":"/sys/city/subset"
}
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6561",
"resCode":"CYZBSZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6578",
"resLevel":4,
"resName":"值班列表设置",
"resType":1,
"resUrl":"",
"sysUrl":"/sys/city/duty",
"meta":{
"title":"值班列表设置",
"icon":"el-icon-s-help",
"sysUrl":"/sys/city/duty"
}
}
]
},
{
"parentId":"07fe2c8b976047e186bb6bcb8f4d6544",
"resCode":"CYQT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6562",
"resLevel":3,
"resName":"前台管理",
"resType":1,
"meta":{
"title":"前台管理",
"icon":"el-icon-s-help"
},
"children":[
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6562",
"resCode":"CYZXT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6572",
"resLevel":4,
"resName":"城运子系统列表",
"resType":1,
"sysUrl":"/sys/city/subsys",
"meta":{
"title":"城运子系统列表",
"icon":"el-icon-s-help",
"sysUrl":"/sys/city/subsys"
}
}
]
}
]
},
{
"parentId":"0",
"resCode":"nbh",
"resId":"07fe2c8c976047e186bb6bcb8f4d6545",
"resLevel":2,
"resName":"居委微平台",
"resOrder":0,
"resType":2,
"meta":{
"title":"居委微平台",
"icon":"el-icon-s-help"
},
"children":[
{
"parentId":"07fe2c8c976047e186bb6bcb8f4d6545",
"resCode":"JWHT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6557",
"resLevel":3,
"resName":"后台管理",
"resType":2,
"sysUrl":"",
"meta":{
"title":"后台管理",
"icon":"el-icon-s-help",
"sysUrl":""
},
"children":[
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6557",
"resCode":"JWTZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6564",
"resLevel":4,
"resName":"通知公告设置",
"resType":2,
"sysUrl":"/sys/nbh/inform",
"meta":{
"title":"通知公告设置",
"icon":"el-icon-s-help",
"sysUrl":"/sys/nbh/inform"
}
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6557",
"resCode":"JWJS",
"resId":"07fe2e8c976047e186bb6bcb8f4d6567",
"resLevel":4,
"resName":"角色管理",
"resType":2,
"sysUrl":"/sys/nbh/role",
"meta":{
"title":"角色管理",
"icon":"el-icon-s-help",
"sysUrl":"/sys/nbh/role"
}
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6557",
"resCode":"JWYH",
"resId":"07fe2e8c976047e186bb6bcb8f4d6570",
"resLevel":4,
"resName":"用户管理",
"resType":2,
"sysUrl":"/sys/nbh/user",
"meta":{
"title":"用户管理",
"icon":"el-icon-s-help",
"sysUrl":"/sys/nbh/user"
}
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6557",
"resCode":"JWZXTSZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6576",
"resLevel":4,
"resName":"子系统设置",
"resType":2,
"sysUrl":"/sys/nbh/subset",
"meta":{
"title":"子系统设置",
"icon":"el-icon-s-help",
"sysUrl":"/sys/nbh/subset"
}
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6557",
"resCode":"JWZBSZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6579",
"resLevel":4,
"resName":"值班列表设置",
"resType":2,
"resUrl":"",
"sysUrl":"/sys/nbh/duty",
"meta":{
"title":"值班列表设置",
"icon":"el-icon-s-help",
"sysUrl":"/sys/nbh/duty"
}
}
]
},
{
"parentId":"07fe2c8c976047e186bb6bcb8f4d6545",
"resCode":"JWQT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6558",
"resLevel":3,
"resName":"前台管理",
"resType":2,
"sysUrl":"",
"meta":{
"title":"前台管理",
"icon":"el-icon-s-help",
"sysUrl":""
},
"children":[
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6558",
"resCode":"JWZXT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6573",
"resLevel":4,
"resName":"居委子系统列表",
"resType":2,
"sysUrl":"/sys/nbh/subsys",
"meta":{
"title":"居委子系统列表",
"icon":"el-icon-s-help",
"sysUrl":"/sys/nbh/subsys"
}
}
]
}
]
},
{
"parentId":"0",
"resCode":"street",
"resId":"07fe2e8c976047e186bb6bcb8f4d6547",
"resLevel":2,
"resName":"街镇微平台",
"resOrder":0,
"resType":3,
"meta":{
"title":"街镇微平台",
"icon":"el-icon-s-help"
},
"children":[
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6547",
"resCode":"JZHT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6559",
"resLevel":3,
"resName":"后台管理",
"resType":3,
"sysUrl":"",
"meta":{
"title":"后台管理",
"icon":"el-icon-s-help",
"sysUrl":""
},
"children":[
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6559",
"resCode":"JZTZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6565",
"resLevel":4,
"resName":"通知公告设置",
"resType":3,
"sysUrl":"/sys/street/inform",
"meta":{
"title":"通知公告设置",
"icon":"el-icon-s-help",
"sysUrl":"/sys/street/inform"
}
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6559",
"resCode":"JZJS",
"resId":"07fe2e8c976047e186bb6bcb8f4d6568",
"resLevel":4,
"resName":"角色管理",
"resType":3,
"sysUrl":"/sys/street/role",
"meta":{
"title":"角色管理",
"icon":"el-icon-s-help",
"sysUrl":"/sys/street/role"
}
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6559",
"resCode":"JZYH",
"resId":"07fe2e8c976047e186bb6bcb8f4d6571",
"resLevel":4,
"resName":"用户管理",
"resType":3,
"sysUrl":"/sys/street/user",
"meta":{
"title":"用户管理",
"icon":"el-icon-s-help",
"sysUrl":"/sys/street/user"
}
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6559",
"resCode":"JZZXTSZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6577",
"resLevel":4,
"resName":"子系统设置",
"resType":3,
"sysUrl":"/sys/street/subset",
"meta":{
"title":"子系统设置",
"icon":"el-icon-s-help",
"sysUrl":"/sys/street/subset"
}
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6559",
"resCode":"JZZBSZ",
"resId":"07fe2e8c976047e186bb6bcb8f4d6580",
"resLevel":4,
"resName":"值班列表设置",
"resType":3,
"resUrl":"",
"sysUrl":"/sys/street/duty",
"meta":{
"title":"值班列表设置",
"icon":"el-icon-s-help",
"sysUrl":"/sys/street/duty"
}
}
]
},
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6547",
"resCode":"JZQT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6560",
"resLevel":3,
"resName":"前台管理",
"resType":3,
"sysUrl":"",
"meta":{
"title":"前台管理",
"icon":"el-icon-s-help",
"sysUrl":""
},
"children":[
{
"parentId":"07fe2e8c976047e186bb6bcb8f4d6560",
"resCode":"JZZXT",
"resId":"07fe2e8c976047e186bb6bcb8f4d6574",
"resLevel":4,
"resName":"街道子系统列表",
"resType":3,
"sysUrl":"/sys/street/subsys",
"meta":{
"title":"街道子系统列表",
"icon":"el-icon-s-help",
"sysUrl":"/sys/street/subsys"
}
}
]
}
]
}
]
第二步:动态生成路由
先上代码,后面解释实现思路:
// router配置文件
import router from '@/router'
// vue-template-admin框架自带
import Layout from '@/layout'
/*routes 后端返回的权限列表数据*/
const addRouter = (routes) => {
//通过第一步的createTree方法生成权限树tree
const routeTree = createTree(routes)
/*根据vue的route规则处理权限树
>>1.每一级的path是必填且唯一值
>>2.原则上有多少级路由嵌套就需要多少个<router-view>(即如果你的路由是三级路由嵌套,不要忘记还要手动在二级目录的根文件下添加一个 <router-view>)
*/
/* 处理二级及二级以上路由 */
const createMenuTree = (adminTree) => {
adminTree.forEach(item => {
item.path = item.resCode.toLowerCase()
item.name = item.resCode
if (item.children && item.children.length) {
createMenuTree(item.children)
// 最后一级菜单以上加载Roeter-view页面
item.component = resolve => require(['@/views/dynamicAdmin/routerView.vue'], resolve)
} else {
// 最后一级菜单根据sysUrl(如:/sys/city/subset)的最后一个值加载对应模块
const file = item.sysUrl && item.sysUrl.split('/')[3]
item.component = resolve => require(['@/views/dynamicAdmin/' + file], resolve)
}
})
return adminTree
}
routeTree.forEach(item => {
// 一级菜单
item.path = '/' + item.resCode.toLowerCase()
item.name = item.resCode
item.component = Layout
// 二级及以下菜单
if (item.children && item.children.length) {
item.children = createMenuTree(item.children)
}
})
console.log({ routeTree })
router.addRoutes(routeTree)
return routeTree
}
实现思路:
1.后端返回的数据中每一级都有resCode,且是唯一值,符合path的规则定义,因此使用resCode作为每一级路由的path(最开始后端让我使用他返回sysUrl作为跳转路由,经过了一天多时间的折腾,根本无法实现,因为只有最后一级数据才返回了sysUrl(如:/sys/city/subset),一二级的为空值,这是其一,其二这个值每一级也不是唯一的,sys是每一级都通用的,不符合path的唯一性定义,最后决定使用resCode)
2.这个项目使用的是vue-template-admin框架,第一级路由的component加载框架中的Layout 组件,第一级以上&最后一级以下(此项目中只有第二级)路由需要手动添加一个"router-view" 组件
3.由于后端使用最后一级菜单路由模块的sysUrl作为请求的基础api,所以在最后一级路由处理时,把sysUrl存入meta中,便于后期使用
4.使用router.addRoutes( )方法把生成的routes动态添加到项目的router中
5.最后一级路由的component加载(这一步操作可以说是整个权限菜单实现比较精妙的地方,可以巧妙地复用文件,减少累赘),首先看后端返回的sysUrl:
城运微平台 >>后台管理>>
通知公告设置('/sys/city/inform'),
角色管理('/sys/city/role'),
用户管理('/sys/city/user'),
子系统设置('/sys/city/subset'),
值班列表设置('/sys/city/duty'),
前台管理>>
城运子系统列表('/sys/city/subsys'),
居委微平台 >>后台管理>>
通知公告设置('/sys/nbh/inform'),
角色管理('/sys/nbh/role'),
用户管理('/sys/nbh/user'),
子系统设置('/sys/nbh/subset'),
值班列表设置('/sys/nbh/duty'),
前台管理>>
居委子系统列表('/sys/nbh/subsys'),
街镇微平台 >>后台管理>>
通知公告设置('/sys/street/inform'),
角色管理('/sys/street/role'),
用户管理('/sys/street/user'),
子系统设置('/sys/street/subset'),
值班列表设置('/sys/street/duty'),
前台管理>>
街道子系统列表('/sys/street/subsys'),
以上数据可以看出,三个微平台下的模块都是一样的,因此可以利用sysUrl的最后一个值作为文件目录(即最后一级加载的component):
// 最后一级菜单根据sysUrl(如:/sys/city/subset)的最后一个值加载对应模块
const file = item.sysUrl && item.sysUrl.split('/')[3]
item.component = resolve => require(['@/views/dynamicAdmin/' + file], resolve)
对应的文件目录:
单个文件模块,存储当前页面的sysUrl,作为请求接口的基础api(必须使用sessionStorage,如果使用localStorage,同时打开多个页面时会获取不到正确的api)
api.js:拼接上当前页面的sysUrl(即current_api),该文件可以写通用api和私有api
到这一步,动态添加权限路由就完工了,接下来就是在合适的地方使用,呈现菜单栏&页面效果
// 1.页面刷新重新设置获取缓存
const getMenuTree = () => {
const data = JSON.parse(getStorage('admin_menu_tree'))
return (data && addRouter(data)) || []
}
// 2.把动态生成的权限路由存放vuex的getters中(adminMenuTree:getMenuTree())
// 3.在Sidebar组件中使用
import { mapGetters } from 'vuex'
computed: {
...mapGetters([
'adminMenuTree'
]),
routes() {
// this.$router.options.routes是为了拼接上router中固定的路由
return [...this.$router.options.routes, ...this.adminMenuTree]
},
}