From 3626ab288b79b63cd7205e92fc385b201200b22a Mon Sep 17 00:00:00 2001 From: sinohqb Date: Mon, 17 Aug 2026 22:59:23 +0800 Subject: [PATCH] fix(intelligent-eval): P0 UI/UX refresh defects (audit) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UI/UX 全面盘点(.scratch/ui-ux-audit.md)P0 功能缺陷修复: - DecisionProcess/ConfigSnapshots:useState(()=>load()) 一次性加载改为 useEffect 依赖 evalId(切评估重拉)+ 头部补 ReloadOutlined 刷新按钮 - useRunSession:自写 setInterval(3000) 轮询改 usePolling(pauseWhenHidden 默认开启:后台标签页暂停、回前台立即刷新);selectedIdRef 守卫保留防串; 删除 pollRef/clearPolling - useFiles:接入 useOnTabActive('/files'),keep-alive 跳回自动刷新 tsc 0 错误 vitest 19 passed --- .../intelligent_eval/ConfigSnapshots.tsx | 12 ++++---- .../intelligent_eval/DecisionProcess.tsx | 12 ++++---- frontend/web/src/hooks/useFiles.ts | 8 +++++- frontend/web/src/hooks/useRunSession.ts | 28 ++++++------------- frontend/web/src/pages/Files.tsx | 2 +- 5 files changed, 30 insertions(+), 32 deletions(-) diff --git a/frontend/web/src/components/intelligent_eval/ConfigSnapshots.tsx b/frontend/web/src/components/intelligent_eval/ConfigSnapshots.tsx index aa0b00a..4a5aa80 100644 --- a/frontend/web/src/components/intelligent_eval/ConfigSnapshots.tsx +++ b/frontend/web/src/components/intelligent_eval/ConfigSnapshots.tsx @@ -1,9 +1,9 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import { Button, Card, Descriptions, Empty, Space, Table, Tag, message, } from 'antd' import type { ColumnsType } from 'antd/es/table' -import { ArrowLeftOutlined, DiffOutlined } from '@ant-design/icons' +import { ArrowLeftOutlined, DiffOutlined, ReloadOutlined } from '@ant-design/icons' import { intelligentEvalsApi, type ConfigSnapshot, type ConfigSnapshotComparison } from '../../api' import { colors } from '../../tokens' import { formatDateTime } from '../../utils/date' @@ -40,9 +40,10 @@ export default function ConfigSnapshots({ evalId, onBack }: ConfigSnapshotsProps } } - useState(() => { - void loadSnapshots() - }) + useEffect(() => { + if (evalId) void loadSnapshots() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [evalId]) const handleViewDetail = async (snapshot: ConfigSnapshot) => { try { @@ -244,6 +245,7 @@ export default function ConfigSnapshots({ evalId, onBack }: ConfigSnapshotsProps {onBack && } 配置历史
+
diff --git a/frontend/web/src/hooks/useFiles.ts b/frontend/web/src/hooks/useFiles.ts index c4a839e..aad4b1b 100644 --- a/frontend/web/src/hooks/useFiles.ts +++ b/frontend/web/src/hooks/useFiles.ts @@ -6,13 +6,14 @@ import { type FileUploadConfig, } from '../api' import { categoryContains, findCategory } from '../utils/fileTree' +import { useOnTabActive } from './useOnTabActive' const DEFAULT_CONFIG: FileUploadConfig = { allowed_extensions: [], max_upload_size_mb: 50, } -export function useFiles() { +export function useFiles(tabPath?: string) { const [categories, setCategories] = useState([]) const [files, setFiles] = useState([]) const [config, setConfig] = useState(DEFAULT_CONFIG) @@ -46,6 +47,11 @@ export function useFiles() { void Promise.allSettled([loadCategories(), loadFiles(), loadConfig()]) }, [loadCategories, loadConfig, loadFiles]) + // keep-alive 下 tab 重新激活时刷新(跳走再回来数据不过期) + useOnTabActive(tabPath ?? '', () => { + if (tabPath) void refresh() + }) + const selectCategory = useCallback((categoryId: string | null) => { setSelectedCategoryId(categoryId) void loadFiles(categoryId) diff --git a/frontend/web/src/hooks/useRunSession.ts b/frontend/web/src/hooks/useRunSession.ts index 3474b91..2866760 100644 --- a/frontend/web/src/hooks/useRunSession.ts +++ b/frontend/web/src/hooks/useRunSession.ts @@ -5,6 +5,7 @@ import { sessionReducer, type WsEvent, } from './sessionReducer' +import { usePolling } from './usePolling' // WebSocket reconnect config const WS_MAX_RETRIES = 5 @@ -81,18 +82,10 @@ export function useRunSession(): RunSession { const [state, dispatch] = useReducer(sessionReducer, initialSessionState) const wsRef = useRef(null) - const pollRef = useRef(null) const selectedIdRef = useRef(null) const reconnectTimerRef = useRef(null) const reconnectCountRef = useRef(0) - const clearPolling = () => { - if (pollRef.current) { - window.clearInterval(pollRef.current) - pollRef.current = null - } - } - const clearReconnect = () => { if (reconnectTimerRef.current) { window.clearTimeout(reconnectTimerRef.current) @@ -174,7 +167,6 @@ export function useRunSession(): RunSession { const select = useCallback((r: Run | null, opts?: { live?: boolean }) => { closeWs() - clearPolling() dispatch({ type: 'RESET' }) setRun(r) selectedIdRef.current = r?.id ?? null @@ -250,21 +242,17 @@ export function useRunSession(): RunSession { } catch { /* noop */ } }, [run]) - // Poll during live runs to keep the Run object fresh (status/summary/completed_at) - useEffect(() => { - if (!state.isLive || !run) { - clearPolling() - return - } - pollRef.current = window.setInterval(() => { + // Poll during live runs to keep the Run object fresh (status/summary/completed_at). + // usePolling 统一轮询:pauseWhenHidden 默认开启,后台标签页暂停、回到前台立即刷新。 + usePolling(() => { + if (run && selectedIdRef.current === run.id) { runsApi.get(run.id).then((res) => { if (selectedIdRef.current === run.id) setRun(res.data) }).catch(() => { /* noop */ }) - }, 3000) - return () => clearPolling() - }, [state.isLive, run?.id]) + } + }, 3000, state.isLive && run != null) - useEffect(() => () => { closeWs(); clearPolling(); clearReconnect() }, []) + useEffect(() => () => { closeWs(); clearReconnect() }, []) return { run, diff --git a/frontend/web/src/pages/Files.tsx b/frontend/web/src/pages/Files.tsx index 3f37c6f..480cc31 100644 --- a/frontend/web/src/pages/Files.tsx +++ b/frontend/web/src/pages/Files.tsx @@ -37,7 +37,7 @@ export default function FilesPage() { uploadFiles, deleteFile, downloadFile, - } = useFiles() + } = useFiles('/files') const [uploadOpen, setUploadOpen] = useState(false) const [categoryDialog, setCategoryDialog] = useState(CLOSED_CATEGORY_DIALOG) const [categorySaving, setCategorySaving] = useState(false)