import { defineStore } from 'pinia'; import router, { constantRoutes, dynamicRoutes } from '@/router'; import store from '@/store'; import { getRouters } from '@/api/menu'; import auth from '@/plugins/auth'; import { RouteRecordRaw } from 'vue-router'; import Layout from '@/layout/index.vue'; import ParentView from '@/components/ParentView/index.vue'; import InnerLink from '@/layout/components/InnerLink/index.vue'; import { ref, computed } from 'vue'; import { createCustomNameComponent } from '@/utils/createCustomNameComponent'; import { isHttp } from '@/utils/validate'; import { useUserStore } from '@/store/modules/user'; // 匹配views里面所有的.vue文件 const modules = import.meta.glob('./../../views/**/*.vue'); /** 系统分类选项(顺序:首页、执法监督码、后台管理) */ export interface SystemCodeOption { label: string; value: string; icon: string; /** 当设置了 iframeUrl 时,切换到该选项会以全屏 iframe 展示,而非加载菜单路由 */ iframeUrl?: string; /** 当设置了 fullscreenRoute 时,切换到该选项会以全屏 Vue 路由展示 */ fullscreenRoute?: string; /** 切换到该系统后默认跳转的路由路径 */ defaultRoute?: string; } /** 404 兜底路由名称,方便动态移除和重新添加 */ export const CATCH_ALL_ROUTE_NAME = 'CatchAll404'; //http://10.132.108.3:85/loginByUserId export const systemCodeOptions: SystemCodeOption[] = [ { label: '首页', value: 'planindex', icon: 'HomeFilled', fullscreenRoute: '/home' }, { label: '统计分析', value: 'supervisionCode', icon: 'Document', defaultRoute: '/statistics/administrative/enterprise-enforcement' }, { label: '后台管理', value: 'authService', icon: 'Setting', defaultRoute: '/system-setting/menu' } ]; export const usePermissionStore = defineStore('permission', () => { /** * 根据基础 iframeUrl 动态拼接登录凭据参数 */ const buildIframeUrl = (baseUrl: string): string => { if (!baseUrl) return ''; // 当配置了 Same-origin 占位符时,替换为浏览器当前的 origin(协议+主机+端口) let resolvedUrl = baseUrl; if (baseUrl.includes('Same-origin')) { resolvedUrl = baseUrl.replace('Same-origin', window.location.origin); } const userStore = useUserStore(); const params = new URLSearchParams(); if (userStore.v1SystemCredential) { params.set('v1_system_credential', userStore.v1SystemCredential); } const separator = resolvedUrl.includes('?') ? '&' : '?'; return resolvedUrl + separator + params.toString(); }; const routes = ref([]); const addRoutes = ref([]); const defaultRoutes = ref([]); const topbarRouters = ref([]); const sidebarRouters = ref([]); /** 缓存后端返回的全量路由原始数据(未经组件转换) */ const allRouteDataCache = ref([]); /** sessionStorage key */ const SYSTEM_CODE_KEY = 'currentSystemCode'; /** 当前选中的系统编码(从 sessionStorage 恢复,默认 planindex) */ const savedCode = sessionStorage.getItem(SYSTEM_CODE_KEY) || 'planindex'; const savedOption = systemCodeOptions.find((o) => o.value === savedCode); const currentSystemCode = ref(savedCode); /** 当前是否处于 iframe 全屏模式 */ const iframeMode = ref(!!(savedOption?.iframeUrl || savedOption?.fullscreenRoute)); /** 当前 iframe 地址 */ const iframeUrl = ref(savedOption?.iframeUrl ? buildIframeUrl(savedOption.iframeUrl) : ''); /** 当前全屏 Vue 路由地址 */ const fullscreenRoute = ref(savedOption?.fullscreenRoute || ''); const getRoutes = (): RouteRecordRaw[] => { return routes.value as RouteRecordRaw[]; }; const getDefaultRoutes = (): RouteRecordRaw[] => { return defaultRoutes.value as RouteRecordRaw[]; }; const getSidebarRoutes = (): RouteRecordRaw[] => { return sidebarRouters.value as RouteRecordRaw[]; }; const getTopbarRoutes = (): RouteRecordRaw[] => { return topbarRouters.value as RouteRecordRaw[]; }; const setRoutes = (newRoutes: RouteRecordRaw[]): void => { addRoutes.value = newRoutes; routes.value = constantRoutes.concat(newRoutes); }; const setDefaultRoutes = (routes: RouteRecordRaw[]): void => { defaultRoutes.value = constantRoutes.concat(routes); }; const setTopbarRoutes = (routes: RouteRecordRaw[]): void => { topbarRouters.value = routes; }; const setSidebarRouters = (routes: RouteRecordRaw[]): void => { sidebarRouters.value = routes; }; const generateRoutes = async (systemCode?: string): Promise => { const code = systemCode || currentSystemCode.value; // 仅首次加载时请求后端,后续从缓存取 if (allRouteDataCache.value.length === 0) { const res = await getRouters(); allRouteDataCache.value = res.data as any[]; console.log('[路由调试] 后端返回全量路由数据 =', JSON.parse(JSON.stringify(allRouteDataCache.value))); } // 按当前 systemCode 过滤出匹配的顶层路由 const filteredData = allRouteDataCache.value.filter((route: any) => route.systemCode === code); console.log('[路由调试] systemCode =', code, ', 过滤后路由数据 =', JSON.parse(JSON.stringify(filteredData))); const sdata = JSON.parse(JSON.stringify(filteredData)); const rdata = JSON.parse(JSON.stringify(filteredData)); const defaultData = JSON.parse(JSON.stringify(filteredData)); const sidebarRoutes = filterAsyncRouter(sdata); const rewriteRoutes = filterAsyncRouter(rdata, undefined, true); const defaultRoutes = filterAsyncRouter(defaultData); const asyncRoutes = filterDynamicRoutes(dynamicRoutes); asyncRoutes.forEach((route) => { router.addRoute(route); }); setRoutes(rewriteRoutes); setSidebarRouters(constantRoutes.concat(sidebarRoutes)); setDefaultRoutes(sidebarRoutes); setTopbarRoutes(defaultRoutes); // 路由name重复检查 duplicateRouteChecker(asyncRoutes, sidebarRoutes); return new Promise((resolve) => resolve(rewriteRoutes)); }; /** * 遍历后台传来的路由字符串,转换为组件对象 * @param asyncRouterMap 后台传来的路由字符串 * @param lastRouter 上一级路由 * @param type 是否是重写路由 */ const filterAsyncRouter = (asyncRouterMap: RouteRecordRaw[], lastRouter?: RouteRecordRaw, type = false): RouteRecordRaw[] => { return asyncRouterMap.filter((route) => { if (type && route.children) { route.children = filterChildren(route.children, undefined); } // Layout ParentView 组件特殊处理 if (route.component?.toString() === 'Layout') { route.component = Layout; } else if (route.component?.toString() === 'ParentView') { route.component = ParentView; } else if (route.component?.toString() === 'InnerLink') { route.component = InnerLink; } else { route.component = loadView(route.component, route.name as string); } if (route.children != null && route.children && route.children.length) { route.children = filterAsyncRouter(route.children, route, type); } else { delete route.children; delete route.redirect; } return true; }); }; const filterChildren = (childrenMap: RouteRecordRaw[], lastRouter?: RouteRecordRaw): RouteRecordRaw[] => { let children: RouteRecordRaw[] = []; childrenMap.forEach((el) => { el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path; if (el.children && el.children.length && el.component?.toString() === 'ParentView') { children = children.concat(filterChildren(el.children, el)); } else { children.push(el); } }); return children; }; /** * 切换菜单组:更新 systemCode,重新加载路由并刷新页面 */ const switchSystemCode = async (systemCode: string) => { currentSystemCode.value = systemCode; sessionStorage.setItem(SYSTEM_CODE_KEY, systemCode); const option = systemCodeOptions.find((o) => o.value === systemCode); // 判断是否为全屏模式(iframe 或 Vue 路由) if (option?.iframeUrl || option?.fullscreenRoute) { iframeMode.value = true; iframeUrl.value = option.iframeUrl ? buildIframeUrl(option.iframeUrl) : ''; fullscreenRoute.value = option.fullscreenRoute || ''; setSidebarRouters([]); if (option.fullscreenRoute) { await router.push(option.fullscreenRoute); } return; } iframeMode.value = false; iframeUrl.value = ''; fullscreenRoute.value = ''; // ====== 彻底清理所有动态路由,保证路由表干净 ====== // 1. 收集所有静态路由名称(包括子路由) const constantNames = new Set(); const collectNames = (routeList: RouteRecordRaw[]) => { routeList.forEach((r) => { if (r.name) constantNames.add(r.name); if (r.children) collectNames(r.children); }); }; collectNames(constantRoutes); // 2. 移除所有非静态路由(包括之前的动态业务路由和404兜底路由) router.getRoutes().forEach((route) => { if (route.name && !constantNames.has(route.name)) { router.removeRoute(route.name); } }); // ====== 重新生成并注册路由 ====== const accessRoutes = await generateRoutes(systemCode); accessRoutes.forEach((route) => { if (!isHttp(route.path)) { router.addRoute(route); } }); // 重新添加404兜底路由,确保放在所有动态路由之后 router.addRoute({ name: CATCH_ALL_ROUTE_NAME, path: '/:pathMatch(.*)*', component: () => import('@/views/error/404.vue') }); console.log('[路由调试] switchSystemCode 完成, 当前路由列表 =', router.getRoutes().map((r) => ({ name: r.name, path: r.path }))); // 跳转到默认路由 if (option?.defaultRoute) { await router.push(option.defaultRoute); } }; /** * 清理路由缓存并重置系统码到首页,用于切换账号登录时 */ const cleanRouteCache = () => { allRouteDataCache.value = []; routes.value = []; addRoutes.value = []; defaultRoutes.value = []; topbarRouters.value = []; sidebarRouters.value = []; // 重置到首页 currentSystemCode.value = 'planindex'; sessionStorage.setItem(SYSTEM_CODE_KEY, 'planindex'); const defaultOption = systemCodeOptions.find((o) => o.value === 'planindex'); iframeMode.value = !!(defaultOption?.iframeUrl || defaultOption?.fullscreenRoute); iframeUrl.value = defaultOption?.iframeUrl ? buildIframeUrl(defaultOption.iframeUrl) : ''; fullscreenRoute.value = defaultOption?.fullscreenRoute || ''; }; /** 根据后端返回的路由数据,动态过滤可见的系统分类选项(首页始终显示) */ const visibleSystemCodeOptions = computed(() => { return systemCodeOptions.filter((option) => { // 首页(有 iframeUrl 或 fullscreenRoute 的)始终显示 if (option.iframeUrl || option.fullscreenRoute) return true; // 其他选项:只有当后端路由中存在匹配的 systemCode 时才显示 return allRouteDataCache.value.some((route: any) => route.systemCode === option.value); }); }); return { routes, topbarRouters, sidebarRouters, defaultRoutes, currentSystemCode, iframeMode, iframeUrl, fullscreenRoute, visibleSystemCodeOptions, getRoutes, getDefaultRoutes, getSidebarRoutes, getTopbarRoutes, setRoutes, generateRoutes, setSidebarRouters, switchSystemCode, cleanRouteCache }; }); // 动态路由遍历,验证是否具备权限 export const filterDynamicRoutes = (routes: RouteRecordRaw[]) => { const res: RouteRecordRaw[] = []; routes.forEach((route) => { if (route.permissions) { if (auth.hasPermiOr(route.permissions)) { res.push(route); } } else if (route.roles) { if (auth.hasRoleOr(route.roles)) { res.push(route); } } }); return res; }; export const loadView = (view: any, name: string) => { let res; const allDirs: string[] = []; for (const path in modules) { const viewsIndex = path.indexOf('/views/'); let dir = path.substring(viewsIndex + 7); dir = dir.substring(0, dir.lastIndexOf('.vue')); allDirs.push(dir); if (dir === view) { res = createCustomNameComponent(modules[path], { name }); console.log('[路由调试] loadView 匹配成功: view =', view, ', name =', name); return res; } } console.warn('[路由调试] loadView 未匹配到组件! view =', JSON.stringify(view), ', name =', name, '\n glob扫描到的路径:', allDirs); return res; }; // 非setup export const usePermissionStoreHook = () => { return usePermissionStore(store); }; interface Route { name?: string | symbol; path: string; children?: Route[]; } /** * 检查路由name是否重复 * @param localRoutes 本地路由 * @param routes 动态路由 */ function duplicateRouteChecker(localRoutes: Route[], routes: Route[]) { // 展平 function flatRoutes(routes: Route[]) { const res: Route[] = []; routes.forEach((route) => { if (route.children) { res.push(...flatRoutes(route.children)); } else { res.push(route); } }); return res; } const allRoutes = flatRoutes([...localRoutes, ...routes]); const nameList: string[] = []; allRoutes.forEach((route) => { const name = route.name.toString(); if (name && nameList.includes(name)) { const message = `路由名称: [${name}] 重复, 会造成 404`; console.error(message); ElNotification({ title: '路由名称重复', message, type: 'error' }); return; } nameList.push(route.name.toString()); }); }