1. 创建菜单路由pinia管理
- 通过searchSelfMenusService 从后端请求菜单树列表, 菜单树查询构建内容见前变后端部分菜单列表树部分
src\stores\menu.ts - vue3 动态加载组件要使用 import.meta.glob(‘…/views/**/*.vue’)
- 使用elementplus el-menu 所以要构建完整路由,以"/" 开头,不然会出现路由跳转时路由拼接问题,使用contactChildRouter 拼接完整路由,在后续递归菜单时使用
- mountRouter 将所有菜单挂载在Layout子菜单下,Layout布局页面见下一章节
- 数据说明
- menuList: 构建菜单
- routerList: 路由缓存,创建过程中会加载vue组件
- menuArray: 构建完整路由后菜单的一维存储,便于后续菜单、路由搜索
import {defineStore} from 'pinia'
import {ref} from 'vue'
import { searchSelfMenusService } from '@/api/sys/menu'
import router from '@/router/index.js'
import Layout from '@/views/layout/Layout.vue'
let modules = import.meta.glob('../views/**/*.vue')
export const useMenuStore = defineStore("menu", () => {
const menuList = ref<Array<any>>([])
const routerList = ref<Array<any>>([])
const menuArray = ref<Array<any>>([]) // 一维结构后菜单数组,便于菜单搜索
// 重载路由列表
const generateRouterAndMenu = async () => {
let menuResult = await searchSelfMenusService()
// menuList.value = menuResult.data
routerList.value = [] // 清除缓存
initMenuAndRouter(menuResult.data, routerList.value)
menuList.value = []
contactChildRouter(menuResult.data, '')
menuList.value = menuResult.data
menuArray.value = flattenMenuTree(menuList.value)
}
const initMenuAndRouter = (menuList: Array<any>, routerList: Array<any>) => {
modules = import.meta.glob('../views/**/*.vue')
initRouterMountComponent(menuList, routerList)
mountRouter()
}
const initRouterMountComponent = (menuList: Array<any>, routerList: Array<any>) => {
menuList.forEach(item => {
// 定义数据结构
let routerInfo: any = {}
if (item.componentPath) {
if (item.children && item.children.length > 0) {
routerInfo = {
name: item.menuName,
path: item.path,
meta: {title: item.menuName},
// 设置组件,
component: modules[`../views/${item.componentPath}.vue`],
children: []
}
} else {
routerInfo = {
name: item.menuName,
path: item.path,
meta: {title: item.menuName},
// 设置组件,
component: modules[`../views/${item.componentPath}.vue`]
}
}
// 将路由信息添加到数组中
routerList.push(routerInfo)
}
// 递归子菜单
if (item.children && item.children.length > 0) {
initRouterMountComponent(item.children, routerInfo.children)
}
})
}
// 拼接子菜单完整路由
const contactChildRouter = (list: Array<any>, parentPath: string) => {
list.forEach(item => {
if (parentPath != '') {
let path = parentPath + '/' + item.path
item.path = path
} else {
let path = "/" + item.path
item.path = path
}
// 递归子菜单
if (item.children && item.children.length > 0) {
contactChildRouter(item.children, item.path)
}
})
}
// 挂载路由, 添加为/ 子路由, 在右侧主区域显示
const mountRouter = () => {
// 所有的页面都是加载到Layout/Main组件的RouterView中, 相当于所有的路由都是Layout的子路由
router.addRoute(
{
component: Layout,
path: "/",
redirect: 'index',
children: routerList.value
}
)
}
// 降维列表树,转为一维数组
const flattenMenuTree = (tree: Array<any>): any[] => {
let result: any[] = []
// 递归函数来遍历树并收集节点
let traverse = (nodes: Array<any>) => {
nodes.forEach(node => {
// 将当前节点添加到结果数组中
result.push({ ...node})
if (node.children && node.children.length > 0) {
traverse(node.children);
}
})
}
// 开始遍历树
traverse(tree)
return result;
}
return {
menuList, routerList, menuArray,
generateRouterAndMenu, initMenuAndRouter
}
},
{
persist: {
paths: ['activeTab']//指定要长久化的字段
}
}
)
2. 创建路由守卫
src\router\permission.ts
检查白名单和pina中数据是否加载,没有缓存数据则重新发请求加载菜单路由
import router from "./index"
import { useMenuStore } from '@/stores/menu'
import { useTokenStore } from "@/stores/token"
// 设置路由白名单
const whiteRouter = ['/login','/error','/404']
// 全局前置路由守卫, 路由挂载, 名单过滤
router.beforeEach((to, from, next) =>{
const menuStore = useMenuStore()
// 判断 to 是否处于白名单
if(whiteRouter.indexOf(to.path) == -1) {
console.log("no white route to path", to.path)
const tokenStore = useTokenStore()
// 是否登录
if (!tokenStore.isLogin()) {
next('/login')
} else {
console.log("menuStore.routerList.length:", menuStore.routerList.length)
// 判断routerList中是否有动态路由的数据
if(menuStore.routerList.length == 0) {
// 设置动态路由数据结构,并且添加到路由中
console.log('loading router-----------------------------')
menuStore.generateRouterAndMenu().then(() => {
// 跳转页面
next({...to,replace: true})
})
}else {
// 情况1:路由的路径是合法的,正常的
if(to.matched.length != 0) {
next();
}else {
// 情况2:路由的页面并没有
next('/404');
}
}
}
}else {
// 直接放行
next()
}
})
登录简单判断:
// 判断是否登录
const isLogin = (): boolean => {
if (tokenInfo.tokenName && tokenInfo.tokenValue) {
return true
} else {
return false
}
}
记得return
3. 加载路由守卫
在main.ts 中:
import './router/permission'