1
0

feat: 完成前端重构,采用 Ant Design 5 和完整测试体系

- 采用 Ant Design 5 作为 UI 组件库,替换自定义组件
- 集成 React Router v7 提供路由导航
- 使用 TanStack Query v5 管理数据获取和缓存
- 建立 Vitest + React Testing Library 测试体系
- 添加 Playwright E2E 测试覆盖
- 使用 MSW mock API 响应
- 配置 TypeScript strict 模式
- 采用 SCSS Modules 组织样式
- 更新 OpenSpec 规格以反映前端架构变更
- 归档 frontend-refactor 变更记录
This commit is contained in:
2026-04-16 11:21:48 +08:00
parent c17903dcbc
commit 9359ca7f62
61 changed files with 4588 additions and 1095 deletions

View File

@@ -0,0 +1,101 @@
import { useState } from 'react';
import type { Provider, Model, UpdateProviderInput, UpdateModelInput } from '@/types';
import { useProviders, useCreateProvider, useUpdateProvider, useDeleteProvider } from '@/hooks/useProviders';
import { useCreateModel, useUpdateModel } from '@/hooks/useModels';
import { ProviderTable } from './ProviderTable';
import { ProviderForm } from './ProviderForm';
import { ModelForm } from './ModelForm';
export function ProvidersPage() {
const { data: providers = [], isLoading } = useProviders();
const createProvider = useCreateProvider();
const updateProvider = useUpdateProvider();
const deleteProvider = useDeleteProvider();
const createModel = useCreateModel();
const updateModel = useUpdateModel();
const [providerFormOpen, setProviderFormOpen] = useState(false);
const [editingProvider, setEditingProvider] = useState<Provider | undefined>();
const [modelFormOpen, setModelFormOpen] = useState(false);
const [editingModel, setEditingModel] = useState<Model | undefined>();
const [modelFormProviderId, setModelFormProviderId] = useState('');
return (
<div>
<h1></h1>
<ProviderTable
providers={providers}
loading={isLoading}
onAdd={() => {
setEditingProvider(undefined);
setProviderFormOpen(true);
}}
onEdit={(provider) => {
setEditingProvider(provider);
setProviderFormOpen(true);
}}
onDelete={(id) => deleteProvider.mutate(id)}
onAddModel={(providerId) => {
setEditingModel(undefined);
setModelFormProviderId(providerId);
setModelFormOpen(true);
}}
onEditModel={(model) => {
setEditingModel(model);
setModelFormProviderId(model.providerId);
setModelFormOpen(true);
}}
/>
<ProviderForm
open={providerFormOpen}
provider={editingProvider}
loading={createProvider.isPending || updateProvider.isPending}
onSave={(values) => {
if (editingProvider) {
const input: Partial<UpdateProviderInput> = {};
if (values.name !== editingProvider.name) input.name = values.name;
if (values.apiKey) input.apiKey = values.apiKey;
if (values.baseUrl !== editingProvider.baseUrl) input.baseUrl = values.baseUrl;
if (values.enabled !== editingProvider.enabled) input.enabled = values.enabled;
updateProvider.mutate(
{ id: editingProvider.id, input },
{ onSuccess: () => setProviderFormOpen(false) },
);
} else {
createProvider.mutate(values, {
onSuccess: () => setProviderFormOpen(false),
});
}
}}
onCancel={() => setProviderFormOpen(false)}
/>
<ModelForm
open={modelFormOpen}
model={editingModel}
providerId={modelFormProviderId}
providers={providers}
loading={createModel.isPending || updateModel.isPending}
onSave={(values) => {
if (editingModel) {
const input: Partial<UpdateModelInput> = {};
if (values.providerId !== editingModel.providerId) input.providerId = values.providerId;
if (values.modelName !== editingModel.modelName) input.modelName = values.modelName;
if (values.enabled !== editingModel.enabled) input.enabled = values.enabled;
updateModel.mutate(
{ id: editingModel.id, input },
{ onSuccess: () => setModelFormOpen(false) },
);
} else {
createModel.mutate(values, {
onSuccess: () => setModelFormOpen(false),
});
}
}}
onCancel={() => setModelFormOpen(false)}
/>
</div>
);
}