| | |
| | | import { REDIRECT_ROUTE } from '/@/router/constant'; |
| | | import type { AppRouteRecordRaw, Menu } from '/@/router/types'; |
| | | import store from '/@/store/index'; |
| | | import { hotModuleUnregisterModule } from '/@/utils/helper/vuexHelper'; |
| | | |
| | | import { VuexModule, Mutation, Module, getModule, Action } from 'vuex-module-decorators'; |
| | | import { defineStore } from 'pinia'; |
| | | import { store } from '/@/store'; |
| | | import { useI18n } from '/@/hooks/web/useI18n'; |
| | | import { useUserStore } from './user'; |
| | | import { useAppStoreWithOut } from './app'; |
| | | import { toRaw } from 'vue'; |
| | | import { transformObjToRoute, flatMultiLevelRoutes } from '/@/router/helper/routeHelper'; |
| | | import { transformRouteToMenu } from '/@/router/helper/menuHelper'; |
| | | |
| | | import projectSetting from '/@/settings/projectSetting'; |
| | | |
| | | import { PermissionModeEnum } from '/@/enums/appEnum'; |
| | | |
| | | import { appStore } from '/@/store/modules/app'; |
| | | import { userStore } from '/@/store/modules/user'; |
| | | |
| | | import { asyncRoutes } from '/@/router/routes'; |
| | | import { filter } from '/@/utils/helper/treeHelper'; |
| | | import { toRaw } from 'vue'; |
| | | import { getMenuListById } from '/@/api/sys/menu'; |
| | | import { ERROR_LOG_ROUTE, PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic'; |
| | | |
| | | import { genRouteModule, transformObjToRoute } from '/@/utils/helper/routeHelper'; |
| | | import { transformRouteToMenu } from '/@/utils/helper/menuHelper'; |
| | | import { filter } from '/@/utils/helper/treeHelper'; |
| | | |
| | | import { getMenuList } from '/@/api/sys/menu'; |
| | | import { getPermCode } from '/@/api/sys/user'; |
| | | |
| | | import { useMessage } from '/@/hooks/web/useMessage'; |
| | | // import { warn } from '/@/utils/log'; |
| | | import { PageEnum } from '/@/enums/pageEnum'; |
| | | |
| | | const { createMessage } = useMessage(); |
| | | const NAME = 'permission'; |
| | | hotModuleUnregisterModule(NAME); |
| | | @Module({ dynamic: true, namespaced: true, store, name: NAME }) |
| | | class Permission extends VuexModule { |
| | | interface PermissionState { |
| | | // Permission code list |
| | | private permCodeListState: string[] = []; |
| | | |
| | | // 权限代码列表 |
| | | permCodeList: string[] | number[]; |
| | | // Whether the route has been dynamically added |
| | | private isDynamicAddedRouteState = false; |
| | | |
| | | // 路由是否动态添加 |
| | | isDynamicAddedRoute: boolean; |
| | | // To trigger a menu update |
| | | private lastBuildMenuTimeState = 0; |
| | | |
| | | // 触发菜单更新 |
| | | lastBuildMenuTime: number; |
| | | // Backstage menu list |
| | | private backMenuListState: Menu[] = []; |
| | | |
| | | get getPermCodeListState() { |
| | | return this.permCodeListState; |
| | | } |
| | | |
| | | get getBackMenuListState() { |
| | | return this.backMenuListState; |
| | | } |
| | | |
| | | get getLastBuildMenuTimeState() { |
| | | return this.lastBuildMenuTimeState; |
| | | } |
| | | |
| | | get getIsDynamicAddedRouteState() { |
| | | return this.isDynamicAddedRouteState; |
| | | } |
| | | |
| | | @Mutation |
| | | commitPermCodeListState(codeList: string[]): void { |
| | | this.permCodeListState = codeList; |
| | | } |
| | | |
| | | @Mutation |
| | | commitBackMenuListState(list: Menu[]): void { |
| | | this.backMenuListState = list; |
| | | } |
| | | |
| | | @Mutation |
| | | commitLastBuildMenuTimeState(): void { |
| | | this.lastBuildMenuTimeState = new Date().getTime(); |
| | | } |
| | | |
| | | @Mutation |
| | | commitDynamicAddedRouteState(added: boolean): void { |
| | | this.isDynamicAddedRouteState = added; |
| | | } |
| | | |
| | | @Mutation |
| | | commitResetState(): void { |
| | | this.isDynamicAddedRouteState = false; |
| | | this.permCodeListState = []; |
| | | this.backMenuListState = []; |
| | | this.lastBuildMenuTimeState = 0; |
| | | } |
| | | |
| | | @Action |
| | | async buildRoutesAction(id?: number | string): Promise<AppRouteRecordRaw[]> { |
| | | let routes: AppRouteRecordRaw[] = []; |
| | | const roleList = toRaw(userStore.getRoleListState); |
| | | |
| | | const { permissionMode } = appStore.getProjectConfig; |
| | | |
| | | // role permissions |
| | | if (permissionMode === PermissionModeEnum.ROLE) { |
| | | routes = filter(asyncRoutes, (route) => { |
| | | const { meta } = route; |
| | | const { roles } = meta!; |
| | | if (!roles) return true; |
| | | return roleList.some((role) => roles.includes(role)); |
| | | }); |
| | | // this.commitRoutesState(routes); |
| | | // Background permissions |
| | | // warn( |
| | | // `当前权限模式为:${PermissionModeEnum.ROLE},请将src/store/modules/permission.ts内的后台菜单获取函数注释,如果已注释可以忽略此信息!` |
| | | // ); |
| | | // 如果确定不需要做后台动态权限,请将下面整个判断注释 |
| | | } else if (permissionMode === PermissionModeEnum.BACK) { |
| | | const messageKey = 'loadMenu'; |
| | | createMessage.loading({ |
| | | content: '菜单加载中...', |
| | | key: messageKey, |
| | | duration: 1, |
| | | }); |
| | | // 这里获取后台路由菜单逻辑自行修改 |
| | | const paramId = id || userStore.getUserInfoState.userId; |
| | | if (!paramId) { |
| | | throw new Error('paramId is undefined!'); |
| | | } |
| | | let routeList: any[] = await getMenuListById({ id: paramId }); |
| | | // 动态引入组件 |
| | | routeList = transformObjToRoute(routeList); |
| | | // 后台路由转菜单结构 |
| | | const backMenuList = transformRouteToMenu(routeList); |
| | | this.commitBackMenuListState(backMenuList); |
| | | // 生成路由 |
| | | routes = genRouteModule(routeList) as AppRouteRecordRaw[]; |
| | | routes.push(REDIRECT_ROUTE); |
| | | } |
| | | return routes; |
| | | } |
| | | // 后台菜单列表 |
| | | backMenuList: Menu[]; |
| | | // 菜单列表 |
| | | frontMenuList: Menu[]; |
| | | } |
| | | export const permissionStore = getModule<Permission>(Permission); |
| | | |
| | | export const usePermissionStore = defineStore({ |
| | | id: 'app-permission', |
| | | state: (): PermissionState => ({ |
| | | // 权限代码列表 |
| | | permCodeList: [], |
| | | // Whether the route has been dynamically added |
| | | // 路由是否动态添加 |
| | | isDynamicAddedRoute: false, |
| | | // To trigger a menu update |
| | | // 触发菜单更新 |
| | | lastBuildMenuTime: 0, |
| | | // Backstage menu list |
| | | // 后台菜单列表 |
| | | backMenuList: [], |
| | | // menu List |
| | | // 菜单列表 |
| | | frontMenuList: [], |
| | | }), |
| | | getters: { |
| | | getPermCodeList(state): string[] | number[] { |
| | | return state.permCodeList; |
| | | }, |
| | | getBackMenuList(state): Menu[] { |
| | | return state.backMenuList; |
| | | }, |
| | | getFrontMenuList(state): Menu[] { |
| | | return state.frontMenuList; |
| | | }, |
| | | getLastBuildMenuTime(state): number { |
| | | return state.lastBuildMenuTime; |
| | | }, |
| | | getIsDynamicAddedRoute(state): boolean { |
| | | return state.isDynamicAddedRoute; |
| | | }, |
| | | }, |
| | | actions: { |
| | | setPermCodeList(codeList: string[]) { |
| | | this.permCodeList = codeList; |
| | | }, |
| | | |
| | | setBackMenuList(list: Menu[]) { |
| | | this.backMenuList = list; |
| | | list?.length > 0 && this.setLastBuildMenuTime(); |
| | | }, |
| | | |
| | | setFrontMenuList(list: Menu[]) { |
| | | this.frontMenuList = list; |
| | | }, |
| | | |
| | | setLastBuildMenuTime() { |
| | | this.lastBuildMenuTime = new Date().getTime(); |
| | | }, |
| | | |
| | | setDynamicAddedRoute(added: boolean) { |
| | | this.isDynamicAddedRoute = added; |
| | | }, |
| | | resetState(): void { |
| | | this.isDynamicAddedRoute = false; |
| | | this.permCodeList = []; |
| | | this.backMenuList = []; |
| | | this.lastBuildMenuTime = 0; |
| | | }, |
| | | async changePermissionCode() { |
| | | const codeList = await getPermCode(); |
| | | this.setPermCodeList(codeList); |
| | | }, |
| | | |
| | | // 构建路由 |
| | | async buildRoutesAction(): Promise<AppRouteRecordRaw[]> { |
| | | const { t } = useI18n(); |
| | | const userStore = useUserStore(); |
| | | const appStore = useAppStoreWithOut(); |
| | | |
| | | let routes: AppRouteRecordRaw[] = []; |
| | | const roleList = toRaw(userStore.getRoleList) || []; |
| | | const { permissionMode = projectSetting.permissionMode } = appStore.getProjectConfig; |
| | | |
| | | // 路由过滤器 在 函数filter 作为回调传入遍历使用 |
| | | const routeFilter = (route: AppRouteRecordRaw) => { |
| | | const { meta } = route; |
| | | // 抽出角色 |
| | | const { roles } = meta || {}; |
| | | if (!roles) return true; |
| | | // 进行角色权限判断 |
| | | return roleList.some((role) => roles.includes(role)); |
| | | }; |
| | | |
| | | const routeRemoveIgnoreFilter = (route: AppRouteRecordRaw) => { |
| | | const { meta } = route; |
| | | // ignoreRoute 为true 则路由仅用于菜单生成,不会在实际的路由表中出现 |
| | | const { ignoreRoute } = meta || {}; |
| | | // arr.filter 返回 true 表示该元素通过测试 |
| | | return !ignoreRoute; |
| | | }; |
| | | |
| | | /** |
| | | * @description 根据设置的首页path,修正routes中的affix标记(固定首页) |
| | | * */ |
| | | const patchHomeAffix = (routes: AppRouteRecordRaw[]) => { |
| | | if (!routes || routes.length === 0) return; |
| | | let homePath: string = userStore.getUserInfo.homePath || PageEnum.BASE_HOME; |
| | | |
| | | function patcher(routes: AppRouteRecordRaw[], parentPath = '') { |
| | | if (parentPath) parentPath = parentPath + '/'; |
| | | routes.forEach((route: AppRouteRecordRaw) => { |
| | | const { path, children, redirect } = route; |
| | | const currentPath = path.startsWith('/') ? path : parentPath + path; |
| | | if (currentPath === homePath) { |
| | | if (redirect) { |
| | | homePath = route.redirect! as string; |
| | | } else { |
| | | route.meta = Object.assign({}, route.meta, { affix: true }); |
| | | throw new Error('end'); |
| | | } |
| | | } |
| | | children && children.length > 0 && patcher(children, currentPath); |
| | | }); |
| | | } |
| | | |
| | | try { |
| | | patcher(routes); |
| | | } catch (e) { |
| | | // 已处理完毕跳出循环 |
| | | } |
| | | return; |
| | | }; |
| | | |
| | | switch (permissionMode) { |
| | | // 角色权限 |
| | | case PermissionModeEnum.ROLE: |
| | | // 对非一级路由进行过滤 |
| | | routes = filter(asyncRoutes, routeFilter); |
| | | // 对一级路由根据角色权限过滤 |
| | | routes = routes.filter(routeFilter); |
| | | // Convert multi-level routing to level 2 routing |
| | | // 将多级路由转换为 2 级路由 |
| | | routes = flatMultiLevelRoutes(routes); |
| | | break; |
| | | |
| | | // 路由映射, 默认进入该case |
| | | case PermissionModeEnum.ROUTE_MAPPING: |
| | | // 对非一级路由进行过滤 |
| | | routes = filter(asyncRoutes, routeFilter); |
| | | // 对一级路由再次根据角色权限过滤 |
| | | routes = routes.filter(routeFilter); |
| | | // 将路由转换成菜单 |
| | | const menuList = transformRouteToMenu(routes, true); |
| | | // 移除掉 ignoreRoute: true 的路由 非一级路由 |
| | | routes = filter(routes, routeRemoveIgnoreFilter); |
| | | // 移除掉 ignoreRoute: true 的路由 一级路由; |
| | | routes = routes.filter(routeRemoveIgnoreFilter); |
| | | // 对菜单进行排序 |
| | | menuList.sort((a, b) => { |
| | | return (a.meta?.orderNo || 0) - (b.meta?.orderNo || 0); |
| | | }); |
| | | |
| | | // 设置菜单列表 |
| | | this.setFrontMenuList(menuList); |
| | | |
| | | // Convert multi-level routing to level 2 routing |
| | | // 将多级路由转换为 2 级路由 |
| | | routes = flatMultiLevelRoutes(routes); |
| | | break; |
| | | |
| | | // If you are sure that you do not need to do background dynamic permissions, please comment the entire judgment below |
| | | // 如果确定不需要做后台动态权限,请在下方注释整个判断 |
| | | case PermissionModeEnum.BACK: |
| | | const { createMessage } = useMessage(); |
| | | |
| | | createMessage.loading({ |
| | | content: t('sys.app.menuLoading'), |
| | | duration: 1, |
| | | }); |
| | | |
| | | // !Simulate to obtain permission codes from the background, |
| | | // 模拟从后台获取权限码, |
| | | // this function may only need to be executed once, and the actual project can be put at the right time by itself |
| | | // 这个功能可能只需要执行一次,实际项目可以自己放在合适的时间 |
| | | let routeList: AppRouteRecordRaw[] = []; |
| | | try { |
| | | await this.changePermissionCode(); |
| | | routeList = (await getMenuList()) as AppRouteRecordRaw[]; |
| | | } catch (error) { |
| | | console.error(error); |
| | | } |
| | | |
| | | // Dynamically introduce components |
| | | // 动态引入组件 |
| | | routeList = transformObjToRoute(routeList); |
| | | |
| | | // Background routing to menu structure |
| | | // 后台路由到菜单结构 |
| | | const backMenuList = transformRouteToMenu(routeList); |
| | | this.setBackMenuList(backMenuList); |
| | | |
| | | // remove meta.ignoreRoute item |
| | | // 删除 meta.ignoreRoute 项 |
| | | routeList = filter(routeList, routeRemoveIgnoreFilter); |
| | | routeList = routeList.filter(routeRemoveIgnoreFilter); |
| | | |
| | | routeList = flatMultiLevelRoutes(routeList); |
| | | routes = [PAGE_NOT_FOUND_ROUTE, ...routeList]; |
| | | break; |
| | | } |
| | | |
| | | routes.push(ERROR_LOG_ROUTE); |
| | | patchHomeAffix(routes); |
| | | return routes; |
| | | }, |
| | | }, |
| | | }); |
| | | |
| | | // Need to be used outside the setup |
| | | // 需要在设置之外使用 |
| | | export function usePermissionStoreWithOut() { |
| | | return usePermissionStore(store); |
| | | } |