import { useCallback, useEffect, useRef, useState } from 'react' import { Card, DotLoading, Empty } from 'antd-mobile' import { api } from '../../api/client.js' import useRefreshOnVisible from '../../hooks/useRefreshOnVisible.js' import { fmtDateTime } from '../../utils/format.js' // 报警 (V0.2 从「首页」并入「宏观」Tab) // - 数据: World Bank 与妙想 MCP 跨源同 (指标, 周期) 数据差异超阈值, 按时间倒序 // - 无限滚动: 触底自动加载下一页 // - onCount: 把总数回传给父级 (宏观 Tab 上显示角标) export default function AlertsView({ onCount }) { const [items, setItems] = useState([]) const [loading, setLoading] = useState(false) const [done, setDone] = useState(false) const cursorRef = useRef(null) const sentinelRef = useRef(null) const inFlightRef = useRef(false) const fetchPage = useCallback(async () => { if (inFlightRef.current || done) return inFlightRef.current = true setLoading(true) try { const url = '/alerts?limit=10' + (cursorRef.current ? '&cursor=' + cursorRef.current : '') const data = await api(url) setItems((prev) => prev.concat(data.items || [])) onCount?.(data.total ?? (data.items?.length || 0)) cursorRef.current = data.nextCursor if (!data.nextCursor) setDone(true) } catch (e) { console.error('alerts load error', e) setDone(true) } finally { inFlightRef.current = false setLoading(false) } }, [done, onCount]) useEffect(() => { fetchPage() }, []) // 后台回前台: 重置游标重拉第一页, 新数据到了再更新视图 const reload = useCallback(async () => { if (inFlightRef.current) return inFlightRef.current = true setLoading(true) try { const data = await api('/alerts?limit=10') setItems(data.items || []) onCount?.(data.total ?? (data.items?.length || 0)) cursorRef.current = data.nextCursor setDone(!data.nextCursor) } catch (e) { console.error('alerts reload error', e) setDone(true) } finally { inFlightRef.current = false setLoading(false) } }, [onCount]) useRefreshOnVisible(reload) useEffect(() => { if (done || items.length === 0) return const el = sentinelRef.current if (!el) return const io = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) fetchPage() }, { rootMargin: '200px' }) io.observe(el) return () => io.disconnect() }, [items.length, done, fetchPage]) if (items.length === 0 && !loading) { return (
) } return (
{items.map((a) => (
{a.indicatorName} {a.period}
{a.sourceA} = {a.valueA} vs {a.sourceB} = {a.valueB} 差异 {(a.diffRatio * 100).toFixed(1)}%
{fmtDateTime(a.createdAt)}
))}
{loading ? : (done ? '— 已加载全部 —' : '')}
) }