/** * TalkingQ Platform - RBAC Permission Management Page * 参考阿里云百炼控制台风格 * 按公司分类的权限管理 */ registerPage('rbac', function(container) { const USERS_STORAGE_KEY = 'talkingq_rbac_users_v1'; const DEFAULT_ROLE_COMPANIES = ['company_a', 'company_b', 'company_c']; const DEFAULT_USERS = [ { id: 'u001', displayName: '张明华', loginName: 'zhangmh@talkingq.com', status: 'active', lastLogin: '2026-05-12 14:10', role: 'admin', remark: '超级管理员' }, { id: 'u002', displayName: '李思远', loginName: 'lisiy@talkingq.com', status: 'active', lastLogin: '2026-05-12 11:30', role: 'worker', remark: '运营人员' }, { id: 'u003', displayName: '王晓燕', loginName: 'wangxy@partner.com', status: 'active', lastLogin: '2026-05-11 09:00', role: 'client', companyId: 'company_a', allowedCompanies: ['company_a'], remark: '智慧星科技-客户' }, { id: 'u004', displayName: '刘亚楠', loginName: 'liuyn@partner.com', status: 'inactive', lastLogin: '2026-04-28 16:00', role: 'client', companyId: 'company_b', allowedCompanies: ['company_b'], remark: '未来玩具厂-客户' }, { id: 'u005', displayName: '陈志强', loginName: 'chenzq@talkingq.com', status: 'active', lastLogin: '2026-05-10 09:15', role: 'worker', remark: '开发人员' }, ]; function cloneUsers(list) { return list.map(item => ({ ...item, allowedCompanies: Array.isArray(item.allowedCompanies) ? [...item.allowedCompanies] : item.allowedCompanies })); } function loadUsers() { try { localStorage.removeItem(USERS_STORAGE_KEY); if (Array.isArray(window.__rbacUsers) && window.__rbacUsers.length) { return window.__rbacUsers.map((user, index) => normalizeUser(user, index)); } } catch (error) { console.warn('Failed to clear legacy RBAC user cache', error); } return cloneUsers(DEFAULT_USERS).map((user, index) => normalizeUser(user, index)); } function saveUsers(users) { window.__rbacUsers = users; } function normalizeUser(user, index = 0) { const role = normalizeRole(user.role || inferRoleFromRemark(user.remark)); const companyId = role === 'client' ? (user.companyId || user.allowedCompanies?.[0] || DEFAULT_ROLE_COMPANIES[index % DEFAULT_ROLE_COMPANIES.length]) : null; return { id: user.id || `u${String(index + 1).padStart(3, '0')}`, displayName: user.displayName || user.loginName || '未命名用户', loginName: String(user.loginName || '').toLowerCase(), status: user.status || 'active', lastLogin: user.lastLogin || '-', role, companyId, allowedCompanies: role === 'client' ? normalizeAllowedCompanies(user.allowedCompanies, companyId) : DEFAULT_ROLE_COMPANIES.slice(), remark: user.remark || (role === 'client' ? `${window.__getCompanyNameById?.(companyId) || companyId}-客户` : role === 'admin' ? '超级管理员' : '运营人员'), }; } function normalizeRole(role) { const raw = String(role || '').toLowerCase(); if (raw === 'admin' || raw === 'administrator') return 'admin'; if (raw === 'worker' || raw === 'employee') return 'worker'; if (raw === 'client' || raw === 'customer') return 'client'; return 'worker'; } function inferRoleFromRemark(remark) { const text = String(remark || ''); if (text.includes('超级管理员')) return 'admin'; if (text.includes('客户')) return 'client'; return 'worker'; } function normalizeAllowedCompanies(value, companyId) { if (Array.isArray(value) && value.length) return value; if (companyId) return [companyId]; return []; } function getCompanyOptionsHtml(selectedCompanyId = '') { const companies = (window.companies || []).filter(company => company.id !== 'all'); return companies.map(company => ` `).join(''); } function getUserById(userId) { return users.find(user => user.id === userId); } function getUserRoleLabel(user) { if (user.role === 'admin') return '管理员'; if (user.role === 'client') return '客户'; return '运营人员'; } function getUserRoleBadgeColor(user) { if (user.role === 'admin') return '#722ed1'; if (user.role === 'client') return '#52c41a'; return '#1890ff'; } function getCompanyName(companyId) { return window.__getCompanyNameById ? window.__getCompanyNameById(companyId) : companyId; } let users = loadUsers(); window.__rbacUsers = users; window.__lookupRbacUserByLoginName = function(loginName) { const normalized = String(loginName || '').trim().toLowerCase(); return users.find(user => user.loginName === normalized) || null; }; const roles = [ { name: 'Admin', desc: '超级管理员,拥有所有权限', color: '#722ed1', perms: ['全部功能'] }, { name: 'Worker', desc: '运营人员,可配置应用,负责日常运营和 Bad Case 处理', color: '#1890ff', perms: ['仪表盘', '对话应用配置', '应用管理', '会话分析', 'Bad Case', '设备管理', 'OTA升级'] }, { name: 'Client', desc: 'B端客户,只读访问自己公司的数据', color: '#52c41a', perms: ['仪表盘(只读)', '设备管理(只读)', 'OTA状态(只读)'] }, ]; container.innerHTML = `
用户管理
角色配置
审计日志
${users.map(u => { const userType = getUserRoleLabel(u); const typeColor = getUserRoleBadgeColor(u); const companyTag = u.role === 'client' && u.companyId ? `${escapeHtml(getCompanyName(u.companyId))}` : ''; return ` `}).join('')}
显示名称 登录名称 用户类型 备注 操作
${u.displayName[0]}
${u.displayName} ${u.status === 'active' ? '' : ''}
${userType} ${u.remark}${companyTag}
${roles.map(r => `
${r.name} ${r.perms.length} 项权限
${r.desc}
可访问功能:
${r.perms.map(p => `${p}`).join('')}
`).join('')}
RBAC 接口:GET /api/v1/rbac/rolesPUT /api/v1/rbac/roles/{id}POST /api/v1/rbac/users/assign-role
${[ { time: '14:22:05', user: '张明华', action: '登录', target: '系统', ip: '192.168.1.10', ok: true }, { time: '14:18:30', user: '张明华', action: '修改 Prompt', target: '小Q故事机器人 v2.3', ip: '192.168.1.10', ok: true }, { time: '13:55:12', user: '李思远', action: '关闭 Bad Case', target: 'BC-2026-004', ip: '10.0.2.15', ok: true }, { time: '11:30:00', user: 'system', action: '自动触发内容安全', target: 'sess-3f7a', ip: 'internal', ok: false }, { time: '09:15:42', user: '张明华', action: '创建 OTA 任务', target: 'OTA-2026-002', ip: '192.168.1.10', ok: true }, ].map(a => ` `).join('')}
时间操作人操作类型目标IP 地址结果
2026-05-12 ${a.time} ${a.user} ${a.action} ${a.target} ${a.ip} ${a.ok ? '成功' : '告警'}
`; lucide.createIcons({ elements: [container] }); // Tab switch container.querySelectorAll('#rbacTabs .tab-item').forEach(tab => { tab.addEventListener('click', () => { container.querySelectorAll('#rbacTabs .tab-item').forEach(t => t.classList.remove('active')); tab.classList.add('active'); container.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); container.querySelector(`#tab-${tab.dataset.tab}`)?.classList.add('active'); }); }); // Add user button document.getElementById('addUserBtn')?.addEventListener('click', () => { createModal({ title: '添加用户', content: `
用户首次登录后需修改密码
`, onConfirm: async () => { const displayName = document.getElementById('newUserDisplayName')?.value.trim(); const loginName = document.getElementById('newUserLoginName')?.value.trim(); const role = document.getElementById('newUserRole')?.value; const companyId = document.getElementById('newUserCompany')?.value; const password = document.getElementById('newUserPassword')?.value; if (!displayName || !loginName || !role || !password) { Toast.warning('请填写用户必填信息'); return false; } if (role === 'client' && !companyId) { Toast.warning('Client 角色必须选择所属公司'); return false; } const normalizedUser = normalizeUser({ id: `u${Date.now()}`, displayName, loginName, role, companyId: role === 'client' ? companyId : null, allowedCompanies: role === 'client' ? [companyId] : DEFAULT_ROLE_COMPANIES.slice(), status: 'active', lastLogin: '-', remark: role === 'client' ? `${getCompanyName(companyId)}-客户` : role === 'admin' ? '超级管理员' : '运营人员', }); users.unshift(normalizedUser); saveUsers(users); await API.rbac.createUser({ displayName, loginName, role }); Toast.success('用户创建成功,已发送激活邮件'); navigateTo('rbac'); }, confirmText: '创建用户', }); }); // Global functions for action buttons window.editUser = function(userId) { const user = getUserById(userId); if (!user) return; createModal({ title: '编辑用户', content: `
仅 Client 角色需要配置,登录后只显示该公司客户可见内容
`, onConfirm: async () => { const displayName = document.getElementById('editUserDisplayName')?.value.trim(); const role = document.getElementById('editUserRole')?.value || 'worker'; const companyId = document.getElementById('editUserCompany')?.value; const remark = document.getElementById('editUserRemark')?.value.trim(); if (role === 'client' && !companyId) { Toast.warning('Client 角色必须选择所属公司'); return false; } const nextUser = normalizeUser({ ...user, displayName: displayName || user.displayName, role, companyId: role === 'client' ? companyId : null, allowedCompanies: role === 'client' ? [companyId] : DEFAULT_ROLE_COMPANIES.slice(), remark: remark || (role === 'client' ? `${getCompanyName(companyId)}-客户` : role === 'admin' ? '超级管理员' : '运营人员'), }); const idx = users.findIndex(item => item.id === user.id); if (idx >= 0) users[idx] = nextUser; saveUsers(users); await API.rbac.updateUser(userId, { role: role, allowedCompanies: role === 'client' ? [companyId] : DEFAULT_ROLE_COMPANIES.slice(), }); Toast.success(`用户 ${nextUser.displayName} 信息已更新`); navigateTo('rbac'); }, confirmText: '保存', }); lucide.createIcons(); }; // 切换公司访问配置显示 window.toggleCompanyAccess = function(role) { const group = document.getElementById('companyAccessGroup'); const select = group?.querySelector('select'); if (group) { const normalized = String(role || '').toLowerCase(); group.style.display = normalized === 'client' ? '' : 'none'; if (select) select.disabled = normalized !== 'client'; } }; window.managePermissions = function(userId) { const user = getUserById(userId); if (!user) return; createModal({ title: `权限管理 - ${user.displayName}`, size: 'modal-lg', content: `
为用户分配角色和细粒度权限
${user.role === 'client' ? 'Client 角色仅可访问所选公司。' : '非 Client 角色可访问全部公司。'}
${user.role === 'client' ? `
Client 角色只能查看已授权公司的数据,无法进行 OTA 升级等操作
` : ''}
细粒度权限
${[ { name: '数据仪表盘', checked: true, readonly: false }, { name: '对话应用配置', checked: user.role !== 'client', readonly: user.role === 'client' }, { name: '应用管理', checked: user.role !== 'client', readonly: user.role === 'client' }, { name: '设备管理', checked: true, readonly: false }, { name: 'OTA 升级', checked: user.role !== 'client', readonly: user.role === 'client' }, { name: '会话详情', checked: true, readonly: false }, { name: 'Bad Case 管理', checked: user.role !== 'client', readonly: user.role === 'client' }, { name: '权限管理', checked: user.role !== 'client', readonly: true }, { name: '系统设置', checked: user.role !== 'client', readonly: true }, ].map(p => ` `).join('')}
`, onConfirm: () => { const companyId = document.getElementById('permCompanySelect')?.value || user.companyId; const nextRole = document.getElementById('permRoleSelect')?.value || 'client'; const idx = users.findIndex(item => item.id === user.id); if (idx >= 0) { users[idx] = normalizeUser({ ...user, role: nextRole, companyId: nextRole === 'client' ? companyId : null, allowedCompanies: nextRole === 'client' ? [companyId] : DEFAULT_ROLE_COMPANIES.slice(), remark: nextRole === 'client' ? `${getCompanyName(companyId)}-客户` : nextRole === 'admin' ? '超级管理员' : '运营人员', }); saveUsers(users); } Toast.success(`${user.displayName} 的权限已更新`); navigateTo('rbac'); }, confirmText: '保存权限', }); lucide.createIcons(); }; window.deleteUser = function(userId) { const user = getUserById(userId); if (!user) return; createModal({ title: '确认删除', content: `

确认删除用户 ${user.displayName}?

此操作不可撤销,删除后该用户将无法登录系统

`, onConfirm: () => { users = users.filter(item => item.id !== userId); saveUsers(users); Toast.success(`用户 ${user.displayName} 已删除`); navigateTo('rbac'); }, confirmText: '确认删除', cancelText: '取消', }); lucide.createIcons(); }; window.editRolePermissions = function(roleName) { const role = roles.find(r => r.name === roleName); if (!role) return; // Client 角色特殊处理 - 需要配置公司级权限 if (roleName === 'Client') { createModal({ title: '编辑 Client 角色权限', content: `
角色说明
${role.desc}
${role.perms.map(p => `${p}`).join('')}
租户隔离:Client 角色只能访问其被授权公司的数据,Admin 可以将用户分配到特定公司
接口:PUT /api/v1/rbac/roles/client
`, onConfirm: async () => { Toast.success('Client 角色权限已更新'); }, confirmText: '保存', }); lucide.createIcons(); } else { // Admin 和 Worker 角色显示功能权限 createModal({ title: `编辑 ${roleName} 角色权限`, content: `
角色说明
${role.desc}
${role.perms.map(p => `${p}`).join('')}
${roleName === 'Admin' ? `
✓ 可访问全部公司及全部功能
` : `
`}
接口:PUT /api/v1/rbac/roles/${roleName.toLowerCase()}
`, onConfirm: async () => { Toast.success(`${roleName} 角色权限已更新`); }, confirmText: '保存', }); lucide.createIcons(); } }; window.viewRoleUsers = function(roleName) { const roleUsers = users.filter(u => { if (roleName === 'Admin') return u.role === 'admin'; if (roleName === 'Worker') return u.role === 'worker'; if (roleName === 'Client') return u.role === 'client'; return false; }); createModal({ title: `${roleName} 角色下的用户`, content: `
共 ${roleUsers.length} 个用户
${roleUsers.map(u => `
${u.displayName[0]}
${u.displayName}
${u.loginName}
`).join('')}
`, confirmText: '关闭', showCancel: false, }); }; });