9a289e1e1f
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 4s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 5s
CI/CD Pipeline / Check push changed paths (push) Successful in 16s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 12s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 13s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 15s
CI/CD Pipeline / Build Staging API Image (push) Successful in 21s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 22s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 23s
CI/CD Pipeline / CI Gate (pull_request) Successful in 9s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 2m7s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m23s
CI/CD Pipeline / Validate - Style (push) Successful in 2m50s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m37s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m6s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m36s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m31s
CI/CD Pipeline / Validate - Security (push) Successful in 5m21s
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 241h44m7s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 241h46m52s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 241h46m53s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 241h47m15s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 241h47m22s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 241h47m25s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 241h47m26s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 241h47m27s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 241h47m38s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 241h47m27s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 241h47m39s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 241h47m41s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 241h47m39s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 241h47m42s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 241h48m8s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 241h48m9s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 241h48m21s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 241h48m22s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 241h48m22s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Failing after 241h48m23s
CI/CD Pipeline / Validate - Security (pull_request) Failing after 241h48m24s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 241h48m24s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 241h48m25s
CI/CD Pipeline / PR Build Web Image (push) Failing after 241h48m25s
CI/CD Pipeline / Frontend Lint (push) Failing after 241h48m27s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 241h48m41s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 241h48m43s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 242h21m52s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 242h22m16s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 242h22m43s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 242h22m57s
CI/CD Pipeline / PR Build API Image (push) Failing after 242h23m2s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
86 lines
3.0 KiB
TypeScript
86 lines
3.0 KiB
TypeScript
/**
|
||
* 全局错误边界:专门兜底"发版后旧标签页懒加载 chunk 失效"导致的白屏,
|
||
* 同时兜住页面级渲染崩溃,避免任何未捕获错误导致整页白屏无反馈。
|
||
*
|
||
* 捕获到 ChunkLoadError / Failed to fetch dynamically imported module:
|
||
* 1. 首次:自动整页刷新一次(sessionStorage 标记,刷新后 index.html 重新拉取,
|
||
* 拿到新 chunk 引用,白屏自愈)
|
||
* 2. 刷新后仍失败(标记未过期):不再自动刷新,显示"系统已更新,请点击刷新"
|
||
* 兜底界面,由用户手动点击
|
||
*
|
||
* 其他非 chunk 错误:显示通用错误页 + "返回首页"按钮(跳首页而非刷新当前 URL,
|
||
* 避免刷新后再次命中同一路由崩溃形成死循环)。
|
||
*/
|
||
import React from "react"
|
||
import { Button, Result } from "antd"
|
||
import {
|
||
getChunkReloadedAt,
|
||
goHomeRecover,
|
||
isChunkLoadError,
|
||
reloadForChunkError,
|
||
} from "@/utils/chunkLoadError"
|
||
|
||
interface Props {
|
||
children: React.ReactNode
|
||
}
|
||
|
||
interface State {
|
||
error: Error | null
|
||
isChunkError: boolean
|
||
/** 捕获错误时是否已经自动刷新过(决定显示自动刷新中还是手动兜底) */
|
||
alreadyReloaded: boolean
|
||
}
|
||
|
||
class ChunkErrorBoundary extends React.Component<Props, State> {
|
||
state: State = { error: null, isChunkError: false, alreadyReloaded: false }
|
||
|
||
static getDerivedStateFromError(error: Error): State {
|
||
const chunk = isChunkLoadError(error)
|
||
return {
|
||
error,
|
||
isChunkError: chunk,
|
||
alreadyReloaded: chunk ? getChunkReloadedAt() !== null : false,
|
||
}
|
||
}
|
||
|
||
componentDidCatch(error: Error): void {
|
||
// 仅 chunk 错误且本次会话没自动刷新过 → 打标记并整页刷新(自愈)
|
||
if (isChunkLoadError(error) && getChunkReloadedAt() === null) {
|
||
reloadForChunkError()
|
||
}
|
||
}
|
||
|
||
render(): React.ReactNode {
|
||
const { error, isChunkError, alreadyReloaded } = this.state
|
||
if (!error) return this.props.children
|
||
|
||
if (isChunkError && !alreadyReloaded) {
|
||
// 已打标记、componentDidCatch 里已触发 reload;极短瞬间展示加载中
|
||
return (
|
||
<Result status="info" title="系统正在更新" subTitle="检测到新版本,正在自动刷新页面…" />
|
||
)
|
||
}
|
||
|
||
// 手动兜底统一跳首页(整页导航):chunk 失效时脱离旧 chunk 引用;
|
||
// 业务崩溃时绕开当前报错路由,避免刷新-再崩死循环
|
||
return (
|
||
<Result
|
||
status="warning"
|
||
title={isChunkError ? "系统已更新" : "页面出现异常"}
|
||
subTitle={
|
||
isChunkError
|
||
? "检测到新版本,请点击下方按钮回到首页加载最新内容。"
|
||
: "页面加载遇到问题,点击返回首页通常可以恢复,未保存的内容可能丢失。"
|
||
}
|
||
extra={
|
||
<Button type="primary" onClick={goHomeRecover}>
|
||
{isChunkError ? "刷新并返回首页" : "返回首页"}
|
||
</Button>
|
||
}
|
||
/>
|
||
)
|
||
}
|
||
}
|
||
|
||
export default ChunkErrorBoundary
|