import type { RouteRecordRaw } from "vue-router"; import { constantRoutes } from "@/router"; import { store } from "@/store"; import router from "@/router"; import MenuAPI, { type RouteVO } from "@/api/account/menu"; const isTest = false//是否是本地调试 const modules = import.meta.glob("../../views/**/**.vue"); const Layout = () => import("@/layout/index.vue"); export const usePermissionStore = defineStore("permission", () => { // 储所有路由,包括静态路由和动态路由 const routes = ref(constantRoutes); // 混合模式左侧菜单路由 const mixedLayoutLeftRoutes = ref([]); // 路由是否加载完成 const isRoutesLoaded = ref(false); const menus = ref([]) /** * 获取后台动态路由数据,解析并注册到全局路由 * * @returns Promise 解析后的动态路由列表 */ function generateRoutes() { return new Promise((resolve, reject) => { if (isTest) { isRoutesLoaded.value = true; resolve(constantRoutes); } MenuAPI.getRoutes() .then((data) => { menus.value = data if (!isTest) { const dynamicRoutes = parseDynamicRoutes(data.filter(v => v.type == 0)); dynamicRoutes.forEach((route) => { //过滤出可见子节点 let onlyOneChild = null const showingChildren = route.children.filter((route) => { if (!route.meta?.hidden) { onlyOneChild = route; return true; } return false; }); // 仅有一个节点 if (showingChildren.length === 1 && onlyOneChild) { route.redirect = onlyOneChild.path; } }) routes.value = [...constantRoutes, ...dynamicRoutes]; isRoutesLoaded.value = true; resolve(dynamicRoutes); } else { isRoutesLoaded.value = true; resolve(constantRoutes); } }) .catch((error) => { reject(error); }); }); } /** * 根据父菜单路径设置混合模式左侧菜单 * * @param parentPath 父菜单的路径,用于查找对应的菜单项 */ const setMixedLayoutLeftRoutes = (parentPath: string) => { const matchedItem = routes.value.find((item) => item.path === parentPath); if (matchedItem && matchedItem.children) { mixedLayoutLeftRoutes.value = matchedItem.children; } }; /** * 重置路由 */ const resetRouter = () => { // 清空本地存储的路由和菜单数据 if (!isTest) { routes.value = []; } routes.value = constantRoutes; mixedLayoutLeftRoutes.value = []; // 从 Vue Router 中移除所有动态注册的路由 router.getRoutes().forEach((route) => { if (route.name) { router.removeRoute(route.name); } }); isRoutesLoaded.value = false; }; function getMenuMap() { // 初始化Map const map = new Map(); // 定义递归函数处理菜单节点 function processMenuNode(menuNode) { // 将当前节点存入Map map.set(menuNode.menuId, menuNode); // 如果有子节点且子节点数组不为空,则递归处理每个子节点 if (menuNode.children && Array.isArray(menuNode.children) && menuNode.children.length > 0) { menuNode.children.forEach((childNode) => { processMenuNode(childNode); }); } } // 遍历根级菜单,逐个处理 if (menus.value && Array.isArray(menus.value)) { menus.value.forEach((rootMenu) => { processMenuNode(rootMenu); }); } return map; } function returnMenuName(menuId: number | string) { const menu = menusIdMap.value.get(`${menuId}`); return menu ? menu.title : ''; } const menusIdMap = computed(() => { const map = getMenuMap(); return map; }); function menuJump(menuId: number | string) { const menu = menusIdMap.value.get(`${menuId}`); console.log('menu', menu); if (menu) { if (menu.name) { router.push({ name: menu.name as string }) } else { router.push({ path: menu.path as string }) } } } return { menuJump, returnMenuName, menus, routes, mixedLayoutLeftRoutes, isRoutesLoaded, generateRoutes, setMixedLayoutLeftRoutes, resetRouter, }; }); /** * 解析后端返回的路由数据并转换为 Vue Router 兼容的路由配置 * * 1. 遍历 `rawRoutes` 并转换为 `RouteRecordRaw` 格式。 * 2. 若 `component` 为 `"Layout"`,则替换为 `Layout` 组件。 * 3. 若 `component` 为字符串路径,则动态加载对应的 Vue 组件,找不到则默认 `404.vue`。 * 4. 递归解析 `children`,确保子路由也被正确转换。 * * @param rawRoutes 后端返回的原始路由数据 * @returns 解析后的路由配置数组 */ const parseDynamicRoutes = (rawRoutes: RouteVO[]): RouteRecordRaw[] => { const parsedRoutes: RouteRecordRaw[] = []; rawRoutes.forEach((route) => { const normalizedRoute = { path: route.path, meta: { title: route.title, icon: route.icon, keepAlive: route.cache, alwaysShow: route.path && route.path.startsWith('/') ? true : false, hidden: route.hidden, }, name: route.name, children: route.children, component: route.component, } as RouteRecordRaw; // 处理组件路径 if (route.pid === null) { normalizedRoute.component = Layout } else { normalizedRoute.component = modules[`../../views/${normalizedRoute.component}.vue`] || modules["../../views/error-page/404.vue"]; } // 递归解析子路由 if (normalizedRoute.children) { // normalizedRoute.redirect = (!normalizedRoute.redirect && route.children.length <= 1) ? normalizedRoute.children[0].path : normalizedRoute.path normalizedRoute.children = parseDynamicRoutes(route.children); } if (normalizedRoute.path !== '/' && normalizedRoute.path) { parsedRoutes.push(normalizedRoute); } }); return parsedRoutes; }; /** * 在组件外使用 Pinia store 实例 @see https://pinia.vuejs.org/core-concepts/outside-component-usage.html */ export function usePermissionStoreHook() { return usePermissionStore(store); }