data&device ready

This commit is contained in:
“yuanmengy”
2026-06-05 17:11:56 +08:00
commit a225272d1e
29 changed files with 49446 additions and 0 deletions

663
scripts/rbac.js Normal file
View File

@@ -0,0 +1,663 @@
/**
* 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,
});
};
});