feat: complete phase2 baseline cdr quality rbac

This commit is contained in:
hectorzhao
2026-06-24 18:40:21 +08:00
parent 7057fd3c42
commit a86de6545f
63 changed files with 7853 additions and 3678 deletions
+117
View File
@@ -0,0 +1,117 @@
import { useMemo, useState } from 'react';
import { Alert, Badge, Button, Checkbox, Field, Input, Select, Textarea } from '../components/ui.jsx';
import { Icon, PageTitle, Panel, ApiNotice, Modal, ConfirmDialog, Drawer, SimpleTable } from '../components/layout.jsx';
import { permissionGroups } from '../fixtures/devFixtures.js';
import { explainApiError } from '../api.js';
export function RolesPage({ roleRows, setRoleRows, userRows, apiLoading, apiError, refreshApi, can = () => true, onDeleteRole }) {
const [editingRole, setEditingRole] = useState(undefined);
const [roleForm, setRoleForm] = useState(emptyRoleForm);
const [permissionRole, setPermissionRole] = useState(null);
const [permissionDraft, setPermissionDraft] = useState([]);
const [deleteRoleTarget, setDeleteRoleTarget] = useState(null);
const [actionError, setActionError] = useState('');
const canManage = can('roles.manage');
const roleUserCounts = useMemo(() => userRows.reduce((counts, user) => ({ ...counts, [user.roleId]: (counts[user.roleId] || 0) + 1 }), {}), [userRows]);
const openCreateRole = () => { setEditingRole(null); setRoleForm(emptyRoleForm); };
const openEditRole = (role) => { setEditingRole(role); setRoleForm({ name: role.name, description: role.description, status: role.status }); };
const closeRoleModal = () => { setEditingRole(undefined); setRoleForm(emptyRoleForm); };
const submitRole = (event) => {
event.preventDefault();
if (!roleForm.name.trim()) return;
if (editingRole) {
setRoleRows((rows) => rows.map((role) => role.id === editingRole.id ? { ...role, ...roleForm } : role));
} else {
setRoleRows((rows) => [...rows, { id: `R${String(rows.length + 1).padStart(3, '0')}`, ...roleForm, builtIn: false, permissions: [] }]);
}
closeRoleModal();
};
const toggleRoleStatus = (role) => {
if (role.builtIn) return;
setRoleRows((rows) => rows.map((item) => item.id === role.id ? { ...item, status: item.status === '启用' ? '禁用' : '启用' } : item));
};
const deleteRole = async (role) => {
try {
setActionError('');
if (onDeleteRole) {
await onDeleteRole(role.id);
} else {
setRoleRows((rows) => rows.filter((item) => item.id !== role.id));
}
setDeleteRoleTarget(null);
} catch (error) {
setActionError(explainApiError(error));
}
};
const openPermissions = (role) => { setPermissionRole(role); setPermissionDraft(role.permissions); };
const togglePermission = (permissionKey) => {
setPermissionDraft((permissions) => permissions.includes(permissionKey) ? permissions.filter((key) => key !== permissionKey) : [...permissions, permissionKey]);
};
const togglePermissionGroup = (group) => {
const groupKeys = group.permissions.map((permission) => permission.key);
const allSelected = groupKeys.every((key) => permissionDraft.includes(key));
setPermissionDraft((permissions) => allSelected ? permissions.filter((key) => !groupKeys.includes(key)) : Array.from(new Set([...permissions, ...groupKeys])));
};
const savePermissions = () => {
setRoleRows((rows) => rows.map((role) => role.id === permissionRole.id ? { ...role, permissions: permissionDraft } : role));
setPermissionRole(null);
};
return (
<>
<PageTitle title="角色与权限" desc="按岗位定义菜单与操作权限,并查看角色关联用户。" actions={canManage ? <Button icon={<Icon type="plus" />} onClick={openCreateRole}>新增角色</Button> : null} />
<ApiNotice loading={apiLoading} error={apiError} onRetry={refreshApi} />
{actionError ? <Alert title="操作失败" tone="danger">{actionError}</Alert> : null}
<Panel title="角色列表" aside={<Badge tone="neutral">{roleRows.length} 个角色</Badge>} className="wide-panel">
<SimpleTable rows={roleRows.map((role) => ({ ...role, type: role.builtIn ? '系统内置' : '自定义', userCount: role.userCount ?? roleUserCounts[role.id] ?? 0, permissionCount: role.permissions.length }))} columns={[
{ key: 'name', label: '角色名称' },
{ key: 'type', label: '类型' },
{ key: 'description', label: '角色说明' },
{ key: 'userCount', label: '用户数' },
{ key: 'permissionCount', label: '权限项' },
{ key: 'status', label: '状态', status: true },
{ key: 'actions', label: '操作', render: (row) => <div className="table-actions"><Button size="sm" variant="secondary" onClick={() => openPermissions(row)}>{canManage && !row.builtIn ? '配置权限' : '查看权限'}</Button>{canManage ? <Button size="sm" variant="outline" onClick={() => openEditRole(row)}>编辑</Button> : null}{canManage ? <Button size="sm" variant="ghost" disabled={row.builtIn} onClick={() => toggleRoleStatus(row)}>{row.status === '启用' ? '禁用' : '启用'}</Button> : null}{canManage ? <Button size="sm" variant="danger" disabled={row.builtIn} onClick={() => setDeleteRoleTarget(row)}>删除</Button> : null}</div> },
]} />
</Panel>
{editingRole !== undefined ? (
<Modal title={editingRole ? '编辑角色' : '新增角色'} onClose={closeRoleModal} size="sm">
<form className="modal-form" onSubmit={submitRole}>
<Field label={<span>角色名称 <span className="required-star">*</span></span>}><Input value={roleForm.name} onChange={(event) => setRoleForm({ ...roleForm, name: event.target.value })} placeholder="请输入角色名称" disabled={Boolean(editingRole?.builtIn)} required /></Field>
<Field label="角色说明"><Textarea rows="4" value={roleForm.description} onChange={(event) => setRoleForm({ ...roleForm, description: event.target.value })} placeholder="说明该角色的职责范围" /></Field>
<Field label="状态"><Select value={roleForm.status} onChange={(event) => setRoleForm({ ...roleForm, status: event.target.value })} disabled={Boolean(editingRole?.builtIn)}><option value="启用">启用</option><option value="禁用">禁用</option></Select></Field>
<div className="modal-actions"><Button type="button" variant="outline" onClick={closeRoleModal}>取消</Button><Button type="submit">保存角色</Button></div>
</form>
</Modal>
) : null}
{permissionRole ? (
<Drawer title={permissionRole.builtIn ? '查看角色权限' : '配置角色权限'} aside={<><Badge tone="info">{permissionRole.name}</Badge><span className="drawer-subtitle">已选择 {permissionDraft.length} 项权限</span></>} onClose={() => setPermissionRole(null)}>
{permissionRole.builtIn || !canManage ? <Alert title={permissionRole.builtIn ? '系统内置角色' : '只读权限'} tone="info">{permissionRole.builtIn ? '内置角色权限由系统维护,仅支持查看。' : '当前账号没有角色管理权限,仅支持查看。'}</Alert> : null}
<div className="permission-groups">
{permissionGroups.map((group) => {
const groupKeys = group.permissions.map((permission) => permission.key);
const selectedCount = groupKeys.filter((key) => permissionDraft.includes(key)).length;
return (
<section className="permission-group" key={group.name}>
<div className="permission-group-head"><div><strong>{group.name}</strong><span>{selectedCount}/{groupKeys.length} 已选择</span></div><Checkbox label="全选" checked={selectedCount === groupKeys.length} disabled={permissionRole.builtIn || !canManage} onChange={() => togglePermissionGroup(group)} /></div>
<div className="permission-options">{group.permissions.map((permission) => <Checkbox key={permission.key} label={permission.label} checked={permissionDraft.includes(permission.key)} disabled={permissionRole.builtIn || !canManage} onChange={() => togglePermission(permission.key)} />)}</div>
</section>
);
})}
</div>
<div className="drawer-actions"><Button variant="outline" onClick={() => setPermissionRole(null)}>关闭</Button>{permissionRole.builtIn || !canManage ? null : <Button onClick={savePermissions}>保存权限</Button>}</div>
</Drawer>
) : null}
{deleteRoleTarget ? (
<ConfirmDialog
title="删除角色确认"
confirmLabel="确认删除"
confirmVariant="danger"
onCancel={() => setDeleteRoleTarget(null)}
onConfirm={() => void deleteRole(deleteRoleTarget)}
>
<p>确认删除角色{deleteRoleTarget.name}如果仍有用户使用该角色系统会拒绝删除</p>
</ConfirmDialog>
) : null}
</>
);
}