Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dd55e5f408 | |||
| 66acad213b | |||
| be7ae1879e |
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import type { SorterResult } from 'antd/es/table/interface';
|
||||
import {
|
||||
@@ -35,7 +35,6 @@ 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 || '';
|
||||
@@ -179,10 +178,6 @@ export default function FeedbacksPage() {
|
||||
const filters: Record<string, unknown> = { ...applied, sort_by: sortBy, sort_order: sortOrder };
|
||||
const { items, total, page, pageSize, loading, onChange: onPageChange, reload } =
|
||||
usePagedList<Feedback>('/admin/api/feedbacks', filters);
|
||||
const reloadRef = useRef(reload);
|
||||
useEffect(() => {
|
||||
reloadRef.current = reload;
|
||||
}, [reload]);
|
||||
|
||||
const canReview = canDo(['operator']);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]);
|
||||
@@ -200,39 +195,17 @@ export default function FeedbacksPage() {
|
||||
}, [items]);
|
||||
|
||||
const [summary, setSummary] = useState<FeedbackSummary | null>(null);
|
||||
const loadSummary = useCallback(async () => {
|
||||
const loadSummary = async () => {
|
||||
try {
|
||||
const { data } = await api.get<FeedbackSummary>('/admin/api/feedbacks/summary');
|
||||
setSummary(data);
|
||||
} catch {
|
||||
/* 统计失败不阻塞列表 */
|
||||
}
|
||||
}, []);
|
||||
|
||||
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]);
|
||||
loadSummary();
|
||||
}, []);
|
||||
|
||||
// 审核/查看抽屉(采纳发金币 / 拒绝填原因 + 看该用户历史反馈)
|
||||
const [drawerFb, setDrawerFb] = useState<Feedback | null>(null);
|
||||
|
||||
@@ -37,8 +37,6 @@ import type {
|
||||
WithdrawSummary,
|
||||
} from '@/lib/types';
|
||||
|
||||
const REVIEW_BADGE_POLL_INTERVAL_MS = 60_000;
|
||||
|
||||
const { Sider, Header, Content } = Layout;
|
||||
|
||||
type NavItem = {
|
||||
@@ -146,13 +144,8 @@ 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' || key === '/invite-withdraws') {
|
||||
const source = key === '/invite-withdraws' ? 'invite_cash' : 'coin_cash';
|
||||
count = (
|
||||
await api.get<WithdrawSummary>('/admin/api/withdraws/summary', {
|
||||
params: { source },
|
||||
})
|
||||
).data.reviewing_count;
|
||||
if (key === '/withdraws') {
|
||||
count = (await api.get<WithdrawSummary>('/admin/api/withdraws/summary')).data.reviewing_count;
|
||||
} else if (key === '/price-reports') {
|
||||
count = (await api.get<PriceReportSummary>('/admin/api/price-reports/summary')).data.pending;
|
||||
} else {
|
||||
@@ -163,39 +156,21 @@ export default function MainLayout({ children }: { children: React.ReactNode })
|
||||
};
|
||||
|
||||
const refreshSafely = (key: ReviewBadgeKey) => refresh(key).catch(() => {});
|
||||
const refreshAll = () => {
|
||||
void Promise.all([
|
||||
refreshSafely('/withdraws'),
|
||||
refreshSafely('/invite-withdraws'),
|
||||
refreshSafely('/price-reports'),
|
||||
refreshSafely('/feedbacks'),
|
||||
]);
|
||||
};
|
||||
|
||||
refreshAll();
|
||||
void Promise.all([
|
||||
refreshSafely('/withdraws'),
|
||||
refreshSafely('/price-reports'),
|
||||
refreshSafely('/feedbacks'),
|
||||
]);
|
||||
|
||||
const onReviewCompleted = (event: Event) => {
|
||||
const key = (event as CustomEvent<ReviewBadgeKey>).detail;
|
||||
if (key) void refreshSafely(key);
|
||||
};
|
||||
const onWindowFocus = () => refreshAll();
|
||||
const onVisibilityChange = () => {
|
||||
if (document.visibilityState === 'visible') refreshAll();
|
||||
};
|
||||
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]);
|
||||
}, [admin]);
|
||||
|
||||
if (!admin) return null; // 守卫期间不闪烁内容
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
App,
|
||||
Button,
|
||||
@@ -30,7 +30,6 @@ 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)。
|
||||
@@ -151,10 +150,6 @@ export default function PriceReportsPage() {
|
||||
|
||||
const { items, total, page, pageSize, loading, onChange: onPageChange, reload } =
|
||||
usePagedList<PriceReport>('/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;
|
||||
@@ -186,39 +181,17 @@ export default function PriceReportsPage() {
|
||||
setSelectedRowKeys((keys) => keys.filter((id) => visiblePendingIds.has(id)));
|
||||
}, [items]);
|
||||
|
||||
const loadSummary = useCallback(async () => {
|
||||
const loadSummary = async () => {
|
||||
try {
|
||||
const { data } = await api.get<PriceReportSummary>('/admin/api/price-reports/summary');
|
||||
setSummary(data);
|
||||
} catch {
|
||||
/* 统计失败不阻塞列表 */
|
||||
}
|
||||
}, []);
|
||||
|
||||
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]);
|
||||
loadSummary();
|
||||
}, []);
|
||||
|
||||
const refreshAfterChange = () => {
|
||||
reload();
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
export type ReviewBadgeKey =
|
||||
| '/invite-withdraws'
|
||||
| '/withdraws'
|
||||
| '/price-reports'
|
||||
| '/feedbacks';
|
||||
export type ReviewBadgeKey = '/withdraws' | '/price-reports' | '/feedbacks';
|
||||
|
||||
export const REVIEW_BADGE_REFRESH_EVENT = 'review-badge-refresh';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user