diff --git a/src/app/(main)/feedbacks/page.tsx b/src/app/(main)/feedbacks/page.tsx index b37008d..10dd71f 100644 --- a/src/app/(main)/feedbacks/page.tsx +++ b/src/app/(main)/feedbacks/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import type { ColumnsType } from 'antd/es/table'; import type { SorterResult } from 'antd/es/table/interface'; import { @@ -35,6 +35,7 @@ import UserRecordsDrawer from '@/components/UserRecordsDrawer'; const { Text, Paragraph } = Typography; const { RangePicker } = DatePicker; const REWARD_MAX = 10000; +const REVIEW_PAGE_POLL_INTERVAL_MS = 60_000; // 截图是 app-server 的 /media 相对路径,本地由 :8770 提供(NEXT_PUBLIC_MEDIA_BASE);生产同域走 nginx 代理。 const MEDIA_BASE = process.env.NEXT_PUBLIC_MEDIA_BASE || ''; @@ -178,6 +179,10 @@ export default function FeedbacksPage() { const filters: Record = { ...applied, sort_by: sortBy, sort_order: sortOrder }; const { items, total, page, pageSize, loading, onChange: onPageChange, reload } = usePagedList('/admin/api/feedbacks', filters); + const reloadRef = useRef(reload); + useEffect(() => { + reloadRef.current = reload; + }, [reload]); const canReview = canDo(['operator']); const [selectedRowKeys, setSelectedRowKeys] = useState([]); @@ -195,18 +200,40 @@ export default function FeedbacksPage() { }, [items]); const [summary, setSummary] = useState(null); - const loadSummary = async () => { + const loadSummary = useCallback(async () => { try { const { data } = await api.get('/admin/api/feedbacks/summary'); setSummary(data); } catch { /* 统计失败不阻塞列表 */ } - }; - useEffect(() => { - loadSummary(); }, []); + const refreshPageData = useCallback(() => { + void reloadRef.current(); + void loadSummary(); + }, [loadSummary]); + + useEffect(() => { + void loadSummary(); + + const onWindowFocus = () => refreshPageData(); + const onVisibilityChange = () => { + if (document.visibilityState === 'visible') refreshPageData(); + }; + const pollTimer = window.setInterval(() => { + if (document.visibilityState === 'visible') refreshPageData(); + }, REVIEW_PAGE_POLL_INTERVAL_MS); + + window.addEventListener('focus', onWindowFocus); + document.addEventListener('visibilitychange', onVisibilityChange); + return () => { + window.clearInterval(pollTimer); + window.removeEventListener('focus', onWindowFocus); + document.removeEventListener('visibilitychange', onVisibilityChange); + }; + }, [loadSummary, refreshPageData]); + // 审核/查看抽屉(采纳发金币 / 拒绝填原因 + 看该用户历史反馈) const [drawerFb, setDrawerFb] = useState(null); const [drawerOpen, setDrawerOpen] = useState(false); diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index 8d5ed63..2b93add 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -146,8 +146,13 @@ export default function MainLayout({ children }: { children: React.ReactNode }) const version = (reviewRefreshVersion.current[key] ?? 0) + 1; reviewRefreshVersion.current[key] = version; let count: number; - if (key === '/withdraws') { - count = (await api.get('/admin/api/withdraws/summary')).data.reviewing_count; + if (key === '/withdraws' || key === '/invite-withdraws') { + const source = key === '/invite-withdraws' ? 'invite_cash' : 'coin_cash'; + count = ( + await api.get('/admin/api/withdraws/summary', { + params: { source }, + }) + ).data.reviewing_count; } else if (key === '/price-reports') { count = (await api.get('/admin/api/price-reports/summary')).data.pending; } else { @@ -161,6 +166,7 @@ export default function MainLayout({ children }: { children: React.ReactNode }) const refreshAll = () => { void Promise.all([ refreshSafely('/withdraws'), + refreshSafely('/invite-withdraws'), refreshSafely('/price-reports'), refreshSafely('/feedbacks'), ]); diff --git a/src/lib/reviewBadge.ts b/src/lib/reviewBadge.ts index 78e95d4..d6d04c0 100644 --- a/src/lib/reviewBadge.ts +++ b/src/lib/reviewBadge.ts @@ -1,4 +1,8 @@ -export type ReviewBadgeKey = '/withdraws' | '/price-reports' | '/feedbacks'; +export type ReviewBadgeKey = + | '/invite-withdraws' + | '/withdraws' + | '/price-reports' + | '/feedbacks'; export const REVIEW_BADGE_REFRESH_EVENT = 'review-badge-refresh';