Initial CMPP frontend prototype

This commit is contained in:
hectorzhao
2026-06-30 16:09:46 +08:00
commit 2f3c274a30
98 changed files with 25255 additions and 0 deletions
+158
View File
@@ -0,0 +1,158 @@
import { useMemo, useState } from 'react';
import { Edit3, Plus, Search, Trash2, Users } from 'lucide-react';
import {
Button,
Input,
Modal,
Pagination,
Select,
Table,
Tag,
type TableColumn,
} from '@/components/ui';
type UserRole = 'admin' | 'user';
type UserStatus = 'active' | 'disabled';
type ClientUser = {
id: string;
name: string;
email: string;
phone: string;
role: UserRole;
status: UserStatus;
lastLoginAt: string;
};
const roleLabelMap: Record<UserRole, string> = {
admin: '管理员',
user: '普通用户',
};
const usersSeed: ClientUser[] = [
{ id: 'USER001', name: '张三', email: 'zhangsan@example.com', phone: '13800138000', role: 'admin', status: 'active', lastLoginAt: '2026-03-17 09:15:00' },
{ id: 'USER002', name: '李四', email: 'lisi@example.com', phone: '13800138001', role: 'user', status: 'active', lastLoginAt: '2026-03-16 16:45:00' },
{ id: 'USER003', name: '王五', email: 'wangwu@example.com', phone: '13800138002', role: 'user', status: 'active', lastLoginAt: '2026-03-15 11:30:00' },
{ id: 'USER004', name: '赵六', email: 'zhaoliu@example.com', phone: '13800138003', role: 'user', status: 'disabled', lastLoginAt: '2026-02-20 14:00:00' },
{ id: 'USER005', name: '孙七', email: 'sunqi@example.com', phone: '13800138004', role: 'user', status: 'active', lastLoginAt: '2026-03-17 08:00:00' },
];
const emptyUser: ClientUser = {
id: 'NEW',
name: '',
email: '',
phone: '',
role: 'user',
status: 'active',
lastLoginAt: '-',
};
export function ClientUsersPage() {
const [keyword, setKeyword] = useState('');
const [editingUser, setEditingUser] = useState<ClientUser | null>(null);
const [draft, setDraft] = useState<ClientUser>(emptyUser);
const filteredUsers = usersSeed.filter((item) => {
const target = `${item.name} ${item.email} ${item.phone}`;
return !keyword || target.toLowerCase().includes(keyword.toLowerCase());
});
function openEditor(user?: ClientUser) {
const nextUser = user ?? emptyUser;
setEditingUser(nextUser);
setDraft(nextUser);
}
const columns = useMemo<Array<TableColumn<ClientUser>>>(() => [
{ key: 'name', title: '用户名', width: '120px', render: (record) => <strong className="text-strong">{record.name}</strong> },
{ key: 'email', title: '邮箱', render: (record) => <span className="muted">{record.email}</span> },
{ key: 'phone', title: '手机号', width: '180px', render: (record) => <span className="muted">{record.phone}</span> },
{
key: 'role',
title: '角色',
width: '150px',
render: (record) => <Tag tone={record.role === 'admin' ? 'info' : 'success'}>{roleLabelMap[record.role]}</Tag>,
},
{
key: 'status',
title: '状态',
width: '130px',
render: (record) => <Tag tone={record.status === 'active' ? 'success' : 'neutral'}>{record.status === 'active' ? '正常' : '禁用'}</Tag>,
},
{ key: 'lastLoginAt', title: '最后登录时间', width: '210px', render: (record) => <span className="muted">{record.lastLoginAt}</span> },
{
key: 'actions',
title: '操作',
width: '170px',
render: (record) => (
<div className="inline-actions">
<Button icon={<Edit3 size={15} />} onClick={() => openEditor(record)} size="sm" variant="ghost"></Button>
<Button icon={<Trash2 size={15} />} size="sm" variant="danger"></Button>
</div>
),
},
], []);
return (
<section className="page-stack system-page">
<div className="system-page-toolbar">
<div className="sms-send-title">
<span className="sms-send-title__icon"><Users size={22} /></span>
<h1></h1>
</div>
<Button icon={<Plus size={18} />} onClick={() => openEditor()}></Button>
</div>
<div className="system-filter-row">
<Input
onChange={(event) => setKeyword(event.target.value)}
placeholder="搜索用户名、邮箱或手机号"
prefix={<Search size={16} />}
value={keyword}
/>
</div>
<div className="surface system-table-card">
<Table columns={columns} data={filteredUsers} emptyText="暂无用户" rowKey="id" />
<Pagination total={filteredUsers.length} page={1} />
</div>
<Modal
footer={(
<>
<Button onClick={() => setEditingUser(null)} variant="secondary"></Button>
<Button onClick={() => setEditingUser(null)}></Button>
</>
)}
onClose={() => setEditingUser(null)}
open={Boolean(editingUser)}
size="xl"
title={editingUser?.id === 'NEW' ? '添加用户' : '编辑用户'}
>
<div className="system-user-form">
<Input label="用户名 *" onChange={(event) => setDraft({ ...draft, name: event.target.value })} placeholder="请输入用户名" value={draft.name} />
<Input label="邮箱 *" onChange={(event) => setDraft({ ...draft, email: event.target.value })} placeholder="请输入邮箱" value={draft.email} />
<Input label="手机号 *" onChange={(event) => setDraft({ ...draft, phone: event.target.value })} placeholder="请输入手机号" value={draft.phone} />
<Select
label="角色 *"
onChange={(event) => setDraft({ ...draft, role: event.target.value as UserRole })}
options={[
{ label: '管理员', value: 'admin' },
{ label: '普通用户', value: 'user' },
]}
value={draft.role}
/>
<Select
label="状态 *"
onChange={(event) => setDraft({ ...draft, status: event.target.value as UserStatus })}
options={[
{ label: '正常', value: 'active' },
{ label: '禁用', value: 'disabled' },
]}
value={draft.status}
/>
</div>
</Modal>
</section>
);
}