mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-08 02:36:07 +00:00
feat: 新增权限指令并集成到多个页面组件
refactor(backend): 重构数据库模型和初始化逻辑 fix(frontend): 修复权限指令在多个组件中的使用问题 perf(backend): 优化数据库连接和初始化性能 docs: 更新低代码生成器的README文档 style: 清理无用代码和注释 chore: 移除不再需要的依赖项 test: 更新用户权限相关测试用例 ci: 更新CI配置以支持新的权限检查 build: 更新依赖项版本
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
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");
|
||||
@@ -69,32 +68,14 @@ export const generator = (routers: MenuTable[]): RouteVO[] => {
|
||||
});
|
||||
};
|
||||
|
||||
// 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 mixLayoutSideMenus = ref<RouteRecordRaw[]>([]);
|
||||
// 路由是否加载完成
|
||||
const routesLoaded = ref(false);
|
||||
const isRouteGenerated = ref(false);
|
||||
|
||||
/**
|
||||
* 获取后台动态路由数据,解析并注册到全局路由
|
||||
@@ -109,65 +90,32 @@ export const usePermissionStore = defineStore("permission", () => {
|
||||
await userStore.getUserInfo();
|
||||
}
|
||||
|
||||
const routersTree = listToTree(userStore.routeList);
|
||||
const routerMap = generator(routersTree);
|
||||
const data = generator(userStore.routeList);
|
||||
|
||||
// 解析动态路由
|
||||
const dynamicRoutes = parseDynamicRoutes(routerMap);
|
||||
|
||||
// 重置路由,移除旧的动态路由
|
||||
// resetRouter();
|
||||
|
||||
// 重新添加所有路由
|
||||
// constantRoutes.forEach(route => {
|
||||
// router.addRoute(route);
|
||||
// });
|
||||
const dynamicRoutes = transformRoutes(data);
|
||||
|
||||
routes.value = [...constantRoutes, ...dynamicRoutes];
|
||||
|
||||
routesLoaded.value = true;
|
||||
isRouteGenerated.value = true;
|
||||
|
||||
return dynamicRoutes;
|
||||
} catch (error: any) {
|
||||
// 即使失败也要设置状态,避免无限重试
|
||||
routesLoaded.value = false;
|
||||
isRouteGenerated.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 setMixLayoutSideMenus = (parentPath: string) => {
|
||||
const parentMenu = routes.value.find((item) => item.path === parentPath);
|
||||
mixLayoutSideMenus.value = parentMenu?.children || [];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -184,54 +132,45 @@ export const usePermissionStore = defineStore("permission", () => {
|
||||
}
|
||||
});
|
||||
|
||||
// 重置为仅包含常量路由
|
||||
// 重置所有状态
|
||||
routes.value = [...constantRoutes];
|
||||
sideMenuRoutes.value = [];
|
||||
routesLoaded.value = false;
|
||||
routePathMap.value = {};
|
||||
mixLayoutSideMenus.value = [];
|
||||
isRouteGenerated.value = false;
|
||||
};
|
||||
|
||||
return {
|
||||
routes,
|
||||
sideMenuRoutes,
|
||||
routesLoaded,
|
||||
mixLayoutSideMenus,
|
||||
isRouteGenerated,
|
||||
generateRoutes,
|
||||
updateSideMenu,
|
||||
setMixLayoutSideMenus,
|
||||
resetRouter,
|
||||
routePathMap,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* 解析后端返回的路由数据并转换为 Vue Router 兼容的路由配置
|
||||
*
|
||||
* @param rawRoutes 后端返回的原始路由数据
|
||||
* @returns 解析后的路由集合
|
||||
* 转换后端路由数据为Vue Router配置
|
||||
* 处理组件路径映射和Layout层级嵌套
|
||||
*/
|
||||
const parseDynamicRoutes = (rawRoutes: RouteVO[]): RouteRecordRaw[] => {
|
||||
const parsedRoutes: RouteRecordRaw[] = [];
|
||||
|
||||
rawRoutes.forEach((route) => {
|
||||
const transformRoutes = (routes: RouteVO[]): RouteRecordRaw[] => {
|
||||
return routes.map((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);
|
||||
// 递归处理子路由
|
||||
if (normalizedRoute.children && normalizedRoute.children.length > 0) {
|
||||
normalizedRoute.children = transformRoutes(route.children);
|
||||
}
|
||||
|
||||
parsedRoutes.push(normalizedRoute);
|
||||
|
||||
return normalizedRoute;
|
||||
});
|
||||
|
||||
return parsedRoutes;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,13 +3,13 @@ import { store, useTagsViewStore, usePermissionStoreHook, useDictStoreHook } fro
|
||||
import AuthAPI, {type LoginFormData } from "@/api/system/auth";
|
||||
import UserAPI, {type UserInfo } from "@/api/system/user";
|
||||
import type { MenuTable } from "@/api/system/menu";
|
||||
|
||||
import { Auth } from "@/utils/auth";
|
||||
|
||||
export const useUserStore = defineStore("user", {
|
||||
state: () => ({
|
||||
basicInfo: {} as UserInfo,
|
||||
routeList: [] as MenuTable[],
|
||||
prems: [] as string[],
|
||||
hasGetRoute: false,
|
||||
|
||||
// 记住我状态
|
||||
@@ -19,6 +19,7 @@ export const useUserStore = defineStore("user", {
|
||||
getters: {
|
||||
getBasicInfo: (state) => state.basicInfo,
|
||||
getRouteList: (state) => state.routeList,
|
||||
getPerms: (state) => state.prems,
|
||||
getHasGetRoute: (state) => state.hasGetRoute,
|
||||
},
|
||||
|
||||
@@ -28,19 +29,61 @@ export const useUserStore = defineStore("user", {
|
||||
const response = await UserAPI.getCurrentUserInfo();
|
||||
const routers: MenuTable[] = response.data.data.menus || [];
|
||||
delete response.data.data.menus;
|
||||
this.setRoute(routers);
|
||||
this.basicInfo = { ...this.basicInfo, ...response.data.data };
|
||||
// 然后设置路由信息
|
||||
this.setRoute(routers);
|
||||
},
|
||||
|
||||
// 设置用户信息
|
||||
setUserInfo(info: UserInfo) {
|
||||
this.basicInfo = info;
|
||||
// 设置用户信息后自动更新权限
|
||||
this.setPermissions([]);
|
||||
},
|
||||
|
||||
// 设置路由
|
||||
setRoute(routers: MenuTable[]) {
|
||||
this.routeList = routers;
|
||||
this.hasGetRoute = true;
|
||||
// 设置路由后自动更新权限
|
||||
this.setPermissions(routers);
|
||||
},
|
||||
|
||||
setPermissions(menus: MenuTable[]) {
|
||||
this.prems = []; // 先清空现有权限
|
||||
|
||||
// 确保 basicInfo.roles 存在,避免空值错误
|
||||
if (!this.basicInfo.roles) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 合并所有角色的菜单列表,使用数组扁平化方法简化代码并过滤undefined
|
||||
const roleMenus = this.basicInfo.roles
|
||||
.filter(role => role.menus && role.menus.length > 0)
|
||||
.flatMap(role => role.menus)
|
||||
.filter((menu): menu is MenuTable => menu !== undefined);
|
||||
|
||||
// 合并传入的菜单和角色菜单
|
||||
const allMenus = [...menus, ...roleMenus];
|
||||
|
||||
const permissionSet = new Set<string>();
|
||||
const collect = (items: MenuTable[]) => {
|
||||
items.forEach((item) => {
|
||||
// 收集当前菜单的权限
|
||||
if (item.permission) {
|
||||
permissionSet.add(item.permission);
|
||||
}
|
||||
|
||||
// 递归收集子菜单的权限,确保子菜单不包含undefined
|
||||
if (item.children && item.children.length > 0) {
|
||||
collect(item.children.filter((child): child is MenuTable => child !== undefined));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 收集所有权限
|
||||
collect(allMenus);
|
||||
this.prems = Array.from(permissionSet);
|
||||
},
|
||||
setAvatar(avatar: string) {
|
||||
this.basicInfo = { ...this.basicInfo, avatar };
|
||||
|
||||
Reference in New Issue
Block a user