From aea012fb74dc2741bcf47d2249b681cc5b4a8c0d Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Sun, 6 Sep 2026 13:38:06 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20storage=E4=B8=8D=E5=8F=AF=E7=94=A8?= =?UTF-8?q?=E6=97=B6=E7=94=A8URL=E5=8F=82=E6=95=B0=E4=BD=9C=E9=99=8D?= =?UTF-8?q?=E7=BA=A7=E6=A0=87=E8=AE=B0=EF=BC=8C=E9=98=B2=E6=97=A0=E7=97=95?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E5=88=B7=E6=96=B0=E6=AD=BB=E5=BE=AA=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AI Review 二次意见:Safari 无痕/禁用 Cookie 环境 sessionStorage 读写都抛异常, 标记写不进去,刷新后 getChunkReloadedAt 仍返回 null,会陷入 '检测到chunk失效→刷新→再检测→再刷新'死循环。 - getChunkReloadedAt 双重判定:先查 URL ?chunkreload=1 参数(刷新后仍在), 再查 sessionStorage 标记 - reloadForChunkError 优先用 location.replace 带上 URL 标记跳转, URL 构造失败再退回 window.location.reload - goHomeRecover 跳首页天然清掉 URL 参数 - 补 2 例:URL 参数判定为已刷新;storage 全不可用时走 URL 标记不崩溃 --- .../web/src/test/utils/chunkLoadError.test.ts | 21 ++++++++++++++ apps/web/src/utils/chunkLoadError.ts | 29 ++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/apps/web/src/test/utils/chunkLoadError.test.ts b/apps/web/src/test/utils/chunkLoadError.test.ts index b7a5b9997..83637c2fe 100644 --- a/apps/web/src/test/utils/chunkLoadError.test.ts +++ b/apps/web/src/test/utils/chunkLoadError.test.ts @@ -77,4 +77,25 @@ describe("reload 标记", () => { spy.mockRestore() setSpy.mockRestore() }) + + it("URL 带 chunkreload 参数时视为已刷新过(storage 不可用的降级标记)", () => { + window.history.replaceState({}, "", "/app/assets?chunkreload=1") + expect(getChunkReloadedAt()).not.toBeNull() + window.history.replaceState({}, "", "/") + }) + + it("storage 完全不可用时,reloadForChunkError 走 URL 标记跳转(防无痕死循环)", () => { + const setSpy = vi.spyOn(Storage.prototype, "setItem").mockImplementation(() => { + throw new Error("Storage disabled") + }) + const getSpy = vi.spyOn(Storage.prototype, "getItem").mockImplementation(() => { + throw new Error("Storage disabled") + }) + window.history.replaceState({}, "", "/app/assets") + // reloadForChunkError 应走 location.replace 带上 ?chunkreload=1(jsdom 不会真导航) + expect(() => reloadForChunkError()).not.toThrow() + setSpy.mockRestore() + getSpy.mockRestore() + window.history.replaceState({}, "", "/") + }) }) diff --git a/apps/web/src/utils/chunkLoadError.ts b/apps/web/src/utils/chunkLoadError.ts index 7dce61a66..280d4b4b7 100644 --- a/apps/web/src/utils/chunkLoadError.ts +++ b/apps/web/src/utils/chunkLoadError.ts @@ -57,8 +57,26 @@ export const isChunkLoadError = (error: unknown): boolean => { ) } -/** 读取上次自动刷新时间戳;过期或不存在返回 null */ +/** URL 降级标记参数:storage 不可用(无痕/禁 Cookie)时靠它在刷新后保留"已刷新"状态 */ +const RELOAD_QUERY_KEY = "chunkreload" + +/** 判断当前 URL 是否已带"为 chunk 失效刷新过"标记 */ +const urlHasReloadFlag = (): boolean => { + try { + return new URLSearchParams(window.location.search).has(RELOAD_QUERY_KEY) + } catch { + return false + } +} + +/** + * 读取"已自动刷新过"状态:storage 与 URL 参数双重判定。 + * storage 在 Safari 无痕等环境可能完全不可用,此时刷新后标记丢失会陷入 + * "检测到 chunk 失效 → 刷新 → 再检测 → 再刷新"死循环;URL 参数刷新后仍在, + * 作为降级标记兜住这种环境。 + */ export const getChunkReloadedAt = (): number | null => { + if (urlHasReloadFlag()) return Date.now() const raw = safeStorage.getItem(RELOAD_FLAG_KEY) if (!raw) return null const ts = Number(raw) @@ -70,6 +88,15 @@ export const getChunkReloadedAt = (): number | null => { /** 标记"已为 chunk 失效自动刷新过",然后刷新页面 */ export const reloadForChunkError = (): void => { safeStorage.setItem(RELOAD_FLAG_KEY, String(Date.now())) + // 同时在 URL 上带标记:storage 写不进(无痕模式)时刷新后仍能识别已刷新过 + try { + const url = new URL(window.location.href) + url.searchParams.set(RELOAD_QUERY_KEY, "1") + window.location.replace(url.toString()) + return + } catch { + /* URL 构造失败则退回普通刷新 */ + } window.location.reload() }