Files
lisglosips/apps/web/src/pages/RolesPage.jsx
T

118 lines
8.5 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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}
</>
);
}