From 9ef20119a93f4cf26b5a7e134b0113fb91e703a5 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);
},
/**