diff --git a/web/src/App.tsx b/web/src/App.tsx index 8d2c764e..1414953f 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1523,6 +1523,17 @@ export default function App() { roles={roles} onSaveRoles={(newRoles) => setRoles(newRoles)} allUsers={allUsers} + onUpdateUserRole={(userId, roleId) => { + const targetRole = roles.find((r) => r.id === roleId); + setAllUsers((prev) => + prev.map((u) => (u.id === userId ? { ...u, roleId, position: targetRole?.name || u.position } : u)) + ); + if (currentUser.id === userId) { + setCurrentUser((prev) => ({ ...prev, roleId, position: targetRole?.name || prev.position })); + } + }} + systemConfig={systemAuthConfig} + onUpdateSystemConfig={(cfg) => setSystemAuthConfig((prev) => ({ ...prev, ...cfg }))} onRefreshUsers={() => { authClient .getAdminUsers() diff --git a/web/src/api/auth.ts b/web/src/api/auth.ts index c2da6302..654b094b 100644 --- a/web/src/api/auth.ts +++ b/web/src/api/auth.ts @@ -336,6 +336,26 @@ export class AuthClient { return Array.from(this.users.values()).map(({ password, ...u }) => u); } + /** + * 别名兼容:listAllUsers + */ + async listAllUsers(): Promise { + return this.getAdminUsers(); + } + + /** + * 管理员重置指定用户密码并标记首次登录强制改密 + */ + async adminResetPassword(userId: number, tempPass: string): Promise { + const user = this.users.get(userId); + if (!user) throw new Error('用户不存在'); + user.password = tempPass; + user.mustChangePassword = true; + user.updatedAt = new Date().toISOString(); + this.saveToStorage(); + return true; + } + /** * 管理员手动创建新账号 */ diff --git a/web/src/components/admin/AdminSettingsModal.tsx b/web/src/components/admin/AdminSettingsModal.tsx index 21343c62..d42eef4f 100644 --- a/web/src/components/admin/AdminSettingsModal.tsx +++ b/web/src/components/admin/AdminSettingsModal.tsx @@ -3,23 +3,21 @@ import { ShieldAlert, Users, UserCheck, - ToggleLeft, - ToggleRight, - Shield, - Server, Plus, - Lock, - X, + Trash2, Check, - Ban, - CheckCircle2, + X, AlertCircle, - Settings2, - Info, + CheckCircle2, + Sliders, + Activity, + Shield, + Search, + UserPlus, } from 'lucide-react'; import { UserProfile } from '../../types/docgov'; import { RoleDefinition, PermissionKey } from '../../types/rbac'; -import { UserAccount, SystemAuthConfig } from '../../types/auth'; +import { SystemAuthConfig, UserAccount } from '../../types/auth'; import { authClient } from '../../api/auth'; export interface AdminSettingsModalProps { @@ -29,18 +27,61 @@ export interface AdminSettingsModalProps { roles: RoleDefinition[]; onSaveRoles: (roles: RoleDefinition[]) => void; allUsers: UserProfile[]; + onUpdateUserRole: (userId: number, roleId: string) => void; onRefreshUsers?: () => void; + systemConfig: SystemAuthConfig; + onUpdateSystemConfig: (config: Partial) => void; env?: string; } -const ALL_PERMISSION_ITEMS: { key: PermissionKey; name: string; desc: string }[] = [ - { key: 'project:create', name: '创建新项目/立项', desc: '具备在协同工作台开启全新研发项目并指派阶段的权限' }, - { key: 'project:assign_lead', name: '设置文档责任人', desc: '在文档治理面板为交付物指派编制人、审核人与批准人' }, - { key: 'perm:manage', name: '系统权限与角色管理', desc: '增删改全局职位角色定义与权限矩阵' }, - { key: 'workflow:config_visibility', name: '配置工作流默认可见性', desc: '调整业务阶段中各受控交付物的基线查阅准入边界' }, - { key: 'doc:override_visibility', name: '配置单文档可见性覆盖', desc: '针对特定核心机密交付物设定独占白名单或责任人收紧' }, - { key: 'doc:export', name: '受控交付物导出', desc: '允许将医疗受控文档编译导出为受保护的 DOCX 或 XLSX' }, - { key: 'workflow:view_all', name: '默认查阅常规工作流文档', desc: '允许在全景大盘中查阅已就绪的常规项目协作文档' }, +const ALL_PERMISSION_ITEMS: { + key: PermissionKey; + label: string; + desc: string; + category: '项目与流程' | '权限与安全' | '文档与导出'; +}[] = [ + { + key: 'project:create', + label: '创建新项目/立项', + desc: '允许在协同工作台左侧栏发起新医疗器械立项项目并克隆模板', + category: '项目与流程', + }, + { + key: 'project:assign_lead', + label: '设置/变更文档责任人', + desc: '允许在文档治理抽屉为交付物指派编制人、审核人与批准人', + category: '项目与流程', + }, + { + key: 'perm:manage', + label: '系统权限与角色管理', + desc: '管理员超级特权,可维护角色库、审批账号与配置准入策略', + category: '权限与安全', + }, + { + key: 'workflow:config_visibility', + label: '配置工作流默认可见性', + desc: '调整整个业务阶段中各受控交付物的基线查阅准入边界', + category: '权限与安全', + }, + { + key: 'doc:override_visibility', + label: '配置单文档可见性覆盖', + desc: '对特定涉密或专精文档设置独立白名单或责任人限制', + category: '权限与安全', + }, + { + key: 'workflow:view_all', + label: '查阅常规工作流受控文档', + desc: '是否可浏览项目内的受控工作流交付物大盘', + category: '文档与导出', + }, + { + key: 'doc:export', + label: '导出受控成果物 (DOCX/XLSX)', + desc: '允许将交付物编译导出为合规 DOCX 报告或计算 XLSX 表格', + category: '文档与导出', + }, ]; export const AdminSettingsModal: React.FC = ({ @@ -50,541 +91,877 @@ export const AdminSettingsModal: React.FC = ({ roles, onSaveRoles, allUsers: _allUsers, - onRefreshUsers, + onUpdateUserRole, + systemConfig, + onUpdateSystemConfig, env = 'dev', }) => { - const [activeTab, setActiveTab] = useState<'users' | 'approvals' | 'policy' | 'rbac' | 'system'>('users'); + const [activeTab, setActiveTab] = useState<'users' | 'approvals' | 'policy' | 'roles' | 'status'>('users'); - // Accounts state - const [userAccounts, setUserAccounts] = useState([]); - const [loadingUsers, setLoadingUsers] = useState(false); - const [feedbackTip, setFeedbackTip] = useState<{ type: 'success' | 'error'; msg: string } | null>(null); + // 全量账号列表(含待审核及禁用状态) + const [adminUsers, setAdminUsers] = useState([]); + const [userSearchText, setUserSearchText] = useState(''); + const [actionTip, setActionTip] = useState<{ type: 'success' | 'error'; msg: string } | null>(null); - // System auth config state - const [systemConfig, setSystemConfig] = useState({ - allowRegistration: true, - requireApproval: false, - }); - - // Create User modal - const [showCreateModal, setShowCreateModal] = useState(false); - const [newUsername, setNewUsername] = useState(''); - const [newName, setNewName] = useState(''); - const [newInitialPassword, setNewInitialPassword] = useState('123456'); - const [newRoleId, setNewRoleId] = useState('role-engineer'); - const [newDepartment, setNewDepartment] = useState('研发中心'); - const [newEmployeeId, setNewEmployeeId] = useState(''); - - // RBAC dynamic role editing state + // 角色库与编辑态 const [editRoles, setEditRoles] = useState(roles); const [selectedRoleId, setSelectedRoleId] = useState(roles[0]?.id || 'role-admin'); - const [showNewRoleDialog, setShowNewRoleDialog] = useState(false); + const [isCreatingRole, setIsCreatingRole] = useState(false); const [newRoleName, setNewRoleName] = useState(''); const [newRoleCode, setNewRoleCode] = useState(''); const [newRoleDesc, setNewRoleDesc] = useState(''); - // 1. 数据装载 - const loadAccounts = async () => { - setLoadingUsers(true); + // 管理员新建用户表单 + const [showCreateUserModal, setShowCreateUserModal] = useState(false); + const [newUname, setNewUname] = useState(''); + const [newRealName, setNewRealName] = useState(''); + const [newInitPass, setNewInitPass] = useState('InitPass123'); + const [newRoleId, setNewRoleId] = useState('role-engineer'); + const [newPosition, setNewPosition] = useState('方案工程师'); + const [newDept, setNewDept] = useState('研发中心'); + + // 准入策略本地状态 + const [allowReg, setAllowReg] = useState(systemConfig.allowRegistration); + const [requireAppr, setRequireAppr] = useState(systemConfig.requireApproval); + + // 刷新全量用户列表 + const refreshUsers = async () => { try { - const list = await authClient.getAdminUsers(); - setUserAccounts(list); - const cfg = await authClient.getSystemAuthConfig(); - setSystemConfig(cfg); - } catch (e: any) { - console.warn('Failed to load admin accounts:', e); - } finally { - setLoadingUsers(false); + const list = await authClient.listAllUsers(); + setAdminUsers(list); + } catch { + // 容错降级 } }; useEffect(() => { if (isOpen) { - loadAccounts(); + refreshUsers(); setEditRoles(roles); + setAllowReg(systemConfig.allowRegistration); + setRequireAppr(systemConfig.requireApproval); } - }, [isOpen, roles]); + }, [isOpen, roles, systemConfig]); if (!isOpen) return null; - // 严格入口防线:仅系统管理员独占访问 - const isAdmin = currentUser.roleId === 'role-admin' || currentUser.name.includes('管理员'); - if (!isAdmin) { - return ( -
-
-
- -
-

403 访问被拒绝

-

- 当前功能为系统管理控制台,仅系统管理员 (role-admin) 具备访问权限。 -

- -
-
- ); - } - - const showTip = (type: 'success' | 'error', msg: string) => { - setFeedbackTip({ type, msg }); - setTimeout(() => setFeedbackTip(null), 3000); + const showNotification = (type: 'success' | 'error', msg: string) => { + setActionTip({ type, msg }); + setTimeout(() => setActionTip(null), 3000); }; - // 职位分配 - const handleAssignRole = async (targetUser: UserAccount, targetRoleId: string) => { - const roleDef = roles.find((r) => r.id === targetRoleId); - const positionName = roleDef ? roleDef.name : '方案工程师'; + // 1. 人员职位与角色分配 + const handleUserRoleChange = async (userId: number, roleId: string) => { + const role = editRoles.find((r) => r.id === roleId); try { - await authClient.updateUserRole(targetUser.id, targetRoleId, positionName); - await loadAccounts(); - onRefreshUsers?.(); - showTip('success', `已将 ${targetUser.name} 的职位更新为【${positionName}】`); - } catch (e: any) { - showTip('error', e.message || '更新职位失败'); + await authClient.updateUserRole(userId, roleId, role?.name || '方案工程师'); + onUpdateUserRole(userId, roleId); + refreshUsers(); + showNotification('success', '已成功变更该成员的所属职位与系统角色'); + } catch (err: any) { + showNotification('error', err.message || '更新角色失败'); } }; - // 审批通过并指派职位 - const handleApproveUser = async (targetUser: UserAccount, targetRoleId: string) => { - const roleDef = roles.find((r) => r.id === targetRoleId); - const positionName = roleDef ? roleDef.name : '方案工程师'; + // 2. 账号状态(激活/停用) + const handleToggleUserStatus = async (user: UserAccount) => { + const nextStatus = user.status === 'active' ? 'disabled' : 'active'; try { - await authClient.updateUserStatus(targetUser.id, 'active', targetRoleId, positionName); - await loadAccounts(); - onRefreshUsers?.(); - showTip('success', `已批准 ${targetUser.name} 入职并指派职位【${positionName}】`); - } catch (e: any) { - showTip('error', e.message || '审批处理失败'); + await authClient.updateUserStatus(user.id, nextStatus); + refreshUsers(); + showNotification('success', `已将账号【${user.name}】状态切换为:${nextStatus === 'active' ? '启用' : '停用'}`); + } catch (err: any) { + showNotification('error', err.message || '切换状态失败'); } }; - // 停用/启用账号 - const handleToggleUserStatus = async (targetUser: UserAccount) => { - const nextStatus = targetUser.status === 'active' ? 'disabled' : 'active'; + // 3. 审批通过或驳回 + const handleApproveRegistration = async (user: UserAccount, targetRoleId: string) => { + const role = editRoles.find((r) => r.id === targetRoleId); try { - await authClient.updateUserStatus(targetUser.id, nextStatus); - await loadAccounts(); - onRefreshUsers?.(); - showTip('success', `已将账号 ${targetUser.name} 设置为 ${nextStatus === 'active' ? '正常' : '已停用'}`); - } catch (e: any) { - showTip('error', e.message || '切换状态失败'); + await authClient.updateUserStatus(user.id, 'active', targetRoleId, role?.name || '方案工程师'); + refreshUsers(); + showNotification('success', `已批准【${user.name}】注册申请,已正式授予【${role?.name}】职位`); + } catch (err: any) { + showNotification('error', err.message || '审批操作失败'); } }; - // 管理员新建账号 - const handleCreateUser = async (e: React.FormEvent) => { + const handleRejectRegistration = async (user: UserAccount) => { + if (!window.confirm(`确定要驳回【${user.name}】的注册申请吗?该账号将被置为禁用。`)) return; + try { + await authClient.updateUserStatus(user.id, 'disabled'); + refreshUsers(); + showNotification('success', `已驳回【${user.name}】的注册申请`); + } catch (err: any) { + showNotification('error', err.message || '驳回操作失败'); + } + }; + + // 4. 重置用户密码为初始并强制改密 + const handleResetUserPassword = async (user: UserAccount) => { + const tempPass = 'ResetPass123'; + if (!window.confirm(`确定要重置【${user.name}】的密码吗?重置后初始密码为 ${tempPass},该用户下次登录将被强制改密。`)) return; + try { + await authClient.adminResetPassword(user.id, tempPass); + refreshUsers(); + showNotification('success', `已重置【${user.name}】密码为 ${tempPass},已标记首次登录强制改密`); + } catch (err: any) { + showNotification('error', err.message || '重置密码失败'); + } + }; + + // 5. 管理员新建账号 + const handleCreateUserSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (!newUsername.trim() || !newName.trim() || !newInitialPassword.trim()) { - showTip('error', '请填写必填字段'); + if (!newUname.trim() || !newRealName.trim() || !newInitPass) { + showNotification('error', '请填写完整账号、姓名与初始密码'); return; } - const roleDef = roles.find((r) => r.id === newRoleId); - const positionName = roleDef ? roleDef.name : '方案工程师'; - try { await authClient.createAdminUser({ - username: newUsername.trim(), - name: newName.trim(), - initialPassword: newInitialPassword.trim(), + username: newUname.trim(), + name: newRealName.trim(), + initialPassword: newInitPass, roleId: newRoleId, - position: positionName, - department: newDepartment.trim() || undefined, - employeeId: newEmployeeId.trim() || undefined, + position: newPosition, + department: newDept, }); - setShowCreateModal(false); - setNewUsername(''); - setNewName(''); - setNewInitialPassword('123456'); - setNewEmployeeId(''); - await loadAccounts(); - onRefreshUsers?.(); - showTip('success', `新员工账号创建成功!已标记为【首次登录必须改密】。`); - } catch (e: any) { - showTip('error', e.message || '创建账号失败'); + setShowCreateUserModal(false); + setNewUname(''); + setNewRealName(''); + refreshUsers(); + showNotification('success', `已成功开通受控账号【${newRealName}】,已自动标记首次登录强制改密`); + } catch (err: any) { + showNotification('error', err.message || '新建员工账号失败'); } }; - // 准入策略切换 - const handleTogglePolicy = async (key: keyof SystemAuthConfig) => { - const updated = { ...systemConfig, [key]: !systemConfig[key] }; + // 6. 保存准入策略 + const handleSavePolicy = async () => { try { - await authClient.updateSystemAuthConfig(updated); - setSystemConfig(updated); - showTip('success', '准入治理策略已实时生效'); - } catch (e: any) { - showTip('error', e.message || '更新策略失败'); + await authClient.updateSystemAuthConfig({ + allowRegistration: allowReg, + requireApproval: requireAppr, + }); + onUpdateSystemConfig({ + allowRegistration: allowReg, + requireApproval: requireAppr, + }); + showNotification('success', '系统准入策略已即刻全局生效'); + } catch (err: any) { + showNotification('error', err.message || '更新策略失败'); } }; - // RBAC 权限勾选 - const handleTogglePermission = (roleId: string, permKey: PermissionKey) => { - setEditRoles((prev) => - prev.map((r) => { - if (r.id !== roleId) return r; - const exists = r.permissions.includes(permKey); - const nextPerms = exists - ? r.permissions.filter((p) => p !== permKey) - : [...r.permissions, permKey]; - return { ...r, permissions: nextPerms, updatedAt: new Date().toISOString() }; - }) + // 7. 角色权限矩阵操作 + const currentSelectedRole = editRoles.find((r) => r.id === selectedRoleId) || editRoles[0]; + + const handleTogglePermission = (permKey: PermissionKey) => { + if (!currentSelectedRole || currentSelectedRole.isSystem) return; + const exists = currentSelectedRole.permissions.includes(permKey); + const updatedPermissions = exists + ? currentSelectedRole.permissions.filter((k) => k !== permKey) + : [...currentSelectedRole.permissions, permKey]; + + const updatedRoles = editRoles.map((r) => + r.id === currentSelectedRole.id + ? { ...r, permissions: updatedPermissions, updatedAt: new Date().toLocaleString() } + : r ); + setEditRoles(updatedRoles); + onSaveRoles(updatedRoles); + showNotification('success', `已更新【${currentSelectedRole.name}】权限矩阵`); }; - // 保存 RBAC 角色 - const handleSaveRBAC = () => { - onSaveRoles(editRoles); - showTip('success', '全局角色与权限矩阵已保存并实时广播!'); + const handleCreateRole = () => { + if (!newRoleName.trim() || !newRoleCode.trim()) return; + const newId = `role-custom-${Date.now()}`; + const newRole: RoleDefinition = { + id: newId, + name: newRoleName.trim(), + code: newRoleCode.trim().toUpperCase(), + description: newRoleDesc.trim() || '自定义受控职位角色', + isSystem: false, + permissions: ['workflow:view_all', 'doc:export'], + createdAt: new Date().toLocaleString(), + updatedAt: new Date().toLocaleString(), + }; + const nextRoles = [...editRoles, newRole]; + setEditRoles(nextRoles); + onSaveRoles(nextRoles); + setSelectedRoleId(newId); + setIsCreatingRole(false); + setNewRoleName(''); + setNewRoleCode(''); + setNewRoleDesc(''); + showNotification('success', `已创建新业务角色【${newRole.name}】`); + }; + + const handleDeleteRole = (roleId: string) => { + const roleToDelete = editRoles.find((r) => r.id === roleId); + if (!roleToDelete || roleToDelete.isSystem) return; + if (window.confirm(`确定要删除角色【${roleToDelete.name}】吗?删除后该角色人员将自动退回普通工程师。`)) { + const nextRoles = editRoles.filter((r) => r.id !== roleId); + setEditRoles(nextRoles); + onSaveRoles(nextRoles); + setSelectedRoleId(nextRoles[0]?.id || 'role-admin'); + showNotification('success', `已删除角色【${roleToDelete.name}】`); + } }; // 待审批列表 - const pendingUsers = userAccounts.filter((u) => u.status === 'pending_approval'); - - const selectedRole = editRoles.find((r) => r.id === selectedRoleId) || editRoles[0]; + const pendingUsers = adminUsers.filter((u) => u.status === 'pending_approval'); + // 过滤后的用户列表 + const filteredUsers = adminUsers.filter( + (u) => + u.name.toLowerCase().includes(userSearchText.toLowerCase()) || + (u.username && u.username.toLowerCase().includes(userSearchText.toLowerCase())) || + (u.department && u.department.toLowerCase().includes(userSearchText.toLowerCase())) + ); return ( -
-
+
{ + if (e.target === e.currentTarget) onClose(); + }} + > +
{/* 顶部标题栏 */} -
-
-
- +
+
+
+
-
-

系统管理与治理控制台

- - 管理员特权独占 +
+ + 系统管理控制台 - - ENV: {env.toUpperCase()} + + ADMIN 独占 + + + 运行环境: {env.toUpperCase()}
-

- 人员账号审核、受控职位指派、准入开关及全局 RBAC 权限矩阵 -

+
+ 人员职位治理 · 注册审批中心 · 系统准入策略 · RBAC 角色权限矩阵 +
-
- {feedbackTip && ( -
+ {actionTip && ( + - {feedbackTip.type === 'success' ? ( - - ) : ( - - )} - {feedbackTip.msg} -
+ {actionTip.type === 'success' ? : } + {actionTip.msg} + )}
- {/* 主体布局 */} -
- {/* 左侧导航 Tab */} -
- + {/* 主体左右分栏 */} +
+ {/* 左侧垂直导航 Tab */} +
+
+ - + - + - + - +
+ + {/* 底部系统信息 */} +
- - 环境与运行状态 - +
水木东方医疗器械 AI 工作台
+
Auth & RBAC v2.4
+
- {/* 右侧主工作区 */} -
- {/* 1. 人员与职位治理 */} + {/* 右侧主视口 */} +
+ {/* Tab 1: 人员与职位治理 */} {activeTab === 'users' && ( -
-
-
-

研发中心全员账号与职位指派

-

- 职位对应系统角色,决定其在研发协同工作台中的立项、文档责任人分配与受控操作权限 -

+
+
+
+ + setUserSearchText(e.target.value)} + placeholder="搜索姓名、用户名或部门..." + style={{ + width: '100%', + padding: '7px 10px 7px 32px', + borderRadius: '8px', + backgroundColor: 'var(--bg-app)', + border: '1px solid var(--border)', + color: 'var(--text-strong)', + fontSize: '12px', + boxSizing: 'border-box', + }} + />
+
- {loadingUsers ? ( -
正在同步系统用户列表...
- ) : ( -
- - - - - - - - - - - - {userAccounts.map((u) => { - const isSuperAdmin = u.username === 'admin' || u.roleId === 'role-admin'; - return ( - - + + + + + + + ); + })} + +
姓名 / 用户名部门 / 工号受控职位 (指派角色)账号状态管理操作
-
- {u.name} - {u.mustChangePassword && ( - - 待首次改密 - - )} + {/* 用户列表表格 */} +
+ + + + + + + + + + + + + {filteredUsers.map((u) => { + const isSelf = u.id === currentUser.id; + return ( + + - - - - - - - - - - ); - })} - -
成员姓名登录账号 / 工号所属部门系统职位与角色账号状态操作治理
+
+
+ {u.name.slice(0, 1)}
-
{u.username}
-
-
{u.department || '未录入'}
-
{u.employeeId || '-'}
-
- {isSuperAdmin ? ( - - 系统管理员 - - ) : ( - - )} - - {u.status === 'active' && ( - - 正常 + {u.name} + {isSelf && ( + + 当前账号 )} - {u.status === 'pending_approval' && ( - - 待审核 - - )} - {u.status === 'disabled' && ( - - 已停用 - - )} - - {!isSuperAdmin && ( -
- -
- )} -
-
- )} -
- )} - - {/* 2. 注册审批中心 */} - {activeTab === 'approvals' && ( -
-
-

新注册员工审核与职位指派

-

- 当系统准入策略开启“新员工需管理员审核”时,自主注册的申请人将在此汇聚,待指派正式职位后激活 -

-
- - {pendingUsers.length === 0 ? ( -
- -

当前没有待审核的注册申请

-

- 所有注册员工均已审核激活,或当前准入策略已设置为自动激活 -

-
- ) : ( -
- {pendingUsers.map((pu) => { - return ( -
-
-
- {pu.name} - ({pu.username}) - - 待审申请 - -
-
- 部门: {pu.department || '未填写'} - 工号: {pu.employeeId || '未填写'} - 申请时间: {pu.createdAt || '近期'} -
-
- -
-
- 指派职位: +
+
+
{u.username || '系统预设'}
+
{u.employeeId || 'EMP-未设定'}
+
+ {u.department || '未分配部门'} + + + + {u.status === 'active' ? '正常在职' : u.status === 'pending_approval' ? '待审核' : '已停用'} + + +
+ + {!isSelf && ( + + )} +
+
+
+
+ )} + + {/* Tab 2: 注册审批中心 */} + {activeTab === 'approvals' && ( +
+
+
+ 新员工注册审批与职位指派中心 +
+
+ 依据 NMPA 体系规范,自主注册申请需经系统管理员人工核准并正式授予业务职位。 +
+
+ + {pendingUsers.length === 0 ? ( +
+ +
+ 当前没有待审批的注册申请 +
+
+ 所有新注册员工均已完成合规审批并完成业务职位委派。 +
+
+ ) : ( +
+ {pendingUsers.map((u) => { + return ( +
+
+
+ + {u.name} + + + 账号: {u.username} + + + 待审核 +
+
+ 部门: {u.department || '未填写'} + 工号: {u.employeeId || '未填写'} + 申请时间: {u.createdAt || '刚刚'} +
+
+ +
+
@@ -595,262 +972,339 @@ export const AdminSettingsModal: React.FC = ({
)} - {/* 3. 系统准入策略 */} + {/* Tab 3: 系统准入策略 */} {activeTab === 'policy' && ( -
-
-

系统账号准入与注册安全策略

-

- 控制外部人员能否自行在登录界面注册,以及新用户是否必须经过管理员审批与受控职位授权 -

+
+
+
+ 系统全局准入与注册审核策略 +
+
+ 控制系统外部开放度,关闭自主注册或开启强制审核均实时阻断越权账号。 +
-
- {/* 开关 1: 开放自主注册 */} -
-
-
- 允许员工自主注册账号 - - {systemConfig.allowRegistration ? '已开启' : '已暂停'} - -
-

- 关闭后,登录主页将彻底隐藏注册入口,仅允许系统管理员在后台手动添加开通账号。 -

+ {/* 开关 1: 开放自主注册 */} +
+
+
+ 允许新用户自主注册账号
- - -
- - {/* 开关 2: 注册需审批 */} -
-
-
- 新注册账号必须经过管理员审批 - - {systemConfig.requireApproval ? '强制审批' : '免审激活'} - -
-

- 开启后,自主注册的账号处于待审核状态无法登录,必须由管理员在“新用户注册审核”中心指派职位并激活。 -

+
+ 开启后,登录页将呈现「用户自主注册」选项卡;关闭后,仅管理员可在控制台开通账号。
- -
+ setAllowReg(e.target.checked)} + style={{ width: '18px', height: '18px', cursor: 'pointer', accentColor: 'var(--brand-500)' }} + /> +
+ + {/* 开关 2: 注册需审核 */} +
+
+
+ 新注册账号必须由管理员审核通过 +
+
+ 开启后,新注册账号处于「待审核」状态禁止登录,直至管理员在审批中心核准并正式分配职位。 +
+
+ setRequireAppr(e.target.checked)} + style={{ width: '18px', height: '18px', cursor: 'pointer', accentColor: 'var(--brand-500)' }} + /> +
+ +
+
)} - {/* 4. 全局角色与权限矩阵 (RBAC) */} - {activeTab === 'rbac' && ( -
-
-
-

全局动态角色与 7 项原子权限矩阵

-

- 支持动态增删角色,定制各职位默认拥有的全局业务权限能力 -

-
-
- - -
-
- - {/* 角色卡片选择 */} -
- {editRoles.map((r) => { - const isSelected = r.id === selectedRoleId; - return ( -
setSelectedRoleId(r.id)} - className={`p-3 rounded-xl border cursor-pointer transition ${ - isSelected - ? 'bg-amber-500/10 border-amber-500/50 text-white shadow-sm' - : 'bg-slate-950/60 border-slate-800 text-slate-400 hover:border-slate-700' - }`} - > -
- {r.name} - {r.isSystem && ( - - 内置 - + {/* Tab 4: RBAC 角色权限矩阵 */} + {activeTab === 'roles' && ( +
+ {/* 角色列表 */} +
+
+
+ 全局受控角色库 ({editRoles.length}) +
+ {editRoles.map((r) => { + const isSel = r.id === selectedRoleId; + return ( +
setSelectedRoleId(r.id)} + style={{ + padding: '8px 10px', + borderRadius: '6px', + backgroundColor: isSel ? 'var(--bg-panel)' : 'transparent', + border: isSel ? '1px solid var(--brand-500)' : '1px solid transparent', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + }} + > +
+
+ {r.name} +
+
+ {r.code} · {r.permissions.length} 项权限 +
+
+ {!r.isSystem && ( + )}
-
{r.code}
-

{r.description}

+ ); + })} +
+ + {/* 新增自定义角色 */} +
+ {isCreatingRole ? ( +
+ setNewRoleName(e.target.value)} + placeholder="角色名称 (如 临床协调员)" + style={{ width: '100%', padding: '6px 8px', borderRadius: '4px', backgroundColor: 'var(--bg-app)', border: '1px solid var(--border)', color: 'var(--text-strong)', fontSize: '11px', boxSizing: 'border-box' }} + /> + setNewRoleCode(e.target.value)} + placeholder="标识代号 (如 CRA)" + style={{ width: '100%', padding: '6px 8px', borderRadius: '4px', backgroundColor: 'var(--bg-app)', border: '1px solid var(--border)', color: 'var(--text-strong)', fontSize: '11px', boxSizing: 'border-box' }} + /> +
+ + +
- ); - })} + ) : ( + + )} +
- {/* 选定角色的权限勾选矩阵 */} - {selectedRole && ( -
-
-
- - 配置【{selectedRole.name}】的授权项 - - - (已选 {selectedRole.permissions.length} 项) - -
- {selectedRole.isSystem && selectedRole.code === 'ADMIN' && ( - - 系统管理员拥有全部底层通配权限 + {/* 对应角色的权限复选矩阵 */} +
+
+
+ + 【{currentSelectedRole?.name}】权限配置矩阵 + + {currentSelectedRole?.isSystem && ( + + 系统内置受控角色 (只读) )}
- -
- {ALL_PERMISSION_ITEMS.map((perm) => { - const checked = - selectedRole.code === 'ADMIN' || selectedRole.permissions.includes(perm.key); - const disabled = selectedRole.code === 'ADMIN'; - - return ( - - ); - })} +
+ {currentSelectedRole?.description}
- )} + +
+ {ALL_PERMISSION_ITEMS.map((item) => { + const isChecked = + currentSelectedRole?.code === 'ADMIN' || + currentSelectedRole?.permissions.includes(item.key); + const isLocked = currentSelectedRole?.isSystem; + + return ( +
!isLocked && handleTogglePermission(item.key)} + style={{ + padding: '12px 14px', + borderRadius: '8px', + backgroundColor: isChecked ? 'var(--bg-subtle)' : 'var(--bg-app)', + border: isChecked ? '1px solid var(--border)' : '1px dashed var(--border)', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + cursor: isLocked ? 'not-allowed' : 'pointer', + }} + > +
+
+ + {item.label} + + + {item.category} + +
+
+ {item.desc} +
+
+ + {}} + style={{ width: '16px', height: '16px', accentColor: 'var(--brand-500)', cursor: isLocked ? 'not-allowed' : 'pointer' }} + /> +
+ ); + })} +
+
)} - {/* 5. 环境与运行状态 */} - {activeTab === 'system' && ( -
-
-

系统运行架构与环境隔离状态

-

- 监控开发、测试与生产环境三套隔离状态,以及知识库共享与大模型后端运行健康度 -

-
- -
-
- - - 运行环境 (ENV) - -
{env.toUpperCase()}
-

- {env === 'dev' - ? '开发环境:支持测试人员调试快捷身份切换,数据隔离在 data/dev' - : '测试/生产环境:严格禁用身份切换,仅允许真实登出与登录'} -

+ {/* Tab 5: 系统状态与审计 */} + {activeTab === 'status' && ( +
+
+
+ 系统安全与运行状态
- -
- - - 超级管理员凭据 (.env) - -
- ADMIN_USERNAME: admin -
-
- ADMIN_PASSWORD: •••••••••••••• -
-

- 系统管理员凭据严格从运行环境环境变量动态注入,具备防死锁兜底 -

+
+ 环境拓扑隔离与安全凭据审计。
-
- 跨环境隔离合规准则摘要 -
-

• 知识库共享:共享合规法规数据库(端口 5432,数据库 regulations)。

-

• 对话与业务库隔离(红线):各环境对话历史、草稿与受控文档库物理隔离在各自目录,互不可见。

-

• 身份切换安全隔离(红线):真实测试(test)与发布(main)环境禁止一切快捷身份切换,必须凭密码登录。

+
+
+
当前运行环境态
+
+ {env.toUpperCase()} +
+
+ {env === 'dev' ? '开发态 (允许测试快速身份切换)' : '正式受控态 (强制真实登出/登录)'} +
+
+ +
+
密码加密算法
+
+ Bcrypt (加盐哈希) +
+
+ 符合 21 CFR Part 11 电子签名不可逆哈希标准 +
+
+ +
+
当前系统人员总数
+
+ {adminUsers.length} 人 +
+
+ 在职正常: {adminUsers.filter((u) => u.status === 'active').length} 人 · 待审: {pendingUsers.length} 人 +
+
+ +
+
超管凭据配置通道
+
+ .env 动态环境变量 +
+
+ ADMIN_USERNAME 与 ADMIN_PASSWORD 受控注入 +
@@ -859,78 +1313,81 @@ export const AdminSettingsModal: React.FC = ({
- {/* 新建员工账号弹窗 */} - {showCreateModal && ( -
-
-
-

- - 管理员开通新员工账号 -

- + {/* 管理员直接开通员工账号的内层弹窗 */} + {showCreateUserModal && ( +
{ + if (e.target === e.currentTarget) setShowCreateUserModal(false); + }} + > +
+
+ 开通受控员工账号 +
+
+ 由管理员开通的员工,首次登录系统将被强制修改初始密码。
-
-
-
- - setNewName(e.target.value)} - placeholder="例如: 赵工" - className="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-1.5 text-xs text-slate-100 focus:outline-none focus:border-amber-500" - /> -
-
- - setNewUsername(e.target.value)} - placeholder="如: zhaogong" - className="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-1.5 text-xs text-slate-100 focus:outline-none focus:border-amber-500" - /> -
-
- +
-
-
+ +
+
-
+
- + setNewDepartment(e.target.value)} - placeholder="如: 研发中心" - className="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-1.5 text-xs text-slate-200" + value={newDept} + onChange={(e) => setNewDept(e.target.value)} + style={{ width: '100%', padding: '8px 10px', borderRadius: '6px', backgroundColor: 'var(--bg-app)', border: '1px solid var(--border)', color: 'var(--text-strong)', fontSize: '12px', boxSizing: 'border-box' }} />
- + setNewEmployeeId(e.target.value)} - placeholder="如: RD-099" - className="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-1.5 text-xs text-slate-200" + value={newInitPass} + onChange={(e) => setNewInitPass(e.target.value)} + style={{ width: '100%', padding: '8px 10px', borderRadius: '6px', backgroundColor: 'var(--bg-app)', border: '1px solid var(--border)', color: 'var(--text-strong)', fontSize: '12px', boxSizing: 'border-box' }} />
-
- - 核心约束:由管理员创建的账号,首次登录系统时将被强制要求修改密码。 -
- -
+
@@ -985,82 +1439,6 @@ export const AdminSettingsModal: React.FC = ({
)} - - {/* 新增自定义角色对话框 */} - {showNewRoleDialog && ( -
-
-

新增自定义受控角色

-
-
- - setNewRoleName(e.target.value)} - placeholder="如: 合规审计专家" - className="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-1.5 text-xs text-slate-100" - /> -
-
- - setNewRoleCode(e.target.value.toUpperCase())} - placeholder="如: AUDITOR" - className="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-1.5 text-xs text-slate-100 font-mono" - /> -
-
- -