From 375567cbd253661249b8672439c0430066bcdaa6 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Mon, 19 Oct 2026 12:27:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8B=86=E5=88=86=E5=8D=8F=E4=BD=9C?= =?UTF-8?q?=E5=8F=B0=E8=B4=A6=E5=85=85=E5=80=BC/=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E5=B9=B6=E6=94=AF=E6=8C=81=E6=9A=82=E5=81=9C?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/frontend/src/pages/HuyaCollabPage.tsx | 103 ++++++++++++++++++---- 1 file changed, 85 insertions(+), 18 deletions(-) diff --git a/web/frontend/src/pages/HuyaCollabPage.tsx b/web/frontend/src/pages/HuyaCollabPage.tsx index 8b67939..68ba97c 100644 --- a/web/frontend/src/pages/HuyaCollabPage.tsx +++ b/web/frontend/src/pages/HuyaCollabPage.tsx @@ -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 = { // 只登记了订单号 + 店铺,客户资料(CK / 礼包 / 主页截图)到了再补 incomplete: { label: '待补资料', color: 'gold' }, @@ -494,6 +505,7 @@ export default function HuyaCollabPage() { const [sortKey, setSortKey] = useState('seq'); const [sortOrder, setSortOrder] = useState('ascend'); const [realtimePending, setRealtimePending] = useState(false); + const [realtimePaused, setRealtimePaused] = useState(readRealtimePaused); const [selectedIds, setSelectedIds] = useState([]); const [detail, setDetail] = useState(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() { - + + + + + @@ -2027,6 +2064,43 @@ export default function HuyaCollabPage() { + {realtimeEnabled && ( + + + + + + 实时同步 + + + + + {realtimePaused + ? '已暂停 · 手动刷新' + : realtimeConnected + ? '已连接 · 自动刷新' + : realtimeReconnecting + ? '重连中…' + : '已离线 · 自动重试'} + + } + /> + + + + )} @@ -2076,13 +2150,6 @@ export default function HuyaCollabPage() { {/* 第二行:动作(固定在这一行;勾选后加「(N)」只是按钮变宽,不会把列表顶下去) */} - {realtimeEnabled && ( - - - {realtimeConnected ? '实时同步' : realtimeReconnecting ? '实时重连中' : '实时离线'} - - - )} {realtimePending && 有更新待同步} {canEdit && (