664 lines
31 KiB
JavaScript
664 lines
31 KiB
JavaScript
/**
|
||
* 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,
|
||
});
|
||
};
|
||
});
|