feat: 拆分协作台账充值/脚本统计并支持暂停实时同步
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user