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
+191
View File
@@ -0,0 +1,191 @@
import { useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Building2, DollarSign, Plus, TrendingDown, TrendingUp } from 'lucide-react';
import { Button, Input, Select, Table, Tag, type TableColumn } from '@/components/ui';
import {
formatCurrency,
getEnterpriseRecords,
statusOptions,
toggleEnterpriseStatus,
type EnterpriseRecord,
} from './adminEnterpriseMock';
export function AdminCustomersPage() {
const navigate = useNavigate();
const [records, setRecords] = useState<EnterpriseRecord[]>(() => getEnterpriseRecords());
const [queryId, setQueryId] = useState('');
const [queryName, setQueryName] = useState('');
const [queryStatus, setQueryStatus] = useState('all');
const [filters, setFilters] = useState({ id: '', name: '', status: 'all' });
const filteredRecords = useMemo(() => records.filter((record) => {
const matchId = filters.id ? record.id.includes(filters.id) : true;
const matchName = filters.name ? record.name.includes(filters.name) : true;
const matchStatus = filters.status === 'all' ? true : record.status === filters.status;
return matchId && matchName && matchStatus;
}), [filters, records]);
const activeCount = records.filter((record) => record.status === 'active').length;
const disabledCount = records.filter((record) => record.status === 'disabled').length;
const todaySpend = records.reduce((sum, record) => sum + record.todaySpend, 0);
const columns: Array<TableColumn<EnterpriseRecord>> = [
{ key: 'id', title: '企业ID', width: '90px', render: (record) => record.id },
{ key: 'name', title: '企业名称', render: (record) => <strong>{record.name}</strong> },
{
key: 'balance',
title: '当前余额',
align: 'right',
render: (record) => (
<span className={record.balance < 0 ? 'status-danger' : ''}>
¥{formatCurrency(record.balance)}
{record.balance < 0 ? <Tag tone="danger" className="enterprise-inline-tag"></Tag> : null}
</span>
),
},
{ key: 'overdraftLimit', title: '透支限额', align: 'right', render: (record) => `¥${formatCurrency(record.overdraftLimit)}` },
{ key: 'todaySpend', title: '今日消费', align: 'right', render: (record) => `¥${formatCurrency(record.todaySpend)}` },
{
key: 'status',
title: '企业状态',
render: (record) => (
<Tag tone={record.status === 'active' ? 'success' : 'warning'}>
{record.status === 'active' ? '正常' : '已禁用'}
</Tag>
),
},
{
key: 'actions',
title: '操作',
align: 'right',
render: (record) => (
<div className="table-actions">
<Button
onClick={() => navigate(`/admin/customers/${record.id}`)}
size="sm"
variant="ghost"
>
</Button>
<Button
onClick={() => navigate(`/admin/customers/${record.id}/edit`)}
size="sm"
variant="ghost"
>
</Button>
<Button
onClick={() => setRecords(toggleEnterpriseStatus(record.id))}
size="sm"
variant={record.status === 'active' ? 'danger' : 'secondary'}
>
{record.status === 'active' ? '禁用' : '启用'}
</Button>
</div>
),
},
];
return (
<section className="page-stack">
<div className="page-heading">
<div>
<p className="eyebrow"></p>
<h1></h1>
</div>
<Button icon={<Plus size={16} />} onClick={() => navigate('/admin/customers/new')}>
</Button>
</div>
<div className="dashboard-grid enterprise-summary-grid">
<div className="surface mini-status-card">
<Building2 size={22} />
<div>
<span></span>
<strong>{records.length}</strong>
<small></small>
</div>
</div>
<div className="surface mini-status-card">
<TrendingUp size={22} />
<div>
<span></span>
<strong>{activeCount}</strong>
<small></small>
</div>
</div>
<div className="surface mini-status-card">
<TrendingDown size={22} />
<div>
<span></span>
<strong>{disabledCount}</strong>
<small></small>
</div>
</div>
<div className="surface mini-status-card">
<DollarSign size={22} />
<div>
<span></span>
<strong>¥{formatCurrency(todaySpend)}</strong>
<small></small>
</div>
</div>
</div>
<div className="surface ui-query-panel">
<h2></h2>
<div className="ui-query-panel__grid enterprise-query-grid">
<Input
label="企业ID"
onChange={(event) => setQueryId(event.target.value)}
placeholder="请输入企业ID"
value={queryId}
/>
<Input
label="企业名称"
onChange={(event) => setQueryName(event.target.value)}
placeholder="请输入企业名称"
value={queryName}
/>
<Select
label="企业状态"
onChange={(event) => setQueryStatus(event.target.value)}
options={statusOptions}
value={queryStatus}
/>
<div className="enterprise-query-actions">
<Button
onClick={() => setFilters({ id: queryId, name: queryName, status: queryStatus })}
variant="secondary"
>
</Button>
<Button
onClick={() => {
setQueryId('');
setQueryName('');
setQueryStatus('all');
setFilters({ id: '', name: '', status: 'all' });
}}
variant="ghost"
>
</Button>
</div>
</div>
</div>
<div className="surface section-stack">
<div className="section-heading">
<div>
<h2></h2>
<p className="muted"> mock </p>
</div>
<Tag tone="info">{filteredRecords.length} </Tag>
</div>
<Table columns={columns} data={filteredRecords} rowKey="id" />
</div>
</section>
);
}