/** * 角色→权限映射表(《角色权限与菜单归类整合方案》B-4) * 每个角色映射一组权限标识,支持通配符 '*' 和前缀通配 'prefix:*' * * 平台角色: admin / operator / finance * 入驻角色: butler / knowledge_admin / teacher / nutritionist / supplier / activity_manager * 遗留角色: article_manager / activity_admin / supplier_admin(历史数据兼容) */ const ROLE_PERMISSIONS = { // 超管:全部权限 admin: ['*'], // 运营:审核全量、活动全量、任务全量、家庭/商城/知识/测评只读 operator: ['dashboard', 'audit:*', 'activity:*', 'task:*', 'assessment:read', 'family:read', 'commerce:read', 'articles:read', 'knowledge:read'], // 财务:财务/退款/提现、订单只读、CF值、商城营销、业务管理 finance: ['dashboard', 'finance:*', 'audit:withdraw', 'audit:refund', 'commerce:read', 'commerce:orders:read', 'biz:*', 'cf-value:*'], // 管家:家庭客服 + 规划师 + 营养师权限并集(getEffectivePermissions 中自动并入 teacher/nutritionist) butler: ['dashboard', 'family:*', 'task:*', 'reward:*', 'service:*', 'growth:*', 'biz:*', 'assessment:dan', 'health:*', 'diet:*', 'report:audit'], // 知识管理员 knowledge_admin: ['dashboard', 'articles:*', 'knowledge:*'], // 成长规划师 teacher: ['dashboard', 'service:*', 'growth:*', 'biz:*', 'assessment:dan', 'family:bound', 'messages'], // 营养师 nutritionist: ['dashboard', 'health:*', 'diet:*', 'report:audit', 'service:family', 'biz:orders', 'messages'], // 供应商(仅自己的商品/库存/订单/结算)+ 业务管理 supplier: ['dashboard', 'commerce:self', 'inventory:self', 'order:self', 'settlement:self', 'supply:member', 'biz:*'], // 活动管理员(仅自己的活动)+ 业务管理 activity_manager: ['dashboard', 'activity:self', 'activity:registration', 'biz:*'], // ---- 遗留角色兼容(与后端 AdminRoles.normalize 对应)---- article_manager: ['dashboard', 'articles:*', 'knowledge:*'], activity_admin: ['dashboard', 'activity:self', 'activity:registration'], supplier_admin: ['dashboard', 'commerce:self', 'inventory:self', 'order:self', 'settlement:self', 'supply:member'], } /** * 遗留角色 → 新角色归一化(与后端 AdminRoles.normalize 保持一致) */ function normalizeRole(role) { if (!role) return role if (role === 'activity_admin') return 'activity_manager' if (role === 'article_admin' || role === 'article_manager') return 'knowledge_admin' if (role === 'supplier_admin') return 'supplier' return role } /** * 获取用户有效权限列表 * butler 单向兼容:分配 butler 时自动附带 teacher 与 nutritionist 的权限(权限求并集,角色记录仍为 butler) * @param {string[]} roles - 用户拥有的角色列表 * @returns {string[]} - 去重后的权限标识列表 */ export function getEffectivePermissions(roles) { if (!roles || roles.length === 0) return [] const permSet = new Set() const normalizedRoles = [] for (let i = 0; i < roles.length; i++) { const normalized = normalizeRole(roles[i]) normalizedRoles.push(normalized) } for (const role of normalizedRoles) { const perms = ROLE_PERMISSIONS[role] if (perms) { for (const p of perms) { permSet.add(p) } } } // B-2 单向兼容:butler 附带 teacher + nutritionist 权限并集 if (normalizedRoles.indexOf('butler') >= 0) { const union = [].concat(ROLE_PERMISSIONS.teacher || [], ROLE_PERMISSIONS.nutritionist || []) for (const p of union) { permSet.add(p) } } return Array.from(permSet) } /** * 判断是否拥有指定权限 * @param {string[]} userPerms - 用户的有效权限列表 * @param {string} required - 需要的权限标识(支持 'prefix:*' 通配、逗号分隔多值任一匹配) * @returns {boolean} * * 匹配规则: * - '*' → 全部放行 * - required 含逗号 → 任一命中即通过(如 'family:read,family:bound') * - 精确匹配 * - required 为单段(组级权限如 'audit')→ 拥有 'audit:*' 或任何 'audit:xxx' 即通过 * - required 为 'p:*'(模块通配)→ 拥有 'p:*' 或任何 'p:xxx' 即通过(模块只读/自管可见) * - required 为 'p:sub' → 拥有 'p:*'、'p:read'(模块只读)或任何以 'p:sub:' 开头的更具体权限即通过 */ export function hasPermission(userPerms, required) { if (!userPerms || !required) return false if (userPerms.indexOf('*') >= 0) return true // 逗号分隔多值:任一命中即通过 if (required.indexOf(',') >= 0) { const parts = required.split(',') for (let i = 0; i < parts.length; i++) { if (matchSingle(userPerms, parts[i].trim())) return true } return false } return matchSingle(userPerms, required) } function matchSingle(userPerms, required) { if (!required) return false // 精确匹配 if (userPerms.indexOf(required) >= 0) return true const colonIndex = required.indexOf(':') if (colonIndex < 0) { // 组级权限 'audit':拥有 'audit:*' 或任何 'audit:xxx' if (userPerms.indexOf(required + ':*') >= 0) return true for (let i = 0; i < userPerms.length; i++) { if (userPerms[i].indexOf(required + ':') === 0) return true } return false } const prefix = required.substring(0, colonIndex) if (colonIndex === required.length - 2 && required.charAt(required.length - 1) === '*') { // 模块通配 'p:*':拥有 'p:*' 或任何 'p:xxx' if (userPerms.indexOf(prefix + ':*') >= 0) return true for (let i = 0; i < userPerms.length; i++) { if (userPerms[i].indexOf(prefix + ':') === 0) return true } return false } // 具体权限 'p:sub' // 1) 拥有模块通配 'p:*' if (userPerms.indexOf(prefix + ':*') >= 0) return true for (let i = 0; i < userPerms.length; i++) { const perm = userPerms[i] // 2) 模块只读 'p:read' 可见模块内页面(数据安全由后端行级隔离保证) if (perm === prefix + ':read') return true // 3) 拥有更具体权限 'p:sub:xxx' 蕴含粗粒度 'p:sub'(如 finance:cf-value:read → cf-value:manage 场景由通配覆盖) if (perm.indexOf(required + ':') === 0) return true } return false } /** * 入驻角色(登录后默认首页为个人版看板) */ export const RESIDENT_ROLES = ['butler', 'knowledge_admin', 'teacher', 'nutritionist', 'supplier', 'activity_manager'] /** * 入驻角色默认首页(登录后/访问 /dashboard 时跳转到个人工作台,B-6) * 平台角色(admin/operator/finance)返回 '/' 即平台首页 */ const ROLE_HOME_PAGES = { butler: '/my-families', teacher: '/teacher-dashboard', nutritionist: '/health-reports', supplier: '/product-manage', activity_manager: '/activities', knowledge_admin: '/article-manage', } export function getRoleHomePage(roles) { if (!roles || roles.length === 0) return '/' let home = null for (let i = 0; i < roles.length; i++) { const normalized = normalizeRole(roles[i]) // 平台角色优先:拥有平台角色则进平台首页 if (normalized === 'admin' || normalized === 'operator' || normalized === 'finance') { return '/' } if (!home && ROLE_HOME_PAGES[normalized]) { home = ROLE_HOME_PAGES[normalized] } } return home || '/' } /** * 判断角色列表是否为入驻角色(非平台角色 admin/operator/finance) */ export function isResident(roles) { if (!roles || roles.length === 0) return false for (let i = 0; i < roles.length; i++) { const normalized = normalizeRole(roles[i]) if (normalized === 'admin' || normalized === 'operator' || normalized === 'finance') { return false } if (RESIDENT_ROLES.indexOf(normalized) >= 0) { return true } } return false } /** * 获取角色中文名 */ export function getRoleLabel(role) { const labels = { admin: '超级管理员', operator: '运营', finance: '财务', butler: '管家', knowledge_admin: '知识管理员', teacher: '成长规划师', nutritionist: '营养师', supplier: '供应商', activity_manager: '活动管理员', // 遗留角色 article_manager: '知识管理员', activity_admin: '活动管理员', supplier_admin: '供应商', } return labels[role] || role }