diff --git a/mock/menu.mock.ts b/mock/menu.mock.ts index 993bc63d..ce58226c 100644 --- a/mock/menu.mock.ts +++ b/mock/menu.mock.ts @@ -16,7 +16,6 @@ export default defineMock([ title: "系统管理", icon: "system", hidden: false, - roles: ["ADMIN"], alwaysShow: false, params: null, }, @@ -29,7 +28,6 @@ export default defineMock([ title: "用户管理", icon: "user", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -43,7 +41,6 @@ export default defineMock([ title: "角色管理", icon: "role", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -57,7 +54,6 @@ export default defineMock([ title: "菜单管理", icon: "menu", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -71,7 +67,6 @@ export default defineMock([ title: "部门管理", icon: "tree", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -85,7 +80,6 @@ export default defineMock([ title: "字典管理", icon: "dict", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -101,7 +95,6 @@ export default defineMock([ title: "接口文档", icon: "api", hidden: false, - roles: ["ADMIN"], alwaysShow: true, params: null, }, @@ -114,7 +107,6 @@ export default defineMock([ title: "Apifox", icon: "api", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -131,7 +123,6 @@ export default defineMock([ title: "平台文档", icon: "document", hidden: false, - roles: ["ADMIN"], alwaysShow: false, params: null, }, @@ -144,7 +135,6 @@ export default defineMock([ title: "平台文档(内嵌)", icon: "document", hidden: false, - roles: ["ADMIN"], alwaysShow: false, params: null, }, @@ -156,7 +146,6 @@ export default defineMock([ title: "平台文档(外链)", icon: "link", hidden: false, - roles: ["ADMIN"], alwaysShow: false, params: null, }, @@ -171,7 +160,6 @@ export default defineMock([ title: "多级菜单", icon: "cascader", hidden: false, - roles: ["ADMIN"], alwaysShow: true, params: null, }, @@ -184,7 +172,6 @@ export default defineMock([ title: "菜单一级", icon: "", hidden: false, - roles: ["ADMIN"], alwaysShow: true, params: null, }, @@ -197,7 +184,6 @@ export default defineMock([ title: "菜单二级", icon: "", hidden: false, - roles: ["ADMIN"], alwaysShow: false, params: null, }, @@ -210,7 +196,6 @@ export default defineMock([ title: "菜单三级-1", icon: "", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -224,7 +209,6 @@ export default defineMock([ title: "菜单三级-2", icon: "", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -244,7 +228,6 @@ export default defineMock([ title: "组件封装", icon: "menu", hidden: false, - roles: ["ADMIN"], alwaysShow: false, params: null, }, @@ -257,7 +240,6 @@ export default defineMock([ title: "增删改查", icon: "", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -271,7 +253,6 @@ export default defineMock([ title: "列表选择器", icon: "", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -285,7 +266,6 @@ export default defineMock([ title: "富文本编辑器", icon: "", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -299,7 +279,6 @@ export default defineMock([ title: "图片上传", icon: "", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -313,7 +292,6 @@ export default defineMock([ title: "图标选择器", icon: "", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -327,7 +305,6 @@ export default defineMock([ title: "字典组件", icon: "", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -343,7 +320,6 @@ export default defineMock([ title: "路由参数", icon: "el-icon-ElementPlus", hidden: false, - roles: ["ADMIN"], alwaysShow: true, params: null, }, @@ -356,7 +332,6 @@ export default defineMock([ title: "参数(type=1)", icon: "el-icon-Star", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: { @@ -372,7 +347,6 @@ export default defineMock([ title: "参数(type=2)", icon: "el-icon-StarFilled", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: { @@ -390,7 +364,6 @@ export default defineMock([ title: "功能演示", icon: "menu", hidden: false, - roles: ["ADMIN"], alwaysShow: false, params: null, }, @@ -403,7 +376,6 @@ export default defineMock([ title: "Icons", icon: "el-icon-Notification", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -417,7 +389,6 @@ export default defineMock([ title: "Websocket", icon: "", hidden: false, - roles: ["ADMIN"], keepAlive: true, alwaysShow: false, params: null, @@ -431,7 +402,6 @@ export default defineMock([ title: "敬请期待...", icon: "", hidden: false, - roles: ["ADMIN"], alwaysShow: false, params: null, }, diff --git a/src/api/menu.ts b/src/api/menu.ts index 31065f16..528aff7e 100644 --- a/src/api/menu.ts +++ b/src/api/menu.ts @@ -8,9 +8,12 @@ class MenuAPI { * * @returns 路由列表 */ - static getRoutes() { + static getRoutes(roles: string[]) { + const queryParams = roles + .map((role) => `roles=${encodeURIComponent(role)}`) + .join("&"); return request({ - url: `${MENU_BASE_URL}/routes`, + url: `${MENU_BASE_URL}/routes?${queryParams}`, method: "get", }); } @@ -202,8 +205,6 @@ export interface Meta { icon?: string; /** 【菜单】是否开启页面缓存 */ keepAlive?: boolean; - /** 拥有路由权限的角色编码 */ - roles?: string[]; /** 路由title */ title?: string; } diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts index 46f37386..31db0e7b 100644 --- a/src/store/modules/app.ts +++ b/src/store/modules/app.ts @@ -99,8 +99,11 @@ export const useAppStore = defineStore("app", () => { }; }); -// 手动提供给 useStore() 函数 pinia 实例 -// https://pinia.vuejs.org/zh/core-concepts/outside-component-usage.html#using-a-store-outside-of-a-component +/** + * 用于在组件外部(如在Pinia Store 中)使用 Pinia 提供的 store 实例。 + * 官方文档解释了如何在组件外部使用 Pinia Store: + * https://pinia.vuejs.org/core-concepts/outside-component-usage.html#using-a-store-outside-of-a-component + */ export function useAppStoreHook() { return useAppStore(store); } diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts index 25306e68..5549e1a5 100644 --- a/src/store/modules/permission.ts +++ b/src/store/modules/permission.ts @@ -6,88 +6,26 @@ import MenuAPI, { RouteVO } from "@/api/menu"; const modules = import.meta.glob("../../views/**/**.vue"); const Layout = () => import("@/layout/index.vue"); -/** - * Use meta.role to determine if the current user has permission - * - * @param roles 用户角色集合 - * @param route 路由 - * @returns - */ -const hasPermission = (roles: string[], route: RouteRecordRaw) => { - if (route.meta && route.meta.roles) { - // 角色【超级管理员】拥有所有权限,忽略校验 - if (roles.includes("ROOT")) { - return true; - } - return roles.some((role) => { - if (route.meta?.roles) { - return route.meta.roles.includes(role); - } - }); - } - return false; -}; - -/** - * 递归过滤有权限的动态路由 - * - * @param routes 接口返回所有的动态路由 - * @param roles 用户角色集合 - * @returns 返回用户有权限的动态路由 - */ -const filterAsyncRoutes = (routes: RouteVO[], roles: string[]) => { - const asyncRoutes: RouteRecordRaw[] = []; - routes.forEach((route) => { - const tmpRoute = { ...route } as RouteRecordRaw; // 深拷贝 route 对象 避免污染 - if (hasPermission(roles, tmpRoute)) { - // 如果是顶级目录,替换为 Layout 组件 - if (tmpRoute.component?.toString() == "Layout") { - tmpRoute.component = Layout; - } else { - // 如果是子目录,动态加载组件 - const component = modules[`../../views/${tmpRoute.component}.vue`]; - if (component) { - tmpRoute.component = component; - } else { - tmpRoute.component = modules[`../../views/error-page/404.vue`]; - } - } - - if (tmpRoute.children) { - tmpRoute.children = filterAsyncRoutes(route.children, roles); - } - - asyncRoutes.push(tmpRoute); - } - }); - - return asyncRoutes; -}; -// setup export const usePermissionStore = defineStore("permission", () => { - // state + /** + * 应用中所有的路由列表,包括静态路由和动态路由 + */ const routes = ref([]); - - // actions - function setRoutes(newRoutes: RouteRecordRaw[]) { - routes.value = constantRoutes.concat(newRoutes); - } + /** + * 混合模式左侧菜单列表 + */ + const mixLeftMenus = ref([]); /** * 生成动态路由 - * - * @param roles 用户角色集合 - * @returns */ function generateRoutes(roles: string[]) { return new Promise((resolve, reject) => { - // 接口获取所有路由 - MenuAPI.getRoutes() + MenuAPI.getRoutes(roles) .then((data) => { - // 过滤有权限的动态路由 - const accessedRoutes = filterAsyncRoutes(data, roles); - setRoutes(accessedRoutes); - resolve(accessedRoutes); + const dynamicRoutes = transformRoutes(data); + routes.value = constantRoutes.concat(dynamicRoutes); + resolve(dynamicRoutes); }) .catch((error) => { reject(error); @@ -96,25 +34,60 @@ export const usePermissionStore = defineStore("permission", () => { } /** - * 获取与激活的顶部菜单项相关的混合模式左侧菜单集合 + * 混合模式菜单下根据顶部菜单路径设置左侧菜单 + * + * @param topMenuPath - 顶部菜单路径 */ - const mixLeftMenus = ref([]); - function setMixLeftMenus(topMenuPath: string) { + const setMixLeftMenus = (topMenuPath: string) => { const matchedItem = routes.value.find((item) => item.path === topMenuPath); if (matchedItem && matchedItem.children) { mixLeftMenus.value = matchedItem.children; } - } + }; + return { routes, - setRoutes, generateRoutes, mixLeftMenus, setMixLeftMenus, }; }); -// 非setup +/** + * 转换路由数据为组件 + */ +const transformRoutes = (routes: RouteVO[]) => { + const asyncRoutes: RouteRecordRaw[] = []; + routes.forEach((route) => { + const tmpRoute = { ...route } as RouteRecordRaw; + // 顶级目录,替换为 Layout 组件 + if (tmpRoute.component?.toString() == "Layout") { + tmpRoute.component = Layout; + } else { + // 其他菜单,根据组件路径动态加载组件 + const component = modules[`../../views/${tmpRoute.component}.vue`]; + if (component) { + tmpRoute.component = component; + } else { + tmpRoute.component = modules[`../../views/error-page/404.vue`]; + } + } + + if (tmpRoute.children) { + tmpRoute.children = transformRoutes(route.children); + } + + asyncRoutes.push(tmpRoute); + }); + + return asyncRoutes; +}; + +/** + * 用于在组件外部(如在Pinia Store 中)使用 Pinia 提供的 store 实例。 + * 官方文档解释了如何在组件外部使用 Pinia Store: + * https://pinia.vuejs.org/core-concepts/outside-component-usage.html#using-a-store-outside-of-a-component + */ export function usePermissionStoreHook() { return usePermissionStore(store); } diff --git a/src/types/router.d.ts b/src/types/router.d.ts index 65c47ca3..a175ea18 100644 --- a/src/types/router.d.ts +++ b/src/types/router.d.ts @@ -50,11 +50,5 @@ declare module "vue-router" { * @default false */ breadcrumb?: boolean; - - /** - * 拥有访问该菜单权限的角色编码集合 - * @example ['admin', 'editor'] - */ - roles?: string[]; } }