refactor: 前端 antd 组件使用最佳实践重构

- 修正 API 响应类型,增加 ProjectResponse 包装类型
- ConfigProvider 配置中文 locale (zhCN)
- 生产入口启用 ErrorBoundary,使用 Result 组件
- ReactQueryDevtools 仅开发环境渲染
- Sider 增加 collapsible 配置,使用 antd 默认折叠行为
- 项目页面拆分为 ProjectToolbar/ProjectTable/ProjectFormModal
- 搜索改用 Input.Search,表单增加 whitespace 校验
- 404/ErrorBoundary/Dashboard 使用 antd Result/Typography/Card/Descriptions
- 清理未使用的 ProtectedRoute 和冗余样式类
- styles.css 仅保留必要布局样式,无 antd 内部类覆盖
- 更新测试覆盖,避免依赖 antd 内部类名
- 更新 docs/development/frontend.md 开发规范
This commit is contained in:
2026-05-28 16:09:01 +08:00
parent 1f232e69fc
commit b5301ec7d1
22 changed files with 458 additions and 432 deletions

18
src/web/hooks/use-meta.ts Normal file
View File

@@ -0,0 +1,18 @@
import { useQuery } from "@tanstack/react-query";
import type { MetaResponse } from "../../shared/api";
export function useMeta() {
return useQuery({
queryFn: fetchMeta,
queryKey: ["meta"],
refetchInterval: 30000,
staleTime: 5000,
});
}
async function fetchMeta(): Promise<MetaResponse> {
const response = await fetch("/api/meta");
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json() as Promise<MetaResponse>;
}

View File

@@ -4,6 +4,7 @@ import type {
CreateProjectRequest,
Project,
ProjectListResponse,
ProjectResponse,
ProjectStatus,
UpdateProjectRequest,
} from "../../shared/api";
@@ -81,7 +82,8 @@ async function archiveProject(id: string): Promise<Project> {
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
return response.json() as Promise<Project>;
const data = (await response.json()) as ProjectResponse;
return data.project;
}
async function createProject(data: CreateProjectRequest): Promise<Project> {
@@ -94,7 +96,8 @@ async function createProject(data: CreateProjectRequest): Promise<Project> {
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
return response.json() as Promise<Project>;
const result = (await response.json()) as ProjectResponse;
return result.project;
}
async function deleteProject(id: string): Promise<void> {
@@ -111,7 +114,8 @@ async function fetchProject(id: string): Promise<Project> {
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
return response.json() as Promise<Project>;
const data = (await response.json()) as ProjectResponse;
return data.project;
}
async function fetchProjectList(params: {
@@ -141,7 +145,8 @@ async function restoreProject(id: string): Promise<Project> {
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
return response.json() as Promise<Project>;
const data = (await response.json()) as ProjectResponse;
return data.project;
}
async function updateProject(id: string, data: UpdateProjectRequest): Promise<Project> {
@@ -154,5 +159,6 @@ async function updateProject(id: string, data: UpdateProjectRequest): Promise<Pr
const body = (await response.json().catch(() => null)) as null | { error?: string };
throw new Error(body?.error ?? `HTTP ${response.status}`);
}
return response.json() as Promise<Project>;
const result = (await response.json()) as ProjectResponse;
return result.project;
}

View File

@@ -22,11 +22,7 @@ export function useSidebarCollapsed() {
writeSidebarCollapsed(nextCollapsed);
};
const toggleCollapsed = () => {
setCollapsed(!collapsed);
};
return { collapsed, setCollapsed, toggleCollapsed };
return { collapsed, setCollapsed };
}
export function writeSidebarCollapsed(collapsed: boolean, storage: Storage = window.localStorage) {