import type { AppRouteRecordRaw, Menu } from '@/router/types'; 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 { asyncRoutes } from '@/router/routes'; import { ERROR_LOG_ROUTE, PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic'; import { filter, forEach } from '@/utils/helper/treeHelper'; import { getMenuList } from '@/api/sys/menu'; import { getPermCode } from '@/api/sys/user'; import { useMessage } from '@/hooks/web/useMessage'; import { PageEnum } from '@/enums/pageEnum'; import { router } from '@/router'; import { LAYOUT } from '@/router/constant'; import dayjs from 'dayjs'; import { getMainPage } from '@/api/path/project.api'; import { previewPath } from '@/utils'; interface PermissionState { // Permission code list // 权限代码列表 permCodeList: string[] | number[]; // Whether the route has been dynamically added // 路由是否动态添加 isDynamicAddedRoute: boolean; // To trigger a menu update // 触发菜单更新 lastBuildMenuTime: number; // Backstage menu list // 后台菜单列表 backMenuList: Menu[]; // 菜单列表 frontMenuList: Menu[]; changeMenu: number; homePage: string; } 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: [], changeMenu: dayjs().valueOf(), // 主页面 homePage: '', }), getters: { getChangeMenu(state): number { return state.changeMenu; }, 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; }, getHomePage(state) { return state.homePage; }, }, actions: { async setHomePage() { this.homePage = await getMainPage(); }, setPermCodeList(codeList: string[]) { this.permCodeList = codeList; }, setBackMenuList(list: Menu[]) { this.backMenuList = list; list?.length > 0 && this.setLastBuildMenuTime(); }, setChangeMenu() { this.changeMenu = dayjs().valueOf(); }, 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 { const { t } = useI18n(); const userStore = useUserStore(); const appStore = useAppStoreWithOut(); let routes: AppRouteRecordRaw[] = []; const roleList = toRaw(userStore.getRoleList) || []; const { permissionMode = projectSetting.permissionMode } = appStore.getProjectConfig; const normalizeMenu = (data) => { let path = ''; if (data.item.url.indexOf('@') > 0) { path = data.item.url.replace('@', '/'); } else { path = data.item.url || '/formCallPage?id=' + data.item.id + '&name=' + data.item.name + '&code=' + data.item.code; } const newPath = { orderNo: data.item.sortNo, icon: data.item.iconName, title: data.item.name, meta: { orderNo: data.item.sortNo, icon: data.item.iconName, title: data.item.name, elements: (data.item && data.item.elements) || '', }, name: data.item.name, hideMenu: data.item.status == 1 ? false : true, path: path, id: data.item.id || '', children: [], }; if (data.children && data.children.length > 0) { data.children.forEach((element) => { newPath.children.push(normalizeMenu(element)); }); } return newPath; }; const childRoute = (childData) => { const modules = import.meta.glob('../../views/**/**/**/index.vue'); childData.forEach((element) => { // 菜单里配置带参数,但没有单独的主路由,先注册主路由 if (element.item.status == 1 && element.item.url.indexOf('@') > 0) { if (router.hasRoute(element.item.code)) { const path = element.item.url.substring(0, element.item.url.indexOf('@')); router.addRoute(element.item.code, { path: path + '/:id', name: element.item.url.replaceAll('/', ''), meta: { title: element.item.name, icon: element.item.iconName, elements: element.item.elements, }, component: modules['../../views/demo' + path + '/index.vue'], }); } else { const path = element.item.url.substring(0, element.item.url.indexOf('@')); router.addRoute('Root', { path: path, name: element.item.code, meta: { title: element.item.name, icon: element.item.iconName, hideChildrenInMenu: true, elements: element.item.elements, }, component: LAYOUT, children: [ { path: path + '/:id', name: element.item.url.replaceAll('/', ''), meta: { title: element.item.name, icon: element.item.iconName, elements: element.item.elements, }, component: modules['../../views/demo' + path + '/index.vue'], }, ], }); } } else if (element.item.code && element.item.status == 1) { router.addRoute(element.item.code, { path: element.item.url, name: element.item.url.replaceAll('/', ''), meta: { title: element.item.name, icon: element.item.iconName, elements: element.item.elements, }, // component: () => import('../../views/demo' + element.item.url + '/index.vue') component: modules['../../views/demo' + element.item.url + '/index.vue'], }); } if (element.children && element.children.length > 0) { childRoute(element.children); } }); }; // 注册路由 const registeredRoute = (data) => { const modules = import.meta.glob('../../views/**/**/**/index.vue'); if (data.item.url.split('/')[1] == 'map') { // 大屏的情况下不继承layout router.addRoute('Root', { path: data.item.url, name: data.item.code, meta: { title: data.item.name, icon: data.item.iconName, elements: data.item.elements, }, component: modules['../../views' + data.item.url + '/index.vue'], }); } else { // 正常菜单 if (data.children.length == 0 && data.item.status == 1) { // 没有子菜单 if (data.item.url.indexOf('@') > 0) { //给带参数的路由先注册主路由 const path = data.item.url.substring(0, data.item.url.indexOf('@')); router.addRoute('Root', { path: path, name: data.item.code, meta: { title: data.item.name, icon: data.item.iconName, hideChildrenInMenu: true, elements: data.item.elements, }, component: LAYOUT, children: [ { path: path + '/:id', name: data.item.url.replaceAll('/', ''), meta: { title: data.item.name, icon: data.item.iconName, elements: data.item.elements, }, component: modules['../../views/demo' + path + '/index.vue'], }, ], }); } else { router.addRoute('Root', { path: data.item.url, name: data.item.code, meta: { title: data.item.name, icon: data.item.iconName, hideChildrenInMenu: true, elements: data.item.elements, }, component: LAYOUT, children: [ { path: data.item.url, name: data.item.url.replaceAll('/', ''), meta: { title: data.item.name, icon: data.item.iconName, elements: data.item.elements, }, component: modules['../../views/demo' + data.item.url + '/index.vue'], }, ], }); } } else { // 有子菜单 if (data.item.status == 0) { return; } router.addRoute('Root', { path: data.item.url, name: data.item.code, meta: { title: data.item.name, icon: data.item.iconName, elements: data.item.elements, }, component: data.item.parentId == 0 ? LAYOUT : modules['../../views/demo' + data.item.url + '/index.vue'], }); if (data.children && data.children.length > 0) { childRoute(data.children); } } } // console.log(router.getRoutes()); }; //通过后端获取菜单 const _this = this; async function buildMenusByServer(list) { const data = await getMenuList({ typeId: userStore.getSubject }); const moduleRoutes = list; await data.forEach((value) => { moduleRoutes.push(normalizeMenu(value)); registeredRoute(value); }); await Promise.all(moduleRoutes); // 设置菜单列表 _this.setFrontMenuList(moduleRoutes); } // 路由过滤器 在 函数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 = async (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 { const homePage = await getMainPage(); const path = previewPath(homePage); if (hoemPage) { window.open(path, '_self'); } else { 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); }); await buildMenusByServer(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); }