要求:
- 后端返回菜单结构,根据菜单结构动态生成除了详情页之外的菜单
实现思路:
- 在store存储菜单的地方添加动态路由
菜单结构
{
"code": 200,
"msg": "成功!",
"data": [
{
"id": 12,
"menuName": "分销管理",
"path": "views/Distribution/index",
"sort": 0,
"label": "分销管理",
"hidden": 0,
"menuKey": "distribution",
"parentIds": "11,12",
"icon": null,
"children": null,
"pid": 11
},
{
"id": 23,
"menuName": "积分记录",
"path": "views/SystemSetting/Integral/index",
"sort": 0,
"label": "积分记录",
"hidden": 0,
"menuKey": "integral",
"parentIds": "15,23",
"icon": null,
"children": null,
"pid": 15
},
{
"id": 22,
"menuName": "充值查询",
"path": "views/SystemSetting/RechargeQuery/index",
"sort": 0,
"label": "充值查询",
"hidden": 0,
"menuKey": "rechargeQuery",
"parentIds": "15,22",
"icon": null,
"children": null,
"pid": 15
},
{
"id": 21,
"menuName": "充值审核",
"path": "views/SystemSetting/RechargeCheck/index",
"sort": 0,
"label": "充值审核",
"hidden": 0,
"menuKey": "rechargeCheck",
"parentIds": "15,21",
"icon": null,
"children": null,
"pid": 15
},
{
"id": 20,
"menuName": "充值服务",
"path": "views/SystemSetting/Recharge/index",
"sort": 0,
"label": "充值服务",
"hidden": 0,
"menuKey": "recharge",
"parentIds": "15,20",
"icon": null,
"children": null,
"pid": 15
},
{
"id": 19,
"menuName": "小程序&系统设置",
"path": "views/SystemSetting/SystemSet/index",
"sort": 0,
"label": "小程序&系统设置",
"hidden": 0,
"menuKey": "systemSet",
"parentIds": "15,19",
"icon": null,
"children": null,
"pid": 15
},
{
"id": 11,
"menuName": "财务管理",
"path": "views/Finance/index",
"sort": 0,
"label": "财务管理",
"hidden": 0,
"menuKey": "finance",
"parentIds": "11",
"icon": "el-icon-s-finance",
"children": null,
"pid": 0
},
{
"id": 5,
"menuName": "商品采购单",
"path": "views/SystemSetting/ProductSalesSummary/index",
"sort": 1,
"label": "商品采购单",
"hidden": 0,
"menuKey": "productSalesSummary",
"parentIds": "2,5",
"icon": "",
"children": null,
"pid": 2
},
{
"id": 6,
"menuName": "订单管理",
"path": "views/Order/index",
"sort": 2,
"label": "订单管理",
"hidden": 0,
"menuKey": "order",
"parentIds": "6",
"icon": "el-icon-s-order",
"children": null,
"pid": 0
},
{
"id": 7,
"menuName": "订单列表",
"path": "views/Order/index",
"sort": 3,
"label": "订单列表",
"hidden": 0,
"menuKey": "order",
"parentIds": "6,7",
"icon": "el-icon-s-order",
"children": null,
"pid": 6
},
{
"id": 1,
"menuName": "门店管理",
"path": "views/Store/index",
"sort": 4,
"label": "门店管理",
"hidden": 0,
"menuKey": "store",
"parentIds": "1",
"icon": "el-icon-s-shop",
"children": null,
"pid": 0
},
{
"id": 2,
"menuName": "商品管理",
"path": null,
"sort": 5,
"label": "商品管理",
"hidden": 0,
"menuKey": "productSet",
"parentIds": "2",
"icon": "el-icon-sell",
"children": null,
"pid": 0
},
{
"id": 3,
"menuName": "商品管理",
"path": "views/Product/index",
"sort": 6,
"label": "商品管理",
"hidden": 0,
"menuKey": "product",
"parentIds": "2,3",
"icon": null,
"children": null,
"pid": 2
},
{
"id": 4,
"menuName": "商品分类",
"path": "views/SystemSetting/Category/index",
"sort": 7,
"label": "商品分类",
"hidden": 0,
"menuKey": "category",
"parentIds": "2,4",
"icon": null,
"children": null,
"pid": 2
},
{
"id": 8,
"menuName": "售后列表",
"path": "views/AfterSales/index",
"sort": 8,
"label": "售后列表",
"hidden": 0,
"menuKey": "afterSales",
"parentIds": "6,8",
"icon": "el-icon-service",
"children": null,
"pid": 6
},
{
"id": 9,
"menuName": "活动管理",
"path": "views/Coupon/index",
"sort": 9,
"label": "活动管理",
"hidden": 0,
"menuKey": "coupon",
"parentIds": "9",
"icon": "el-icon-s-ticket",
"children": null,
"pid": 0
},
{
"id": 10,
"menuName": "公益服务",
"path": "views/activity/index",
"sort": 10,
"label": "公益服务",
"hidden": 0,
"menuKey": "activity",
"parentIds": "10",
"icon": "el-icon-s-ticket",
"children": null,
"pid": 0
},
{
"id": 13,
"menuName": "资金记录",
"path": "views/Finance/FinancialRecords/index",
"sort": 20,
"label": "资金记录",
"hidden": 0,
"menuKey": "financialRecords",
"parentIds": "11,13",
"icon": null,
"children": null,
"pid": 11
},
{
"id": 14,
"menuName": "员工工资",
"path": "views/Finance/EmplyeeIncome/index",
"sort": 21,
"label": "员工工资",
"hidden": 0,
"menuKey": "emplyeeIncome",
"parentIds": "11,14",
"icon": "",
"children": null,
"pid": 11
},
{
"id": 15,
"menuName": "系统管理",
"path": "",
"sort": 22,
"label": "系统管理",
"hidden": 0,
"menuKey": "systemSetting",
"parentIds": "15",
"icon": "el-icon-setting",
"children": null,
"pid": 0
},
{
"id": 16,
"menuName": "角色管理",
"path": "views/SystemSetting/Role/index",
"sort": 23,
"label": "角色管理",
"hidden": 0,
"menuKey": "role",
"parentIds": "15,16",
"icon": null,
"children": null,
"pid": 15
},
{
"id": 17,
"menuName": "用户管理",
"path": "views/SystemSetting/User/index",
"sort": 24,
"label": "用户管理",
"hidden": 0,
"menuKey": "user",
"parentIds": "15,17",
"icon": null,
"children": null,
"pid": 15
},
{
"id": 18,
"menuName": "用户审核",
"path": "views/SystemSetting/CheckUser/index",
"sort": 25,
"label": "用户审核",
"hidden": 0,
"menuKey": "checkUser",
"parentIds": "15,18",
"icon": null,
"children": null,
"pid": 15
}
]
}
store代码
import { getLoginResources } from '@/api/menu';
import { menus } from '@/api/menu';
import utils from '@/utils/utils';
import Layout from '@/layout'
import Nest2 from '@/layout/Nest2.vue'
import router from '@/router'
const Types = {
SET_MENU: 'SET_MENU',
};
// const rm = new RouteMenu()
const menu = {
state: {
activeMenuInfo: { activeMenuObj: {}, menuList: [] },
menus: [],
},
actions: {
getMenu({ commit }, menus) {
return new Promise(async (resolve, rejct) => {
const res = await getLoginResources();
if (res.status) {
//将数组变成层级树
res.data =
res.data &&
res.data.map((item) => ({
...item,
menuName: item.label,
label: item.label,
// menuKey: item.path,
// hidden: item.hidden === 0,
}));
let menus = res.data;
//菜单不是树形的,先将菜单变成树形结构
menus = rebuildTree(res.data);
//遍历菜单添加路由
menus.forEach(menu => {
//获取路由深度
let deep = getRouteDeep(menu, 1, true)
//路由对象
let obj = {}
generateRoute(menu, obj, 1, deep, '')
console.log(obj);
router.addRoute(obj)
})
console.log(router.getRoutes());
commit('SET_MENU', menus);
resolve(menus);
}
// commit('SET_MENU', menus);
// resolve(menus);
});
},
},
mutations: {
//设置菜单
[Types.SET_MENU]: (state, data) => {
state.menus = data;
},
},
namespaced: true,
};
export default menu;
最终生成的路由结构,以systemSetting下的checkUser和user为例
image.png注释掉本地router中注册的路由,点击菜单可以正常访问就是动态路由没问题
但是会遇到另外一个问题,刷新页面后会跳转到404问题
需要在路由导航守卫中进行处理beforeEach,判断store中menus的长度为空的话重新获取菜单结构,重新生成就可以了,然后将路由重定向到to的路由
用到的工具类
function getRouteDeep(menu, deep, addRoute) {
//判断obj.children的长度是否大于0,大于0则deep+1,否则返回0
if (menu.children.length) {
deep++
menu.children.forEach(item => {
getRouteDeep(item, deep)
})
}
//包含layout和Nest2的层级
return deep + 2
}
//生成路由
/**
*
* @param {*} obj 路由对象
* @param {*} index 当前路由深度
* @param {*} deep 总路由深度
*/
function generateRoute(menu, obj, index, deep, path) {
obj.path = index === 1 ? '/' : ''
// // / 重定向到home
// if (menu.menuKey === 'home') {
// obj.redirect = '/home'
// }
if (index === 1) {
obj.component = Layout
// obj.path = '/'
if (menu.children.length) {
obj.path += menu.menuKey
}
}
if (index === 2) {
obj.component = Nest2
// obj.path = ''
}
// //其他层级
if (index === 3) {
obj.component = () => import(`@/${menu.path}.vue`)
obj.path += menu.menuKey
obj.name = menu.menuKey
}
//路由小于deep时,要生成children
if (index < deep) {
obj.children = []
//children多个
if (menu.children.length > 0) {
for (let v of menu.children) {
const objSub = {}
obj.children.push(generateRoute(v, objSub, index + 1, deep, obj.path))
}
} else {
const objCur = {}
obj.children.push(generateRoute(menu, objCur, index + 1, deep, obj.path))
}
}
return obj
}
router代码
import Vue from 'vue'
import VueRouter from 'vue-router'
import Layout from '@/layout'
import Nest2 from '@/layout/Nest2.vue'
import NProgress from 'nprogress'
import Utils from '@/utils/utils'
import { constantRouterMap } from './constant'
import { LoaclRouterMap } from './local'
import store from '@/store'
// import {rebuildTree,getRouteDeep} from "@/store/modules/menu"
Vue.use(VueRouter)
//固定路由
const routes = [
{
path: '/',
name: 'Layout',
redirect: '/home',
component: Layout,
children: [
{
path: 'home',
name: 'home',
component: () => import('@/views/HomeView.vue'),
meta: {
label: '首页',
cache: true,
closable: false
}
},
//本地一些404和详情页路由
...constantRouterMap,
...LoaclRouterMap
]
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue')
}
]
//处理路由导航守卫
let router = new VueRouter({
routes
})
//重写push方法,跳转相同路由报错问题
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location, onResolve, onReject) {
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location).catch(err => err)
}
router.beforeEach(async (to, from, next) => {
console.log(store.state.menu.menus)
NProgress.start()
if (store.state.menu.menus.length) {
//未匹配到的路由
if (!to.matched || !to.matched.length) {
next({ name: 'Error404' })
return
}
// console.log(Utils.getStorage('userInfo'))
let userInfo = Utils.getStorage('userInfo')
if (to.path === '/login') {
next()
return
}
//用户信息存在
if (!userInfo || Object.keys(userInfo).length === 0) {
next({ name: 'login' })
return
} else {
next()
NProgress.done()
return
}
} else {
router = new VueRouter({
routes
})
store
.dispatch('menu/getMenu')
.then(res => {
res.forEach(menu => {
//获取路由深度
let deep = getRouteDeep(menu, 1, true)
//路由对象
let obj = {}
generateRoute(menu, obj, 1, deep, '')
router.addRoute(obj)
})
console.log(router);
//重新添加路由后重定向
next({ ...to, replace: true })
})
.catch(err => {
next({ name: 'home' })
})
}
})
export default router
验证跳转到一个不是本地注册的路由里面,刷新路由可以正常访问,即达到目的。
网友评论