chore: 前端代码优化 - ESLint 配置、Prettier 格式化、加载骨架屏、React 组件规范

- 更新 ESLint 配置添加 react-hooks 规则
- 运行 Prettier 格式化所有前端文件
- 为 users、apps、audit、quotas 页面添加 Skeleton 加载骨架屏
- 修复动态组件渲染问题(CategoryIcon/TypeIcon 使用 React.createElement)
- 修复 useRef 渲染期间访问问题(改用 useMemo)
- 修复 effect 中 setState 同步调用问题
- 新增 global-not-found.tsx 404 页面
- 修复 knowledge 页面引号转义问题
This commit is contained in:
selfrelease
2026-06-23 14:43:04 +08:00
parent 6ca1c27162
commit 96b944ac8d
68 changed files with 2182 additions and 1969 deletions
+1 -5
View File
@@ -1,8 +1,4 @@
export {
useSSEStream,
updateLastAssistantMessage,
setStreamErrorMessage,
} from "./use-sse-stream";
export { useSSEStream, updateLastAssistantMessage, setStreamErrorMessage } from "./use-sse-stream";
export { useCopyToClipboard } from "./use-copy-clipboard";
export { useScrollToBottom } from "./use-scroll-bottom";
export { useFileExport } from "./use-file-export";
+11 -14
View File
@@ -1,6 +1,6 @@
"use client";
import { useMemo, useRef } from "react";
import { useMemo } from "react";
import type { App } from "@/lib/types";
/**
@@ -10,8 +10,7 @@ import type { App } from "@/lib/types";
export function useAppConfig(app: App): Record<string, unknown> {
return useMemo(() => {
try {
if (typeof app.app_config === "string")
return JSON.parse(app.app_config);
if (typeof app.app_config === "string") return JSON.parse(app.app_config);
return app.app_config || {};
} catch {
return {};
@@ -24,15 +23,13 @@ export function useAppConfig(app: App): Record<string, unknown> {
* chatbot-ui / agent-ui 共用。
*/
export function useSuggestedPrompts(app: App): string[] {
return useRef(
(() => {
try {
if (typeof app.suggested_prompts === "string")
return JSON.parse(app.suggested_prompts) as string[];
return (app.suggested_prompts as string[]) || [];
} catch {
return [];
}
})(),
).current;
return useMemo(() => {
try {
if (typeof app.suggested_prompts === "string")
return JSON.parse(app.suggested_prompts) as string[];
return (app.suggested_prompts as string[]) || [];
} catch {
return [];
}
}, [app.suggested_prompts]);
}
+15 -18
View File
@@ -8,24 +8,21 @@ import { toast } from "sonner";
* chatbot-ui / agent-ui / doc-writer-ui / analysis-ui 的导出功能共用。
*/
export function useFileExport() {
const download = useCallback(
(content: string, filename: string, successMsg = "已导出") => {
const blob = new Blob([content], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.style.display = "none";
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 30000);
toast.success(successMsg);
},
[],
);
const download = useCallback((content: string, filename: string, successMsg = "已导出") => {
const blob = new Blob([content], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.style.display = "none";
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 30000);
toast.success(successMsg);
}, []);
return { download };
}
+3 -11
View File
@@ -21,10 +21,7 @@ export function useSSEStream(options: UseSSEStreamOptions = {}) {
const abortRef = useRef<AbortController | null>(null);
const processStream = useCallback(
async (
response: Response,
onChunk: (accumulated: string) => void,
) => {
async (response: Response, onChunk: (accumulated: string) => void) => {
if (!response.ok) throw new Error("请求失败");
const reader = response.body?.getReader();
const decoder = new TextDecoder();
@@ -90,14 +87,9 @@ export function useSSEStream(options: UseSSEStreamOptions = {}) {
* 更新消息列表中最后一条 assistant 消息的内容。
* chatbot-ui / agent-ui / doc-writer-ui / analysis-ui 共用此逻辑。
*/
export function updateLastAssistantMessage(
prev: Message[],
content: string,
): Message[] {
export function updateLastAssistantMessage(prev: Message[], content: string): Message[] {
return prev.map((m, i) =>
i === prev.length - 1 && m.role === "assistant"
? { ...m, content }
: m,
i === prev.length - 1 && m.role === "assistant" ? { ...m, content } : m,
);
}