路由优化
This commit is contained in:
23
src/api/menu.js
Normal file
23
src/api/menu.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import request from '../utils/request'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户菜单
|
||||||
|
* @returns {Promise} 菜单数据
|
||||||
|
*/
|
||||||
|
export function getUserMenu() {
|
||||||
|
return request({
|
||||||
|
url: '/menu',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户权限
|
||||||
|
* @returns {Promise} 权限数据
|
||||||
|
*/
|
||||||
|
export function getUserPermissions() {
|
||||||
|
return request({
|
||||||
|
url: '/permissions',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
app_title: 'vueadmin',
|
app_title: 'vueadmin',
|
||||||
DASHBOARD_URL: '/home',
|
DASHBOARD_URL: '/home',
|
||||||
baseURL: ''
|
baseURL: '',
|
||||||
|
// 白名单路由(不需要登录即可访问)
|
||||||
|
whiteList: ['/login', '/register', '/reset-password']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* 用户静态路由配置
|
* 静态路由配置
|
||||||
* 这些路由会根据用户角色进行过滤后添加到路由中
|
* 这些路由会根据用户角色进行过滤后添加到路由中
|
||||||
*/
|
*/
|
||||||
const userRoutes = [
|
const userRoutes = [
|
||||||
|
|||||||
@@ -1,192 +1,139 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import { useUserStore } from '@/stores/modules/user'
|
|
||||||
import { ElNotification } from 'element-plus'
|
|
||||||
import systemRouter from './systemRouter'
|
|
||||||
import userRoutes from '@/config/routes'
|
|
||||||
import NProgress from 'nprogress'
|
import NProgress from 'nprogress'
|
||||||
import tool from '@/utils/tool'
|
import 'nprogress/nprogress.css'
|
||||||
import i18n from '@/i18n'
|
import config from '../config'
|
||||||
import { beforeEach, afterEach } from './scrollBehavior'
|
import userRoutes from '../config/routes'
|
||||||
import '@/assets/css/nprogress.css'
|
import { useUserStore } from '../stores/modules/user'
|
||||||
|
import systemRoutes from './systemRoutes'
|
||||||
|
|
||||||
// 配置 NProgress
|
// 配置 NProgress
|
||||||
NProgress.configure({
|
NProgress.configure({
|
||||||
easing: 'ease',
|
|
||||||
speed: 500,
|
|
||||||
showSpinner: false,
|
showSpinner: false,
|
||||||
trickleSpeed: 200,
|
trickleSpeed: 200,
|
||||||
minimum: 0.3
|
minimum: 0.3
|
||||||
})
|
})
|
||||||
|
|
||||||
// 匹配pages里面所有的.vue文件
|
/**
|
||||||
const modules = import.meta.glob('@/pages/**/*.vue')
|
* 404 路由
|
||||||
|
*/
|
||||||
// 特殊路由模块
|
const notFoundRoute = {
|
||||||
const otherModules = {
|
path: '/:pathMatch(.*)*',
|
||||||
'404': () => import('@/layouts/other/404.vue'),
|
name: 'NotFound',
|
||||||
empty: () => import('@/layouts/other/empty.vue')
|
component: () => import('../layouts/other/404.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '404',
|
||||||
|
hidden: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 系统路由
|
// 创建路由实例
|
||||||
const routes = systemRouter
|
|
||||||
|
|
||||||
// 是否已加载过动态/静态路由
|
|
||||||
let isGetRouter = false
|
|
||||||
|
|
||||||
// 404路由移除函数
|
|
||||||
let routes_404_r = null
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes: routes
|
routes: systemRoutes
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置页面标题
|
* 组件导入映射
|
||||||
* @param {Object} meta - 路由元信息
|
|
||||||
*/
|
*/
|
||||||
const setPageTitle = (meta) => {
|
const modules = import.meta.glob('../pages/**/*.vue')
|
||||||
const title = 'VueAdmin'
|
|
||||||
if (meta?.title) {
|
/**
|
||||||
try {
|
* 动态加载组件
|
||||||
const translatedTitle = i18n.global.te(meta.title) ? i18n.global.t(meta.title) : meta.title
|
* @param {string} componentPath - 组件路径
|
||||||
document.title = `${translatedTitle} - ${title}`
|
* @returns {Promise} 组件
|
||||||
} catch (error) {
|
*/
|
||||||
document.title = `${meta.title} - ${title}`
|
function loadComponent(componentPath) {
|
||||||
}
|
// 如果组件路径以 'views/' 或 'pages/' 开头,则从相应目录加载
|
||||||
} else {
|
if (componentPath.startsWith('views/')) {
|
||||||
document.title = title
|
const path = componentPath.replace('views/', '../pages/')
|
||||||
|
return modules[`${path}.vue`]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是简单的组件名称,从 pages 目录加载
|
||||||
|
return modules[`../pages/${componentPath}/index.vue`]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查路由是否需要认证
|
* 将后端菜单转换为路由格式
|
||||||
* @param {Object} to - 目标路由
|
* @param {Array} menus - 后端返回的菜单数据
|
||||||
* @returns {boolean}
|
* @returns {Array} 路由数组
|
||||||
*/
|
*/
|
||||||
const checkAuthRequired = (to) => {
|
function transformMenusToRoutes(menus) {
|
||||||
return to.matched.some((record) => record.meta.requiresAuth !== false)
|
if (!menus || !Array.isArray(menus)) {
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return menus
|
||||||
* 移除404路由
|
.filter(menu => menu && menu.path)
|
||||||
*/
|
.map(menu => {
|
||||||
const remove404Route = () => {
|
const route = {
|
||||||
if (routes_404_r) {
|
path: menu.path,
|
||||||
router.removeRoute('404')
|
name: menu.name || menu.path.replace(/\//g, '-'),
|
||||||
routes_404_r = null
|
meta: {
|
||||||
|
title: menu.meta?.title || menu.title,
|
||||||
|
icon: menu.meta?.icon || menu.icon,
|
||||||
|
hidden: menu.hidden || menu.meta?.hidden,
|
||||||
|
keepAlive: menu.meta?.keepAlive || false,
|
||||||
|
role: menu.meta?.role || []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 处理组件
|
||||||
* 添加404路由
|
if (menu.component) {
|
||||||
*/
|
route.component = loadComponent(menu.component)
|
||||||
const add404Route = () => {
|
}
|
||||||
if (!routes_404_r) {
|
|
||||||
routes_404_r = router.addRoute({
|
// 处理子路由
|
||||||
path: '/:pathMatch(.*)*',
|
if (menu.children && menu.children.length > 0) {
|
||||||
name: '404',
|
route.children = transformMenusToRoutes(menu.children)
|
||||||
hidden: true,
|
}
|
||||||
component: otherModules['404']
|
|
||||||
|
// 处理重定向
|
||||||
|
if (menu.redirect) {
|
||||||
|
route.redirect = menu.redirect
|
||||||
|
}
|
||||||
|
|
||||||
|
return route
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加路由到路由器
|
||||||
|
* @param {Array} routes - 要添加的路由数组
|
||||||
|
*/
|
||||||
|
function addRoutes(routes) {
|
||||||
|
routes.forEach(route => {
|
||||||
|
router.addRoute(route)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载动态路由
|
* 路由守卫
|
||||||
* @param {Object} to - 目标路由对象
|
|
||||||
* @returns {boolean} 是否加载成功
|
|
||||||
*/
|
*/
|
||||||
const loadDynamicRoutes = async (to) => {
|
let isDynamicRouteLoaded = false
|
||||||
try {
|
|
||||||
// 从 store 获取菜单和用户信息
|
|
||||||
const userStore = useUserStore()
|
|
||||||
|
|
||||||
const apiMenu = userStore.getMenu() || []
|
|
||||||
const userInfo = userStore.userInfo
|
|
||||||
|
|
||||||
// 如果没有用户信息,不做处理
|
|
||||||
if (!userInfo) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据用户角色过滤静态路由
|
|
||||||
const userMenu = treeFilter(userRoutes, (node) => {
|
|
||||||
return node.meta.role
|
|
||||||
? node.meta.role.some((item) => userInfo.role?.includes(item))
|
|
||||||
: true
|
|
||||||
})
|
|
||||||
|
|
||||||
// 合并静态路由和API菜单
|
|
||||||
const menu = [...userMenu, ...apiMenu]
|
|
||||||
|
|
||||||
// 转换异步路由
|
|
||||||
const menuRouter = filterAsyncRouter(menu)
|
|
||||||
|
|
||||||
// 将树形路由转平铺
|
|
||||||
const flatMenuRouter = tool.tree_to_list(menuRouter)
|
|
||||||
|
|
||||||
// 添加所有路由
|
|
||||||
flatMenuRouter.forEach((item) => {
|
|
||||||
router.addRoute('layout', item)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 添加404路由(必须在所有路由之后)
|
|
||||||
add404Route()
|
|
||||||
|
|
||||||
isGetRouter = true
|
|
||||||
|
|
||||||
// 检查目标路由是否存在
|
|
||||||
const hasRoute = router.hasRoute(to.name) || to.matched.length > 0
|
|
||||||
|
|
||||||
if (!hasRoute && to.name !== '404') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载动态路由失败:', error)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 路由拦截器 - 全局前置守卫
|
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
// 开始进度条
|
// 开始进度条
|
||||||
NProgress.start()
|
NProgress.start()
|
||||||
|
|
||||||
// 设置页面标题
|
// 设置页面标题
|
||||||
setPageTitle(to.meta)
|
document.title = to.meta.title
|
||||||
|
? `${to.meta.title} - ${config.app_title}`
|
||||||
|
: config.app_title
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const isLoggedIn = userStore.isLoggedIn()
|
const isLoggedIn = userStore.isLoggedIn()
|
||||||
const requiresAuth = checkAuthRequired(to)
|
const whiteList = config.whiteList || []
|
||||||
|
|
||||||
// 处理404页面
|
// 1. 如果在白名单中,直接放行
|
||||||
if (to.name === '404') {
|
if (whiteList.includes(to.path)) {
|
||||||
next()
|
next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理登录页
|
// 2. 如果未登录,跳转到登录页
|
||||||
if (to.path === '/login') {
|
if (!isLoggedIn) {
|
||||||
isGetRouter = false
|
// 保存目标路由,登录后跳转
|
||||||
remove404Route()
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是系统路由,直接放行
|
|
||||||
if (routes.some((r) => r.path === to.path)) {
|
|
||||||
next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 需要认证但未登录,重定向到登录页
|
|
||||||
if (requiresAuth && !isLoggedIn) {
|
|
||||||
NProgress.done()
|
|
||||||
isGetRouter = false
|
|
||||||
userStore.clearMenu()
|
|
||||||
next({
|
next({
|
||||||
path: '/login',
|
path: '/login',
|
||||||
query: { redirect: to.fullPath }
|
query: { redirect: to.fullPath }
|
||||||
@@ -194,123 +141,105 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 整页路由处理
|
// 3. 已登录情况
|
||||||
if (to.meta.fullpage) {
|
// 如果访问登录页,重定向到首页
|
||||||
to.matched = [to.matched[to.matched.length - 1]]
|
if (to.path === '/login') {
|
||||||
}
|
next({ path: config.DASHBOARD_URL })
|
||||||
|
|
||||||
// 调用前置守卫
|
|
||||||
beforeEach(to, from)
|
|
||||||
|
|
||||||
// 加载动态/静态路由
|
|
||||||
if (!isGetRouter) {
|
|
||||||
const loadSuccess = await loadDynamicRoutes(to)
|
|
||||||
|
|
||||||
// 如果路由加载失败,跳转到404
|
|
||||||
if (!loadSuccess) {
|
|
||||||
next({ name: '404', replace: true })
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 检查路由是否存在(动态路由已加载后)
|
// 4. 动态路由加载
|
||||||
if (isGetRouter) {
|
if (!isDynamicRouteLoaded) {
|
||||||
const hasRoute = router.hasRoute(to.name)
|
try {
|
||||||
// 如果路由不存在且不是404页面,跳转到404
|
// 获取静态菜单配置
|
||||||
if (!hasRoute && to.name !== '404' && to.matched.length === 0) {
|
const staticMenus = userRoutes || []
|
||||||
next({ name: '404', replace: true })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
next()
|
// 获取后端返回的用户菜单
|
||||||
|
const backendMenus = userStore.getMenu()
|
||||||
|
|
||||||
|
// 合并静态菜单和后端菜单
|
||||||
|
// 如果后端菜单为空,只使用静态菜单
|
||||||
|
// 如果后端菜单不为空,合并两个菜单,后端菜单优先
|
||||||
|
let mergedMenus = [...staticMenus]
|
||||||
|
|
||||||
|
if (backendMenus && backendMenus.length > 0) {
|
||||||
|
// 创建菜单映射,用于去重(以路径为唯一标识)
|
||||||
|
const menuMap = new Map()
|
||||||
|
|
||||||
|
// 先添加静态菜单
|
||||||
|
staticMenus.forEach(menu => {
|
||||||
|
if (menu.path) {
|
||||||
|
menuMap.set(menu.path, menu)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 全局后置钩子
|
// 添加后端菜单,如果路径重复则覆盖
|
||||||
router.afterEach((to, from) => {
|
backendMenus.forEach(menu => {
|
||||||
// 调用后置钩子
|
if (menu.path) {
|
||||||
afterEach(to, from)
|
menuMap.set(menu.path, menu)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 转换为数组
|
||||||
|
mergedMenus = Array.from(menuMap.values())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mergedMenus && mergedMenus.length > 0) {
|
||||||
|
// 将合并后的菜单转换为路由
|
||||||
|
const dynamicRoutes = transformMenusToRoutes(mergedMenus)
|
||||||
|
|
||||||
|
// 添加动态路由到 Layout 的子路由
|
||||||
|
dynamicRoutes.forEach(route => {
|
||||||
|
router.addRoute('Layout', route)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 添加 404 路由(必须在最后添加)
|
||||||
|
router.addRoute(notFoundRoute)
|
||||||
|
|
||||||
|
isDynamicRouteLoaded = true
|
||||||
|
|
||||||
|
// 重新导航,确保新添加的路由被正确匹配
|
||||||
|
next({ ...to, replace: true })
|
||||||
|
} else {
|
||||||
|
// 如果没有菜单数据,重置并跳转到登录页
|
||||||
|
userStore.logout()
|
||||||
|
next({ path: '/login', query: { redirect: to.fullPath } })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('动态路由加载失败:', error)
|
||||||
|
|
||||||
|
// 加载失败,清除用户信息并跳转到登录页
|
||||||
|
userStore.logout()
|
||||||
|
next({
|
||||||
|
path: '/login',
|
||||||
|
query: { redirect: to.fullPath }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 动态路由已加载,直接放行
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.afterEach(() => {
|
||||||
// 结束进度条
|
// 结束进度条
|
||||||
NProgress.done()
|
NProgress.done()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 路由错误处理
|
|
||||||
router.onError((error) => {
|
|
||||||
NProgress.done()
|
|
||||||
ElNotification.error({
|
|
||||||
title: '路由错误',
|
|
||||||
message: error.message
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 动态加载组件
|
* 重置路由(用于登出时)
|
||||||
* @param {string} component - 组件路径
|
|
||||||
* @returns {Function}
|
|
||||||
*/
|
*/
|
||||||
function loadComponent(component) {
|
export function resetRouter() {
|
||||||
if (!component) {
|
// 移除所有动态添加的路由
|
||||||
return otherModules.empty
|
isDynamicRouteLoaded = false
|
||||||
}
|
|
||||||
|
|
||||||
for (const path in modules) {
|
// 重置为初始路由
|
||||||
const dir = path.split('pages/')[1]?.split('.vue')[0]
|
const newRouter = createRouter({
|
||||||
if (dir === component || dir === `${component}/index`) {
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
return () => modules[path]()
|
routes: systemRoutes
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return otherModules.empty
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换异步路由
|
|
||||||
* @param {Array} routerMap - 路由映射
|
|
||||||
* @returns {Array} 转换后的路由数组
|
|
||||||
*/
|
|
||||||
function filterAsyncRouter(routerMap) {
|
|
||||||
if (!routerMap || !Array.isArray(routerMap)) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
return routerMap.map((item) => {
|
|
||||||
const meta = item.meta || {}
|
|
||||||
|
|
||||||
// 处理外部链接特殊路由
|
|
||||||
if (meta.type === 'iframe') {
|
|
||||||
meta.url = item.path
|
|
||||||
item.path = `/i/${item.name}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
path: item.path,
|
|
||||||
name: item.name,
|
|
||||||
meta: meta,
|
|
||||||
redirect: item.redirect,
|
|
||||||
children: item.children ? filterAsyncRouter(item.children) : undefined,
|
|
||||||
component: loadComponent(item.component)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
router.matcher = newRouter.matcher
|
||||||
* 过滤树结构
|
|
||||||
* @param {Array} tree - 树结构数据
|
|
||||||
* @param {Function} func - 过滤函数
|
|
||||||
* @returns {Array} 过滤后的树结构
|
|
||||||
*/
|
|
||||||
function treeFilter(tree, func) {
|
|
||||||
if (!tree || !Array.isArray(tree)) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
return tree
|
|
||||||
.map((node) => ({ ...node }))
|
|
||||||
.filter((node) => {
|
|
||||||
node.children = node.children ? treeFilter(node.children, func) : undefined
|
|
||||||
return func(node) || (node.children && node.children.length > 0)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import { useLayoutStore } from '@/stores/modules/layout'
|
|
||||||
import { nextTick } from 'vue'
|
|
||||||
|
|
||||||
export function beforeEach(to, from) {
|
|
||||||
const adminMain = document.querySelector('#adminui-main')
|
|
||||||
if (!adminMain) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const layoutStore = useLayoutStore()
|
|
||||||
layoutStore.updateViewTags({
|
|
||||||
fullPath: from.fullPath,
|
|
||||||
scrollTop: adminMain.scrollTop
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function afterEach(to) {
|
|
||||||
const adminMain = document.querySelector('#adminui-main')
|
|
||||||
if (!adminMain) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
const layoutStore = useLayoutStore()
|
|
||||||
const beforeRoute = layoutStore.viewTags.find((v) => v.fullPath === to.fullPath)
|
|
||||||
if (beforeRoute) {
|
|
||||||
adminMain.scrollTop = beforeRoute.scrollTop || 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import config from "@/config";
|
|
||||||
|
|
||||||
//系统路由
|
|
||||||
const routes = [
|
|
||||||
{
|
|
||||||
name: "layout",
|
|
||||||
path: "/",
|
|
||||||
component: () => import("@/layouts/index.vue"),
|
|
||||||
redirect: config.DASHBOARD_URL || "/dashboard",
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/login",
|
|
||||||
component: () =>
|
|
||||||
import("@/pages/login/index.vue"),
|
|
||||||
meta: {
|
|
||||||
title: "登录",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/register",
|
|
||||||
component: () => import("@/pages/login/userRegister.vue"),
|
|
||||||
meta: {
|
|
||||||
title: "注册",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/reset-password",
|
|
||||||
component: () => import("@/pages/login/resetPassword.vue"),
|
|
||||||
meta: {
|
|
||||||
title: "重置密码",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default routes;
|
|
||||||
41
src/router/systemRoutes.js
Normal file
41
src/router/systemRoutes.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 基础路由(不需要登录)
|
||||||
|
*/
|
||||||
|
const systemRoutes = [
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'Login',
|
||||||
|
component: () => import('../pages/login/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: 'login',
|
||||||
|
hidden: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/register',
|
||||||
|
name: 'Register',
|
||||||
|
component: () => import('../pages/login/userRegister.vue'),
|
||||||
|
meta: {
|
||||||
|
title: 'register',
|
||||||
|
hidden: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/reset-password',
|
||||||
|
name: 'ResetPassword',
|
||||||
|
component: () => import('../pages/login/resetPassword.vue'),
|
||||||
|
meta: {
|
||||||
|
title: 'resetPassword',
|
||||||
|
hidden: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Layout',
|
||||||
|
component: () => import('@/layouts/index.vue'),
|
||||||
|
redirect: '/home',
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default systemRoutes
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
import { resetRouter } from '../../router'
|
||||||
|
|
||||||
export const useUserStore = defineStore('user', () => {
|
export const useUserStore = defineStore('user', () => {
|
||||||
const token = ref(localStorage.getItem('token') || '')
|
const token = ref(localStorage.getItem('token') || '')
|
||||||
@@ -52,6 +53,9 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
localStorage.removeItem('refreshToken')
|
localStorage.removeItem('refreshToken')
|
||||||
localStorage.removeItem('userInfo')
|
localStorage.removeItem('userInfo')
|
||||||
localStorage.removeItem('MENU')
|
localStorage.removeItem('MENU')
|
||||||
|
|
||||||
|
// 重置路由
|
||||||
|
resetRouter()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否已登录
|
// 检查是否已登录
|
||||||
|
|||||||
136
src/utils/menu.js
Normal file
136
src/utils/menu.js
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import routesConfig from '../config/routes'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并静态菜单和后端菜单
|
||||||
|
* @param {Array} backendMenus - 后端返回的菜单
|
||||||
|
* @returns {Array} 合并后的菜单
|
||||||
|
*/
|
||||||
|
export function mergeMenus(backendMenus = []) {
|
||||||
|
// 深拷贝静态菜单
|
||||||
|
const staticMenus = JSON.parse(JSON.stringify(routesConfig.userRoutes || []))
|
||||||
|
|
||||||
|
// 如果后端菜单为空,直接返回静态菜单
|
||||||
|
if (!backendMenus || backendMenus.length === 0) {
|
||||||
|
return staticMenus
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建菜单映射,用于去重
|
||||||
|
const menuMap = new Map()
|
||||||
|
|
||||||
|
// 添加静态菜单
|
||||||
|
staticMenus.forEach(menu => {
|
||||||
|
menuMap.set(menu.path, menu)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 添加后端菜单,如果路径重复则覆盖
|
||||||
|
backendMenus.forEach(menu => {
|
||||||
|
if (menu.path) {
|
||||||
|
menuMap.set(menu.path, menu)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 返回合并后的菜单数组
|
||||||
|
return Array.from(menuMap.values())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据路由生成菜单树
|
||||||
|
* @param {Array} routes - 路由数组
|
||||||
|
* @returns {Array} 菜单树
|
||||||
|
*/
|
||||||
|
export function generateMenuTree(routes) {
|
||||||
|
if (!routes || !Array.isArray(routes)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes
|
||||||
|
.filter(route => {
|
||||||
|
// 过滤掉隐藏的路由和没有 meta 的路由
|
||||||
|
return !route.meta?.hidden && route.meta?.title
|
||||||
|
})
|
||||||
|
.map(route => {
|
||||||
|
const menu = {
|
||||||
|
path: route.path,
|
||||||
|
name: route.name,
|
||||||
|
meta: {
|
||||||
|
title: route.meta.title,
|
||||||
|
icon: route.meta.icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理子路由
|
||||||
|
if (route.children && route.children.length > 0) {
|
||||||
|
const children = generateMenuTree(route.children)
|
||||||
|
if (children.length > 0) {
|
||||||
|
menu.children = children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return menu
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据权限过滤菜单
|
||||||
|
* @param {Array} menus - 菜单数组
|
||||||
|
* @param {Array} roles - 用户角色
|
||||||
|
* @returns {Array} 过滤后的菜单
|
||||||
|
*/
|
||||||
|
export function filterMenusByRole(menus, roles = []) {
|
||||||
|
if (!menus || !Array.isArray(menus)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return menus
|
||||||
|
.filter(menu => {
|
||||||
|
// 如果菜单没有角色要求,直接显示
|
||||||
|
if (!menu.meta?.role || menu.meta.role.length === 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户是否有菜单要求的任一角色
|
||||||
|
return menu.meta.role.some(role => roles.includes(role))
|
||||||
|
})
|
||||||
|
.map(menu => {
|
||||||
|
// 递归处理子菜单
|
||||||
|
if (menu.children && menu.children.length > 0) {
|
||||||
|
const filteredChildren = filterMenusByRole(menu.children, roles)
|
||||||
|
menu.children = filteredChildren
|
||||||
|
|
||||||
|
// 如果过滤后没有子菜单,且菜单本身没有组件,则隐藏此菜单
|
||||||
|
if (filteredChildren.length === 0 && !menu.component) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return menu
|
||||||
|
})
|
||||||
|
.filter(menu => menu !== null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据路径查找菜单
|
||||||
|
* @param {Array} menus - 菜单数组
|
||||||
|
* @param {string} path - 路径
|
||||||
|
* @returns {Object|null} 找到的菜单对象
|
||||||
|
*/
|
||||||
|
export function findMenuByPath(menus, path) {
|
||||||
|
if (!menus || !Array.isArray(menus)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const menu of menus) {
|
||||||
|
if (menu.path === path) {
|
||||||
|
return menu
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menu.children && menu.children.length > 0) {
|
||||||
|
const found = findMenuByPath(menu.children, path)
|
||||||
|
if (found) {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user