import { asyncRoutes, constantRoutes } from "@/router"; // 用浏览器 UA 判断当前是不是手机端,后面会按 PC / 手机过滤菜单。 const MOBILE_UA_REGEXP = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i; // 项目里用到的固定角色名,集中放这里,避免代码里到处写字符串。 const CUSTOMER_ROLE = '客户'; const OPERATION_ROLE = '运营'; const FINANCE_ROLE = '财务'; // 这个用户能看到订单管理里的特殊子菜单,比如历史订单和订单详情。 const SPECIAL_ORDER_USER = 'ZhipuHZ'; // 超级管理员只放行这个一级菜单。 const SUPER_ADMIN_ROUTE_PATH = '/superAdministrator'; // 所有登录用户都能访问的公共路由,不依赖后端 auths 和角色。hidden 路由不会显示在菜单里。 const COMMON_ROUTE_PATHS = ['/product', '/tokenManagement', '/tokenUsage', '/modelExperience', '/modelDetail', '/modelApiDocument']; // 运营角色需要额外补出来的菜单。 const OPERATION_EXTRA_ROUTE_PATHS = ['/modelManagement', '/modelInfoConfig', '/operationReport']; // 财务角色需要额外补出来的菜单。 const FINANCE_EXTRA_ROUTE_PATHS = ['/financialOverview']; // 普通客户账号默认要补出来的基础菜单。 const BASE_USER_ROUTE_PATHS = ['/orderManagement', '/resourceManagement']; // 客户角色额外能看到的一级菜单。 const CUSTOMER_EXTRA_ROUTE_PATHS = [ '/containerInstance', '/unsubscribeManagement', '/informationPerfect', '/rechargeManagement', '/invoiceManagement', '/workOrderManagement' ]; // 这些菜单只允许客户角色看到,非客户就算后端给了权限也不展示。 const CUSTOMER_ONLY_ROUTE_PATHS = [ '/overview', ...CUSTOMER_EXTRA_ROUTE_PATHS ]; // 客户登录后必须能看到的入口菜单,不完全依赖后端 auths 返回。 const CUSTOMER_ALWAYS_VISIBLE_ROUTE_PATHS = ['/overview']; // 订单管理里只给 SPECIAL_ORDER_USER 看的子菜单 path。 const ORDER_CHILDREN_ONLY_FOR_SPECIAL_USER = ['HistoricalOrders', 'orderDetails']; const isMobile = MOBILE_UA_REGEXP.test(window.navigator.userAgent); // 把角色统一整理成数组,兼容 undefined、数组、逗号字符串这几种写法。 function normalizeRoles(roles) { if (!roles) { return []; } if (Array.isArray(roles)) { return roles; } if (typeof roles === 'string') { return roles.split(',').filter(Boolean); } return []; } // 从 sessionStorage 里取 roles,取不到或格式坏了就当成没有角色。 function getSessionRoles() { try { return JSON.parse(sessionStorage.getItem('roles') || '[]'); } catch (error) { console.warn('读取 roles 失败:', error); return []; } } // 汇总当前用户的所有角色来源:接口参数、vuex、sessionStorage、新旧字段。 function getCurrentRoles(params, rootState) { return [ ...normalizeRoles(params.roles), ...normalizeRoles(rootState.user.roles), ...normalizeRoles(getSessionRoles()), ...normalizeRoles(sessionStorage.getItem('jueseNew')) ]; } // 判断当前用户是不是客户角色。 function isCustomer(userRoles = []) { return userRoles.includes(CUSTOMER_ROLE); } // 把布尔值转成更好读的设备类型,后面的判断都用 pc / mobile。 function getDeviceType(isMobileDevice) { return isMobileDevice ? 'mobile' : 'pc'; } // 判断路由 meta.roles 是否满足。没写 roles 的路由默认所有角色都能继续往下判断。 function hasRouteRole(route, userRoles = []) { const routeRoles = route.meta?.roles; if (!routeRoles || routeRoles.length === 0) { return true; } return routeRoles.some(role => userRoles.includes(role)); } // 根据设备过滤路由:手机只要手机路由,PC 不要手机专用路由。 function isRouteAllowedByDevice(route, deviceType) { if (deviceType === 'mobile') { return route.meta?.isMobile || route.meta?.isMobile === true; } return route.meta?.isMobile !== true; } // 在一组路由里按 path 找某个路由。 function findRouteByPath(routes, path) { return routes.find(route => route.path === path); } // 后端 auths 里的 path 要和路由 meta.fullPath 对上,对上才算有权限。 function routeHasPermission(route, permissions) { return permissions.some(permission => permission.path === route.meta?.fullPath); } // 客户专属菜单要再卡一层客户角色,防止非客户误展示。 function canShowCustomerOnlyRoute(route, userRoles) { return !CUSTOMER_ONLY_ROUTE_PATHS.includes(route.path) || isCustomer(userRoles); } // 把所有动态路由的 fullPath 收集出来。后端返回 path 为空时,表示拥有全部权限。 function getAllRoutePermissions(routes) { const permissions = []; routes.forEach(route => { if (route.meta?.fullPath) { permissions.push({ path: route.meta.fullPath }); } if (route.children) { permissions.push(...getAllRoutePermissions(route.children)); } }); return permissions; } // 复制路由对象。这里不能用 JSON 深拷贝,因为路由里的 component 是函数,会被 JSON 丢掉。 function cloneRoute(route) { const clonedRoute = { ...route }; if (route.meta) { clonedRoute.meta = { ...route.meta }; } if (route.children) { clonedRoute.children = route.children.map(cloneRoute); } return clonedRoute; } // 根据 path 列表批量找到对应路由,没找到的自动过滤掉。 function getRoutesByPath(routes, paths) { return paths .map(path => findRouteByPath(routes, path)) .filter(Boolean); } // 判断这个一级路由是不是已经加过了,避免菜单重复出现。 function shouldAppendRoute(accessedRoutes, route) { return !accessedRoutes.some(item => item.path === route.path); } // 把缺少的路由补到最终菜单里,补之前会先去重并复制一份。 function appendMissingRoutes(accessedRoutes, routesToAppend) { routesToAppend.forEach(route => { if (shouldAppendRoute(accessedRoutes, route)) { accessedRoutes.push(cloneRoute(route)); } }); return accessedRoutes; } // 如果是手机访问,额外把根路径导到 H5 首页,并注册 H5 首页菜单。 if (isMobile) { console.log("检测到移动设备,添加移动端路由"); constantRoutes.unshift({ path: '/', redirect: '/h5HomePage', hidden: true }); constantRoutes.push({ path: '/h5HomePage', name: 'H5HomePage', title: 'H5首页', component: () => import('@/views/H5/index.vue'), hidden: true, redirect: "/h5HomePage/index", meta: { isMobile: true }, children: [ { path: "index", title: 'H5首页', component: () => import('@/views/H5/official/index.vue'), meta: { title: "H5首页", fullPath: "/h5HomePage/index", isMobile: true }, }, { path: "cloud", title: '云', component: () => import('@/views/H5/cloud/index.vue'), meta: { title: "云", fullPath: "/h5HomePage/cloud", isMobile: true }, }, { path: "calculate", title: '算', component: () => import('@/views/H5/calculate/index.vue'), meta: { title: "算", fullPath: "/h5HomePage/calculate", isMobile: true }, }, { path: "net", title: '网', component: () => import('@/views/H5/net/index.vue'), meta: { title: "网", fullPath: "/h5HomePage/net", isMobile: true }, }, { path: "use", title: '用', component: () => import('@/views/H5/use/index.vue'), meta: { title: "用", fullPath: "/h5HomePage/use", isMobile: true }, }, ] }); } // 核心过滤函数:拿后端权限、角色和设备类型,一层层筛出最终可访问路由。 function filterAsyncRoutes(routes, permissions, userRoles = [], deviceType = 'pc') { const res = []; routes.forEach(route => { // 先复制一份,避免直接改原始 asyncRoutes。 const tmpRoute = cloneRoute(route); // 第一步:角色不符合,或者客户专属菜单但当前用户不是客户,直接跳过。 if (!hasRouteRole(tmpRoute, userRoles) || !canShowCustomerOnlyRoute(route, userRoles)) { return; } // 第二步:设备不符合也跳过,比如 PC 端不展示 H5 专用路由。 if (!isRouteAllowedByDevice(route, deviceType)) { return; } // 第三步:看后端 auths 里有没有当前路由的 fullPath。 const hasPermission = routeHasPermission(route, permissions); // 第四步:客户首页入口特殊处理,客户登录后默认展示。 const isAlwaysVisibleCustomerRoute = CUSTOMER_ALWAYS_VISIBLE_ROUTE_PATHS.includes(route.path) && isCustomer(userRoles); // 有权限,或者是客户默认入口,就把这个路由放进最终菜单。 if (hasPermission || isAlwaysVisibleCustomerRoute) { res.push(tmpRoute); } else if (tmpRoute.children) { // 父级没权限时继续看子级。只要子级有权限,父级也要保留,否则子菜单没地方挂。 const filteredChildren = filterAsyncRoutes(tmpRoute.children, permissions, userRoles, deviceType); if (filteredChildren.length > 0) { tmpRoute.children = filteredChildren; res.push(tmpRoute); } } }); return res; } // 给普通用户和客户补充固定菜单:订单、资源,以及客户专属的工单/充值/发票等。 function addUserRoutes(routes, userType, orgType, userRoles = [], deviceType = 'pc') { console.log("addUserRoutes - userType:", userType, "orgType:", orgType, "userRoles:", userRoles); const userRoutes = []; // orgType 为 2 或 3 时也按客户账号处理。 const isUserAccount = userType === 'user' || orgType == 2 || orgType == 3; if (isUserAccount) { // 普通客户账号默认补订单管理和资源管理。 const baseUserRoutes = getRoutesByPath(routes, BASE_USER_ROUTE_PATHS) .filter(route => isRouteAllowedByDevice(route, deviceType)); console.log("添加基础用户菜单路由:", baseUserRoutes.map(route => route.path)); userRoutes.push(...baseUserRoutes); } if (isCustomer(userRoles)) { // 只有客户角色才补客户专属菜单。 const customerRoutes = getRoutesByPath(routes, CUSTOMER_EXTRA_ROUTE_PATHS) .filter(route => isRouteAllowedByDevice(route, deviceType)); console.log("添加客户菜单路由:", customerRoutes.map(route => route.path)); userRoutes.push(...customerRoutes); } return userRoutes; } // 运营角色额外补模型管理菜单,目前只在 PC 端展示。 function addOperationRoutes(accessedRoutes, routes, userRoles = [], deviceType = 'pc') { if (!userRoles.includes(OPERATION_ROLE) || deviceType !== 'pc') { return accessedRoutes; } return appendMissingRoutes(accessedRoutes, getRoutesByPath(routes, OPERATION_EXTRA_ROUTE_PATHS)); } // 财务角色额外补财务菜单,目前只在 PC 端展示。 function addFinanceRoutes(accessedRoutes, routes, userRoles = [], deviceType = 'pc') { if (!userRoles.includes(FINANCE_ROLE) || deviceType !== 'pc') { return accessedRoutes; } return appendMissingRoutes(accessedRoutes, getRoutesByPath(routes, FINANCE_EXTRA_ROUTE_PATHS)); } // token市集是公共菜单,所有登录用户都要能看到。 function addCommonRoutes(accessedRoutes, routes, deviceType = 'pc') { const commonRoutes = getRoutesByPath(routes, COMMON_ROUTE_PATHS) .filter(route => isRouteAllowedByDevice(route, deviceType)); return appendMissingRoutes(accessedRoutes, commonRoutes); } // 订单管理有两个特殊子菜单,只有 SPECIAL_ORDER_USER 能看到,其他用户过滤掉。 function filterOrderChildrenByUser(routes, username) { if (username === SPECIAL_ORDER_USER) { console.log(`用户 ${username} 是 ${SPECIAL_ORDER_USER},保留所有订单子路由`); return routes; } return routes.map(route => { const nextRoute = cloneRoute(route); // 找到订单管理后,移除特殊用户专属的子菜单。 if (nextRoute.path === '/orderManagement' && nextRoute.children) { console.log(`用户 ${username} 不是 ${SPECIAL_ORDER_USER},过滤订单管理子路由`); nextRoute.children = nextRoute.children.filter(child => !ORDER_CHILDREN_ONLY_FOR_SPECIAL_USER.includes(child.path) ); console.log('过滤后订单子路由:', nextRoute.children.map(child => child.path)); } if (nextRoute.children) { // 子路由里如果还有订单管理,也继续递归处理。 nextRoute.children = filterOrderChildrenByUser(nextRoute.children, username); } return nextRoute; }); } // 整理后端权限列表。如果包含空 path,就按“拥有全部动态路由权限”处理。 function getPermissionList(auths = []) { const permissions = JSON.parse(JSON.stringify(auths)); const permissionPaths = permissions.map(item => item.path); if (permissionPaths.includes('')) { return getAllRoutePermissions(asyncRoutes); } return permissions; } // 根据后端 auths 生成第一版可访问路由。没有 auths 就不展示动态菜单。 function getAccessedRoutesByPermission(auths, userRoles, deviceType) { if (!auths.length) { return []; } const permissions = getPermissionList(auths); return filterAsyncRoutes(asyncRoutes, permissions, userRoles, deviceType); } // 判断是不是超级管理员账号:用户名包含 admin,并且不是客户组织。 function isSuperAdminUser(username, orgType) { return username && username.includes('admin') && orgType != 2 && orgType != 3; } // 超级管理员只拿超级管理员菜单;手机端不展示这个菜单。 function getSuperAdminRoutes(deviceType) { if (deviceType !== 'pc') { return []; } return getRoutesByPath(asyncRoutes, [SUPER_ADMIN_ROUTE_PATH]).map(cloneRoute); } // 在已有权限菜单基础上,补充用户类型/客户角色需要固定展示的菜单。 function addUserSpecificRoutes(accessedRoutes, userType, orgType, userRoles, deviceType) { const userSpecificRoutes = addUserRoutes(asyncRoutes, userType, orgType, userRoles, deviceType); return appendMissingRoutes(accessedRoutes, userSpecificRoutes); } const state = { routes: [], addRoutes: [], users: [], isMobile: isMobile // 保存设备类型状态 }; const mutations = { SET_ROUTES: (state, routes) => { console.log("MUTATION SET_ROUTES - received routes:", routes); // addRoutes 只保存动态生成的菜单,方便 router.addRoutes 使用。 state.addRoutes = routes; sessionStorage.setItem("routes", JSON.stringify(routes)); // routes 是侧边栏最终读取的数据:基础路由 + 动态权限路由。 state.routes = constantRoutes.concat(routes); console.log("MUTATION SET_ROUTES - final state.routes:", state.routes); }, RESET_ROUTES: (state) => { // 退出登录或切换账号时,必须清掉内存里的旧菜单,否则不刷新页面会继续显示上个角色的菜单。 state.routes = []; state.addRoutes = []; sessionStorage.removeItem("routes"); }, SETUSERS: (state, user) => { state.users = user; }, SET_DEVICE_TYPE: (state, isMobile) => { state.isMobile = isMobile; } }; const actions = { /** * 生成动态路由 * * 根据用户类型、组织类型和权限列表生成对应的动态路由配置 * 包含管理员和普通用户的不同路由生成逻辑 * * @param {Object} context - Vuex上下文对象 * @param {Function} context.commit - 提交mutation的方法 * @param {Object} context.rootState - 根模块的状态 * @param {Object} params - 参数对象 * @param {string} [params.userType] - 用户类型 * @param {number} [params.orgType] - 组织类型 * @param {Array} [params.auths] - 权限列表 * @param {Object} [params.user] - 用户信息对象 * @returns {Promise} 解析后的动态路由数组 */ generateRoutes({ commit, rootState, state }, params) { console.log("ACTION generateRoutes - params:", params); return new Promise((resolve) => { // 1. 先拿到用户基础信息,优先用传进来的参数,没有就从 sessionStorage / vuex 兜底。 const userType = params.userType || sessionStorage.getItem('userType') || ''; const orgType = params.orgType || parseInt(sessionStorage.getItem('orgType')) || 0; const username = params.user || rootState.user.user || ''; const userRoles = getCurrentRoles(params, rootState); const deviceType = getDeviceType(state.isMobile); const auths = params.auths ? JSON.parse(JSON.stringify(params.auths)) : []; // 2. 判断是不是超级管理员,超级管理员走单独菜单逻辑。 const isSuperAdmin = isSuperAdminUser(params.user, orgType); console.log("用户角色:", userRoles); console.log("当前用户名:", username, `检查是否是${SPECIAL_ORDER_USER}:`, username === SPECIAL_ORDER_USER); console.log("用户类型:", userType, "orgType:", orgType, "设备类型:", deviceType); console.log("ACTION generateRoutes - auths:", auths); // 3. 先生成第一版菜单:超级管理员只拿超管菜单,普通用户按后端 auths 过滤。 let accessedRoutes = isSuperAdmin ? getSuperAdminRoutes(deviceType) : getAccessedRoutesByPermission(auths, userRoles, deviceType); // 4. token市集是公共入口,所有登录用户都补上。 accessedRoutes = addCommonRoutes(accessedRoutes, asyncRoutes, deviceType); if (!isSuperAdmin) { // 5. 普通用户再补一些固定入口,比如订单、资源、客户专属菜单。 console.log("为用户添加特定路由"); accessedRoutes = addUserSpecificRoutes(accessedRoutes, userType, orgType, userRoles, deviceType); console.log("添加用户特定路由后的accessedRoutes:", accessedRoutes); } // 6. 运营角色额外补模型管理。 accessedRoutes = addOperationRoutes(accessedRoutes, asyncRoutes, userRoles, deviceType); // 6.1 财务角色额外补财务菜单。 accessedRoutes = addFinanceRoutes(accessedRoutes, asyncRoutes, userRoles, deviceType); // 7. 最后处理订单管理里的特殊子菜单权限。 accessedRoutes = filterOrderChildrenByUser(accessedRoutes, username); console.log("ACTION generateRoutes - 最终 calculated accessedRoutes:", accessedRoutes); // 8. 保存到 vuex 和 sessionStorage,侧边栏会读取 state.permission.routes。 commit("SET_ROUTES", accessedRoutes); resolve(accessedRoutes); }); }, }; export default { namespaced: true, state, mutations, actions, };