| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- /**
- * 角色→权限映射表(《角色权限与菜单归类整合方案》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
- }
|