Files
vue3-element-admin/src/store/modules/permission.store.ts
zimo493 d90ccb248c feat: (keep-alive)优化页面缓存机制
- 重构 AppMain 组件,引入 KeepCache 组件实现统一缓存

- 新增 DemoDetail 组件作为缓存测试页面

- 更新 TagsView 组件,优化缓存路由逻辑

- 修改 permission.store.ts,增加 allCacheRoutes 状态管理

- 更新 tags-view.store.ts,实现缓存路由的动态设置

- 调整多级菜单示例,支持缓存功能
2025-08-14 09:40:48 +08:00

161 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { RouteRecordRaw } from "vue-router";
import { constantRoutes } from "@/router";
import { store } from "@/store";
import router from "@/router";
import MenuAPI, { type RouteVO } from "@/api/system/menu.api";
const modules = import.meta.glob("../../views/**/**.vue");
const Layout = () => import("@/layouts/index.vue");
export const usePermissionStore = defineStore("permission", () => {
// 所有路由(静态路由 + 动态路由)
const routes = ref<RouteRecordRaw[]>([]);
// 混合布局的左侧菜单路由
const mixLayoutSideMenus = ref<RouteRecordRaw[]>([]);
// 动态路由是否已生成
const isDynamicRoutesGenerated = ref(false);
const allCacheRoutes = ref<string[][]>([]);
/**
* 生成动态路由
*/
async function generateRoutes(): Promise<RouteRecordRaw[]> {
try {
const data = await MenuAPI.getRoutes();
const dynamicRoutes = parseDynamicRoutes(data);
routes.value = [...constantRoutes, ...dynamicRoutes];
setAllCacheRoutes(routes.value);
isDynamicRoutesGenerated.value = true;
return dynamicRoutes;
} catch (error) {
console.error("❌ Failed to generate routes:", error);
isDynamicRoutesGenerated.value = false;
throw error;
}
}
/**
* 设置混合布局的左侧菜单
*/
const setMixLayoutSideMenus = (parentPath: string) => {
const parentMenu = routes.value.find((item) => item.path === parentPath);
mixLayoutSideMenus.value = parentMenu?.children || [];
};
/**
* 重置路由状态
*/
const resetRouter = () => {
// 移除动态路由
const constantRouteNames = new Set(constantRoutes.map((route) => route.name).filter(Boolean));
routes.value.forEach((route) => {
if (route.name && !constantRouteNames.has(route.name)) {
router.removeRoute(route.name);
}
});
// 重置状态
routes.value = [...constantRoutes];
mixLayoutSideMenus.value = [];
isDynamicRoutesGenerated.value = false;
};
/**
* 获取所有的缓存路由
* @param userRoutes 用户路由配置
*/
const setAllCacheRoutes = (userRoutes: RouteRecordRaw[]) => {
if (!userRoutes?.length) {
allCacheRoutes.value = [];
return;
}
const result: string[][] = [];
userRoutes.forEach((route) => {
if (route.children?.length) {
traverseRoutes(route.children, [], result);
}
});
allCacheRoutes.value = result;
};
return {
routes,
mixLayoutSideMenus,
isDynamicRoutesGenerated,
allCacheRoutes,
generateRoutes,
setMixLayoutSideMenus,
resetRouter,
};
});
/**
* 解析后端返回的路由数据并转换为 Vue Router 兼容的路由配置
*
* @param rawRoutes 后端返回的原始路由数据
* @returns 解析后的路由集合
*/
const parseDynamicRoutes = (rawRoutes: RouteVO[]): RouteRecordRaw[] => {
const parsedRoutes: RouteRecordRaw[] = [];
rawRoutes.forEach((route) => {
const normalizedRoute = { ...route } as RouteRecordRaw;
// 处理组件路径
normalizedRoute.component =
normalizedRoute.component?.toString() === "Layout"
? Layout
: modules[`../../views/${normalizedRoute.component}.vue`] ||
modules["../../views/error-page/404.vue"];
// 递归解析子路由
if (normalizedRoute.children) {
normalizedRoute.children = parseDynamicRoutes(route.children);
}
parsedRoutes.push(normalizedRoute);
});
return parsedRoutes;
};
/**
* 遍历路由树收集缓存路由
* @param nodes 路由节点
* @param path 当前路径
* @param result 结果数组
*/
const traverseRoutes = (nodes: RouteRecordRaw[], path: string[], result: string[][]) => {
nodes.forEach((node) => {
const newPath: string[] = node.name ? [...path, String(node.name)] : [...path];
// 叶子节点且需要缓存
if (!node.children?.length && node.meta?.keepAlive) {
result.push(newPath);
}
// 递归处理子节点
if (node.children?.length) {
traverseRoutes(node.children, newPath, result);
}
});
};
/**
* 导出此hook函数用于在非组件环境(如其他store、工具函数等)中获取权限store实例
*
* 在组件中可直接使用usePermissionStore()但在组件外部需要传入store实例
* 此函数简化了这个过程避免每次都手动传入store参数
*/
export function usePermissionStoreHook() {
return usePermissionStore(store);
}