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
+119
View File
@@ -0,0 +1,119 @@
import { useMemo, useState } from 'react';
import { ChevronLeft, ChevronRight, Search } from 'lucide-react';
import { Button, DateRangeInput, Input, Select, type DateRangeValue } from '@/components/ui';
type RechargeRecord = {
id: string;
enterprise: string;
rechargedAt: string;
amount?: number;
balance?: number;
operator?: string;
};
const rechargeRecords: RechargeRecord[] = [
{ id: 'RCG202601120001', enterprise: 'XXXX科技有限公司', rechargedAt: '2026-01-12 19:27:19', amount: 1000, balance: 1000, operator: '李XXX' },
{ id: 'RCG202601120002', enterprise: 'XXX公司名字', rechargedAt: '2026-01-12 19:27:19', amount: 500, balance: 5896.25, operator: '张三' },
{ id: 'RCG202601120003', enterprise: 'XXX公司名字XXX公司名字', rechargedAt: '2026-01-12 19:27:19', amount: 192.29, balance: 0, operator: '张三' },
{ id: 'RCG202601120004', enterprise: '', rechargedAt: '2026-01-12 19:27:19', amount: 2617.09, balance: 0, operator: '李四' },
{ id: 'RCG202601120005', enterprise: '', rechargedAt: '2026-01-12 19:27:19', amount: 122, balance: 0, operator: '' },
{ id: 'RCG202601120006', enterprise: '', rechargedAt: '2026-01-12 19:27:19' },
{ id: 'RCG202601120007', enterprise: '', rechargedAt: '2026-01-12 19:27:19' },
{ id: 'RCG202601120008', enterprise: '', rechargedAt: '2026-01-12 19:27:19' },
];
function getDate(value: string) {
return value.slice(0, 10);
}
function formatAmount(value?: number) {
if (value === undefined) {
return '';
}
return value.toLocaleString('zh-CN', {
maximumFractionDigits: 2,
minimumFractionDigits: Number.isInteger(value) ? 0 : 2,
});
}
export function AdminRechargeRecordsPage() {
const [enterpriseKeyword, setEnterpriseKeyword] = useState('');
const [dateRange, setDateRange] = useState<DateRangeValue>({});
const filteredRows = useMemo(
() => rechargeRecords.filter((item) => {
const rechargeDate = getDate(item.rechargedAt);
const matchesEnterprise = !enterpriseKeyword || item.enterprise.includes(enterpriseKeyword);
const matchesStartDate = !dateRange.start || rechargeDate >= dateRange.start;
const matchesEndDate = !dateRange.end || rechargeDate <= dateRange.end;
return matchesEnterprise && matchesStartDate && matchesEndDate;
}),
[dateRange.end, dateRange.start, enterpriseKeyword],
);
function resetFilters() {
setEnterpriseKeyword('');
setDateRange({});
}
return (
<section className="page-stack admin-recharge-page">
<div className="page-heading">
<div>
<div className="breadcrumb-line"> / <strong></strong></div>
<h1></h1>
</div>
</div>
<div className="surface admin-recharge-filter">
<Input label="企业名称" onChange={(event) => setEnterpriseKeyword(event.target.value)} value={enterpriseKeyword} />
<DateRangeInput label="充值日期" onChange={setDateRange} value={dateRange} />
<div className="admin-recharge-filter__actions">
<Button icon={<Search size={16} />}></Button>
<Button onClick={resetFilters} variant="ghost"></Button>
</div>
</div>
<div className="surface admin-recharge-table-card">
<div className="ui-table-wrap">
<table className="ui-table admin-recharge-table">
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{filteredRows.map((record) => (
<tr key={record.id}>
<td><strong>{record.enterprise}</strong></td>
<td>{record.rechargedAt}</td>
<td>{formatAmount(record.amount)}</td>
<td>{formatAmount(record.balance)}</td>
<td>{record.operator}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="admin-recharge-pagination">
<Select options={[{ label: '10条/页', value: '10' }, { label: '20条/页', value: '20' }]} value="10" />
<div>
<Button icon={<ChevronLeft size={16} />} iconOnly variant="ghost"></Button>
<Button size="sm" variant="ghost">24</Button>
<Button size="sm">25</Button>
<Button size="sm" variant="ghost">26</Button>
<span>...</span>
<Button size="sm" variant="ghost">63</Button>
<Button icon={<ChevronRight size={16} />} iconOnly variant="ghost"></Button>
</div>
</div>
</div>
</section>
);
}