generator-mico-cli 0.1.29 → 0.2.2-8.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +199 -15
- package/bin/mico.js +232 -27
- package/generators/micro-react/index.js +200 -18
- package/generators/micro-react/meta.json +13 -0
- package/generators/micro-react/templates/.commitlintrc.js +1 -0
- package/generators/micro-react/templates/.cursor/rules/always-read-docs.mdc +14 -4
- package/generators/micro-react/templates/.cursor/rules/cicd-deploy.mdc +10 -8
- package/generators/micro-react/templates/.cursor/rules/coding-conventions.mdc +1 -1
- package/generators/micro-react/templates/.cursor/rules/development-guide.mdc +3 -4
- package/generators/micro-react/templates/.cursor/rules/layout-app.mdc +38 -31
- package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +7 -4
- package/generators/micro-react/templates/.cursor/rules/theme-system.mdc +10 -12
- package/generators/micro-react/templates/.eslintrc.js +25 -1
- package/generators/micro-react/templates/AGENTS.md +5 -2
- package/generators/micro-react/templates/CICD/before_build.sh +76 -0
- package/generators/micro-react/templates/CICD/start_dev.sh +27 -3
- package/generators/micro-react/templates/CICD/start_prod.sh +26 -3
- package/generators/micro-react/templates/CICD/start_test.sh +28 -3
- package/generators/micro-react/templates/CICD/wangsu_fresh_dev.sh +4 -4
- package/generators/micro-react/templates/CICD/wangsu_fresh_prod.sh +4 -4
- package/generators/micro-react/templates/CICD/wangsu_fresh_test.sh +4 -4
- package/generators/micro-react/templates/CLAUDE.md +16 -9
- package/generators/micro-react/templates/README.md +42 -4
- package/generators/micro-react/templates/_gitignore +4 -0
- package/generators/micro-react/templates/_npmrc +4 -0
- package/generators/micro-react/templates/apps/layout/config/config.dev.ts +33 -17
- package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +24 -29
- package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +25 -6
- package/generators/micro-react/templates/apps/layout/config/config.prod.ts +16 -7
- package/generators/micro-react/templates/apps/layout/config/config.ts +27 -4
- package/generators/micro-react/templates/apps/layout/config/routes.ts +10 -5
- package/generators/micro-react/templates/apps/layout/docs/arch-/346/227/245/345/277/227/344/270/216/345/270/270/351/207/217.md +2 -2
- package/generators/micro-react/templates/apps/layout/docs/arch-/350/257/267/346/261/202/346/250/241/345/235/227.md +1 -1
- package/generators/micro-react/templates/apps/layout/docs/common-intl.md +372 -0
- package/generators/micro-react/templates/apps/layout/docs/feat-/346/236/204/345/273/272define/344/270/216/345/205/215/350/256/244/350/257/201/345/210/235/345/247/213/346/200/201.md +44 -0
- package/generators/micro-react/templates/apps/layout/docs/feature-404/351/241/265/351/235/242.md +103 -0
- package/generators/micro-react/templates/apps/layout/docs/feature-/344/270/273/351/242/230/350/211/262/345/210/207/346/215/242.md +22 -26
- package/generators/micro-react/templates/apps/layout/docs/feature-/345/276/256/345/211/215/347/253/257/346/250/241/345/274/217.md +185 -28
- package/generators/micro-react/templates/apps/layout/docs/feature-/350/217/234/345/215/225/346/235/203/351/231/220/346/216/247/345/210/266.md +420 -0
- package/generators/micro-react/templates/apps/layout/docs/feature-/350/267/257/347/224/261/344/270/216/350/217/234/345/215/225/350/247/243/350/200/246.md +179 -0
- package/generators/micro-react/templates/apps/layout/docs/fix-SSO/346/227/240/351/231/220/351/207/215/345/256/232/345/220/221.md +88 -0
- package/generators/micro-react/templates/apps/layout/docs/utils-timezone.md +324 -0
- package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +81 -61
- package/generators/micro-react/templates/apps/layout/mock/menus.ts +114 -4
- package/generators/micro-react/templates/apps/layout/mock/pages.ts +86 -0
- package/generators/micro-react/templates/apps/layout/package.json +7 -4
- package/generators/micro-react/templates/apps/layout/src/app.tsx +122 -83
- package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +3 -0
- package/generators/micro-react/templates/apps/layout/src/common/helpers.ts +177 -0
- package/generators/micro-react/templates/apps/layout/src/common/locale.ts +22 -17
- package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +283 -28
- package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +69 -5
- package/generators/micro-react/templates/apps/layout/src/common/micro/index.ts +34 -0
- package/generators/micro-react/templates/apps/layout/src/common/micro-prefetch.ts +109 -0
- package/generators/micro-react/templates/apps/layout/src/common/portal-data.ts +45 -0
- package/generators/micro-react/templates/apps/layout/src/common/request/config.ts +72 -10
- package/generators/micro-react/templates/apps/layout/src/common/request/index.ts +2 -2
- package/generators/micro-react/templates/apps/layout/src/common/request/interceptors.ts +31 -3
- package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +29 -11
- package/generators/micro-react/templates/apps/layout/src/common/request/url-resolver.ts +23 -8
- package/generators/micro-react/templates/apps/layout/src/common/route-guard.ts +345 -0
- package/generators/micro-react/templates/apps/layout/src/common/theme.ts +2 -4
- package/generators/micro-react/templates/apps/layout/src/common/upload/oss.ts +3 -4
- package/generators/micro-react/templates/apps/layout/src/common/upload/types.ts +1 -1
- package/generators/micro-react/templates/apps/layout/src/common/uploadFiles.ts +1 -1
- package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.less +8 -3
- package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.tsx +25 -8
- package/generators/micro-react/templates/apps/layout/src/components/HeaderDropdown/index.tsx +20 -0
- package/generators/micro-react/templates/apps/layout/src/components/IconFont/index.tsx +5 -6
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.less +21 -6
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +83 -107
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/micro-app-manager.ts +569 -0
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +383 -0
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/avatar-dropdown.less +35 -0
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/index.ts +2 -0
- package/generators/micro-react/templates/apps/layout/src/constants/index.ts +170 -6
- package/generators/micro-react/templates/apps/layout/src/global.less +19 -6
- package/generators/micro-react/templates/apps/layout/src/hooks/useMenu.ts +3 -2
- package/generators/micro-react/templates/apps/layout/src/hooks/useRoutePermissionRefresh.ts +72 -0
- package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.less +3 -1
- package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +10 -55
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +34 -4
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +33 -9
- package/generators/micro-react/templates/apps/layout/src/layouts/index.less +84 -13
- package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +178 -47
- package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +12 -0
- package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +12 -0
- package/generators/micro-react/templates/apps/layout/src/pages/403/index.tsx +34 -0
- package/generators/micro-react/templates/apps/layout/src/pages/404/index.tsx +78 -0
- package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +3 -0
- package/generators/micro-react/templates/apps/layout/src/pages/Home/index.tsx +7 -1
- package/generators/micro-react/templates/apps/layout/src/pages/User/Login/index.less +1 -1
- package/generators/micro-react/templates/apps/layout/src/pages/User/Login/index.tsx +9 -5
- package/generators/micro-react/templates/apps/layout/src/requestErrorConfig.ts +1 -1
- package/generators/micro-react/templates/apps/layout/src/services/config/index.ts +63 -0
- package/generators/micro-react/templates/apps/layout/src/services/config/type.ts +30 -0
- package/generators/micro-react/templates/apps/layout/src/services/user.ts +29 -2
- package/generators/micro-react/templates/apps/layout/tailwind.config.js +3 -0
- package/generators/micro-react/templates/deployDesc.md +3 -3
- package/generators/micro-react/templates/dev.preset.json +14 -0
- package/generators/micro-react/templates/docs/dev-preset.md +130 -0
- package/generators/micro-react/templates/package.json +21 -6
- package/generators/micro-react/templates/packages/common-intl/README.md +427 -0
- package/generators/micro-react/templates/packages/common-intl/package.json +34 -0
- package/generators/micro-react/templates/packages/common-intl/src/index.ts +7 -0
- package/generators/micro-react/templates/packages/common-intl/src/indexedDBUtils.ts +51 -0
- package/generators/micro-react/templates/packages/common-intl/src/intl.ts +50 -0
- package/generators/micro-react/templates/packages/common-intl/src/utils.ts +482 -0
- package/generators/micro-react/templates/packages/common-intl/tsconfig.json +22 -0
- package/generators/micro-react/templates/packages/common-intl/vite.config.ts +25 -0
- package/generators/micro-react/templates/scripts/apply-sentry-plugin.ts +45 -0
- package/generators/micro-react/templates/scripts/collect-dist.js +10 -0
- package/generators/micro-react/templates/scripts/dev-preset.js +265 -0
- package/generators/micro-react/templates/scripts/dev-preset.schema.json +39 -0
- package/generators/micro-react/templates/turbo.json +4 -1
- package/generators/subapp-react/index.js +326 -40
- package/generators/subapp-react/meta.json +10 -0
- package/generators/subapp-react/templates/homepage/.env +2 -1
- package/generators/subapp-react/templates/homepage/README.md +3 -3
- package/generators/subapp-react/templates/homepage/config/config.dev.ts +14 -7
- package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +16 -5
- package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +16 -5
- package/generators/subapp-react/templates/homepage/config/config.prod.ts +14 -5
- package/generators/subapp-react/templates/homepage/config/config.ts +27 -0
- package/generators/subapp-react/templates/homepage/config/routes.ts +2 -2
- package/generators/subapp-react/templates/homepage/mock/api.mock.ts +2 -2
- package/generators/subapp-react/templates/homepage/package.json +7 -4
- package/generators/subapp-react/templates/homepage/src/app.tsx +18 -27
- package/generators/subapp-react/templates/homepage/src/common/request.ts +29 -2
- package/generators/subapp-react/templates/homepage/src/global.less +6 -5
- package/generators/subapp-react/templates/homepage/src/pages/index.less +3 -3
- package/generators/subapp-react/templates/homepage/src/pages/index.tsx +99 -60
- package/generators/subapp-react/templates/homepage/src/styles/theme.less +1 -1
- package/generators/subapp-umd/ignore-list.json +5 -0
- package/generators/subapp-umd/index.js +309 -0
- package/generators/subapp-umd/meta.json +11 -0
- package/generators/subapp-umd/templates/README.md +94 -0
- package/generators/subapp-umd/templates/package.json +35 -0
- package/generators/subapp-umd/templates/public/index.html +34 -0
- package/generators/subapp-umd/templates/src/App.less +15 -0
- package/generators/subapp-umd/templates/src/App.tsx +13 -0
- package/generators/subapp-umd/templates/src/index.ts +2 -0
- package/generators/subapp-umd/templates/tsconfig.json +27 -0
- package/generators/subapp-umd/templates/webpack.config.js +70 -0
- package/lib/utils.js +332 -2
- package/package.json +15 -2
- package/generators/micro-react/templates/apps/layout/mock/menus.json +0 -100
- package/generators/micro-react/templates/apps/layout/src/common/constants.ts +0 -38
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/container-manager.ts +0 -202
- package/generators/micro-react/templates/packages/shared-styles/README.md +0 -124
- package/generators/micro-react/templates/packages/shared-styles/arco-design-mobile-override.less +0 -91
- package/generators/micro-react/templates/packages/shared-styles/arco-override.less +0 -119
- package/generators/micro-react/templates/packages/shared-styles/index.d.ts +0 -44
- package/generators/micro-react/templates/packages/shared-styles/index.less +0 -13
- package/generators/micro-react/templates/packages/shared-styles/package.json +0 -30
- package/generators/micro-react/templates/packages/shared-styles/theme-inject.less +0 -10
- package/generators/micro-react/templates/packages/shared-styles/themes/dark/custom-var.less +0 -290
- package/generators/micro-react/templates/packages/shared-styles/themes/normal/custom-var.less +0 -269
- package/generators/micro-react/templates/packages/shared-styles/variables-only.less +0 -433
- package/generators/micro-react/templates/packages/shared-styles/variables.less +0 -452
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局数据源管理
|
|
3
|
+
*
|
|
4
|
+
* 统一管理 window.__MICO_PAGES__ 和 window.__MICO_MENUS__ 的访问与索引。
|
|
5
|
+
* 数据由中台注入,每次直接从 window 读取(避免缓存导致时序问题)。
|
|
6
|
+
* pageId 索引惰性构建,数据就绪后缓存。
|
|
7
|
+
*/
|
|
8
|
+
import type { MenuItem, PublicPageItem } from './menu/types';
|
|
9
|
+
|
|
10
|
+
/** 获取页面列表 (window.__MICO_PAGES__) */
|
|
11
|
+
export const getPages = (): PublicPageItem[] => {
|
|
12
|
+
if (typeof window === 'undefined') return [];
|
|
13
|
+
return Array.isArray(window.__MICO_PAGES__) ? window.__MICO_PAGES__ : [];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** 获取菜单树 (window.__MICO_MENUS__) */
|
|
17
|
+
export const getMenus = (): MenuItem[] => {
|
|
18
|
+
if (typeof window === 'undefined') return [];
|
|
19
|
+
return Array.isArray(window.__MICO_MENUS__) ? window.__MICO_MENUS__ : [];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** 是否有页面数据可用 */
|
|
23
|
+
export const hasPages = (): boolean => {
|
|
24
|
+
return getPages().length > 0;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
let _pageIdIndex: Map<number, PublicPageItem> | null = null;
|
|
28
|
+
|
|
29
|
+
const getPageIdIndex = (): Map<number, PublicPageItem> => {
|
|
30
|
+
const pages = getPages();
|
|
31
|
+
if (!_pageIdIndex || (_pageIdIndex.size === 0 && pages.length > 0)) {
|
|
32
|
+
_pageIdIndex = new Map(pages.map((p) => [p.id, p]));
|
|
33
|
+
}
|
|
34
|
+
return _pageIdIndex;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** 通过 pageId 查找页面(O(1)) */
|
|
38
|
+
export const getPageById = (pageId: number): PublicPageItem | undefined => {
|
|
39
|
+
return getPageIdIndex().get(pageId);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** 获取菜单项关联的页面 */
|
|
43
|
+
export const getMenuPage = (item: MenuItem): PublicPageItem | undefined => {
|
|
44
|
+
return item.pageId ? getPageById(item.pageId) : undefined;
|
|
45
|
+
};
|
|
@@ -1,20 +1,82 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 请求客户端配置管理
|
|
3
|
+
* appId 优先从 window.__MICO_WORKSPACE__ 获取,其次从 window.__MICO_CONFIG__ 获取;
|
|
4
|
+
* externalLoginPath 优先从 window.__MICO_WORKSPACE__.casServerLoginUrl 获取;
|
|
5
|
+
* 其余 apiBaseUrl / proxySuffix / loginEndpoint / refreshEndpoint 优先从 window.__MICO_CONFIG__ 同名字段获取,兜底取 process.env 环境变量。
|
|
6
|
+
* proxySuffix:若 __MICO_CONFIG__.proxySuffix 与 process.env.PROXY_SUFFIX 均未配置,则按已解析的 appId 推导为 /proxy/${appId}_svr。
|
|
3
7
|
*/
|
|
4
8
|
|
|
5
9
|
import { getStoredAuthToken } from '../auth/auth-manager';
|
|
6
10
|
import type { RequestClientOptions, TokenResolver } from './types';
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
function getMicoConfig(): Window['__MICO_CONFIG__'] {
|
|
13
|
+
if (typeof window === 'undefined') return undefined;
|
|
14
|
+
return window.__MICO_CONFIG__;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getMicoWorkspace(): Window['__MICO_WORKSPACE__'] {
|
|
18
|
+
if (typeof window === 'undefined') return undefined;
|
|
19
|
+
return window.__MICO_WORKSPACE__ ?? undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 显式 proxySuffix 优先;否则在有 appId 时使用 /proxy/${appId}_svr */
|
|
23
|
+
function resolveProxySuffixWithFallback(
|
|
24
|
+
explicit: string,
|
|
25
|
+
appId: string,
|
|
26
|
+
): string {
|
|
27
|
+
if (explicit) return explicit;
|
|
28
|
+
return appId ? `/proxy/${appId}_svr` : '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildDefaultClientOptions(): RequestClientOptions {
|
|
32
|
+
const envDefaults: RequestClientOptions = {
|
|
33
|
+
appId: '',
|
|
34
|
+
ticketParam: 'ticket',
|
|
35
|
+
logoutPath: '/logout',
|
|
36
|
+
apiBaseUrl: process.env.API_BASE_URL ?? '',
|
|
37
|
+
proxySuffix: process.env.PROXY_SUFFIX ?? '',
|
|
38
|
+
loginEndpoint: process.env.LOGIN_ENDPOINT ?? '',
|
|
39
|
+
refreshEndpoint: process.env.REFRESH_ENDPOINT ?? '',
|
|
40
|
+
externalLoginPath: process.env.EXTERNAL_LOGIN_PATH ?? '',
|
|
41
|
+
};
|
|
42
|
+
const mico = getMicoConfig();
|
|
43
|
+
const workspace = getMicoWorkspace();
|
|
44
|
+
const appIdFromWorkspace =
|
|
45
|
+
workspace?.appId !== undefined && workspace?.appId !== null
|
|
46
|
+
? String(workspace.appId)
|
|
47
|
+
: undefined;
|
|
48
|
+
const externalLoginFromWorkspace = workspace?.casServerLoginUrl;
|
|
49
|
+
if (!mico && !appIdFromWorkspace && !externalLoginFromWorkspace) {
|
|
50
|
+
return {
|
|
51
|
+
...envDefaults,
|
|
52
|
+
proxySuffix: resolveProxySuffixWithFallback(
|
|
53
|
+
envDefaults.proxySuffix,
|
|
54
|
+
envDefaults.appId,
|
|
55
|
+
),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const resolvedAppId =
|
|
59
|
+
appIdFromWorkspace ?? mico?.appId ?? envDefaults.appId;
|
|
60
|
+
const explicitProxy = mico?.proxySuffix ?? envDefaults.proxySuffix;
|
|
61
|
+
return {
|
|
62
|
+
...envDefaults,
|
|
63
|
+
...(mico?.defaultClientOptions ?? {}),
|
|
64
|
+
appId: resolvedAppId,
|
|
65
|
+
apiBaseUrl: mico?.apiBaseUrl ?? envDefaults.apiBaseUrl,
|
|
66
|
+
proxySuffix: resolveProxySuffixWithFallback(
|
|
67
|
+
explicitProxy,
|
|
68
|
+
resolvedAppId,
|
|
69
|
+
),
|
|
70
|
+
loginEndpoint: mico?.loginEndpoint ?? envDefaults.loginEndpoint,
|
|
71
|
+
refreshEndpoint: mico?.refreshEndpoint ?? envDefaults.refreshEndpoint,
|
|
72
|
+
externalLoginPath:
|
|
73
|
+
externalLoginFromWorkspace ??
|
|
74
|
+
mico?.externalLoginPath ??
|
|
75
|
+
envDefaults.externalLoginPath,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const defaultClientOptions: RequestClientOptions = buildDefaultClientOptions();
|
|
18
80
|
|
|
19
81
|
let clientOptions: RequestClientOptions = { ...defaultClientOptions };
|
|
20
82
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { request as rawRequest } from '@umijs/max';
|
|
18
18
|
import { setStoredAuthToken } from '../auth/auth-manager';
|
|
19
|
-
import {
|
|
19
|
+
import { isNoAuthRoute } from '@/constants';
|
|
20
20
|
|
|
21
21
|
// 配置相关
|
|
22
22
|
import {
|
|
@@ -64,7 +64,7 @@ initDefaultInterceptors(isFetchingToken, addToPendingQueue);
|
|
|
64
64
|
* 判断当前路由是否跳过认证
|
|
65
65
|
*/
|
|
66
66
|
const shouldSkipAuth = (): boolean => {
|
|
67
|
-
return
|
|
67
|
+
return isNoAuthRoute(location.pathname);
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
/**
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { getFromStorage, safeParseJSON } from '@/common/helpers';
|
|
6
|
-
import { Message } from '@
|
|
6
|
+
import { Message } from '@mico-platform/ui';
|
|
7
7
|
import { maybePersistTokens } from '@/common/auth/auth-manager';
|
|
8
8
|
import { UID } from '@/common/auth/type';
|
|
9
9
|
import { getClientOptions, resolveAuthToken } from './config';
|
|
@@ -68,7 +68,7 @@ export const runResponseInterceptors = async <T>(
|
|
|
68
68
|
): Promise<T> => {
|
|
69
69
|
let current = response;
|
|
70
70
|
for (const { handler } of responseInterceptors) {
|
|
71
|
-
current = await handler(current, ctx);
|
|
71
|
+
current = await handler(current, ctx) as T;
|
|
72
72
|
}
|
|
73
73
|
return current;
|
|
74
74
|
};
|
|
@@ -164,7 +164,7 @@ export const initDefaultInterceptors = (
|
|
|
164
164
|
return data;
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
-
//
|
|
167
|
+
// 处理业务错误码({ result: { code, desc } } 格式)
|
|
168
168
|
addResponseInterceptor(async (data: unknown) => {
|
|
169
169
|
if (data && typeof data === 'object' && 'result' in data) {
|
|
170
170
|
const typedData = data as { result?: { code?: number; desc?: string } };
|
|
@@ -183,4 +183,32 @@ export const initDefaultInterceptors = (
|
|
|
183
183
|
}
|
|
184
184
|
return data;
|
|
185
185
|
});
|
|
186
|
+
|
|
187
|
+
// 处理业务错误码({ code, msg, data } 格式)
|
|
188
|
+
addResponseInterceptor(async (data: unknown) => {
|
|
189
|
+
if (
|
|
190
|
+
data &&
|
|
191
|
+
typeof data === 'object' &&
|
|
192
|
+
'code' in data &&
|
|
193
|
+
!('result' in data) &&
|
|
194
|
+
!('success' in data)
|
|
195
|
+
) {
|
|
196
|
+
const typedData = data as { code?: number | string; msg?: string };
|
|
197
|
+
const code = Number(typedData.code);
|
|
198
|
+
if (!isNaN(code) && code !== 200 && code !== 0) {
|
|
199
|
+
const error = new Error(
|
|
200
|
+
typedData.msg || `请求失败 (${code})`,
|
|
201
|
+
) as Error & {
|
|
202
|
+
code: number;
|
|
203
|
+
msg: string;
|
|
204
|
+
response: unknown;
|
|
205
|
+
};
|
|
206
|
+
error.code = code;
|
|
207
|
+
error.msg = typedData.msg || '';
|
|
208
|
+
error.response = data;
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return data;
|
|
213
|
+
});
|
|
186
214
|
};
|
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
maybePersistTokens,
|
|
8
8
|
setStoredAuthToken,
|
|
9
9
|
} from '@/common/auth/auth-manager';
|
|
10
|
-
import {
|
|
10
|
+
import { isPageAuthFree } from '@/common/menu';
|
|
11
|
+
import { isNoAuthRoute, ROUTES } from '@/constants';
|
|
11
12
|
import {
|
|
12
13
|
getTicketParam,
|
|
13
14
|
resolveAuthToken,
|
|
@@ -31,6 +32,17 @@ let ticketPromise: Promise<void> | null = null;
|
|
|
31
32
|
export const handleAuthFailureRedirect = (): void => {
|
|
32
33
|
if (typeof window === 'undefined') return;
|
|
33
34
|
|
|
35
|
+
// 如果当前路由在免认证列表中,不触发 SSO 重定向
|
|
36
|
+
if (isNoAuthRoute(window.location.pathname)) {
|
|
37
|
+
console.log('[SSO] 当前路由在免认证列表中,跳过 SSO 重定向');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (isPageAuthFree(window.location.pathname)) {
|
|
42
|
+
console.log('[SSO] 页面 accessControlEnabled=false,跳过 SSO 重定向');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
34
46
|
// 从 URL 中获取当前 redirect 登录的次数
|
|
35
47
|
const currentUrl = new URL(window.location.href);
|
|
36
48
|
const redirectCountParam = currentUrl.searchParams.get('redirect_count');
|
|
@@ -101,16 +113,6 @@ export const ensureSsoSession = async (): Promise<void> => {
|
|
|
101
113
|
setFetchingToken(false);
|
|
102
114
|
|
|
103
115
|
if (resolveAuthToken()) {
|
|
104
|
-
// SSO 认证成功,删除 URL 中的 redirect_count 参数
|
|
105
|
-
if (typeof window !== 'undefined') {
|
|
106
|
-
const current = new URL(window.location.href);
|
|
107
|
-
current.searchParams.delete('redirect_count');
|
|
108
|
-
const nextUrl = `${current.pathname}${
|
|
109
|
-
current.search ? `?${current.searchParams}` : ''
|
|
110
|
-
}${current.hash}`;
|
|
111
|
-
window.history.replaceState(null, '', nextUrl);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
116
|
processPendingRequests();
|
|
115
117
|
} else {
|
|
116
118
|
throw new Error('SSO 认证失败');
|
|
@@ -131,3 +133,19 @@ export const ensureSsoSession = async (): Promise<void> => {
|
|
|
131
133
|
|
|
132
134
|
await ticketPromise;
|
|
133
135
|
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 清除 URL 中的 redirect_count 参数
|
|
139
|
+
* 仅在整个认证流程(SSO + fetchUserInfo)完全成功后调用,
|
|
140
|
+
* 避免过早清除导致无限重定向循环
|
|
141
|
+
*/
|
|
142
|
+
export const clearRedirectCount = (): void => {
|
|
143
|
+
if (typeof window === 'undefined') return;
|
|
144
|
+
const current = new URL(window.location.href);
|
|
145
|
+
if (!current.searchParams.has('redirect_count')) return;
|
|
146
|
+
current.searchParams.delete('redirect_count');
|
|
147
|
+
const nextUrl = `${current.pathname}${
|
|
148
|
+
current.search ? `?${current.searchParams}` : ''
|
|
149
|
+
}${current.hash}`;
|
|
150
|
+
window.history.replaceState(null, '', nextUrl);
|
|
151
|
+
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* URL 解析与拼接工具
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { resolveProxySuffix } from './config';
|
|
5
|
+
import { resolveApiBaseUrl, resolveProxySuffix } from './config';
|
|
6
6
|
import type { UnifiedRequestOptions } from './types';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -33,8 +33,12 @@ const joinProxyAndPath = (proxySuffix: string, path: string): string => {
|
|
|
33
33
|
* 配置优先级:
|
|
34
34
|
* 1. rawUrl: true → 直接返回原始 url
|
|
35
35
|
* 2. 绝对 URL → 直接返回
|
|
36
|
-
* 3. skipProxy: true →
|
|
37
|
-
* 4. 其他情况 → 拼接 proxySuffix + url
|
|
36
|
+
* 3. skipProxy: true → 拼接 apiBaseUrl + url(跳过 proxySuffix)
|
|
37
|
+
* 4. 其他情况 → 拼接 apiBaseUrl + proxySuffix + url
|
|
38
|
+
*
|
|
39
|
+
* 示例(testing 环境,apiBaseUrl = https://dashboard-api-test.micoplatform.com):
|
|
40
|
+
* - 默认: /api/user/info → https://dashboard-api-test.micoplatform.com/proxy/<%= projectName %>_svr/api/user/info
|
|
41
|
+
* - skipProxy: /api/user/info → https://dashboard-api-test.micoplatform.com/api/user/info
|
|
38
42
|
*/
|
|
39
43
|
export const resolveRequestUrl = (
|
|
40
44
|
url: string,
|
|
@@ -50,13 +54,24 @@ export const resolveRequestUrl = (
|
|
|
50
54
|
return url;
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
// 拼接 apiBaseUrl + proxySuffix + url
|
|
58
|
+
const apiBaseUrl = resolveApiBaseUrl();
|
|
59
|
+
// skipProxy: true 时不拼接 proxySuffix
|
|
60
|
+
const proxySuffix = options?.skipProxy
|
|
61
|
+
? ''
|
|
62
|
+
: resolveProxySuffix(options?.proxySuffix);
|
|
63
|
+
|
|
64
|
+
if (apiBaseUrl) {
|
|
65
|
+
// 有 apiBaseUrl 时,拼接完整的绝对路径
|
|
66
|
+
const pathPart = proxySuffix ? joinProxyAndPath(proxySuffix, url) : url;
|
|
67
|
+
const normalizedBase = apiBaseUrl.endsWith('/')
|
|
68
|
+
? apiBaseUrl.slice(0, -1)
|
|
69
|
+
: apiBaseUrl;
|
|
70
|
+
const normalizedPath = pathPart.startsWith('/') ? pathPart : `/${pathPart}`;
|
|
71
|
+
return `${normalizedBase}${normalizedPath}`;
|
|
56
72
|
}
|
|
57
73
|
|
|
58
|
-
//
|
|
59
|
-
const proxySuffix = resolveProxySuffix(options?.proxySuffix);
|
|
74
|
+
// 没有 apiBaseUrl 时,仅拼接 proxySuffix(相对路径)
|
|
60
75
|
if (proxySuffix) {
|
|
61
76
|
return joinProxyAndPath(proxySuffix, url);
|
|
62
77
|
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 路由守卫 - 自动检测用户意图
|
|
3
|
+
*
|
|
4
|
+
* 核心问题:
|
|
5
|
+
* 当用户快速切换微应用(A→B→A)时,子应用 B 的代码可能在后台继续执行,
|
|
6
|
+
* 并调用 history.pushState('/route-b') 试图修改路由,导致用户界面混乱。
|
|
7
|
+
*
|
|
8
|
+
* 解决方案:
|
|
9
|
+
* 自动检测"用户触发的导航"和"程序触发的导航":
|
|
10
|
+
* - 用户触发:在 click/keydown 等交互事件的同步调用栈中
|
|
11
|
+
* - 程序触发:在异步回调、定时器、微应用生命周期中
|
|
12
|
+
*
|
|
13
|
+
* 对于程序触发的导航,如果目标路径与当前用户意图不匹配,则拦截。
|
|
14
|
+
*
|
|
15
|
+
* 使用方式:
|
|
16
|
+
* 1. 在应用入口处导入此文件(import '@/common/route-guard')
|
|
17
|
+
* 2. 业务代码无需任何改动,正常使用 history.push、react-router、<a> 标签等
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { microAppLogger } from './logger';
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// 类型定义
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
interface UserIntent {
|
|
27
|
+
/** 用户意图的目标路径 */
|
|
28
|
+
path: string;
|
|
29
|
+
/** 意图设置时间 */
|
|
30
|
+
timestamp: number;
|
|
31
|
+
/** 是否来自用户交互(点击/键盘) */
|
|
32
|
+
fromInteraction: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// 配置常量
|
|
37
|
+
// ============================================================================
|
|
38
|
+
|
|
39
|
+
/** 用户意图有效期(毫秒) */
|
|
40
|
+
const INTENT_TTL = 5000;
|
|
41
|
+
|
|
42
|
+
/** 交互事件结束后的保护窗口期(毫秒)
|
|
43
|
+
* 用于处理 click → setTimeout(() => navigate(), 0) 这种微延迟情况
|
|
44
|
+
*/
|
|
45
|
+
const INTERACTION_GRACE_PERIOD = 100;
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// 状态
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
/** 当前用户意图 */
|
|
52
|
+
let currentIntent: UserIntent | null = null;
|
|
53
|
+
|
|
54
|
+
/** 是否正在用户交互中(click/keydown 事件处理期间) */
|
|
55
|
+
let isInUserInteraction = false;
|
|
56
|
+
|
|
57
|
+
/** 交互结束时间(用于 grace period) */
|
|
58
|
+
let interactionEndTime = 0;
|
|
59
|
+
|
|
60
|
+
/** 当前用户交互开始时间(用于判断意图是否在当前交互期间设置的) */
|
|
61
|
+
let currentInteractionStartTime = 0;
|
|
62
|
+
|
|
63
|
+
/** 调试模式 */
|
|
64
|
+
const DEBUG = true;
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// 工具函数
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
function log(...args: unknown[]): void {
|
|
71
|
+
if (DEBUG) {
|
|
72
|
+
console.log('🛡️[RouteGuard]', ...args);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function warn(...args: unknown[]): void {
|
|
77
|
+
console.warn('🛡️[RouteGuard]', ...args);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 获取路径的基础部分(第一级路径)
|
|
82
|
+
* /lineup/list → /lineup
|
|
83
|
+
* /permission/role → /permission
|
|
84
|
+
*/
|
|
85
|
+
function getBasePath(path: string): string {
|
|
86
|
+
const normalized = path.startsWith('/') ? path : '/' + path;
|
|
87
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
88
|
+
return '/' + (segments[0] || '');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 解析 URL 获取 pathname
|
|
93
|
+
*/
|
|
94
|
+
function parsePathname(url: string | URL | null | undefined): string | null {
|
|
95
|
+
if (!url) return null;
|
|
96
|
+
|
|
97
|
+
if (typeof url === 'string') {
|
|
98
|
+
// 相对路径
|
|
99
|
+
if (url.startsWith('/')) return url.split('?')[0].split('#')[0];
|
|
100
|
+
// 绝对 URL
|
|
101
|
+
try {
|
|
102
|
+
return new URL(url, window.location.origin).pathname;
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return url.pathname;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 检查意图是否有效
|
|
113
|
+
*/
|
|
114
|
+
function isIntentValid(): boolean {
|
|
115
|
+
if (!currentIntent) return false;
|
|
116
|
+
return Date.now() - currentIntent.timestamp < INTENT_TTL;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 是否在用户交互上下文中
|
|
121
|
+
* 包括:正在交互 或 刚刚结束交互(grace period 内)
|
|
122
|
+
*/
|
|
123
|
+
function isInInteractionContext(): boolean {
|
|
124
|
+
if (isInUserInteraction) return true;
|
|
125
|
+
if (Date.now() - interactionEndTime < INTERACTION_GRACE_PERIOD) return true;
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ============================================================================
|
|
130
|
+
// 公开 API
|
|
131
|
+
// ============================================================================
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 设置用户意图(供 MicroAppManager 内部调用)
|
|
135
|
+
* 业务代码不需要调用此函数,路由守卫会自动检测用户交互
|
|
136
|
+
*/
|
|
137
|
+
export function setUserIntent(path: string): void {
|
|
138
|
+
log('设置意图:', path);
|
|
139
|
+
currentIntent = {
|
|
140
|
+
path,
|
|
141
|
+
timestamp: Date.now(),
|
|
142
|
+
fromInteraction: false,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 刷新意图时间戳(供 MicroAppManager 内部调用)
|
|
148
|
+
* 在长时间操作(如加载微应用)过程中保持意图有效
|
|
149
|
+
*/
|
|
150
|
+
export function refreshUserIntent(): void {
|
|
151
|
+
if (currentIntent) {
|
|
152
|
+
currentIntent.timestamp = Date.now();
|
|
153
|
+
log('刷新意图:', currentIntent.path);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 清除用户意图
|
|
159
|
+
*/
|
|
160
|
+
export function clearUserIntent(): void {
|
|
161
|
+
log('清除意图');
|
|
162
|
+
currentIntent = null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 获取当前意图信息(调试用)
|
|
167
|
+
*/
|
|
168
|
+
export function getIntentDebugInfo(): object {
|
|
169
|
+
return {
|
|
170
|
+
intent: currentIntent,
|
|
171
|
+
isValid: isIntentValid(),
|
|
172
|
+
isInInteraction: isInUserInteraction,
|
|
173
|
+
interactionEndTime,
|
|
174
|
+
isInContext: isInInteractionContext(),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// 路由守卫核心逻辑
|
|
180
|
+
// ============================================================================
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 判断是否应该拦截此次导航
|
|
184
|
+
*/
|
|
185
|
+
function shouldIntercept(targetPath: string): boolean {
|
|
186
|
+
const targetBase = getBasePath(targetPath);
|
|
187
|
+
|
|
188
|
+
// 1. 在用户交互上下文中
|
|
189
|
+
if (isInInteractionContext()) {
|
|
190
|
+
// 检查是否有有效意图,且意图是在当前用户交互期间由 setUserIntent 设置的
|
|
191
|
+
// 只有这种情况才需要检查匹配,以防止微应用在 mount 过程中推送错误的路由
|
|
192
|
+
//
|
|
193
|
+
// 关键区分:
|
|
194
|
+
// - 意图在当前交互之前设置(如页面加载时):允许用户的新导航覆盖旧意图
|
|
195
|
+
// - 意图在当前交互期间由 setUserIntent 设置:正在进行微应用切换,需要保护
|
|
196
|
+
if (isIntentValid() && !currentIntent!.fromInteraction) {
|
|
197
|
+
// 检查意图是否在当前交互期间设置的
|
|
198
|
+
const isIntentFromCurrentInteraction = currentIntent!.timestamp >= currentInteractionStartTime;
|
|
199
|
+
if (isIntentFromCurrentInteraction) {
|
|
200
|
+
const intentBase = getBasePath(currentIntent!.path);
|
|
201
|
+
if (targetBase !== intentBase) {
|
|
202
|
+
warn('❌ 拦截不匹配意图的导航(交互上下文中):', {
|
|
203
|
+
targetPath,
|
|
204
|
+
targetBase,
|
|
205
|
+
intentPath: currentIntent!.path,
|
|
206
|
+
intentBase,
|
|
207
|
+
});
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// 目标匹配意图、无有效意图、或意图是旧的,允许导航并更新意图
|
|
213
|
+
log('用户交互触发导航:', targetPath);
|
|
214
|
+
currentIntent = {
|
|
215
|
+
path: targetPath,
|
|
216
|
+
timestamp: Date.now(),
|
|
217
|
+
fromInteraction: true,
|
|
218
|
+
};
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 2. 没有有效意图,允许导航
|
|
223
|
+
if (!isIntentValid()) {
|
|
224
|
+
log('无有效意图,允许导航:', targetPath);
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 3. 有有效意图,检查目标路径是否匹配
|
|
229
|
+
const intentBase = getBasePath(currentIntent!.path);
|
|
230
|
+
|
|
231
|
+
if (targetBase === intentBase) {
|
|
232
|
+
log('目标匹配意图,允许导航:', { targetPath, intentPath: currentIntent!.path });
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 4. 不匹配,拦截
|
|
237
|
+
warn('❌ 拦截程序触发的导航:', {
|
|
238
|
+
targetPath,
|
|
239
|
+
targetBase,
|
|
240
|
+
intentPath: currentIntent!.path,
|
|
241
|
+
intentBase,
|
|
242
|
+
intentAge: Date.now() - currentIntent!.timestamp + 'ms',
|
|
243
|
+
});
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ============================================================================
|
|
248
|
+
// 初始化
|
|
249
|
+
// ============================================================================
|
|
250
|
+
|
|
251
|
+
if (typeof window !== 'undefined') {
|
|
252
|
+
// 保存原始方法
|
|
253
|
+
const originalPushState = window.history.pushState.bind(window.history);
|
|
254
|
+
const originalReplaceState = window.history.replaceState.bind(window.history);
|
|
255
|
+
|
|
256
|
+
// 拦截 pushState
|
|
257
|
+
window.history.pushState = function (
|
|
258
|
+
state: unknown,
|
|
259
|
+
unused: string,
|
|
260
|
+
url?: string | URL | null,
|
|
261
|
+
) {
|
|
262
|
+
const targetPath = parsePathname(url);
|
|
263
|
+
log('pushState 被调用:', { url, targetPath, isInInteraction: isInUserInteraction });
|
|
264
|
+
|
|
265
|
+
if (targetPath && shouldIntercept(targetPath)) {
|
|
266
|
+
// 拦截,不执行导航
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return originalPushState(state, unused, url);
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// 拦截 replaceState
|
|
274
|
+
window.history.replaceState = function (
|
|
275
|
+
state: unknown,
|
|
276
|
+
unused: string,
|
|
277
|
+
url?: string | URL | null,
|
|
278
|
+
) {
|
|
279
|
+
const targetPath = parsePathname(url);
|
|
280
|
+
log('replaceState 被调用:', { url, targetPath, isInInteraction: isInUserInteraction });
|
|
281
|
+
|
|
282
|
+
if (targetPath && shouldIntercept(targetPath)) {
|
|
283
|
+
// 拦截,不执行导航
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return originalReplaceState(state, unused, url);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// 监听用户交互事件
|
|
291
|
+
// 使用 capture 阶段确保在任何其他处理器之前执行
|
|
292
|
+
const markInteractionStart = (event: Event) => {
|
|
293
|
+
// 只处理可能触发导航的交互
|
|
294
|
+
if (event.type === 'click' || event.type === 'keydown') {
|
|
295
|
+
const keyEvent = event as KeyboardEvent;
|
|
296
|
+
// 只有 Enter 键可能触发导航
|
|
297
|
+
if (event.type === 'keydown' && keyEvent.key !== 'Enter') {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// log('用户交互开始:', event.type);
|
|
302
|
+
isInUserInteraction = true;
|
|
303
|
+
// 记录交互开始时间,用于判断意图是否在当前交互期间设置的
|
|
304
|
+
currentInteractionStartTime = Date.now();
|
|
305
|
+
|
|
306
|
+
// 在当前事件循环结束后重置标记
|
|
307
|
+
// 使用 setTimeout 0 确保同步代码执行完毕
|
|
308
|
+
setTimeout(() => {
|
|
309
|
+
isInUserInteraction = false;
|
|
310
|
+
interactionEndTime = Date.now();
|
|
311
|
+
// log('用户交互结束');
|
|
312
|
+
}, 0);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
document.addEventListener('click', markInteractionStart, { capture: true });
|
|
317
|
+
document.addEventListener('keydown', markInteractionStart, { capture: true });
|
|
318
|
+
|
|
319
|
+
// 监听 popstate 事件(浏览器前进/后退)
|
|
320
|
+
window.addEventListener('popstate', () => {
|
|
321
|
+
// 浏览器前进后退是用户行为,更新意图
|
|
322
|
+
const newPath = window.location.pathname;
|
|
323
|
+
log('popstate 事件:', newPath);
|
|
324
|
+
currentIntent = {
|
|
325
|
+
path: newPath,
|
|
326
|
+
timestamp: Date.now(),
|
|
327
|
+
fromInteraction: true,
|
|
328
|
+
};
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
microAppLogger.log('RouteGuard initialized');
|
|
332
|
+
|
|
333
|
+
// 暴露调试接口
|
|
334
|
+
(window as unknown as Record<string, unknown>).__ROUTE_GUARD__ = {
|
|
335
|
+
getDebugInfo: getIntentDebugInfo,
|
|
336
|
+
setIntent: setUserIntent,
|
|
337
|
+
clearIntent: clearUserIntent,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export default {
|
|
342
|
+
setUserIntent,
|
|
343
|
+
clearUserIntent,
|
|
344
|
+
getIntentDebugInfo,
|
|
345
|
+
};
|