diff --git a/web/src/components/user/UserSettingsModal.tsx b/web/src/components/user/UserSettingsModal.tsx new file mode 100644 index 00000000..00c8e6e0 --- /dev/null +++ b/web/src/components/user/UserSettingsModal.tsx @@ -0,0 +1,518 @@ +import React, { useState } from 'react'; +import { + User, + Shield, + KeyRound, + LogOut, + Check, + X, + AlertCircle, + CheckCircle2, + Lock, + FileText, + Sliders, +} from 'lucide-react'; +import { UserProfile } from '../../types/docgov'; +import { RoleDefinition } from '../../types/rbac'; +import { authClient } from '../../api/auth'; + +export interface UserSettingsModalProps { + isOpen: boolean; + onClose: () => void; + currentUser: UserProfile; + roles: RoleDefinition[]; + onUpdateProfile: (updated: Partial) => void; + onLogout: () => void; + theme?: 'dark' | 'light'; + onToggleTheme?: () => void; +} + +const ALL_PERMISSION_DESCRIPTIONS: { key: string; 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: '允许在全景大盘中查阅已就绪的常规项目协作文档' }, +]; + +export const UserSettingsModal: React.FC = ({ + isOpen, + onClose, + currentUser, + roles, + onUpdateProfile, + onLogout, + theme = 'dark', + onToggleTheme, +}) => { + const [activeTab, setActiveTab] = useState<'profile' | 'security' | 'preferences' | 'permissions'>('profile'); + + // Profile form state + const [name, setName] = useState(currentUser.name); + const [department, setDepartment] = useState(currentUser.department); + const [employeeId] = useState(currentUser.employeeId); + const [profileSuccessTip, setProfileSuccessTip] = useState(false); + + // Security password state + const [oldPassword, setOldPassword] = useState(''); + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [passwordError, setPasswordError] = useState(''); + const [passwordSuccessTip, setPasswordSuccessTip] = useState(false); + const [passwordLoading, setPasswordLoading] = useState(false); + + // Preferences + const [exportFormat, setExportFormat] = useState<'docx' | 'xlsx'>('docx'); + + if (!isOpen) return null; + + // 获取当前用户的角色对象 + const currentRole = roles.find((r) => r.id === (currentUser.roleId || 'role-engineer')); + const userPermissions = new Set(currentRole?.permissions || []); + if (currentUser.roleId === 'role-admin' || currentRole?.code === 'ADMIN') { + ALL_PERMISSION_DESCRIPTIONS.forEach((p) => userPermissions.add(p.key)); + } + + const handleSaveProfile = (e: React.FormEvent) => { + e.preventDefault(); + onUpdateProfile({ + name: name.trim(), + department: department.trim(), + }); + setProfileSuccessTip(true); + setTimeout(() => setProfileSuccessTip(false), 2500); + }; + + const handleChangePassword = async (e: React.FormEvent) => { + e.preventDefault(); + setPasswordError(''); + setPasswordSuccessTip(false); + + if (!oldPassword) { + setPasswordError('请输入当前原密码'); + return; + } + if (!newPassword || newPassword.length < 6) { + setPasswordError('新密码长度不能少于 6 位'); + return; + } + if (newPassword !== confirmPassword) { + setPasswordError('两次输入的新密码不一致'); + return; + } + + setPasswordLoading(true); + try { + await authClient.changePassword(currentUser.id, oldPassword, newPassword); + setPasswordSuccessTip(true); + setOldPassword(''); + setNewPassword(''); + setConfirmPassword(''); + setTimeout(() => setPasswordSuccessTip(false), 3000); + } catch (err: any) { + setPasswordError(err.message || '密码修改失败'); + } finally { + setPasswordLoading(false); + } + }; + + return ( +
+
+ {/* 顶栏 */} +
+
+
+ +
+
+

+ 个人中心与偏好设置 + + {currentUser.name} + +

+

+ 管理您的个人资料、登录安全凭证及操作偏好 +

+
+
+ +
+ + {/* 主体左右分栏 */} +
+ {/* 左侧菜单 Tab */} +
+
+ + + + + + + +
+ + {/* 底部退出登录 */} +
+ +
+
+ + {/* 右侧内容面板 */} +
+ {/* 1. 个人资料 */} + {activeTab === 'profile' && ( +
+
+
+

基础档案

+

查看工号与职位,完善姓名与部门信息

+
+ {profileSuccessTip && ( + + 资料已保存 + + )} +
+ +
+
+ + setName(e.target.value)} + className="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-100 focus:outline-none focus:border-cyan-500" + /> +
+ +
+ + +
+
+ +
+
+ + setDepartment(e.target.value)} + className="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-100 focus:outline-none focus:border-cyan-500" + /> +
+ +
+ +
+ {currentUser.position} + + {currentRole?.name || '方案工程师'} + +
+
+
+ + {/* 职位受控说明 */} +
+ + + 您的职位与权限由系统管理员在后台统一设定与审核,个人不可调整。如需变更职位或增加权限,请联系管理员。 + +
+ +
+ +
+
+ )} + + {/* 2. 安全与改密 */} + {activeTab === 'security' && ( +
+
+

修改登录密码

+

定期更新密码保障受控医疗器械工程研发数据安全

+
+ + {passwordError && ( +
+ + {passwordError} +
+ )} + + {passwordSuccessTip && ( +
+ + 密码已成功更新!请妥善保管新密码。 +
+ )} + +
+ + setOldPassword(e.target.value)} + placeholder="请输入当前密码" + className="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-100 focus:outline-none focus:border-cyan-500" + /> +
+ +
+
+ + setNewPassword(e.target.value)} + placeholder="不少于6位" + className="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-100 focus:outline-none focus:border-cyan-500" + /> +
+ +
+ + setConfirmPassword(e.target.value)} + placeholder="再次输入新密码" + className="w-full bg-slate-950 border border-slate-800 rounded-xl px-3 py-2 text-xs text-slate-100 focus:outline-none focus:border-cyan-500" + /> +
+
+ +
+ +
+
+ )} + + {/* 3. 偏好设定 */} + {activeTab === 'preferences' && ( +
+
+

协同工作台偏好

+

个性化定制您的操作体验与交付物导出配置

+
+ + {/* 主题切换 */} +
+
+ 界面色彩主题 +

切换深色或明亮设计视觉风格

+
+ +
+ + {/* 默认导出格式 */} +
+
+ 受控文档默认导出格式 +

快捷导出受控交付物时的优先格式

+
+
+ + +
+
+
+ )} + + {/* 4. 我的权限清单 */} + {activeTab === 'permissions' && ( +
+
+
+

当前账号权限能力清单

+

+ 基于职位关联角色 [{currentRole?.name}] 分配的 7 项原子权限 +

+
+ + 已获授权: {userPermissions.size} / {ALL_PERMISSION_DESCRIPTIONS.length} + +
+ +
+ {ALL_PERMISSION_DESCRIPTIONS.map((perm) => { + const hasIt = userPermissions.has(perm.key); + return ( +
+
+
+ + {perm.name} + + + {perm.key} + +
+

{perm.desc}

+
+ +
+ {hasIt ? ( + + + 已授权 + + ) : ( + + + 未获得 + + )} +
+
+ ); + })} +
+
+ )} +
+
+
+
+ ); +};