feat: complete phase2 baseline cdr quality rbac
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Alert, Badge, Button, Field, Input, Select } from '../components/ui.jsx';
|
||||
import { Icon, PageTitle, Toolbar, Panel, ApiNotice, Modal, ConfirmDialog, SimpleTable } from '../components/layout.jsx';
|
||||
import { explainApiError } from '../api.js';
|
||||
|
||||
export function UsersPage({ userRows, setUserRows, roleRows, apiLoading, apiError, refreshApi, can = () => true, onDeleteUser }) {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [roleFilter, setRoleFilter] = useState('all');
|
||||
const [statusFilter, setStatusFilter] = useState('all');
|
||||
const [editingUser, setEditingUser] = useState(undefined);
|
||||
const [userForm, setUserForm] = useState(emptyUserForm);
|
||||
const [resetTarget, setResetTarget] = useState(null);
|
||||
const [deleteUserTarget, setDeleteUserTarget] = useState(null);
|
||||
const [resetMessage, setResetMessage] = useState('');
|
||||
const [actionError, setActionError] = useState('');
|
||||
const canManage = can('users.manage');
|
||||
const roleMap = useMemo(() => new Map(roleRows.map((role) => [role.id, role.name])), [roleRows]);
|
||||
const visibleUsers = useMemo(() => {
|
||||
const normalized = keyword.trim().toLowerCase();
|
||||
return userRows.filter((user) => {
|
||||
const matchesKeyword = !normalized || [user.username, user.name, user.phone, user.email].some((value) => value.toLowerCase().includes(normalized));
|
||||
return matchesKeyword && (roleFilter === 'all' || user.roleId === roleFilter) && (statusFilter === 'all' || user.status === statusFilter);
|
||||
});
|
||||
}, [keyword, roleFilter, statusFilter, userRows]);
|
||||
const openCreateUser = () => {
|
||||
setEditingUser(null);
|
||||
setUserForm(emptyUserForm);
|
||||
};
|
||||
const openEditUser = (user) => {
|
||||
setEditingUser(user);
|
||||
setUserForm({ username: user.username, name: user.name, phone: user.phone, email: user.email, roleId: user.roleId, status: user.status });
|
||||
};
|
||||
const closeUserModal = () => {
|
||||
setEditingUser(undefined);
|
||||
setUserForm(emptyUserForm);
|
||||
};
|
||||
const submitUser = (event) => {
|
||||
event.preventDefault();
|
||||
if (!userForm.username.trim() || !userForm.name.trim() || !userForm.roleId) return;
|
||||
if (editingUser) {
|
||||
setUserRows((rows) => rows.map((user) => user.id === editingUser.id ? { ...user, ...userForm } : user));
|
||||
} else {
|
||||
const nextId = `U${String(1001 + userRows.length).padStart(4, '0')}`;
|
||||
setUserRows((rows) => [...rows, { id: nextId, ...userForm, lastLogin: '从未登录', lastIp: '-' }]);
|
||||
}
|
||||
closeUserModal();
|
||||
};
|
||||
const toggleUserStatus = (user) => {
|
||||
setUserRows((rows) => rows.map((item) => item.id === user.id ? { ...item, status: item.status === '启用' ? '禁用' : '启用' } : item));
|
||||
};
|
||||
const deleteUser = async (user) => {
|
||||
try {
|
||||
setActionError('');
|
||||
if (onDeleteUser) {
|
||||
await onDeleteUser(user.id);
|
||||
} else {
|
||||
setUserRows((rows) => rows.filter((item) => item.id !== user.id));
|
||||
}
|
||||
setDeleteUserTarget(null);
|
||||
} catch (error) {
|
||||
setActionError(explainApiError(error));
|
||||
}
|
||||
};
|
||||
const confirmResetPassword = () => {
|
||||
setResetMessage(`${resetTarget.name}(${resetTarget.username})的密码已重置,下次登录需修改密码。`);
|
||||
setResetTarget(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title="用户管理" desc="管理运营端登录用户、所属角色、账号状态和登录安全。" actions={canManage ? <Button icon={<Icon type="plus" />} onClick={openCreateUser}>新增用户</Button> : null} />
|
||||
<ApiNotice loading={apiLoading} error={apiError} onRetry={refreshApi} />
|
||||
{resetMessage ? <Alert title="密码重置完成" tone="success">{resetMessage}</Alert> : null}
|
||||
{actionError ? <Alert title="操作失败" tone="danger">{actionError}</Alert> : null}
|
||||
<Toolbar>
|
||||
<Field label="用户信息"><Input value={keyword} onChange={(event) => setKeyword(event.target.value)} placeholder="用户名、姓名、手机号或邮箱" /></Field>
|
||||
<Field label="角色"><Select value={roleFilter} onChange={(event) => setRoleFilter(event.target.value)}><option value="all">全部角色</option>{roleRows.map((role) => <option key={role.id} value={role.id}>{role.name}</option>)}</Select></Field>
|
||||
<Field label="状态"><Select value={statusFilter} onChange={(event) => setStatusFilter(event.target.value)}><option value="all">全部状态</option><option value="启用">启用</option><option value="禁用">禁用</option></Select></Field>
|
||||
<Button variant="outline" onClick={() => { setKeyword(''); setRoleFilter('all'); setStatusFilter('all'); }}>重置</Button>
|
||||
</Toolbar>
|
||||
<Panel title="用户列表" aside={<Badge tone="neutral">共 {visibleUsers.length} 个用户</Badge>} className="wide-panel">
|
||||
<SimpleTable rows={visibleUsers.map((user) => ({ ...user, roleName: roleMap.get(user.roleId) || '-' }))} columns={[
|
||||
{ key: 'username', label: '用户名' },
|
||||
{ key: 'name', label: '姓名' },
|
||||
{ key: 'phone', label: '手机号' },
|
||||
{ key: 'roleName', label: '角色' },
|
||||
{ key: 'status', label: '状态', status: true },
|
||||
{ key: 'lastLogin', label: '最后登录时间' },
|
||||
{ key: 'lastIp', label: '最后登录 IP' },
|
||||
{ key: 'actions', label: '操作', render: (row) => <div className="table-actions">{canManage ? <Button size="sm" variant="outline" onClick={() => openEditUser(row)}>编辑</Button> : null}{canManage ? <Button size="sm" variant={row.status === '启用' ? 'ghost' : 'secondary'} onClick={() => toggleUserStatus(row)}>{row.status === '启用' ? '禁用' : '启用'}</Button> : null}{canManage ? <Button size="sm" variant="outline" onClick={() => setResetTarget(row)}>重置密码</Button> : null}{canManage ? <Button size="sm" variant="danger" onClick={() => setDeleteUserTarget(row)}>删除</Button> : '-'}</div> },
|
||||
]} />
|
||||
</Panel>
|
||||
{editingUser !== undefined ? (
|
||||
<Modal title={editingUser ? '编辑用户' : '新增用户'} onClose={closeUserModal}>
|
||||
<form className="modal-form" onSubmit={submitUser}>
|
||||
<div className="form-grid admin-form-grid">
|
||||
<Field label={<span>用户名 <span className="required-star">*</span></span>}><Input value={userForm.username} onChange={(event) => setUserForm({ ...userForm, username: event.target.value })} placeholder="用于登录,不可重复" disabled={Boolean(editingUser)} required /></Field>
|
||||
<Field label={<span>姓名 <span className="required-star">*</span></span>}><Input value={userForm.name} onChange={(event) => setUserForm({ ...userForm, name: event.target.value })} placeholder="请输入用户姓名" required /></Field>
|
||||
<Field label="手机号"><Input value={userForm.phone} onChange={(event) => setUserForm({ ...userForm, phone: event.target.value })} placeholder="请输入手机号" /></Field>
|
||||
<Field label="邮箱"><Input type="email" value={userForm.email} onChange={(event) => setUserForm({ ...userForm, email: event.target.value })} placeholder="请输入邮箱" /></Field>
|
||||
<Field label={<span>角色 <span className="required-star">*</span></span>}><Select value={userForm.roleId} onChange={(event) => setUserForm({ ...userForm, roleId: event.target.value })} required>{roleRows.filter((role) => role.status === '启用').map((role) => <option key={role.id} value={role.id}>{role.name}</option>)}</Select></Field>
|
||||
<Field label="状态"><Select value={userForm.status} onChange={(event) => setUserForm({ ...userForm, status: event.target.value })}><option value="启用">启用</option><option value="禁用">禁用</option></Select></Field>
|
||||
</div>
|
||||
<div className="modal-actions"><Button type="button" variant="outline" onClick={closeUserModal}>取消</Button><Button type="submit">{editingUser ? '保存修改' : '保存用户'}</Button></div>
|
||||
</form>
|
||||
</Modal>
|
||||
) : null}
|
||||
{resetTarget ? (
|
||||
<ConfirmDialog
|
||||
title="确认重置密码"
|
||||
confirmLabel="确认重置"
|
||||
onCancel={() => setResetTarget(null)}
|
||||
onConfirm={confirmResetPassword}
|
||||
>
|
||||
<p>确认重置用户「{resetTarget.name}({resetTarget.username})」的登录密码吗?重置后该用户需要使用临时密码登录并立即修改密码。</p>
|
||||
</ConfirmDialog>
|
||||
) : null}
|
||||
{deleteUserTarget ? (
|
||||
<ConfirmDialog
|
||||
title="删除用户确认"
|
||||
confirmLabel="确认删除"
|
||||
confirmVariant="danger"
|
||||
onCancel={() => setDeleteUserTarget(null)}
|
||||
onConfirm={() => void deleteUser(deleteUserTarget)}
|
||||
>
|
||||
<p>确认删除用户「{deleteUserTarget.name}({deleteUserTarget.username})」吗?删除后该用户将不能登录,也不再出现在用户列表中。</p>
|
||||
</ConfirmDialog>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user