1
0

refactor: 迁移 UI 组件库从 Ant Design 至 TDesign

- 替换 antd 为 tdesign-react 作为主要 UI 组件库
- 引入 Recharts 替代 @ant-design/charts 实现图表功能
- 移除主题系统相关代码(ThemeContext、themes 目录)
- 更新所有组件以适配 TDesign 组件 API
- 更新测试用例以匹配新的组件实现
- 新增 TDesign 和 Recharts 集成规范文档
This commit is contained in:
2026-04-17 18:22:13 +08:00
parent 6eeb38c15e
commit 2b1c5e96c3
55 changed files with 1622 additions and 2541 deletions

View File

@@ -1,4 +1,4 @@
import { Row, Col, Card, Statistic } from 'antd';
import { Row, Col, Card, Statistic } from 'tdesign-react';
import type { UsageStats } from '@/types';
interface StatCardsProps {
@@ -17,22 +17,22 @@ export function StatCards({ stats }: StatCardsProps) {
return (
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
<Col xs={24} sm={12} md={6}>
<Col xs={12} md={6}>
<Card>
<Statistic title="总请求量" value={totalRequests} />
</Card>
</Col>
<Col xs={24} sm={12} md={6}>
<Col xs={12} md={6}>
<Card>
<Statistic title="活跃模型数" value={activeModels} />
</Card>
</Col>
<Col xs={24} sm={12} md={6}>
<Col xs={12} md={6}>
<Card>
<Statistic title="活跃供应商数" value={activeProviders} />
</Card>
</Col>
<Col xs={24} sm={12} md={6}>
<Col xs={12} md={6}>
<Card>
<Statistic title="今日请求量" value={todayRequests} />
</Card>

View File

@@ -1,7 +1,6 @@
import { useMemo } from 'react';
import { Table, Select, Input, DatePicker, Space, Card } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import type { Dayjs } from 'dayjs';
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 {
@@ -10,10 +9,10 @@ interface StatsTableProps {
loading: boolean;
providerId?: string;
modelName?: string;
dateRange: [Dayjs | null, Dayjs | null] | null;
dateRange: [Date | null, Date | null] | null;
onProviderIdChange: (value: string | undefined) => void;
onModelNameChange: (value: string | undefined) => void;
onDateRangeChange: (dates: [Dayjs | null, Dayjs | null] | null) => void;
onDateRangeChange: (dates: [Date | null, Date | null] | null) => void;
}
export function StatsTable({
@@ -35,69 +34,76 @@ export function StatsTable({
return map;
}, [providers]);
const columns: ColumnsType<UsageStats> = [
const columns: PrimaryTableCol<UsageStats>[] = [
{
title: '供应商',
dataIndex: 'providerId',
key: 'providerId',
colKey: 'providerId',
width: 180,
ellipsis: { showTitle: true },
render: (id: string) => providerMap.get(id) ?? id,
ellipsis: true,
cell: ({ row }) => providerMap.get(row.providerId) ?? row.providerId,
},
{
title: '模型',
dataIndex: 'modelName',
key: 'modelName',
colKey: 'modelName',
width: 250,
ellipsis: { showTitle: true },
ellipsis: true,
},
{
title: '日期',
dataIndex: 'date',
key: 'date',
colKey: 'date',
width: 120,
},
{
title: '请求数',
dataIndex: 'requestCount',
key: 'requestCount',
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="统计数据">
<Space wrap style={{ marginBottom: 16 }}>
<Space style={{ marginBottom: 16 }}>
<Select
allowClear
clearable
placeholder="所有供应商"
style={{ width: 200 }}
value={providerId}
onChange={(value) => onProviderIdChange(value)}
onChange={(value) => onProviderIdChange(value as string | undefined)}
options={providers.map((p) => ({ label: p.name, value: p.id }))}
/>
<Input
allowClear
clearable
placeholder="模型名称"
style={{ width: 200 }}
value={modelName ?? ''}
onChange={(e) => onModelNameChange(e.target.value || undefined)}
onChange={(value) => onModelNameChange((value as string) || undefined)}
/>
<DatePicker.RangePicker
value={dateRange}
onChange={(dates) => onDateRangeChange(dates)}
<DateRangePicker
mode="date"
value={dateRange && dateRange[0] && dateRange[1] ? [dateRange[0], dateRange[1]] : []}
onChange={handleDateChange}
/>
</Space>
<Table<UsageStats>
columns={columns}
dataSource={stats}
data={stats}
rowKey="id"
loading={loading}
pagination={{ pageSize: 20 }}
scroll={{ x: 650 }}
locale={{ emptyText: '暂无统计数据' }}
empty="暂无统计数据"
/>
</Card>
);

View File

@@ -1,5 +1,5 @@
import { Card } from 'antd';
import { Line } from '@ant-design/charts';
import { Card } from 'tdesign-react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer, Tooltip } from 'recharts';
import type { UsageStats } from '@/types';
interface UsageChartProps {
@@ -16,17 +16,24 @@ export function UsageChart({ stats }: UsageChartProps) {
.map(([date, requestCount]) => ({ date, requestCount }))
.sort((a, b) => a.date.localeCompare(b.date));
const config = {
data: chartData,
xField: 'date',
yField: 'requestCount',
smooth: true,
};
return (
<Card title="请求趋势" style={{ marginBottom: 16 }}>
{chartData.length > 0 ? (
<Line {...config} />
<ResponsiveContainer width="100%" height={300}>
<LineChart data={chartData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Line
type="monotone"
dataKey="requestCount"
stroke="#0052D9"
strokeWidth={2}
dot={{ fill: '#0052D9' }}
/>
</LineChart>
</ResponsiveContainer>
) : (
<div style={{ textAlign: 'center', padding: '40px 0', color: '#999' }}>

View File

@@ -1,5 +1,4 @@
import { useState, useMemo } from 'react';
import type { Dayjs } from 'dayjs';
import { useProviders } from '@/hooks/useProviders';
import { useStats } from '@/hooks/useStats';
import { StatCards } from './StatCards';
@@ -11,14 +10,14 @@ export function StatsPage() {
const [providerId, setProviderId] = useState<string | undefined>();
const [modelName, setModelName] = useState<string | undefined>();
const [dateRange, setDateRange] = useState<[Dayjs | null, Dayjs | null] | null>(null);
const [dateRange, setDateRange] = useState<[Date | null, Date | null] | null>(null);
const params = useMemo(
() => ({
providerId,
modelName,
startDate: dateRange?.[0]?.format('YYYY-MM-DD'),
endDate: dateRange?.[1]?.format('YYYY-MM-DD'),
startDate: dateRange?.[0]?.toISOString().split('T')[0],
endDate: dateRange?.[1]?.toISOString().split('T')[0],
}),
[providerId, modelName, dateRange],
);