From d8793db5931088752307e6285a4af0695de85257 Mon Sep 17 00:00:00 2001
From: zhaoxing <2558691827@qq.com>
Date: Fri, 13 Nov 2026 16:19:17 +0800
Subject: [PATCH] =?UTF-8?q?fix(worker):=20=E4=BF=AE=E5=A4=8D=E5=AF=84?=
=?UTF-8?q?=E5=8D=96=E9=A1=B5=E5=88=86=E7=89=87=E5=8A=A0=E8=BD=BD=E5=A4=B1?=
=?UTF-8?q?=E8=B4=A5=E5=AF=BC=E8=87=B4=E7=9A=84=E7=99=BD=E5=B1=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增全局/页面级错误边界,页面渲染异常不再整页白屏
- 监听 vite:preloadError,部署后旧分片加载失败自动刷新一次
- 修复长会话 setTimeout 2^31 溢出导致立即登出
- 寄卖页资料接口失败给出重试态,不再误报未开通
---
.../components/common/AppErrorBoundary.tsx | 107 ++++++++++++++++++
apps/frontend/src/layouts/AdminLayout.tsx | 23 +++-
apps/frontend/src/layouts/WorkerLayout.tsx | 34 +++++-
apps/frontend/src/main.tsx | 17 ++-
.../pages/worker/WorkerConsignmentPage.tsx | 25 ++++
apps/frontend/src/utils/chunk-load-error.ts | 71 ++++++++++++
.../src/utils/session-expiry-timer.ts | 16 +++
apps/frontend/tests/chunk-load-error.test.ts | 71 ++++++++++++
.../tests/session-expiry-timer.test.ts | 34 ++++++
9 files changed, 389 insertions(+), 9 deletions(-)
create mode 100644 apps/frontend/src/components/common/AppErrorBoundary.tsx
create mode 100644 apps/frontend/src/utils/chunk-load-error.ts
create mode 100644 apps/frontend/src/utils/session-expiry-timer.ts
create mode 100644 apps/frontend/tests/chunk-load-error.test.ts
create mode 100644 apps/frontend/tests/session-expiry-timer.test.ts
diff --git a/apps/frontend/src/components/common/AppErrorBoundary.tsx b/apps/frontend/src/components/common/AppErrorBoundary.tsx
new file mode 100644
index 00000000..ebb8e7ee
--- /dev/null
+++ b/apps/frontend/src/components/common/AppErrorBoundary.tsx
@@ -0,0 +1,107 @@
+import { Component, type ErrorInfo, type ReactNode } from 'react'
+import { Button, Result, Space, Typography } from 'antd'
+
+import { isChunkLoadError, shouldAutoReloadChunkFailure } from '@/utils/chunk-load-error'
+import { isWorkerSite } from '@/utils/site'
+
+type AppErrorBoundaryProps = {
+ children: ReactNode
+ /** inline:只占内容区,保留侧边栏/导航;page:整页错误页。 */
+ variant?: 'page' | 'inline'
+ /** 路由变化时自动清除错误,避免一个页面崩溃后整个会话都停在错误页。 */
+ resetKey?: string
+}
+
+type AppErrorBoundaryState = {
+ error: Error | null
+}
+
+/**
+ * 全局兜底错误边界。
+ *
+ * 没有它时,任意一个页面在渲染期抛错(典型场景:部署后点击尚未加载过的路由,
+ * 旧的 lazy chunk 已被新镜像删除,`import()` 直接失败)会让 React 卸载整棵树,
+ * 用户看到的就是没有任何提示的纯白页面。这里统一转成可操作的错误页,并对
+ * 分片加载失败自动刷新一次拿新版本。
+ */
+export default class AppErrorBoundary extends Component<
+ AppErrorBoundaryProps,
+ AppErrorBoundaryState
+> {
+ state: AppErrorBoundaryState = { error: null }
+
+ static getDerivedStateFromError(error: unknown): AppErrorBoundaryState {
+ return { error: error instanceof Error ? error : new Error(String(error)) }
+ }
+
+ componentDidCatch(error: unknown, info: ErrorInfo) {
+ if (isChunkLoadError(error) && typeof window !== 'undefined') {
+ // 旧版本页面引用了已删除的分片:刷新一次即可拿到新 index.html 与新分片。
+ // 防抖窗口内再次失败则不再刷新,落到下面错误页,避免无限刷新。
+ if (shouldAutoReloadChunkFailure(window.sessionStorage)) {
+ window.location.reload()
+ return
+ }
+ }
+
+ console.error('[AppErrorBoundary] 页面渲染失败', error, info.componentStack)
+ }
+
+ componentDidUpdate(prevProps: AppErrorBoundaryProps) {
+ if (this.state.error && prevProps.resetKey !== this.props.resetKey) {
+ this.setState({ error: null })
+ }
+ }
+
+ render() {
+ const { error } = this.state
+ if (!error) return this.props.children
+
+ const chunkFailed = isChunkLoadError(error)
+ const inline = this.props.variant === 'inline'
+ const homePath = isWorkerSite() ? '/hall' : '/'
+
+ return (
+