/** * 全局错误边界:专门兜底"发版后旧标签页懒加载 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 { 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 ( ) } // 手动兜底统一跳首页(整页导航):chunk 失效时脱离旧 chunk 引用; // 业务崩溃时绕开当前报错路由,避免刷新-再崩死循环 return ( {isChunkError ? "刷新并返回首页" : "返回首页"} } /> ) } } export default ChunkErrorBoundary