From 8c5e6bcef84e8ed0d12b6b3abd8be60242d4b819 Mon Sep 17 00:00:00 2001 From: linkeyu <798648091@qq.com> Date: Mon, 27 Jul 2026 10:09:56 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E4=BD=8E=E4=BB=B7=E5=AE=A1=E6=A0=B8=E7=BA=A2=E7=82=B9?= =?UTF-8?q?=E4=B8=8E=E5=88=97=E8=A1=A8=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 进入页面、窗口重新聚焦及标签页恢复可见时刷新审核数据,并在页面可见期间每分钟同步一次,避免已无待审核记录时仍显示旧红点。 --- src/app/(main)/layout.tsx | 37 ++++++++++++++++++++------- src/app/(main)/price-reports/page.tsx | 37 +++++++++++++++++++++++---- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index b63fdc5..8d5ed63 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -37,6 +37,8 @@ import type { WithdrawSummary, } from '@/lib/types'; +const REVIEW_BADGE_POLL_INTERVAL_MS = 60_000; + const { Sider, Header, Content } = Layout; type NavItem = { @@ -156,21 +158,38 @@ export default function MainLayout({ children }: { children: React.ReactNode }) }; const refreshSafely = (key: ReviewBadgeKey) => refresh(key).catch(() => {}); - void Promise.all([ - refreshSafely('/withdraws'), - refreshSafely('/price-reports'), - refreshSafely('/feedbacks'), - ]); + const refreshAll = () => { + void Promise.all([ + refreshSafely('/withdraws'), + refreshSafely('/price-reports'), + refreshSafely('/feedbacks'), + ]); + }; + + refreshAll(); const onReviewCompleted = (event: Event) => { const key = (event as CustomEvent).detail; if (key) void refreshSafely(key); }; - window.addEventListener(REVIEW_BADGE_REFRESH_EVENT, onReviewCompleted); - return () => { - window.removeEventListener(REVIEW_BADGE_REFRESH_EVENT, onReviewCompleted); + const onWindowFocus = () => refreshAll(); + const onVisibilityChange = () => { + if (document.visibilityState === 'visible') refreshAll(); }; - }, [admin]); + const pollTimer = window.setInterval(() => { + if (document.visibilityState === 'visible') refreshAll(); + }, REVIEW_BADGE_POLL_INTERVAL_MS); + + window.addEventListener(REVIEW_BADGE_REFRESH_EVENT, onReviewCompleted); + window.addEventListener('focus', onWindowFocus); + document.addEventListener('visibilitychange', onVisibilityChange); + return () => { + window.clearInterval(pollTimer); + window.removeEventListener(REVIEW_BADGE_REFRESH_EVENT, onReviewCompleted); + window.removeEventListener('focus', onWindowFocus); + document.removeEventListener('visibilitychange', onVisibilityChange); + }; + }, [admin, pathname]); if (!admin) return null; // 守卫期间不闪烁内容 diff --git a/src/app/(main)/price-reports/page.tsx b/src/app/(main)/price-reports/page.tsx index 44d10d1..4ae1180 100644 --- a/src/app/(main)/price-reports/page.tsx +++ b/src/app/(main)/price-reports/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { App, Button, @@ -30,6 +30,7 @@ import type { PriceReport, PriceReportSummary } from '@/lib/types'; import UserRecordsDrawer from '@/components/UserRecordsDrawer'; const { Text } = Typography; +const REVIEW_PAGE_POLL_INTERVAL_MS = 60_000; // 后端 created_at/reviewed_at 存的是北京 wall-clock(naive,同 savings/comparison), // 直接本地解析、不加 Z(否则会差 8h)。 @@ -150,6 +151,10 @@ export default function PriceReportsPage() { const { items, total, page, pageSize, loading, onChange: onPageChange, reload } = usePagedList('/admin/api/price-reports', filters); + const reloadRef = useRef(reload); + useEffect(() => { + reloadRef.current = reload; + }, [reload]); const sortOrderOf = (field: SortField) => sortBy === field ? (sortOrder === 'asc' ? 'ascend' : 'descend') : null; @@ -181,18 +186,40 @@ export default function PriceReportsPage() { setSelectedRowKeys((keys) => keys.filter((id) => visiblePendingIds.has(id))); }, [items]); - const loadSummary = async () => { + const loadSummary = useCallback(async () => { try { const { data } = await api.get('/admin/api/price-reports/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 refreshAfterChange = () => { reload(); loadSummary(); -- 2.52.0 From 52acff82436df6a12df331ea80d62ba3de48efb9 Mon Sep 17 00:00:00 2001 From: linkeyu <798648091@qq.com> Date: Mon, 27 Jul 2026 10:20:03 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E5=8F=8D=E9=A6=88=E4=B8=8E=E6=8F=90=E7=8E=B0=E5=AE=A1?= =?UTF-8?q?=E6=A0=B8=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充用户反馈页自动刷新,并让公共红点支持邀请提现与其他提现独立统计。 --- src/app/(main)/feedbacks/page.tsx | 37 ++++++++++++++++++++++++++----- src/app/(main)/layout.tsx | 10 +++++++-- src/lib/reviewBadge.ts | 6 ++++- 3 files changed, 45 insertions(+), 8 deletions(-) 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'; -- 2.52.0