Files
FastapiAdmin/frontend/src/store/modules/permission.store.ts
T
zhangtao 0374292b67 fix(请求拦截器): 处理blob类型错误响应并优化导出功能
refactor(路由): 优化混合布局路由解析和菜单更新逻辑
perf(权限存储): 使用路由映射表提高菜单查找效率
style(顶部菜单): 优化菜单项显示逻辑和过滤隐藏项
fix(导航栏): 确保用户菜单在移动端正常显示
refactor(导出功能): 统一处理导出错误和资源释放
2025-08-01 00:46:28 +08:00

246 lines
7.2 KiB
TypeScript

import type { RouteRecordRaw } from "vue-router";
import router, { constantRoutes } from "@/router";
import { store, useUserStore } from "@/store";
import { listToTree } from "@/utils/common";
import { MenuTable } from "@/api/system/menu";
const modules = import.meta.glob("../../views/**/**.vue");
const Layout = () => import("@/layouts/index.vue");
export interface Meta {
/** 【目录】只有一个子路由是否始终显示 */
alwaysShow?: boolean;
/** 是否隐藏(true-是 false-否) */
hidden?: boolean;
/** ICON */
icon?: string;
/** 【菜单】是否开启页面缓存 */
keepAlive?: boolean;
/** 路由title */
title?: string;
/** 排序 */
order?: number;
/** 参数 */
params?: { key: string; value: string; }[];
/** 是否固定路由 */
affix?: boolean;
}
// 修改 component 类型,使其能接受动态导入函数
export interface RouteVO {
/** 子路由列表 */
children: RouteVO[];
/** 组件路径或组件导入函数 */
component?: string | (() => Promise<Component>);
/** 路由属性 */
meta?: Meta;
/** 路由名称 */
name?: string;
/** 路由路径 */
path?: string;
/** 跳转链接 */
redirect?: string;
}
export const generator = (routers: MenuTable[]): RouteVO[] => {
return routers.map((item) => {
const currentRouter: RouteVO = {
children: [],
path: item.route_path,
name: item.route_name,
component: item.component_path,
redirect: item.redirect,
meta: {
title: item.title,
icon: item.icon || undefined,
keepAlive: item.keep_alive,
hidden: item.hidden,
order: item.order,
alwaysShow: item.always_show,
params: item.params,
affix: item.affix,
},
};
if (item.children && item.children.length > 0) {
currentRouter.children = item.children ? generator(item.children) : [];
}
return currentRouter;
});
};
// router.beforeEach(async (to) => {
// const accessToken = Auth.getAccessToken();
// const userStore = useUserStore();
// if (!accessToken && to.name !== "Login") {
// return { name: "Login" };
// } else if (accessToken && to.name === "Login") {
// return { name: "Index" };
// } else if (accessToken && !userStore.hasGetRoute) {
// await userStore.getUserInfo();
// const routersTree = listToTree(userStore.routeList);
// const routerMap = generator(routersTree);
// constantRoutes[0].children = [...(constantRoutes[0].children || []), ...routerMap].filter(Boolean) as RouteRecordRaw[];
// router.addRoute(constantRoutes[0]);
// return to.fullPath;
// }
// });
export const usePermissionStore = defineStore("permission", () => {
// 存储所有路由,包括静态路由和动态路由
const routes = ref<RouteRecordRaw[]>([]);
// 混合模式左侧菜单路由
const sideMenuRoutes = ref<RouteRecordRaw[]>([]);
// 路由是否加载完成
const routesLoaded = ref(false);
/**
* 获取后台动态路由数据,解析并注册到全局路由
*
* @returns Promise<RouteRecordRaw[]> 解析后的动态路由列表
*/
async function generateRoutes() {
try {
const userStore = useUserStore();
// 确保获取用户信息和路由列表
if (!userStore.hasGetRoute) {
await userStore.getUserInfo();
}
const routersTree = listToTree(userStore.routeList);
const routerMap = generator(routersTree);
// 解析动态路由
const dynamicRoutes = parseDynamicRoutes(routerMap);
// 重置路由,移除旧的动态路由
// resetRouter();
// 重新添加所有路由
// constantRoutes.forEach(route => {
// router.addRoute(route);
// });
routes.value = [...constantRoutes, ...dynamicRoutes];
routesLoaded.value = true;
return dynamicRoutes;
} catch (error: any) {
// 即使失败也要设置状态,避免无限重试
routesLoaded.value = false;
throw error;
}
}
// 用于存储路由路径到路由项的映射,提高查找效率
const routePathMap = ref<Record<string, RouteRecordRaw>>({});
// 当routes更新时,同步更新routePathMap
watch(routes, (newRoutes) => {
const newMap: Record<string, RouteRecordRaw> = {};
const buildMap = (routes: RouteRecordRaw[]) => {
routes.forEach(route => {
if (route.path) newMap[route.path] = route;
if (route.children) buildMap(route.children);
});
};
buildMap(newRoutes);
routePathMap.value = newMap;
}, { immediate: true });
/**
* 根据父菜单路径设置侧边菜单
*
* @param parentPath 父菜单的路径,用于查找对应的菜单项
*/
const updateSideMenu = (parentPath: string) => {
// 使用映射表进行O(1)时间复杂度的查找
const matchedItem = routePathMap.value[parentPath];
if (matchedItem && matchedItem.children) {
// 只有当子菜单发生变化时才更新,避免不必要的重渲染
if (JSON.stringify(matchedItem.children) !== JSON.stringify(sideMenuRoutes.value)) {
sideMenuRoutes.value = matchedItem.children;
}
} else {
sideMenuRoutes.value = [];
}
};
/**
* 重置路由
*/
const resetRouter = () => {
// 创建常量路由名称集合,用于O(1)时间复杂度的查找
const constantRouteNames = new Set(constantRoutes.map((route) => route.name).filter(Boolean));
// 从 router 实例中移除动态路由
routes.value.forEach((route) => {
if (route.name && !constantRouteNames.has(route.name)) {
router.removeRoute(route.name);
}
});
// 重置为仅包含常量路由
routes.value = [...constantRoutes];
sideMenuRoutes.value = [];
routesLoaded.value = false;
routePathMap.value = {};
};
return {
routes,
sideMenuRoutes,
routesLoaded,
generateRoutes,
updateSideMenu,
resetRouter,
routePathMap,
};
});
/**
* 解析后端返回的路由数据并转换为 Vue Router 兼容的路由配置
*
* @param rawRoutes 后端返回的原始路由数据
* @returns 解析后的路由集合
*/
const parseDynamicRoutes = (rawRoutes: RouteVO[]): RouteRecordRaw[] => {
const parsedRoutes: RouteRecordRaw[] = [];
rawRoutes.forEach((route) => {
const normalizedRoute = { ...route } as RouteRecordRaw;
// http://localhost:5180/#/dashboard/workplace
// http://localhost:5180/#/dashboard/workplace
// 处理组件路径
normalizedRoute.component =
!normalizedRoute.component
? Layout
: modules[`../../views/${normalizedRoute.component}.vue`] ||
modules["../../views/error/404.vue"];
// 递归解析子路由
if (normalizedRoute.children) {
normalizedRoute.children = parseDynamicRoutes(route.children);
}
parsedRoutes.push(normalizedRoute);
});
return parsedRoutes;
};
/**
* 导出此hook函数用于在非组件环境(如其他store、工具函数等)中获取权限store实例
*
* 在组件中可直接使用usePermissionStore(),但在组件外部需要传入store实例
* 此函数简化了这个过程,避免每次都手动传入store参数
*/
export function usePermissionStoreHook() {
return usePermissionStore(store);
}