diff --git a/src/app/(main)/admins/page.tsx b/src/app/(main)/admins/page.tsx index ac8b70a..713e1a5 100644 --- a/src/app/(main)/admins/page.tsx +++ b/src/app/(main)/admins/page.tsx @@ -22,6 +22,10 @@ function genPassword(len = 10): string { type View = 'list' | 'person' | 'roles' | 'roleForm'; +// 「自定义」哨兵角色:不是共享角色,选它时可见页由本人逐页勾选(存 pages_override),与后端一致。 +const CUSTOM_ROLE = 'custom'; +const CUSTOM_LABEL = '自定义'; + // ===== 只读权限矩阵:角色详情 / 人员「可见页面」预览共用(深色=有,灰色=无)===== function PermMatrix({ catalog, pages }: { catalog: PermissionGroup[]; pages: string[] }) { const has = useMemo(() => new Set(pages), [pages]); @@ -109,6 +113,11 @@ export default function AdminsPage() { return m; }, [roles]); const roleOptions = roles.map((r) => ({ value: r.name, label: r.label })); + // 人员表单角色下拉:真实角色 + 末尾「自定义」。选「自定义」→ 下方可见页改为逐页勾选。 + const personRoleOptions = [...roleOptions, { value: CUSTOM_ROLE, label: CUSTOM_LABEL }]; + // 角色 key → 展示名(custom 显示「自定义」;查不到回退 key 原文) + const roleLabelOf = (name: string) => + (name === CUSTOM_ROLE ? CUSTOM_LABEL : roleByName[name]?.label ?? name); const loadAll = async () => { setLoading(true); @@ -134,11 +143,14 @@ export default function AdminsPage() { const [personName, setPersonName] = useState(''); const [personRole, setPersonRole] = useState(''); const [initPw, setInitPw] = useState(''); + // 「自定义」角色时逐页勾选的可见页集(role==custom 才用到) + const [personPages, setPersonPages] = useState([]); const openCreatePerson = () => { setPersonEditing(null); setPersonName(''); setPersonRole(roles.find((r) => !r.is_builtin)?.name ?? roles[0]?.name ?? 'operator'); + setPersonPages([]); setInitPw(genPassword()); setView('person'); }; @@ -146,6 +158,8 @@ export default function AdminsPage() { setPersonEditing(a); setPersonName(a.username); setPersonRole(a.role); + // 编辑「自定义」用户时,回填其已勾选的可见页(供继续增删) + setPersonPages(a.role === CUSTOM_ROLE ? (a.pages_override ?? []) : []); setInitPw(''); // 编辑态密码只读展示 a.password(已确定的登录密码),不用 initPw setView('person'); }; @@ -160,18 +174,30 @@ export default function AdminsPage() { ); + const isCustom = personRole === CUSTOM_ROLE; const submitPerson = async () => { + if (isCustom && personPages.length === 0) { message.error('自定义角色请至少勾选一个可见页面'); return; } try { if (personEditing) { - // 密码只读、不改;只提交变更的角色 - if (personRole !== personEditing.role) { - await api.patch(`/admin/api/admins/${personEditing.id}`, { role: personRole }); + // 密码只读、不改;提交变更的角色 +(自定义时)勾选的可见页 + const roleChanged = personRole !== personEditing.role; + const pagesChanged = isCustom + && JSON.stringify(personPages) !== JSON.stringify(personEditing.pages_override ?? []); + if (roleChanged || pagesChanged) { + const payload: Record = {}; + if (roleChanged) payload.role = personRole; + // 自定义:带上勾选集(切到 custom 或改现有 custom 勾选都要);切回普通角色不带,后端会清空 override + if (isCustom) payload.pages_override = personPages; + await api.patch(`/admin/api/admins/${personEditing.id}`, payload); } message.success('已保存'); } else { if (personName.trim().length < 3) { message.error('用户名至少 3 位'); return; } await api.post('/admin/api/admins', { - username: personName.trim(), password: initPw, role: personRole, + username: personName.trim(), + password: initPw, + role: personRole, + ...(isCustom ? { pages_override: personPages } : {}), }); message.success('已创建,请把初始密码转交本人'); } @@ -184,8 +210,10 @@ export default function AdminsPage() { // ===== 列表操作 ===== const changeRole = (a: AdminInfo, role: string) => { if (role === a.role) return; + // 改成「自定义」需要逐页勾选 → 转去编辑页(列表里没有页选择器,直接切会得到空可见页) + if (role === CUSTOM_ROLE) { openEditPerson({ ...a, role: CUSTOM_ROLE }); return; } modal.confirm({ - title: `把 ${a.username} 的角色改为 ${role}?`, + title: `把 ${a.username} 的角色改为 ${roleLabelOf(role)}?`, onOk: async () => { try { await api.patch(`/admin/api/admins/${a.id}`, { role }); message.success('已更新'); loadAll(); } catch (e) { message.error(errMsg(e)); } @@ -266,8 +294,9 @@ export default function AdminsPage() { { title: '用户名', dataIndex: 'username' }, { title: '角色', dataIndex: 'role', width: 170, + // options 含「自定义」→ custom 用户在列表里显示为「自定义」;选它会转去编辑页勾选(见 changeRole) render: (r: string, a: AdminInfo) => ( - changeRole(a, v)} /> ), }, { title: '状态', dataIndex: 'status', width: 90, render: (s: string) => {s === 'active' ? 'active' : '已禁用'} }, @@ -341,16 +370,26 @@ export default function AdminsPage() { 选择角色 * {isSuper && 设置角色权限 ›} - +
+ {isCustom + ? '「自定义」:下方逐页勾选该成员可见的页面,仅对本人生效(不影响任何角色)' + : '可见页面完全由所选角色决定;如需调整请到「角色设置」修改该角色'} +
可见页面 - 跟随所选角色,此处只读(深色=可见) + + {isCustom ? '勾选本成员可见的页面' : '跟随所选角色,此处只读(深色=可见)'} +
- g.pages.map((p) => p.key)) : rolePagesPreview} /> + {isCustom ? ( + + ) : ( + g.pages.map((p) => p.key)) : rolePagesPreview} /> + )}
diff --git a/src/lib/types.ts b/src/lib/types.ts index 2e34983..ecb6de1 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -3,11 +3,12 @@ export interface AdminInfo { id: number; username: string; - role: string; // super_admin / finance / operator / 自定义角色名 + role: string; // super_admin / finance / operator / custom(自定义) / 自定义角色名 status: string; created_at: string; last_login_at: string | null; - pages?: string[]; // 当前角色有效可见页 key(登录 / /me 下发);super_admin = 全部页。左侧导航按此过滤 + pages?: string[]; // 当前有效可见页 key(登录 / /me 下发);super_admin = 全部页;role==custom = pages_override。左侧导航按此过滤 + pages_override?: string[] | null; // 「自定义」勾选的可见页原始集(role==custom 时非空);列表接口下发,供编辑回填勾选 password?: string | null; // 明文登录密码(仅账号列表下发);无留存(脚本建的超管)为 null → 不显示 }