refactor(web): React 最佳实践优化 — memo/callback + 目录边界 + 路由增强

- useLogger: useMemo + JSON.stringify 替代 useState 派生
- useIsDark: effectiveTheme 替代 token 色值比较
- useCurrentProject: layouts/ 提升到 shared/hooks/
- ConsoleShell: locale useMemo 缓存
- ConsoleOutlet: 添加 Suspense 边界
- routes: 添加 layout 级 errorElement
- Table 组件: operationColumn useMemo + useCallback
- ChatPanel: footer 合并为 useCallback, props 传入模型数据
- ChatPage: textModels/conversations useMemo 缓存
This commit is contained in:
2026-06-03 11:32:28 +08:00
parent 297293cb61
commit 5b09a16bc3
18 changed files with 342 additions and 245 deletions

View File

@@ -2,12 +2,12 @@ import { DeleteOutlined, MoreOutlined } from "@ant-design/icons";
import { Conversations } from "@ant-design/x";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { App, Spin } from "antd";
import { useState } from "react";
import { useMemo, useState } from "react";
import type { Conversation } from "../../../shared/api";
import { useCurrentProject } from "../../layouts/workbench-layout/useCurrentProject";
import { createConversation, deleteConversation, fetchConversations } from "../../shared/hooks/use-conversations";
import { useCurrentProject } from "../../shared/hooks/use-current-project";
import { useModelList } from "../../shared/hooks/use-models";
import { ChatPanel } from "./ChatPanel";
@@ -25,8 +25,13 @@ export function ChatPage() {
});
const { data: modelsData } = useModelList({ pageSize: 200 });
const textModels = (modelsData?.items ?? []).filter((m) => m.capabilities.includes("text"));
const defaultModelId = textModels[0]?.id;
const textModels = useMemo(
() => (modelsData?.items ?? []).filter((m) => m.capabilities.includes("text")),
[modelsData],
);
const defaultModelId = textModels[0]?.id ?? null;
const deleteMutation = useMutation({
mutationFn: (id: string) => deleteConversation(project.id, id),
@@ -39,10 +44,10 @@ export function ChatPage() {
},
});
const conversations = (data?.items ?? []).map((c: Conversation) => ({
key: c.id,
label: c.title,
}));
const conversations = useMemo(
() => (data?.items ?? []).map((c: Conversation) => ({ key: c.id, label: c.title })),
[data],
);
return (
<div className="app-chat-page">
@@ -54,7 +59,7 @@ export function ChatPage() {
activeKey={activeConversationId ?? ""}
creation={{
onClick: () => {
void createConversation(project.id, defaultModelId)
void createConversation(project.id, defaultModelId ?? undefined)
.then((conv) => {
void queryClient.invalidateQueries({ queryKey: CONVERSATIONS_KEY });
setActiveConversationId(conv.id);
@@ -85,8 +90,10 @@ export function ChatPage() {
</div>
<ChatPanel
conversationId={activeConversationId}
defaultModelId={defaultModelId}
onConversationCreated={setActiveConversationId}
projectId={project.id}
textModels={textModels}
/>
</div>
);