1
0
Files
nex/frontend/src/pages/Stats/StatsTable.tsx
lanyuanxiaoyao 5ae9d85272 style: 优化前端样式,提升现代化设计感
- ConfigProvider 注入全局配置(动画、表格尺寸)
- CSS Variables 主题微调(页面背景、圆角、字体栈)
- AppLayout Menu 支持 logo/operations/collapsed
- Statistic 组件增加 color/prefix/suffix/animation
- Card 组件启用 hoverShadow/headerBordered
- Table 组件启用 stripe 斑马纹
- Tag 组件使用 variant="light" + shape="round"
- Dialog 居中显示并设置固定宽度
- 布局样式硬编码颜色替换为 TDesign Token
- UsageChart 改用 AreaChart + 渐变填充
- 更新 frontend spec 同步样式体系要求
2026-04-22 18:09:22 +08:00

117 lines
3.2 KiB
TypeScript

import { useMemo } from 'react';
import { Table, Select, Input, DateRangePicker, Space, Card } from 'tdesign-react';
import type { PrimaryTableCol } from 'tdesign-react/es/table/type';
import type { UsageStats, Provider } from '@/types';
interface StatsTableProps {
providers: Provider[];
stats: UsageStats[];
loading: boolean;
providerId?: string;
modelName?: string;
dateRange: [Date | null, Date | null] | null;
onProviderIdChange: (value: string | undefined) => void;
onModelNameChange: (value: string | undefined) => void;
onDateRangeChange: (dates: [Date | null, Date | null] | null) => void;
}
export function StatsTable({
providers,
stats,
loading,
providerId,
modelName,
dateRange,
onProviderIdChange,
onModelNameChange,
onDateRangeChange,
}: StatsTableProps) {
const providerMap = useMemo(() => {
const map = new Map<string, string>();
for (const p of providers) {
map.set(p.id, p.name);
}
return map;
}, [providers]);
const columns: PrimaryTableCol<UsageStats>[] = [
{
title: '供应商',
colKey: 'providerId',
width: 180,
ellipsis: true,
cell: ({ row }) => providerMap.get(row.providerId) ?? row.providerId,
},
{
title: '模型',
colKey: 'modelName',
width: 250,
ellipsis: true,
cell: ({ row }) => {
// 如果后端返回统一 ID 格式(包含 /),直接显示
// 否则显示原始 model_name
return row.modelName;
},
},
{
title: '日期',
colKey: 'date',
width: 120,
},
{
title: '请求数',
colKey: 'requestCount',
width: 100,
align: 'right',
},
];
const handleDateChange = (value: unknown) => {
if (Array.isArray(value) && value.length === 2) {
// 将值转换为Date对象
const startDate = value[0] ? new Date(value[0] as string | number | Date) : null;
const endDate = value[1] ? new Date(value[1] as string | number | Date) : null;
onDateRangeChange([startDate, endDate]);
} else {
onDateRangeChange(null);
}
};
return (
<Card title="统计数据" headerBordered hoverShadow>
<Space style={{ marginBottom: 16 }} size="medium" breakLine>
<Select
clearable
placeholder="所有供应商"
style={{ width: 200 }}
value={providerId}
onChange={(value) => onProviderIdChange(value as string | undefined)}
options={providers.map((p) => ({ label: p.name, value: p.id }))}
/>
<Input
clearable
placeholder="模型名称"
style={{ width: 200 }}
value={modelName ?? ''}
onChange={(value) => onModelNameChange((value as string) || undefined)}
/>
<DateRangePicker
mode="date"
value={dateRange && dateRange[0] && dateRange[1] ? [dateRange[0], dateRange[1]] : []}
onChange={handleDateChange}
/>
</Space>
<Table<UsageStats>
columns={columns}
data={stats}
rowKey="id"
loading={loading}
stripe
pagination={{ pageSize: 20 }}
empty="暂无统计数据"
/>
</Card>
);
}