fix(admin): 日志页改为保持倒序,打开自动定位到顶部(最新)

按用户预期调整方案:列表保持最新在最上面(后端倒序),打开页面/
自动刷新时 scrollTop=0 定位顶部;向下滚动回看历史时暂停定位,
拉回顶部即恢复。替代上一版的时间正序+贴底方案。
This commit is contained in:
WorkBuddy
2026-09-22 15:39:19 +08:00
parent 51fbc1828e
commit 77ecb078a3
+10 -13
View File
@@ -7,7 +7,7 @@
* - 缓冲上限 2000 条,进程重启后清零 * - 缓冲上限 2000 条,进程重启后清零
*/ */
import { useEffect, useState, useCallback, useRef, useMemo } from 'react' import { useEffect, useState, useCallback, useRef } from 'react'
import { fetchLogs } from '../dal' import { fetchLogs } from '../dal'
import type { LogEntry } from '../types' import type { LogEntry } from '../types'
import { Card, CardBody, CardHeader, Badge, SectionHeader, Alert, Spinner, SkeletonBlock } from '../components' import { Card, CardBody, CardHeader, Badge, SectionHeader, Alert, Spinner, SkeletonBlock } from '../components'
@@ -53,24 +53,21 @@ export default function LogsPage() {
}, [load]) }, [load])
const scrollRef = useRef<HTMLDivElement>(null) const scrollRef = useRef<HTMLDivElement>(null)
const userScrolledUp = useRef(false) const userScrolledDown = useRef(false)
// 检测用户是否向滚动 // 检测用户是否向滚动回看历史(旧日志在下方)
const handleScroll = () => { const handleScroll = () => {
const el = scrollRef.current const el = scrollRef.current
if (!el) return if (!el) return
const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 50 const atTop = el.scrollTop < 50
userScrolledUp.current = !atBottom userScrolledDown.current = !atTop
} }
// 后端返回最新在前;渲染转为时间正序(旧→新),配合「自动贴底」滚动: // 列表最新在最上面(后端倒序返回);打开页面/自动刷新时定位到顶部=最新。
// 打开页面/自动刷新默认定位到最新日志,用户上滚回看时暂停跟随 // 用户向下滚动回看历史时暂停定位,拉回顶部即恢复。
const ordered = useMemo(() => [...entries].reverse(), [entries])
// 加载后自动滚动到底部(仅当用户未向上滚动时)
useEffect(() => { useEffect(() => {
if (autoRefresh && !userScrolledUp.current && scrollRef.current) { if (autoRefresh && !userScrolledDown.current && scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight scrollRef.current.scrollTop = 0
} }
}, [entries, autoRefresh]) }, [entries, autoRefresh])
@@ -146,7 +143,7 @@ export default function LogsPage() {
<p className="py-10 text-center text-xs text-ink-400"></p> <p className="py-10 text-center text-xs text-ink-400"></p>
) : ( ) : (
<div ref={scrollRef} onScroll={handleScroll} className="max-h-[60vh] overflow-y-auto"> <div ref={scrollRef} onScroll={handleScroll} className="max-h-[60vh] overflow-y-auto">
{ordered.map((e, i) => { {entries.map((e, i) => {
const badge = LEVEL_BADGE[e.level] ?? { status: 'info' as const, text: e.level } const badge = LEVEL_BADGE[e.level] ?? { status: 'info' as const, text: e.level }
return ( return (
<div <div