From 3e34b65bcb6c4307d70562041254ac7a819581d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AD=90=E9=BB=98?= <925456043@qq.com> Date: Mon, 31 Aug 2026 18:47:54 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20Hash=20=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E8=B7=B3=E8=BD=AC=E4=B8=A2=E5=A4=B1=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E5=9F=BA=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复懒加载分包失败后跳转到站点根路径的问题 - 统一管理端和用户中心新窗口路由解析 - 增加部署基路径与 Hash 路由回归测试 --- .../__tests__/chunk-error-reload.test.ts | 26 +++++++++++++++++++ easyflow-ui-admin/app/src/router/guard.ts | 8 ++++-- easyflow-ui-admin/package.json | 2 +- .../shared/src/utils/__tests__/window.test.ts | 18 ++++++++++++- .../@core/base/shared/src/utils/window.ts | 9 +++---- .../packages/effects/hooks/src/use-tabs.ts | 2 +- .../packages/stores/src/modules/tabbar.ts | 6 +++-- .../shared/src/utils/__tests__/window.test.ts | 18 ++++++++++++- .../@core/base/shared/src/utils/window.ts | 9 +++---- .../packages/effects/hooks/src/use-tabs.ts | 2 +- .../packages/stores/src/modules/tabbar.ts | 6 +++-- 11 files changed, 83 insertions(+), 23 deletions(-) create mode 100644 easyflow-ui-admin/app/src/router/__tests__/chunk-error-reload.test.ts diff --git a/easyflow-ui-admin/app/src/router/__tests__/chunk-error-reload.test.ts b/easyflow-ui-admin/app/src/router/__tests__/chunk-error-reload.test.ts new file mode 100644 index 00000000..71332730 --- /dev/null +++ b/easyflow-ui-admin/app/src/router/__tests__/chunk-error-reload.test.ts @@ -0,0 +1,26 @@ +import { createRouter, createWebHashHistory } from 'vue-router'; + +import { describe, expect, it } from 'vitest'; + +import { resolveChunkReloadHref } from '../guard'; + +describe('chunk error reload', () => { + it('preserves the deployment base and hash route', () => { + const router = createRouter({ + history: createWebHashHistory('/flow/'), + routes: [ + { + component: { template: '
workflow
' }, + path: '/ai/workflow', + }, + ], + }); + + const href = resolveChunkReloadHref(router, '/ai/workflow?source=sidebar'); + + expect(href).toBe('#/ai/workflow?source=sidebar'); + expect(new URL(href, 'https://easyflowtech.cn/flow/').href).toBe( + 'https://easyflowtech.cn/flow/#/ai/workflow?source=sidebar', + ); + }); +}); diff --git a/easyflow-ui-admin/app/src/router/guard.ts b/easyflow-ui-admin/app/src/router/guard.ts index edc56ce3..b96ec685 100644 --- a/easyflow-ui-admin/app/src/router/guard.ts +++ b/easyflow-ui-admin/app/src/router/guard.ts @@ -71,6 +71,10 @@ function isDynamicImportChunkError(error: unknown) { ); } +function resolveChunkReloadHref(router: Router, fullPath: string) { + return router.resolve(fullPath).href; +} + function setupChunkErrorGuard(router: Router) { router.onError((error, to) => { if (!isDynamicImportChunkError(error)) { @@ -94,7 +98,7 @@ function setupChunkErrorGuard(router: Router) { return; } sessionStorage.setItem(CHUNK_ERROR_RELOAD_KEY, fullPath); - window.location.assign(fullPath); + window.location.assign(resolveChunkReloadHref(router, fullPath)); }); router.isReady().finally(() => { @@ -325,4 +329,4 @@ function createRouterGuard(router: Router) { setupChunkErrorGuard(router); } -export { createRouterGuard }; +export { createRouterGuard, resolveChunkReloadHref }; diff --git a/easyflow-ui-admin/package.json b/easyflow-ui-admin/package.json index f0b3309d..df999b97 100644 --- a/easyflow-ui-admin/package.json +++ b/easyflow-ui-admin/package.json @@ -30,7 +30,7 @@ "preview": "turbo-run preview", "publint": "vsh publint", "reinstall": "pnpm clean --del-lock && pnpm install", - "test:deployment-contract": "vitest run --dom app/vite-base-path-redirect.test.ts app/src/startup-error.test.ts app/src/router/__tests__/environment-contract.test.ts app/src/router/navigation-user-info.test.ts app/src/utils/share-route-context.test.ts app/src/utils/__tests__/login-redirect.test.ts app/src/views/ai/workflow/workflow-share-context.test.ts", + "test:deployment-contract": "vitest run --dom app/vite-base-path-redirect.test.ts app/src/startup-error.test.ts app/src/router/__tests__/environment-contract.test.ts app/src/router/__tests__/chunk-error-reload.test.ts app/src/router/navigation-user-info.test.ts app/src/utils/share-route-context.test.ts app/src/utils/__tests__/login-redirect.test.ts app/src/views/ai/workflow/workflow-share-context.test.ts", "test:unit": "vitest run --dom", "test:e2e": "turbo run test:e2e", "update:deps": "npx taze -r -w", diff --git a/easyflow-ui-admin/packages/@core/base/shared/src/utils/__tests__/window.test.ts b/easyflow-ui-admin/packages/@core/base/shared/src/utils/__tests__/window.test.ts index ebb04bb0..105c7dfe 100644 --- a/easyflow-ui-admin/packages/@core/base/shared/src/utils/__tests__/window.test.ts +++ b/easyflow-ui-admin/packages/@core/base/shared/src/utils/__tests__/window.test.ts @@ -1,17 +1,20 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { openWindow } from '../window'; +import { openRouteInNewWindow, openWindow } from '../window'; describe('openWindow', () => { // 保存原始的 window.open 函数 let originalOpen: typeof window.open; + let originalUrl: string; beforeEach(() => { originalOpen = window.open; + originalUrl = window.location.href; }); afterEach(() => { window.open = originalOpen; + window.history.replaceState({}, '', originalUrl); }); it('should call window.open with correct arguments', () => { @@ -30,4 +33,17 @@ describe('openWindow', () => { 'noopener=yes,noreferrer=yes', ); }); + + it('should preserve a router-resolved base and hash route', () => { + window.history.replaceState({}, '', '/flow/#/dashboard'); + window.open = vi.fn(); + + openRouteInNewWindow('#/ai/workflow'); + + expect(window.open).toHaveBeenCalledWith( + `${window.location.origin}/flow/#/ai/workflow`, + '_blank', + 'noopener=yes,noreferrer=yes', + ); + }); }); diff --git a/easyflow-ui-admin/packages/@core/base/shared/src/utils/window.ts b/easyflow-ui-admin/packages/@core/base/shared/src/utils/window.ts index 2d8697de..53415b84 100644 --- a/easyflow-ui-admin/packages/@core/base/shared/src/utils/window.ts +++ b/easyflow-ui-admin/packages/@core/base/shared/src/utils/window.ts @@ -25,13 +25,10 @@ function openWindow(url: string, options: OpenWindowOptions = {}): void { /** * 在新窗口中打开路由。 - * @param path + * @param href Vue Router 解析后的地址 */ -function openRouteInNewWindow(path: string) { - const { hash, origin } = location; - const fullPath = path.startsWith('/') ? path : `/${path}`; - const url = `${origin}${hash && !fullPath.startsWith('/#') ? '/#' : ''}${fullPath}`; - openWindow(url, { target: '_blank' }); +function openRouteInNewWindow(href: string) { + openWindow(new URL(href, location.href).href, { target: '_blank' }); } export { openRouteInNewWindow, openWindow }; diff --git a/easyflow-ui-admin/packages/effects/hooks/src/use-tabs.ts b/easyflow-ui-admin/packages/effects/hooks/src/use-tabs.ts index 57dd8f2c..a42918be 100644 --- a/easyflow-ui-admin/packages/effects/hooks/src/use-tabs.ts +++ b/easyflow-ui-admin/packages/effects/hooks/src/use-tabs.ts @@ -47,7 +47,7 @@ export function useTabs() { } async function openTabInNewWindow(tab?: RouteLocationNormalized) { - await tabbarStore.openTabInNewWindow(tab || route); + await tabbarStore.openTabInNewWindow(tab || route, router); } async function closeTabByKey(key: string) { diff --git a/easyflow-ui-admin/packages/stores/src/modules/tabbar.ts b/easyflow-ui-admin/packages/stores/src/modules/tabbar.ts index 5a3d20fa..c826644c 100644 --- a/easyflow-ui-admin/packages/stores/src/modules/tabbar.ts +++ b/easyflow-ui-admin/packages/stores/src/modules/tabbar.ts @@ -307,9 +307,11 @@ export const useTabbarStore = defineStore('core-tabbar', { /** * @zh_CN 新窗口打开标签页 * @param tab + * @param router */ - async openTabInNewWindow(tab: TabDefinition) { - openRouteInNewWindow(tab.fullPath || tab.path); + async openTabInNewWindow(tab: TabDefinition, router: Router) { + const href = router.resolve(tab.fullPath || tab.path).href; + openRouteInNewWindow(href); }, /** diff --git a/easyflow-ui-usercenter/packages/@core/base/shared/src/utils/__tests__/window.test.ts b/easyflow-ui-usercenter/packages/@core/base/shared/src/utils/__tests__/window.test.ts index ebb04bb0..105c7dfe 100644 --- a/easyflow-ui-usercenter/packages/@core/base/shared/src/utils/__tests__/window.test.ts +++ b/easyflow-ui-usercenter/packages/@core/base/shared/src/utils/__tests__/window.test.ts @@ -1,17 +1,20 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { openWindow } from '../window'; +import { openRouteInNewWindow, openWindow } from '../window'; describe('openWindow', () => { // 保存原始的 window.open 函数 let originalOpen: typeof window.open; + let originalUrl: string; beforeEach(() => { originalOpen = window.open; + originalUrl = window.location.href; }); afterEach(() => { window.open = originalOpen; + window.history.replaceState({}, '', originalUrl); }); it('should call window.open with correct arguments', () => { @@ -30,4 +33,17 @@ describe('openWindow', () => { 'noopener=yes,noreferrer=yes', ); }); + + it('should preserve a router-resolved base and hash route', () => { + window.history.replaceState({}, '', '/flow/#/dashboard'); + window.open = vi.fn(); + + openRouteInNewWindow('#/ai/workflow'); + + expect(window.open).toHaveBeenCalledWith( + `${window.location.origin}/flow/#/ai/workflow`, + '_blank', + 'noopener=yes,noreferrer=yes', + ); + }); }); diff --git a/easyflow-ui-usercenter/packages/@core/base/shared/src/utils/window.ts b/easyflow-ui-usercenter/packages/@core/base/shared/src/utils/window.ts index 2d8697de..53415b84 100644 --- a/easyflow-ui-usercenter/packages/@core/base/shared/src/utils/window.ts +++ b/easyflow-ui-usercenter/packages/@core/base/shared/src/utils/window.ts @@ -25,13 +25,10 @@ function openWindow(url: string, options: OpenWindowOptions = {}): void { /** * 在新窗口中打开路由。 - * @param path + * @param href Vue Router 解析后的地址 */ -function openRouteInNewWindow(path: string) { - const { hash, origin } = location; - const fullPath = path.startsWith('/') ? path : `/${path}`; - const url = `${origin}${hash && !fullPath.startsWith('/#') ? '/#' : ''}${fullPath}`; - openWindow(url, { target: '_blank' }); +function openRouteInNewWindow(href: string) { + openWindow(new URL(href, location.href).href, { target: '_blank' }); } export { openRouteInNewWindow, openWindow }; diff --git a/easyflow-ui-usercenter/packages/effects/hooks/src/use-tabs.ts b/easyflow-ui-usercenter/packages/effects/hooks/src/use-tabs.ts index 57dd8f2c..a42918be 100644 --- a/easyflow-ui-usercenter/packages/effects/hooks/src/use-tabs.ts +++ b/easyflow-ui-usercenter/packages/effects/hooks/src/use-tabs.ts @@ -47,7 +47,7 @@ export function useTabs() { } async function openTabInNewWindow(tab?: RouteLocationNormalized) { - await tabbarStore.openTabInNewWindow(tab || route); + await tabbarStore.openTabInNewWindow(tab || route, router); } async function closeTabByKey(key: string) { diff --git a/easyflow-ui-usercenter/packages/stores/src/modules/tabbar.ts b/easyflow-ui-usercenter/packages/stores/src/modules/tabbar.ts index 8463a60f..461ed1ba 100644 --- a/easyflow-ui-usercenter/packages/stores/src/modules/tabbar.ts +++ b/easyflow-ui-usercenter/packages/stores/src/modules/tabbar.ts @@ -307,9 +307,11 @@ export const useTabbarStore = defineStore('core-tabbar', { /** * @zh_CN 新窗口打开标签页 * @param tab + * @param router */ - async openTabInNewWindow(tab: TabDefinition) { - openRouteInNewWindow(tab.fullPath || tab.path); + async openTabInNewWindow(tab: TabDefinition, router: Router) { + const href = router.resolve(tab.fullPath || tab.path).href; + openRouteInNewWindow(href); }, /**