Files
TalkingQ_Dashboard/scripts/rbac.js
“yuanmengy” a225272d1e data&device ready
2026-06-05 17:11:56 +08:00

664 lines
31 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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 => `
<option value="${company.id}" ${company.id === selectedCompanyId ? 'selected' : ''}>${company.name}</option>
`).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 = `
<div class="page-header">
<div>
<div class="page-title">权限管理 (RBAC)</div>
<div class="page-desc">基于角色的访问控制,管理用户权限和公司数据隔离</div>
</div>
<div class="page-actions">
<button class="btn btn-primary" id="addUserBtn">
<i data-lucide="user-plus"></i> 添加用户
</button>
</div>
</div>
<div class="tabs" id="rbacTabs">
<div class="tab-item active" data-tab="users">用户管理</div>
<div class="tab-item" data-tab="roles">角色配置</div>
<div class="tab-item" data-tab="audit">审计日志</div>
</div>
<!-- Users Tab -->
<div class="tab-content active" id="tab-users">
<div class="card" style="padding:0;overflow:hidden">
<div class="table-wrapper">
<table class="rbac-table">
<thead>
<tr>
<th>显示名称</th>
<th>登录名称</th>
<th>用户类型</th>
<th>备注</th>
<th>操作</th>
</tr>
</thead>
<tbody>
${users.map(u => {
const userType = getUserRoleLabel(u);
const typeColor = getUserRoleBadgeColor(u);
const companyTag = u.role === 'client' && u.companyId
? `<span class="badge badge-default" style="font-size:11px;margin-left:6px">${escapeHtml(getCompanyName(u.companyId))}</span>`
: '';
return `
<tr>
<td>
<div class="user-cell">
<div class="user-avatar-sm">${u.displayName[0]}</div>
<span class="user-display-name">${u.displayName}</span>
${u.status === 'active'
? '<span class="status-indicator online" title="启用中"></span>'
: '<span class="status-indicator offline" title="已禁用"></span>'}
</div>
</td>
<td class="login-name-cell">${u.loginName}</td>
<td><span class="badge" style="background:${typeColor}20;color:${typeColor};border:1px solid ${typeColor}40">${userType}</span></td>
<td class="remark-cell">${u.remark}${companyTag}</td>
<td>
<div class="action-links">
<button class="action-link green" onclick="editUser('${u.id}')">编辑</button>
<span class="action-divider">|</span>
<button class="action-link primary" onclick="managePermissions('${u.id}')">权限管理</button>
<span class="action-divider">|</span>
<button class="action-link danger" onclick="deleteUser('${u.id}')">删除</button>
</div>
</td>
</tr>
`}).join('')}
</tbody>
</table>
</div>
</div>
</div>
<!-- Roles Tab -->
<div class="tab-content" id="tab-roles">
<div style="display:grid;grid-template-columns:repeat(auto-fill, minmax(280px, 1fr));gap:16px">
${roles.map(r => `
<div class="card role-card" style="border-top:3px solid ${r.color}">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:12px">
<span style="font-size:16px;font-weight:700;color:${r.color}">${r.name}</span>
<span class="badge badge-default" style="font-size:11px">${r.perms.length} 项权限</span>
</div>
<div style="font-size:13px;color:var(--text-muted);margin-bottom:14px">${r.desc}</div>
<div style="font-size:12px;font-weight:600;margin-bottom:8px">可访问功能:</div>
<div style="display:flex;flex-wrap:wrap;gap:6px">
${r.perms.map(p => `<span class="badge badge-default" style="font-size:11px">${p}</span>`).join('')}
</div>
<div style="margin-top:14px;display:flex;gap:8px">
<button class="btn btn-outline btn-sm" style="flex:1" onclick="editRolePermissions('${r.name}')">编辑权限</button>
<button class="btn btn-ghost btn-sm" onclick="viewRoleUsers('${r.name}')">查看用户</button>
</div>
</div>
`).join('')}
</div>
<div class="api-note" style="margin-top:16px">
<i data-lucide="info"></i>
<span>RBAC 接口:<code>GET /api/v1/rbac/roles</code>、<code>PUT /api/v1/rbac/roles/{id}</code>、<code>POST /api/v1/rbac/users/assign-role</code></span>
</div>
</div>
<!-- Audit Tab -->
<div class="tab-content" id="tab-audit">
<div class="card" style="padding:0;overflow:hidden">
<div class="table-wrapper">
<table>
<thead><tr><th>时间</th><th>操作人</th><th>操作类型</th><th>目标</th><th>IP 地址</th><th>结果</th></tr></thead>
<tbody>
${[
{ 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 => `
<tr>
<td style="font-family:monospace;font-size:12px">2026-05-12 ${a.time}</td>
<td>${a.user}</td>
<td>${a.action}</td>
<td style="font-size:12px;color:var(--text-muted)">${a.target}</td>
<td style="font-family:monospace;font-size:12px">${a.ip}</td>
<td><span class="badge ${a.ok ? 'badge-success' : 'badge-danger'}">${a.ok ? '成功' : '告警'}</span></td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
</div>
`;
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: `
<div class="form-row">
<div class="form-group">
<label class="form-label">显示名称<span class="required">*</span></label>
<input type="text" class="form-control" id="newUserDisplayName" placeholder="用户显示名称">
</div>
<div class="form-group">
<label class="form-label">登录名称<span class="required">*</span></label>
<input type="text" class="form-control" id="newUserLoginName" placeholder="username@talkingq.com">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">角色<span class="required">*</span></label>
<select class="form-control" id="newUserRole" onchange="window.toggleCompanyAccess(this.value)">
<option value="">请选择角色</option>
<option value="admin">Admin - 超级管理员</option>
<option value="worker">Worker - 运营人员(可配置应用和 Prompt</option>
<option value="client">Client - B端客户</option>
</select>
</div>
<div class="form-group">
<label class="form-label">备注</label>
<input type="text" class="form-control" placeholder="可选">
</div>
</div>
<div class="form-group" id="companyAccessGroup" style="display:none">
<label class="form-label">所属公司<span class="required">*</span></label>
<select class="form-control" id="newUserCompany">
<option value="">请选择公司</option>
${getCompanyOptionsHtml()}
</select>
<div class="form-hint">Client 角色必须绑定一个公司,登录后仅能查看该公司数据。</div>
</div>
<div class="form-group">
<label class="form-label">初始密码<span class="required">*</span></label>
<input type="password" class="form-control" id="newUserPassword" placeholder="设置初始密码">
<div class="form-hint">用户首次登录后需修改密码</div>
</div>`,
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: `
<div class="form-row">
<div class="form-group">
<label class="form-label">显示名称</label>
<input type="text" class="form-control" id="editUserDisplayName" value="${user.displayName}">
</div>
<div class="form-group">
<label class="form-label">登录名称</label>
<input type="text" class="form-control" value="${user.loginName}" disabled style="background:#f5f5f5">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">角色</label>
<select class="form-control" id="editUserRole" onchange="window.toggleCompanyAccess(this.value)">
<option value="admin" ${user.role === 'admin' ? 'selected' : ''}>Admin</option>
<option value="worker" ${user.role === 'worker' ? 'selected' : ''}>Worker</option>
<option value="client" ${user.role === 'client' ? 'selected' : ''}>Client</option>
</select>
</div>
<div class="form-group">
<label class="form-label">状态</label>
<select class="form-control">
<option ${user.status === 'active' ? 'selected' : ''}>启用</option>
<option ${user.status === 'inactive' ? 'selected' : ''}>禁用</option>
</select>
</div>
</div>
<div class="form-group" id="companyAccessGroup" style="${user.role === 'client' ? '' : 'display:none'}">
<label class="form-label">所属公司<span class="required">*</span></label>
<select class="form-control" id="editUserCompany">
<option value="">请选择公司</option>
${getCompanyOptionsHtml(user.companyId)}
</select>
<div style="font-size:12px;color:var(--text-muted);margin-top:6px">仅 Client 角色需要配置,登录后只显示该公司客户可见内容</div>
</div>
<div class="form-group">
<label class="form-label">备注</label>
<input type="text" class="form-control" id="editUserRemark" value="${user.remark === '-' ? '' : user.remark}">
</div>`,
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: `
<div style="margin-bottom:16px">
<div style="font-size:13px;color:var(--text-muted);margin-bottom:12px">为用户分配角色和细粒度权限</div>
<div class="form-group">
<label class="form-label">当前角色</label>
<select class="form-control" id="permRoleSelect" onchange="window.toggleCompanyAccess(this.value)">
<option value="admin" ${user.role === 'admin' ? 'selected' : ''}>Admin - 超级管理员</option>
<option value="worker" ${user.role === 'worker' ? 'selected' : ''}>Worker - 运营人员</option>
<option value="client" ${user.role === 'client' ? 'selected' : ''}>Client - B端客户</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label">可访问公司(租户隔离)</label>
<div style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px">
<select class="form-control" id="permCompanySelect" ${user.role === 'client' ? '' : 'disabled'}>
${getCompanyOptionsHtml(user.companyId)}
</select>
<div style="font-size:12px;color:var(--text-muted);margin-top:8px">${user.role === 'client' ? 'Client 角色仅可访问所选公司。' : '非 Client 角色可访问全部公司。'}</div>
</div>
${user.role === 'client' ? `
<div style="font-size:12px;color:var(--info);margin-top:8px">
<i data-lucide="info" style="width:14px;height:14px;display:inline"></i>
Client 角色只能查看已授权公司的数据,无法进行 OTA 升级等操作
</div>` : ''}
</div>
<div style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px">
<div style="font-size:13px;font-weight:600;margin-bottom:10px">细粒度权限</div>
<div style="display:flex;flex-direction:column;gap:8px">
${[
{ 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 => `
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;opacity:${p.readonly ? '0.7' : '1'}">
<input type="checkbox" ${p.checked ? 'checked' : ''} ${p.readonly ? 'disabled' : ''} style="accent-color:var(--primary)">
<span>${p.name}</span>
${p.readonly ? '<span style="font-size:11px;color:var(--text-muted)">(只读)</span>' : ''}
</label>
`).join('')}
</div>
</div>`,
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: `
<div style="text-align:center;padding:20px 0">
<i data-lucide="alert-triangle" style="width:48px;height:48px;color:var(--warning);margin-bottom:16px"></i>
<p style="font-size:15px;font-weight:600;margin-bottom:8px">确认删除用户 ${user.displayName}</p>
<p style="font-size:13px;color:var(--text-muted)">此操作不可撤销,删除后该用户将无法登录系统</p>
</div>`,
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: `
<div style="margin-bottom:16px">
<div style="font-weight:600;margin-bottom:8px">角色说明</div>
<div style="font-size:13px;color:var(--text-muted)">${role.desc}</div>
</div>
<div class="form-group">
<label class="form-label">功能权限</label>
<div style="display:flex;flex-wrap:wrap;gap:8px">
${role.perms.map(p => `<span class="badge badge-info">${p}</span>`).join('')}
</div>
</div>
<div class="form-group">
<label class="form-label">可访问公司(租户隔离)<span class="required">*</span></label>
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:12px">
<label style="display:flex;align-items:center;gap:8px;margin-bottom:8px;cursor:pointer">
<input type="checkbox" id="companyA" checked>
<span class="badge badge-info"> 智慧星科技</span>
<span style="font-size:12px;color:var(--text-muted)">(可分配)</span>
</label>
<label style="display:flex;align-items:center;gap:8px;margin-bottom:8px;cursor:pointer">
<input type="checkbox" id="companyB" checked>
<span class="badge badge-success"> 未来玩具厂</span>
<span style="font-size:12px;color:var(--text-muted)">(可分配)</span>
</label>
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
<input type="checkbox" id="companyC">
<span class="badge badge-warning"> 童趣电子</span>
<span style="font-size:12px;color:var(--text-muted)">(可分配)</span>
</label>
</div>
<div style="font-size:12px;color:var(--text-muted);margin-top:8px">
<i data-lucide="info" style="width:14px;height:14px;display:inline"></i>
租户隔离Client 角色只能访问其被授权公司的数据Admin 可以将用户分配到特定公司
</div>
</div>
<div class="api-note">
<i data-lucide="info"></i>
<span>接口:<code>PUT /api/v1/rbac/roles/client</code></span>
</div>`,
onConfirm: async () => {
Toast.success('Client 角色权限已更新');
},
confirmText: '保存',
});
lucide.createIcons();
} else {
// Admin 和 Worker 角色显示功能权限
createModal({
title: `编辑 ${roleName} 角色权限`,
content: `
<div style="margin-bottom:16px">
<div style="font-weight:600;margin-bottom:8px">角色说明</div>
<div style="font-size:13px;color:var(--text-muted)">${role.desc}</div>
</div>
<div class="form-group">
<label class="form-label">功能权限</label>
<div style="border:1px solid var(--border);border-radius:var(--radius);padding:12px">
<div style="display:flex;flex-wrap:wrap;gap:8px">
${role.perms.map(p => `<span class="badge badge-info">${p}</span>`).join('')}
</div>
</div>
</div>
${roleName === 'Admin' ? `
<div class="form-group">
<label class="form-label">公司访问权限</label>
<div style="color:var(--success);font-size:13px">✓ 可访问全部公司及全部功能</div>
</div>` : `
<div class="form-group">
<label class="form-label">可访问公司</label>
<div style="display:flex;flex-wrap:wrap;gap:8px">
<label style="display:flex;align-items:center;gap:6px;cursor:pointer">
<input type="checkbox" checked>
<span class="badge badge-info"> 智慧星科技</span>
</label>
<label style="display:flex;align-items:center;gap:6px;cursor:pointer">
<input type="checkbox" checked>
<span class="badge badge-success"> 未来玩具厂</span>
</label>
<label style="display:flex;align-items:center;gap:6px;cursor:pointer">
<input type="checkbox" checked>
<span class="badge badge-warning"> 童趣电子</span>
</label>
</div>
</div>`}
<div class="api-note">
<i data-lucide="info"></i>
<span>接口:<code>PUT /api/v1/rbac/roles/${roleName.toLowerCase()}</code></span>
</div>`,
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: `
<div style="margin-bottom:12px;font-size:13px;color:var(--text-muted)">
${roleUsers.length} 个用户
</div>
<div style="max-height:300px;overflow-y:auto">
${roleUsers.map(u => `
<div style="display:flex;align-items:center;gap:12px;padding:10px 0;border-bottom:1px solid var(--border)">
<div class="user-avatar-sm">${u.displayName[0]}</div>
<div>
<div style="font-weight:500">${u.displayName}</div>
<div style="font-size:12px;color:var(--text-muted)">${u.loginName}</div>
</div>
</div>
`).join('')}
</div>`,
confirmText: '关闭',
showCancel: false,
});
};
});