fix: 懒加载chunk失败时自动刷新页面
部署后旧chunk文件名(带hash)被删除,浏览器若缓存了旧index.html 仍会请求旧chunk导致 "Failed to fetch dynamically imported module"。 新增 lazyRetry 包装器,检测到此错误时自动刷新页面获取新index.html。 用 sessionStorage 标记防止无限刷新。 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
+55
-54
@@ -1,4 +1,4 @@
|
|||||||
import { lazy, Suspense, useState, useEffect } from 'react'
|
import { Suspense, useState, useEffect } from 'react'
|
||||||
import { Routes, Route, Navigate } from 'react-router-dom'
|
import { Routes, Route, Navigate } from 'react-router-dom'
|
||||||
import { Toaster } from 'sonner'
|
import { Toaster } from 'sonner'
|
||||||
import { useAuthStore } from './store/authStore'
|
import { useAuthStore } from './store/authStore'
|
||||||
@@ -11,64 +11,65 @@ import PageContainer from './components/layout/PageContainer'
|
|||||||
import { CommandPalette } from './components/ui/CommandPalette'
|
import { CommandPalette } from './components/ui/CommandPalette'
|
||||||
import { SkeletonPage } from './components/ui/Skeleton'
|
import { SkeletonPage } from './components/ui/Skeleton'
|
||||||
import ErrorBoundary from './components/ui/ErrorBoundary'
|
import ErrorBoundary from './components/ui/ErrorBoundary'
|
||||||
|
import { lazyRetry } from './lib/lazyRetry'
|
||||||
|
|
||||||
const Login = lazy(() => import('./pages/auth/Login'))
|
const Login = lazyRetry(() => import('./pages/auth/Login'))
|
||||||
const Register = lazy(() => import('./pages/auth/Register'))
|
const Register = lazyRetry(() => import('./pages/auth/Register'))
|
||||||
const ForgotPassword = lazy(() => import('./pages/auth/ForgotPassword'))
|
const ForgotPassword = lazyRetry(() => import('./pages/auth/ForgotPassword'))
|
||||||
const Dashboard = lazy(() => import('./pages/Dashboard'))
|
const Dashboard = lazyRetry(() => import('./pages/Dashboard'))
|
||||||
const Money = lazy(() => import('./pages/Money'))
|
const Money = lazyRetry(() => import('./pages/Money'))
|
||||||
const SocialInsurance = lazy(() => import('./pages/SocialInsurance'))
|
const SocialInsurance = lazyRetry(() => import('./pages/SocialInsurance'))
|
||||||
const Roster = lazy(() => import('./pages/Roster'))
|
const Roster = lazyRetry(() => import('./pages/Roster'))
|
||||||
const OrgChart = lazy(() => import('./pages/OrgChart'))
|
const OrgChart = lazyRetry(() => import('./pages/OrgChart'))
|
||||||
const SupportDashboard = lazy(() => import('./pages/support/SupportDashboard'))
|
const SupportDashboard = lazyRetry(() => import('./pages/support/SupportDashboard'))
|
||||||
const Termination = lazy(() => import('./pages/Termination'))
|
const Termination = lazyRetry(() => import('./pages/Termination'))
|
||||||
const AIAssistant = lazy(() => import('./pages/AIAssistant'))
|
const AIAssistant = lazyRetry(() => import('./pages/AIAssistant'))
|
||||||
const Settings = lazy(() => import('./pages/Settings'))
|
const Settings = lazyRetry(() => import('./pages/Settings'))
|
||||||
const Evidence = lazy(() => import('./pages/Evidence'))
|
const Evidence = lazyRetry(() => import('./pages/Evidence'))
|
||||||
const Policies = lazy(() => import('./pages/Policies'))
|
const Policies = lazyRetry(() => import('./pages/Policies'))
|
||||||
const Attendance = lazy(() => import('./pages/Attendance'))
|
const Attendance = lazyRetry(() => import('./pages/Attendance'))
|
||||||
const Templates = lazy(() => import('./pages/Templates'))
|
const Templates = lazyRetry(() => import('./pages/Templates'))
|
||||||
const AuditLog = lazy(() => import('./pages/AuditLog'))
|
const AuditLog = lazyRetry(() => import('./pages/AuditLog'))
|
||||||
const Notifications = lazy(() => import('./pages/Notifications'))
|
const Notifications = lazyRetry(() => import('./pages/Notifications'))
|
||||||
const PortalLogin = lazy(() => import('./pages/portal/PortalLogin'))
|
const PortalLogin = lazyRetry(() => import('./pages/portal/PortalLogin'))
|
||||||
const Payslip = lazy(() => import('./pages/portal/Payslip'))
|
const Payslip = lazyRetry(() => import('./pages/portal/Payslip'))
|
||||||
const MyContract = lazy(() => import('./pages/portal/MyContract'))
|
const MyContract = lazyRetry(() => import('./pages/portal/MyContract'))
|
||||||
const Onboarding = lazy(() => import('./pages/portal/Onboarding'))
|
const Onboarding = lazyRetry(() => import('./pages/portal/Onboarding'))
|
||||||
const ContractConfirm = lazy(() => import('./pages/portal/ContractConfirm'))
|
const ContractConfirm = lazyRetry(() => import('./pages/portal/ContractConfirm'))
|
||||||
const MyPolicies = lazy(() => import('./pages/portal/MyPolicies'))
|
const MyPolicies = lazyRetry(() => import('./pages/portal/MyPolicies'))
|
||||||
const AutoLogin = lazy(() => import('./pages/portal/AutoLogin'))
|
const AutoLogin = lazyRetry(() => import('./pages/portal/AutoLogin'))
|
||||||
const MedicalPeriodCalculator = lazy(() => import('./pages/tools/MedicalPeriodCalculator'))
|
const MedicalPeriodCalculator = lazyRetry(() => import('./pages/tools/MedicalPeriodCalculator'))
|
||||||
const HealthCheck = lazy(() => import('./pages/tools/HealthCheck'))
|
const HealthCheck = lazyRetry(() => import('./pages/tools/HealthCheck'))
|
||||||
const AnnualValueReport = lazy(() => import('./pages/tools/AnnualValueReport'))
|
const AnnualValueReport = lazyRetry(() => import('./pages/tools/AnnualValueReport'))
|
||||||
const CalendarPage = lazy(() => import('./pages/Calendar'))
|
const CalendarPage = lazyRetry(() => import('./pages/Calendar'))
|
||||||
const MyAttendance = lazy(() => import('./pages/portal/MyAttendance'))
|
const MyAttendance = lazyRetry(() => import('./pages/portal/MyAttendance'))
|
||||||
const MyLeave = lazy(() => import('./pages/portal/MyLeave'))
|
const MyLeave = lazyRetry(() => import('./pages/portal/MyLeave'))
|
||||||
const SpecialStatus = lazy(() => import('./pages/SpecialStatus'))
|
const SpecialStatus = lazyRetry(() => import('./pages/SpecialStatus'))
|
||||||
const CompanyFiles = lazy(() => import('./pages/CompanyFiles'))
|
const CompanyFiles = lazyRetry(() => import('./pages/CompanyFiles'))
|
||||||
const LeaveApproval = lazy(() => import('./pages/LeaveApproval'))
|
const LeaveApproval = lazyRetry(() => import('./pages/LeaveApproval'))
|
||||||
const TrainingRecords = lazy(() => import('./pages/roster/TrainingRecords'))
|
const TrainingRecords = lazyRetry(() => import('./pages/roster/TrainingRecords'))
|
||||||
const PerformanceRecords = lazy(() => import('./pages/roster/PerformanceRecords'))
|
const PerformanceRecords = lazyRetry(() => import('./pages/roster/PerformanceRecords'))
|
||||||
const DisciplinaryRecords = lazy(() => import('./pages/roster/DisciplinaryRecords'))
|
const DisciplinaryRecords = lazyRetry(() => import('./pages/roster/DisciplinaryRecords'))
|
||||||
|
|
||||||
// Sprint 4-5 新增页面
|
// Sprint 4-5 新增页面
|
||||||
const EmployeeHome = lazy(() => import('./pages/portal/EmployeeHome'))
|
const EmployeeHome = lazyRetry(() => import('./pages/portal/EmployeeHome'))
|
||||||
const OnboardingProgress = lazy(() => import('./pages/portal/OnboardingProgress'))
|
const OnboardingProgress = lazyRetry(() => import('./pages/portal/OnboardingProgress'))
|
||||||
const ResignationApply = lazy(() => import('./pages/portal/ResignationApply'))
|
const ResignationApply = lazyRetry(() => import('./pages/portal/ResignationApply'))
|
||||||
const MyEsign = lazy(() => import('./pages/portal/MyEsign'))
|
const MyEsign = lazyRetry(() => import('./pages/portal/MyEsign'))
|
||||||
const MyRecords = lazy(() => import('./pages/portal/MyRecords'))
|
const MyRecords = lazyRetry(() => import('./pages/portal/MyRecords'))
|
||||||
const RiskCenter = lazy(() => import('./pages/compliance/RiskCenter'))
|
const RiskCenter = lazyRetry(() => import('./pages/compliance/RiskCenter'))
|
||||||
const SalaryDashboard = lazy(() => import('./pages/SalaryDashboard'))
|
const SalaryDashboard = lazyRetry(() => import('./pages/SalaryDashboard'))
|
||||||
const CommercialInsurance = lazy(() => import('./pages/CommercialInsurance'))
|
const CommercialInsurance = lazyRetry(() => import('./pages/CommercialInsurance'))
|
||||||
const EmployeeBenefits = lazy(() => import('./pages/EmployeeBenefits'))
|
const EmployeeBenefits = lazyRetry(() => import('./pages/EmployeeBenefits'))
|
||||||
const ESign = lazy(() => import('./pages/ESign'))
|
const ESign = lazyRetry(() => import('./pages/ESign'))
|
||||||
const CommissionBonus = lazy(() => import('./pages/CommissionBonus'))
|
const CommissionBonus = lazyRetry(() => import('./pages/CommissionBonus'))
|
||||||
|
|
||||||
// 平台管理端
|
// 平台管理端
|
||||||
const PlatformLogin = lazy(() => import('./pages/platform/PlatformLogin'))
|
const PlatformLogin = lazyRetry(() => import('./pages/platform/PlatformLogin'))
|
||||||
const PlatformDashboard = lazy(() => import('./pages/platform/PlatformDashboard'))
|
const PlatformDashboard = lazyRetry(() => import('./pages/platform/PlatformDashboard'))
|
||||||
const PlatformOrgs = lazy(() => import('./pages/platform/PlatformOrgs'))
|
const PlatformOrgs = lazyRetry(() => import('./pages/platform/PlatformOrgs'))
|
||||||
const PlatformUsers = lazy(() => import('./pages/platform/PlatformUsers'))
|
const PlatformUsers = lazyRetry(() => import('./pages/platform/PlatformUsers'))
|
||||||
const PlatformSidebar = lazy(() => import('./components/layout/PlatformSidebar'))
|
const PlatformSidebar = lazyRetry(() => import('./components/layout/PlatformSidebar'))
|
||||||
|
|
||||||
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
||||||
const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
|
const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { lazy, ComponentType } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带自动重试的懒加载。
|
||||||
|
*
|
||||||
|
* 部署后旧 chunk 文件名(带 hash)被删除,浏览器若缓存了旧 index.html
|
||||||
|
* 仍会请求旧 chunk 导致 "Failed to fetch dynamically imported module"。
|
||||||
|
* 此时自动刷新页面,让浏览器获取新 index.html 中的新 chunk 引用。
|
||||||
|
*/
|
||||||
|
export function lazyRetry<T extends ComponentType<any>>(
|
||||||
|
factory: () => Promise<{ default: T } | { [key: string]: T }>,
|
||||||
|
): React.LazyExoticComponent<T> {
|
||||||
|
return lazy(async () => {
|
||||||
|
try {
|
||||||
|
const m = await factory()
|
||||||
|
// 统一为 { default: T } 格式
|
||||||
|
if ('default' in m) return { default: m.default }
|
||||||
|
// named export 模式:取第一个导出作为 default
|
||||||
|
const firstKey = Object.keys(m)[0]
|
||||||
|
return { default: m[firstKey] }
|
||||||
|
} catch (err: any) {
|
||||||
|
// 仅对动态导入失败自动刷新,避免其他错误误触发
|
||||||
|
if (
|
||||||
|
err &&
|
||||||
|
(err.message?.includes('Failed to fetch dynamically imported module') ||
|
||||||
|
err.message?.includes('Importing a module script failed') ||
|
||||||
|
err.message?.includes('error loading dynamically imported module'))
|
||||||
|
) {
|
||||||
|
// 防止无限刷新:用 sessionStorage 标记,仅刷新一次
|
||||||
|
const key = 'lazyRetry:' + (err.message.match(/[\w-]+\.js/)?.[0] || 'unknown')
|
||||||
|
if (!sessionStorage.getItem(key)) {
|
||||||
|
sessionStorage.setItem(key, '1')
|
||||||
|
window.location.reload()
|
||||||
|
// 返回一个永不 resolve 的 Promise,等待页面刷新
|
||||||
|
return new Promise<{ default: T }>(() => {})
|
||||||
|
}
|
||||||
|
sessionStorage.removeItem(key)
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import { useState, lazy, Suspense } from 'react'
|
import { useState, Suspense } from 'react'
|
||||||
import { Bot, FileSearch, Scale, Sparkles, BookOpen, TrendingUp, Loader2 } from 'lucide-react'
|
import { Bot, FileSearch, Scale, Sparkles, BookOpen, TrendingUp, Loader2 } from 'lucide-react'
|
||||||
import PageGuide from '../components/ui/PageGuide'
|
import PageGuide from '../components/ui/PageGuide'
|
||||||
|
import { lazyRetry } from '../lib/lazyRetry'
|
||||||
|
|
||||||
const ChatTab = lazy(() => import('./ai-assistant/ChatTab').then(m => ({ default: m.ChatTab })))
|
const ChatTab = lazyRetry(() => import('./ai-assistant/ChatTab').then(m => ({ default: m.ChatTab })))
|
||||||
const PredictTab = lazy(() => import('./ai-assistant/PredictTab').then(m => ({ default: m.PredictTab })))
|
const PredictTab = lazyRetry(() => import('./ai-assistant/PredictTab').then(m => ({ default: m.PredictTab })))
|
||||||
const ReviewTab = lazy(() => import('./ai-assistant/ReviewTab').then(m => ({ default: m.ReviewTab })))
|
const ReviewTab = lazyRetry(() => import('./ai-assistant/ReviewTab').then(m => ({ default: m.ReviewTab })))
|
||||||
const CaseTab = lazy(() => import('./ai-assistant/CaseTab').then(m => ({ default: m.CaseTab })))
|
const CaseTab = lazyRetry(() => import('./ai-assistant/CaseTab').then(m => ({ default: m.CaseTab })))
|
||||||
const KnowledgeTab = lazy(() => import('./ai-assistant/KnowledgeTab').then(m => ({ default: m.KnowledgeTab })))
|
const KnowledgeTab = lazyRetry(() => import('./ai-assistant/KnowledgeTab').then(m => ({ default: m.KnowledgeTab })))
|
||||||
const HRReportTab = lazy(() => import('./ai-assistant/HRReportTab').then(m => ({ default: m.HRReportTab })))
|
const HRReportTab = lazyRetry(() => import('./ai-assistant/HRReportTab').then(m => ({ default: m.HRReportTab })))
|
||||||
|
|
||||||
type Tab = 'chat' | 'predict' | 'review' | 'case' | 'knowledge' | 'hr-report'
|
type Tab = 'chat' | 'predict' | 'review' | 'case' | 'knowledge' | 'hr-report'
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { useState, lazy, Suspense } from 'react'
|
import { useState, Suspense } from 'react'
|
||||||
import { useSearchParams } from 'react-router-dom'
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import { Layers, Wallet, LayoutTemplate, Clock, Receipt, Loader2 } from 'lucide-react'
|
import { Layers, Wallet, LayoutTemplate, Clock, Receipt, Loader2 } from 'lucide-react'
|
||||||
import PageGuide from '../components/ui/PageGuide'
|
import PageGuide from '../components/ui/PageGuide'
|
||||||
|
import { lazyRetry } from '../lib/lazyRetry'
|
||||||
|
|
||||||
const BatchManager = lazy(() => import('./money/BatchTab').then(m => ({ default: m.BatchManager })))
|
const BatchManager = lazyRetry(() => import('./money/BatchTab').then(m => ({ default: m.BatchManager })))
|
||||||
const TemplateManager = lazy(() => import('./money/TemplateTab').then(m => ({ default: m.TemplateManager })))
|
const TemplateManager = lazyRetry(() => import('./money/TemplateTab').then(m => ({ default: m.TemplateManager })))
|
||||||
const OvertimeCalculator = lazy(() => import('./money/OvertimeTab').then(m => ({ default: m.OvertimeCalculator })))
|
const OvertimeCalculator = lazyRetry(() => import('./money/OvertimeTab').then(m => ({ default: m.OvertimeCalculator })))
|
||||||
const PayslipManager = lazy(() => import('./money/PayslipTab').then(m => ({ default: m.PayslipManager })))
|
const PayslipManager = lazyRetry(() => import('./money/PayslipTab').then(m => ({ default: m.PayslipManager })))
|
||||||
|
|
||||||
type Tab = 'batch' | 'template' | 'overtime' | 'payslip'
|
type Tab = 'batch' | 'template' | 'overtime' | 'payslip'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user