fix(intelligent-eval): P0 UI/UX refresh defects (audit)
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
This commit is contained in:
parent
00ad929d68
commit
3626ab288b
@ -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 && <Button icon={<ArrowLeftOutlined />} onClick={onBack}>返回</Button>}
|
||||
<span style={{ fontSize: 16, fontWeight: 600 }}>配置历史</span>
|
||||
<div style={{ flex: 1 }} />
|
||||
<Button icon={<ReloadOutlined />} onClick={() => void loadSnapshots()}>刷新</Button>
|
||||
<Button
|
||||
icon={<DiffOutlined />}
|
||||
disabled={selectedForCompare.length !== 2}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import {
|
||||
Button, Card, Empty, Select, Table, Tag, Timeline, message,
|
||||
} from 'antd'
|
||||
import type { ColumnsType } from 'antd/es/table'
|
||||
import { ArrowLeftOutlined, DownloadOutlined } from '@ant-design/icons'
|
||||
import { ArrowLeftOutlined, DownloadOutlined, ReloadOutlined } from '@ant-design/icons'
|
||||
import { intelligentEvalsApi, type DecisionLog } from '../../api'
|
||||
import { colors } from '../../tokens'
|
||||
import { formatDateTime } from '../../utils/date'
|
||||
@ -38,9 +38,10 @@ export default function DecisionProcess({ evalId, onBack }: DecisionProcessProps
|
||||
}
|
||||
}
|
||||
|
||||
useState(() => {
|
||||
void loadLogs()
|
||||
})
|
||||
useEffect(() => {
|
||||
if (evalId) void loadLogs()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [evalId])
|
||||
|
||||
const handleExport = () => {
|
||||
if (!logs) return
|
||||
@ -120,6 +121,7 @@ export default function DecisionProcess({ evalId, onBack }: DecisionProcessProps
|
||||
{ label: '开始分析', value: 'start_analysis' },
|
||||
]}
|
||||
/>
|
||||
<Button icon={<ReloadOutlined />} onClick={() => void loadLogs()}>刷新</Button>
|
||||
<Button icon={<DownloadOutlined />} onClick={handleExport}>导出</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@ -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<FileCategory[]>([])
|
||||
const [files, setFiles] = useState<FileRecord[]>([])
|
||||
const [config, setConfig] = useState<FileUploadConfig>(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)
|
||||
|
||||
@ -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<WebSocket | null>(null)
|
||||
const pollRef = useRef<number | null>(null)
|
||||
const selectedIdRef = useRef<string | null>(null)
|
||||
const reconnectTimerRef = useRef<number | null>(null)
|
||||
const reconnectCountRef = useRef<number>(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,
|
||||
|
||||
@ -37,7 +37,7 @@ export default function FilesPage() {
|
||||
uploadFiles,
|
||||
deleteFile,
|
||||
downloadFile,
|
||||
} = useFiles()
|
||||
} = useFiles('/files')
|
||||
const [uploadOpen, setUploadOpen] = useState(false)
|
||||
const [categoryDialog, setCategoryDialog] = useState<CategoryDialogState>(CLOSED_CATEGORY_DIALOG)
|
||||
const [categorySaving, setCategorySaving] = useState(false)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user