feat: 拆分协作台账充值/脚本统计并支持暂停实时同步

This commit is contained in:
yml2213
2026-10-19 12:27:05 +08:00
parent 52043adbc1
commit 375567cbd2
+85 -18
View File
@@ -8,7 +8,7 @@
*/
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
Alert, App, Button, Card, Checkbox, Col, Descriptions, Divider, Drawer, Dropdown,
Alert, App, Badge, Button, Card, Checkbox, Col, Descriptions, Divider, Drawer, Dropdown,
Empty, Form, Image, Input, Modal, Popconfirm, Popover, Progress, Radio, Row, Select, Space, Statistic,
Switch, Table, Tag, Timeline, Tooltip, Typography, Upload,
} from 'antd';
@@ -56,6 +56,17 @@ function hasTextInputFocus(): boolean {
);
}
/** 实时同步暂停开关按浏览器记住:刷新页面后仍保持暂停,直到用户手动恢复。 */
const REALTIME_PAUSED_STORAGE_KEY = 'huya.collab.realtime-paused';
function readRealtimePaused(): boolean {
try {
return window.localStorage.getItem(REALTIME_PAUSED_STORAGE_KEY) === '1';
} catch {
return false;
}
}
const STATUS_META: Record<HuyaCollabStatus, { label: string; color: string }> = {
// 只登记了订单号 + 店铺,客户资料(CK / 礼包 / 主页截图)到了再补
incomplete: { label: '待补资料', color: 'gold' },
@@ -494,6 +505,7 @@ export default function HuyaCollabPage() {
const [sortKey, setSortKey] = useState<CollabSortKey>('seq');
const [sortOrder, setSortOrder] = useState<CollabSortOrder>('ascend');
const [realtimePending, setRealtimePending] = useState(false);
const [realtimePaused, setRealtimePaused] = useState(readRealtimePaused);
const [selectedIds, setSelectedIds] = useState<number[]>([]);
const [detail, setDetail] = useState<HuyaCollabOrder | null>(null);
@@ -606,29 +618,49 @@ export default function HuyaCollabPage() {
useEffect(() => { void load(); }, [load]);
const realtimeEnabled = can('huya:collab:view');
// 暂停后连 WebSocket 也不建:既不自动重拉,也不做断线兜底轮询。
const realtimeSyncEnabled = realtimeEnabled && !realtimePaused;
const collabModalOpen = Boolean(
createOpen || supplementTarget || detail || statusAction || batchStatusOpen || ckTarget || ckExportOpen || shopModalOpen,
);
const toggleRealtimeSync = (nextEnabled: boolean) => {
const nextPaused = !nextEnabled;
setRealtimePaused(nextPaused);
try {
window.localStorage.setItem(REALTIME_PAUSED_STORAGE_KEY, nextPaused ? '1' : '0');
} catch {
// 隐私模式下 localStorage 不可写也不影响本次会话的开关。
}
if (nextPaused) {
setRealtimePending(false);
message.info('已暂停实时同步,点「刷新」手动获取最新数据');
return;
}
message.success('已恢复实时同步');
// 暂停期间可能漏过变更,恢复连接后立即补拉一次。
void load({ silent: true, withMeta: true });
};
const handleRealtimeInvalidate = useCallback((event: CollabRealtimeEvent) => {
if (!realtimeEnabled) return;
if (!realtimeSyncEnabled) return;
if (collabModalOpen || hasTextInputFocus()) {
setRealtimePending(true);
return;
}
void load({ silent: true, withMeta: event.scope !== 'orders' });
}, [realtimeEnabled, collabModalOpen, load]);
}, [realtimeSyncEnabled, collabModalOpen, load]);
const {
connected: realtimeConnected,
reconnecting: realtimeReconnecting,
} = useCollabRealtime({
enabled: realtimeEnabled,
enabled: realtimeSyncEnabled,
onInvalidate: handleRealtimeInvalidate,
});
useEffect(() => {
if (!realtimePending || collabModalOpen) return undefined;
if (!realtimePending || !realtimeSyncEnabled || collabModalOpen) return undefined;
let cancelled = false;
const flush = () => {
if (cancelled || collabModalOpen || hasTextInputFocus()) return;
@@ -643,17 +675,17 @@ export default function HuyaCollabPage() {
window.clearTimeout(timer);
window.removeEventListener('focusout', handleFocusOut);
};
}, [realtimePending, collabModalOpen, load]);
}, [realtimePending, realtimeSyncEnabled, collabModalOpen, load]);
// WebSocket 断开时用低频轮询兜底;连接正常时完全不轮询。
// WebSocket 断开时用低频轮询兜底;连接正常或手动暂停时完全不轮询。
useEffect(() => {
if (!realtimeEnabled || realtimeConnected) return undefined;
if (!realtimeSyncEnabled || realtimeConnected) return undefined;
const timer = window.setInterval(() => {
if (document.visibilityState !== 'visible' || collabModalOpen) return;
void load({ silent: true, withMeta: false });
}, 30_000);
return () => window.clearInterval(timer);
}, [realtimeEnabled, realtimeConnected, collabModalOpen, load]);
}, [realtimeSyncEnabled, realtimeConnected, collabModalOpen, load]);
const applyUpdated = useCallback((updated: HuyaCollabOrder) => {
setRows((list) => list.map((row) => (row.id === updated.id ? updated : row)));
@@ -2005,8 +2037,13 @@ export default function HuyaCollabPage() {
</Col>
<Col flex="1">
<Card size="small">
<Statistic title="已充值 / 已上脚本"
value={(summary?.by_status.recharged ?? 0) + (summary?.by_status.scripted ?? 0)}
<Statistic title={STATUS_META.recharged.label} value={summary?.by_status.recharged ?? 0}
valueStyle={{ color: '#2f54eb' }} />
</Card>
</Col>
<Col flex="1">
<Card size="small">
<Statistic title={STATUS_META.scripted.label} value={summary?.by_status.scripted ?? 0}
valueStyle={{ color: '#722ed1' }} />
</Card>
</Col>
@@ -2027,6 +2064,43 @@ export default function HuyaCollabPage() {
<Statistic title="CK 失效" value={summary?.ck_invalid ?? 0} valueStyle={{ color: '#ff4d4f' }} />
</Card>
</Col>
{realtimeEnabled && (
<Col flex="none">
<Card
size="small"
style={{ width: 172, height: '100%' }}
styles={{ body: { height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '10px 12px' } }}
>
<Space direction="vertical" size={4} style={{ width: '100%' }}>
<Space size={6} style={{ width: '100%', justifyContent: 'space-between' }}>
<Tooltip title="关闭后停止自动同步;需要最新数据时点「刷新」手动获取">
<Text strong style={{ fontSize: 13 }}>实时同步</Text>
</Tooltip>
<Switch
size="small"
checked={!realtimePaused}
onChange={toggleRealtimeSync}
aria-label="实时同步开关"
/>
</Space>
<Badge
status={realtimePaused ? 'default' : realtimeConnected ? 'success' : realtimeReconnecting ? 'warning' : 'error'}
text={
<Text type="secondary" style={{ fontSize: 12 }}>
{realtimePaused
? '已暂停 · 手动刷新'
: realtimeConnected
? '已连接 · 自动刷新'
: realtimeReconnecting
? '重连中…'
: '已离线 · 自动重试'}
</Text>
}
/>
</Space>
</Card>
</Col>
)}
</Row>
<Card size="small">
@@ -2076,13 +2150,6 @@ export default function HuyaCollabPage() {
{/* 第二行:动作(固定在这一行;勾选后加「(N)」只是按钮变宽,不会把列表顶下去) */}
<Space wrap style={{ width: '100%' }}>
<Button icon={<ReloadOutlined />} onClick={() => void load()} disabled={loading}>刷新</Button>
{realtimeEnabled && (
<Tooltip title={realtimeConnected ? '协作台账实时同步已连接' : '实时连接已断开,正在自动重连'}>
<Tag color={realtimeConnected ? 'success' : realtimeReconnecting ? 'warning' : 'default'}>
{realtimeConnected ? '实时同步' : realtimeReconnecting ? '实时重连中' : '实时离线'}
</Tag>
</Tooltip>
)}
{realtimePending && <Tag color="processing">有更新待同步</Tag>}
{canEdit && (
<Button icon={<SafetyCertificateOutlined />} disabled={selectedIds.length === 0 || busy}