From 8f12a6cf6574196af9d03747d8d3c86784acb259 Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Wed, 9 Sep 2026 16:33:07 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=83=A8=E7=BD=B2=E5=90=8E=20chunk=2040?= =?UTF-8?q?4=20=E8=87=AA=E5=8A=A8=E5=88=B7=E6=96=B0=EF=BC=8CSPA=20index=20?= =?UTF-8?q?=E7=A6=81=E6=AD=A2=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前端检测 lazy import 失败自动 reload;nginx @spa 回退统一 no-cache。 --- nginx-k3s.conf | 164 ++++++++++++++++++++------------------- src/main.ts | 6 ++ src/utils/chunkReload.ts | 42 ++++++++++ 3 files changed, 134 insertions(+), 78 deletions(-) create mode 100644 src/utils/chunkReload.ts diff --git a/nginx-k3s.conf b/nginx-k3s.conf index 6646a5d..c153901 100644 --- a/nginx-k3s.conf +++ b/nginx-k3s.conf @@ -55,109 +55,118 @@ server { } location = /login { - try_files /index.html =200; + try_files $uri @spa; } location = /login/ { - try_files /index.html =200; + try_files $uri @spa; } rewrite ^/admin/?$ /index.html last; location = /admin { - try_files /index.html =200; + try_files $uri @spa; } location = /admin/ { - try_files /index.html =200; + try_files $uri @spa; } location ~ ^/api/(upstreamInterface|upstreamInterfaceConfig|upstreamInterfaceConfigEdit|downstreamInterface(|/newPort)|appList(|/createApplication)|requestLog|callStatistics|apiDebugger|apiTest)(/|$) { - try_files /index.html =200; + try_files $uri @spa; } - location ^~ /xhs/startService { try_files /index.html =200; } - location ^~ /xhs/creation { try_files /index.html =200; } - location ^~ /xhs/publishHistory { try_files /index.html =200; } - location ^~ /xhs/skills { try_files /index.html =200; } - location ^~ /xhs/humanSettings { try_files /index.html =200; } - location ^~ /xhs/agentPair { try_files /index.html =200; } - location = /xhs/analysis { try_files /index.html =200; } - location = /xhs/analysis/ { try_files /index.html =200; } + location ^~ /xhs/startService { try_files $uri @spa; } + location ^~ /xhs/creation { try_files $uri @spa; } + location ^~ /xhs/publishHistory { try_files $uri @spa; } + location ^~ /xhs/skills { try_files $uri @spa; } + location ^~ /xhs/humanSettings { try_files $uri @spa; } + location ^~ /xhs/agentPair { try_files $uri @spa; } + location = /xhs/analysis { try_files $uri @spa; } + location = /xhs/analysis/ { try_files $uri @spa; } - location ^~ /wechat/register { try_files /index.html =200; } - location ^~ /wechat/bind { try_files /index.html =200; } - location ^~ /h5 { try_files /index.html =200; } - location ^~ /track/query { try_files /index.html =200; } - location ^~ /operation/inboundScan { try_files /index.html =200; } - location ^~ /operation/outboundScan { try_files /index.html =200; } - location ^~ /operation/printLabel { try_files /index.html =200; } - location ^~ /operation/parcelArrival { try_files /index.html =200; } - location ^~ /operation/fulfillPack { try_files /index.html =200; } + location ^~ /wechat/register { try_files $uri @spa; } + location ^~ /wechat/bind { try_files $uri @spa; } + location ^~ /h5 { try_files $uri @spa; } + location ^~ /track/query { try_files $uri @spa; } + location ^~ /operation/inboundScan { try_files $uri @spa; } + location ^~ /operation/outboundScan { try_files $uri @spa; } + location ^~ /operation/printLabel { try_files $uri @spa; } + location ^~ /operation/parcelArrival { try_files $uri @spa; } + location ^~ /operation/fulfillPack { try_files $uri @spa; } # 订单前端路由(^~ 须比下方 /order/ API 正则代理更优先) - location ^~ /order/priceCheck { try_files /index.html =200; } - location ^~ /order/placeOrder { try_files /index.html =200; } - location ^~ /order/myOrders { try_files /index.html =200; } - location ^~ /order/fulfillOrders { try_files /index.html =200; } - location ^~ /order/waybillManage { try_files /index.html =200; } - location ^~ /order/pickupOrder { try_files /index.html =200; } - location ^~ /order/customs9610Order { try_files /index.html =200; } - location ^~ /order/batchJobs { try_files /index.html =200; } - location ^~ /order/templateMapping { try_files /index.html =200; } - location ^~ /order/platformStore { try_files /index.html =200; } - location ^~ /order/platformStoreOrder { try_files /index.html =200; } + location ^~ /order/priceCheck { try_files $uri @spa; } + location ^~ /order/placeOrder { try_files $uri @spa; } + location ^~ /order/myOrders { try_files $uri @spa; } + location ^~ /order/fulfillOrders { try_files $uri @spa; } + location ^~ /order/waybillManage { try_files $uri @spa; } + location ^~ /order/pickupOrder { try_files $uri @spa; } + location ^~ /order/customs9610Order { try_files $uri @spa; } + location ^~ /order/batchJobs { try_files $uri @spa; } + location ^~ /order/templateMapping { try_files $uri @spa; } + location ^~ /order/platformStore { try_files $uri @spa; } + location ^~ /order/platformStoreOrder { try_files $uri @spa; } # 知识库前端路由(^~ 须比下方 /kb/ API 代理更优先) - location ^~ /kb/category { try_files /index.html =200; } - location ^~ /kb/article { try_files /index.html =200; } + location ^~ /kb/category { try_files $uri @spa; } + location ^~ /kb/article { try_files $uri @spa; } - location = /warehouse/parcel { try_files /index.html =200; } - location = /warehouse/inventory { try_files /index.html =200; } - location = /warehouse/sku { try_files /index.html =200; } - location = /warehouse/binLocation { try_files /index.html =200; } - location ^~ /warehouse/inboundNotice { try_files /index.html =200; } - location ^~ /Plugin/ { try_files /index.html =200; } + location = /warehouse/parcel { try_files $uri @spa; } + location = /warehouse/inventory { try_files $uri @spa; } + location = /warehouse/sku { try_files $uri @spa; } + location = /warehouse/binLocation { try_files $uri @spa; } + location ^~ /warehouse/inboundNotice { try_files $uri @spa; } + location ^~ /Plugin/ { try_files $uri @spa; } - location ^~ /channel/salesProducts { try_files /index.html =200; } - location ^~ /channel/serviceChannels { try_files /index.html =200; } - location ^~ /channel/priceArea { try_files /index.html =200; } - location ^~ /channel/blacklist { try_files /index.html =200; } - location ^~ /channel/remoteZones { try_files /index.html =200; } - location ^~ /channel/weightRules { try_files /index.html =200; } - location ^~ /channel/weightRounding { try_files /index.html =200; } - location ^~ /channel/autoTrigger { try_files /index.html =200; } - location ^~ /channel/issueTemplate { try_files /index.html =200; } - location ^~ /channel/labelDesign { try_files /index.html =200; } + location ^~ /channel/salesProducts { try_files $uri @spa; } + location ^~ /channel/serviceChannels { try_files $uri @spa; } + location ^~ /channel/priceArea { try_files $uri @spa; } + location ^~ /channel/blacklist { try_files $uri @spa; } + location ^~ /channel/remoteZones { try_files $uri @spa; } + location ^~ /channel/weightRules { try_files $uri @spa; } + location ^~ /channel/weightRounding { try_files $uri @spa; } + location ^~ /channel/autoTrigger { try_files $uri @spa; } + location ^~ /channel/issueTemplate { try_files $uri @spa; } + location ^~ /channel/labelDesign { try_files $uri @spa; } # 营销前端路由(^~ 须比下方 /marketing/ API 正则代理更优先) - location ^~ /marketing/memberLevel { try_files /index.html =200; } - location ^~ /marketing/pointsRule { try_files /index.html =200; } - location ^~ /marketing/pointsFlow { try_files /index.html =200; } - location ^~ /marketing/couponTemplate { try_files /index.html =200; } - location ^~ /marketing/couponGrant { try_files /index.html =200; } - location ^~ /marketing/commissionRule { try_files /index.html =200; } - location ^~ /marketing/commissionDetail { try_files /index.html =200; } - location ^~ /marketing/commissionWithdraw { try_files /index.html =200; } - location ^~ /marketing/referralTree { try_files /index.html =200; } + location ^~ /marketing/memberLevel { try_files $uri @spa; } + location ^~ /marketing/pointsRule { try_files $uri @spa; } + location ^~ /marketing/pointsFlow { try_files $uri @spa; } + location ^~ /marketing/couponTemplate { try_files $uri @spa; } + location ^~ /marketing/couponGrant { try_files $uri @spa; } + location ^~ /marketing/commissionRule { try_files $uri @spa; } + location ^~ /marketing/commissionDetail { try_files $uri @spa; } + location ^~ /marketing/commissionWithdraw { try_files $uri @spa; } + location ^~ /marketing/referralTree { try_files $uri @spa; } # 系统管理前端路由(^~ 须比下方 /system/ API 正则代理更优先) - location = /system/dict { try_files /index.html =200; } - location = /system/warehouseAddress { try_files /index.html =200; } - location = /system/announcement { try_files /index.html =200; } - location ^~ /system/users { try_files /index.html =200; } - location ^~ /system/roles { try_files /index.html =200; } - location ^~ /system/menus { try_files /index.html =200; } - location ^~ /system/organizations { try_files /index.html =200; } - location ^~ /system/batchEmail { try_files /index.html =200; } - location ^~ /system/otherConfig { try_files /index.html =200; } - location ^~ /system/aiAssistant { try_files /index.html =200; } - location ^~ /system/aiSkills { try_files /index.html =200; } - location ^~ /system/mcpTools { try_files /index.html =200; } - location ^~ /system/dataPerm/ { try_files /index.html =200; } + location = /system/dict { try_files $uri @spa; } + location = /system/warehouseAddress { try_files $uri @spa; } + location = /system/announcement { try_files $uri @spa; } + location ^~ /system/users { try_files $uri @spa; } + location ^~ /system/roles { try_files $uri @spa; } + location ^~ /system/menus { try_files $uri @spa; } + location ^~ /system/organizations { try_files $uri @spa; } + location ^~ /system/batchEmail { try_files $uri @spa; } + location ^~ /system/otherConfig { try_files $uri @spa; } + location ^~ /system/aiAssistant { try_files $uri @spa; } + location ^~ /system/aiSkills { try_files $uri @spa; } + location ^~ /system/mcpTools { try_files $uri @spa; } + location ^~ /system/dataPerm/ { try_files $uri @spa; } + + # SPA 回退:try_files $uri @spa 统一带 no-cache,避免部署后浏览器缓存旧 index.html + location @spa { + add_header Cache-Control "no-cache, no-store, must-revalidate" always; + add_header Pragma "no-cache" always; + add_header Expires "0" always; + try_files /index.html =404; + } location = /index.html { - add_header Cache-Control "no-cache, no-store, must-revalidate"; - add_header Pragma "no-cache"; - add_header Expires "0"; + add_header Cache-Control "no-cache, no-store, must-revalidate" always; + add_header Pragma "no-cache" always; + add_header Expires "0" always; + try_files /index.html =404; } # 微信 JS 安全域名校验:原样返回 txt,禁止 SPA 回落 index.html @@ -176,7 +185,6 @@ server { } location / { - index index.html; - try_files $uri /index.html; + try_files $uri @spa; } } diff --git a/src/main.ts b/src/main.ts index fb5e954..56abfb1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,6 +14,7 @@ import { } from './plugins/heavy-plugins'; import { initAuthKeepAlive } from '@/utils/auth-keepalive'; import { installWeChatH5HistoryGuard } from '@/utils/device'; +import { installChunkReloadHandlers, reloadOnChunkLoadError } from '@/utils/chunkReload'; import { setAuthToken } from './api/rbac'; import { useRbacUserStore } from '@/store/modules/rbac-user'; import { applyRbacUserToUserStore } from '@/utils/enter-system'; @@ -75,6 +76,11 @@ const app = createApp(App); app.use(ArcoVue, {}); app.use(ArcoVueIcon); app.use(router); +installChunkReloadHandlers(); +router.onError((error) => { + if (reloadOnChunkLoadError(error)) return; + throw error; +}); app.use(store); useAppStore().hydrateTheme(); app.use(i18n); diff --git a/src/utils/chunkReload.ts b/src/utils/chunkReload.ts new file mode 100644 index 0000000..48d57df --- /dev/null +++ b/src/utils/chunkReload.ts @@ -0,0 +1,42 @@ +/** 部署后旧 chunk 404 时自动刷新,避免 lazy route 白屏 */ + +const RELOAD_KEY = 'oms_chunk_reload_ts'; +const RELOAD_COOLDOWN_MS = 15_000; + +export function isChunkLoadError(error: unknown): boolean { + const msg = error instanceof Error ? error.message : String(error ?? ''); + return ( + /Failed to fetch dynamically imported module/i.test(msg) || + /Importing a module script failed/i.test(msg) || + /Loading chunk [\da-zA-Z_-]+ failed/i.test(msg) || + /ChunkLoadError/i.test(msg) + ); +} + +/** 检测到 chunk 加载失败则刷新页面;10s 内仅刷新一次,防止死循环 */ +export function reloadOnChunkLoadError(error: unknown): boolean { + if (typeof window === 'undefined' || !isChunkLoadError(error)) { + return false; + } + try { + const last = Number(sessionStorage.getItem(RELOAD_KEY) || 0); + const now = Date.now(); + if (now - last < RELOAD_COOLDOWN_MS) { + return false; + } + sessionStorage.setItem(RELOAD_KEY, String(now)); + } catch { + /* ignore */ + } + window.location.reload(); + return true; +} + +export function installChunkReloadHandlers(): void { + if (typeof window === 'undefined') return; + window.addEventListener('unhandledrejection', (event) => { + if (reloadOnChunkLoadError(event.reason)) { + event.preventDefault(); + } + }); +}