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
+152
View File
@@ -0,0 +1,152 @@
import { useMemo, useState } from 'react';
import { Plus, Search, Trash2 } from 'lucide-react';
import { Button, Input, Modal, Select, Table, type TableColumn } from '@/components/ui';
type PhoneSegment = {
id: string;
segment: string;
carrier: string;
province: string;
city: string;
createdAt: string;
updatedAt: string;
};
const initialSegments: PhoneSegment[] = [
{ id: 'SEG20260630001', segment: '1367556', carrier: '中国移动', province: '四川省', city: '成都市', createdAt: '2026-06-11 09:30:12', updatedAt: '2026-06-28 15:40:00' },
{ id: 'SEG20260630002', segment: '1860763', carrier: '中国联通', province: '重庆市', city: '重庆市', createdAt: '2026-06-12 10:18:44', updatedAt: '2026-06-27 11:22:13' },
{ id: 'SEG20260630003', segment: '1525066', carrier: '中国电信', province: '江苏省', city: '南京市', createdAt: '2026-06-15 14:22:31', updatedAt: '2026-06-26 17:05:39' },
{ id: 'SEG20260630004', segment: '1501234', carrier: '中国移动', province: '广东省', city: '深圳市', createdAt: '2026-06-18 16:10:25', updatedAt: '2026-06-24 09:15:26' },
];
function createSegmentId() {
return `SEG${Date.now()}`;
}
type SegmentFormModalProps = {
item?: PhoneSegment;
onClose: () => void;
onSubmit: (item: PhoneSegment) => void;
};
function SegmentFormModal({ item, onClose, onSubmit }: SegmentFormModalProps) {
const [form, setForm] = useState<PhoneSegment>(() => item ?? {
id: createSegmentId(),
segment: '',
carrier: '中国移动',
province: '',
city: '',
createdAt: '2026-06-30 10:00:00',
updatedAt: '2026-06-30 10:00:00',
});
function updateField<Key extends keyof PhoneSegment>(key: Key, value: PhoneSegment[Key]) {
setForm((current) => ({ ...current, [key]: value }));
}
return (
<Modal
footer={(
<>
<Button onClick={onClose} variant="ghost"></Button>
<Button onClick={() => onSubmit({ ...form, updatedAt: '2026-06-30 10:00:00' })}></Button>
</>
)}
onClose={onClose}
open
title={item ? '编辑手机号段' : '新增手机号段'}
>
<div className="admin-system-modal-form">
<Input label="手机号段" maxLength={7} onChange={(event) => updateField('segment', event.target.value)} placeholder="手机号码前7位" value={form.segment} />
<Select
label="运营商"
onChange={(event) => updateField('carrier', event.target.value)}
options={[
{ label: '中国移动', value: '中国移动' },
{ label: '中国联通', value: '中国联通' },
{ label: '中国电信', value: '中国电信' },
]}
value={form.carrier}
/>
<Input label="省份" onChange={(event) => updateField('province', event.target.value)} value={form.province} />
<Input label="城市" onChange={(event) => updateField('city', event.target.value)} value={form.city} />
</div>
</Modal>
);
}
export function AdminPhoneSegmentsPage() {
const [segments, setSegments] = useState(initialSegments);
const [keyword, setKeyword] = useState('');
const [editingSegment, setEditingSegment] = useState<PhoneSegment | null>(null);
const [creating, setCreating] = useState(false);
const filteredSegments = useMemo(
() => segments.filter((segment) => [segment.segment, segment.carrier, segment.province, segment.city].some((value) => value.includes(keyword))),
[keyword, segments],
);
function upsertSegment(nextSegment: PhoneSegment) {
setSegments((current) => {
const exists = current.some((item) => item.id === nextSegment.id);
if (exists) {
return current.map((item) => (item.id === nextSegment.id ? nextSegment : item));
}
return [nextSegment, ...current];
});
setEditingSegment(null);
setCreating(false);
}
const columns = useMemo<Array<TableColumn<PhoneSegment>>>(() => [
{ key: 'segment', title: '手机号段(手机号码前7位)', width: '230px', render: (record) => <strong>{record.segment}</strong> },
{ key: 'carrier', title: '运营商', width: '150px', render: (record) => record.carrier },
{ key: 'province', title: '省份', width: '140px', render: (record) => record.province },
{ key: 'city', title: '城市', width: '140px', render: (record) => record.city },
{ key: 'createdAt', title: '创建时间', width: '190px', render: (record) => record.createdAt },
{ key: 'updatedAt', title: '更新时间', width: '190px', render: (record) => record.updatedAt },
{
key: 'actions',
title: '操作',
width: '150px',
align: 'right',
render: (record) => (
<div className="admin-system-actions">
<Button onClick={() => setEditingSegment(record)} size="sm" variant="ghost"></Button>
<Button
icon={<Trash2 size={15} />}
onClick={() => setSegments((current) => current.filter((item) => item.id !== record.id))}
size="sm"
variant="danger"
>
</Button>
</div>
),
},
], []);
return (
<section className="page-stack admin-system-page">
<div className="page-heading">
<div>
<div className="breadcrumb-line"> / <strong></strong></div>
<h1></h1>
</div>
</div>
<div className="surface admin-system-toolbar">
<Input onChange={(event) => setKeyword(event.target.value)} placeholder="搜索手机号段、运营商、省份或城市" prefix={<Search size={16} />} value={keyword} />
<Button icon={<Plus size={16} />} onClick={() => setCreating(true)}></Button>
</div>
<div className="surface admin-system-table-card">
<Table columns={columns} data={filteredSegments} emptyText="暂无手机号段" rowKey="id" />
</div>
{creating ? <SegmentFormModal onClose={() => setCreating(false)} onSubmit={upsertSegment} /> : null}
{editingSegment ? <SegmentFormModal item={editingSegment} onClose={() => setEditingSegment(null)} onSubmit={upsertSegment} /> : null}
</section>
);
}