fix: 修复 Hash 路由跳转丢失部署基路径
- 修复懒加载分包失败后跳转到站点根路径的问题 - 统一管理端和用户中心新窗口路由解析 - 增加部署基路径与 Hash 路由回归测试
This commit is contained in:
@@ -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: '<div>workflow</div>' },
|
||||||
|
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',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -71,6 +71,10 @@ function isDynamicImportChunkError(error: unknown) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveChunkReloadHref(router: Router, fullPath: string) {
|
||||||
|
return router.resolve(fullPath).href;
|
||||||
|
}
|
||||||
|
|
||||||
function setupChunkErrorGuard(router: Router) {
|
function setupChunkErrorGuard(router: Router) {
|
||||||
router.onError((error, to) => {
|
router.onError((error, to) => {
|
||||||
if (!isDynamicImportChunkError(error)) {
|
if (!isDynamicImportChunkError(error)) {
|
||||||
@@ -94,7 +98,7 @@ function setupChunkErrorGuard(router: Router) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sessionStorage.setItem(CHUNK_ERROR_RELOAD_KEY, fullPath);
|
sessionStorage.setItem(CHUNK_ERROR_RELOAD_KEY, fullPath);
|
||||||
window.location.assign(fullPath);
|
window.location.assign(resolveChunkReloadHref(router, fullPath));
|
||||||
});
|
});
|
||||||
|
|
||||||
router.isReady().finally(() => {
|
router.isReady().finally(() => {
|
||||||
@@ -325,4 +329,4 @@ function createRouterGuard(router: Router) {
|
|||||||
setupChunkErrorGuard(router);
|
setupChunkErrorGuard(router);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { createRouterGuard };
|
export { createRouterGuard, resolveChunkReloadHref };
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"preview": "turbo-run preview",
|
"preview": "turbo-run preview",
|
||||||
"publint": "vsh publint",
|
"publint": "vsh publint",
|
||||||
"reinstall": "pnpm clean --del-lock && pnpm install",
|
"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:unit": "vitest run --dom",
|
||||||
"test:e2e": "turbo run test:e2e",
|
"test:e2e": "turbo run test:e2e",
|
||||||
"update:deps": "npx taze -r -w",
|
"update:deps": "npx taze -r -w",
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { openWindow } from '../window';
|
import { openRouteInNewWindow, openWindow } from '../window';
|
||||||
|
|
||||||
describe('openWindow', () => {
|
describe('openWindow', () => {
|
||||||
// 保存原始的 window.open 函数
|
// 保存原始的 window.open 函数
|
||||||
let originalOpen: typeof window.open;
|
let originalOpen: typeof window.open;
|
||||||
|
let originalUrl: string;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
originalOpen = window.open;
|
originalOpen = window.open;
|
||||||
|
originalUrl = window.location.href;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
window.open = originalOpen;
|
window.open = originalOpen;
|
||||||
|
window.history.replaceState({}, '', originalUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call window.open with correct arguments', () => {
|
it('should call window.open with correct arguments', () => {
|
||||||
@@ -30,4 +33,17 @@ describe('openWindow', () => {
|
|||||||
'noopener=yes,noreferrer=yes',
|
'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',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,13 +25,10 @@ function openWindow(url: string, options: OpenWindowOptions = {}): void {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 在新窗口中打开路由。
|
* 在新窗口中打开路由。
|
||||||
* @param path
|
* @param href Vue Router 解析后的地址
|
||||||
*/
|
*/
|
||||||
function openRouteInNewWindow(path: string) {
|
function openRouteInNewWindow(href: string) {
|
||||||
const { hash, origin } = location;
|
openWindow(new URL(href, location.href).href, { target: '_blank' });
|
||||||
const fullPath = path.startsWith('/') ? path : `/${path}`;
|
|
||||||
const url = `${origin}${hash && !fullPath.startsWith('/#') ? '/#' : ''}${fullPath}`;
|
|
||||||
openWindow(url, { target: '_blank' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { openRouteInNewWindow, openWindow };
|
export { openRouteInNewWindow, openWindow };
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function useTabs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function openTabInNewWindow(tab?: RouteLocationNormalized) {
|
async function openTabInNewWindow(tab?: RouteLocationNormalized) {
|
||||||
await tabbarStore.openTabInNewWindow(tab || route);
|
await tabbarStore.openTabInNewWindow(tab || route, router);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function closeTabByKey(key: string) {
|
async function closeTabByKey(key: string) {
|
||||||
|
|||||||
@@ -307,9 +307,11 @@ export const useTabbarStore = defineStore('core-tabbar', {
|
|||||||
/**
|
/**
|
||||||
* @zh_CN 新窗口打开标签页
|
* @zh_CN 新窗口打开标签页
|
||||||
* @param tab
|
* @param tab
|
||||||
|
* @param router
|
||||||
*/
|
*/
|
||||||
async openTabInNewWindow(tab: TabDefinition) {
|
async openTabInNewWindow(tab: TabDefinition, router: Router) {
|
||||||
openRouteInNewWindow(tab.fullPath || tab.path);
|
const href = router.resolve(tab.fullPath || tab.path).href;
|
||||||
|
openRouteInNewWindow(href);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { openWindow } from '../window';
|
import { openRouteInNewWindow, openWindow } from '../window';
|
||||||
|
|
||||||
describe('openWindow', () => {
|
describe('openWindow', () => {
|
||||||
// 保存原始的 window.open 函数
|
// 保存原始的 window.open 函数
|
||||||
let originalOpen: typeof window.open;
|
let originalOpen: typeof window.open;
|
||||||
|
let originalUrl: string;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
originalOpen = window.open;
|
originalOpen = window.open;
|
||||||
|
originalUrl = window.location.href;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
window.open = originalOpen;
|
window.open = originalOpen;
|
||||||
|
window.history.replaceState({}, '', originalUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call window.open with correct arguments', () => {
|
it('should call window.open with correct arguments', () => {
|
||||||
@@ -30,4 +33,17 @@ describe('openWindow', () => {
|
|||||||
'noopener=yes,noreferrer=yes',
|
'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',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,13 +25,10 @@ function openWindow(url: string, options: OpenWindowOptions = {}): void {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 在新窗口中打开路由。
|
* 在新窗口中打开路由。
|
||||||
* @param path
|
* @param href Vue Router 解析后的地址
|
||||||
*/
|
*/
|
||||||
function openRouteInNewWindow(path: string) {
|
function openRouteInNewWindow(href: string) {
|
||||||
const { hash, origin } = location;
|
openWindow(new URL(href, location.href).href, { target: '_blank' });
|
||||||
const fullPath = path.startsWith('/') ? path : `/${path}`;
|
|
||||||
const url = `${origin}${hash && !fullPath.startsWith('/#') ? '/#' : ''}${fullPath}`;
|
|
||||||
openWindow(url, { target: '_blank' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { openRouteInNewWindow, openWindow };
|
export { openRouteInNewWindow, openWindow };
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function useTabs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function openTabInNewWindow(tab?: RouteLocationNormalized) {
|
async function openTabInNewWindow(tab?: RouteLocationNormalized) {
|
||||||
await tabbarStore.openTabInNewWindow(tab || route);
|
await tabbarStore.openTabInNewWindow(tab || route, router);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function closeTabByKey(key: string) {
|
async function closeTabByKey(key: string) {
|
||||||
|
|||||||
@@ -307,9 +307,11 @@ export const useTabbarStore = defineStore('core-tabbar', {
|
|||||||
/**
|
/**
|
||||||
* @zh_CN 新窗口打开标签页
|
* @zh_CN 新窗口打开标签页
|
||||||
* @param tab
|
* @param tab
|
||||||
|
* @param router
|
||||||
*/
|
*/
|
||||||
async openTabInNewWindow(tab: TabDefinition) {
|
async openTabInNewWindow(tab: TabDefinition, router: Router) {
|
||||||
openRouteInNewWindow(tab.fullPath || tab.path);
|
const href = router.resolve(tab.fullPath || tab.path).href;
|
||||||
|
openRouteInNewWindow(href);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user