generator-mico-cli 0.2.31 → 0.2.33
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 +145 -18
- package/bin/mico.js +76 -0
- package/generators/h5-react/ignore-list.json +1 -0
- package/generators/h5-react/index.js +349 -0
- package/generators/h5-react/meta.json +11 -0
- package/generators/h5-react/templates/.commitlintrc.js +7 -0
- package/generators/h5-react/templates/.cursor/rules/cicd-deploy.mdc +104 -0
- package/generators/h5-react/templates/.cursor/rules/common-intl.mdc +42 -0
- package/generators/h5-react/templates/.cursor/rules/git-hooks.mdc +40 -0
- package/generators/h5-react/templates/.cursor/rules/internal-packages.mdc +46 -0
- package/generators/h5-react/templates/.cursor/rules/monorepo.mdc +64 -0
- package/generators/h5-react/templates/.cursor/rules/package-json.mdc +52 -0
- package/generators/h5-react/templates/.cursor/rules/tailwind-umi.mdc +60 -0
- package/generators/h5-react/templates/.cursor/rules/umi-app.mdc +74 -0
- package/generators/h5-react/templates/.cursor/rules/umi-config.mdc +86 -0
- package/generators/h5-react/templates/.cursor/rules/umi-mock.mdc +80 -0
- package/generators/h5-react/templates/.cursor/rules/workspace-request.mdc +52 -0
- package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/SKILL.md +213 -0
- package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/evals/evals.json +23 -0
- package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/cursor-rule-template.md +60 -0
- package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/phase-1-scanning.md +102 -0
- package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/phase-2-context-analysis.md +102 -0
- package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/phase-3-pattern-extraction.md +105 -0
- package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/phase-4-module-mapping.md +65 -0
- package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/phase-5-glossary.md +63 -0
- package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/templates/DEV_PATTERNS.tpl.md +77 -0
- package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/templates/GLOSSARY.tpl.md +17 -0
- package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/templates/MODULE_MAP.tpl.md +45 -0
- package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/templates/PROJECT_CONTEXT.tpl.md +155 -0
- package/generators/h5-react/templates/.cursor/skills/biz-app-analyzer/references/update-mode.md +116 -0
- package/generators/h5-react/templates/.env.development +5 -0
- package/generators/h5-react/templates/.env.production +5 -0
- package/generators/h5-react/templates/.env.testing +5 -0
- package/generators/h5-react/templates/.husky/commit-msg +2 -0
- package/generators/h5-react/templates/.husky/pre-commit +2 -0
- package/generators/h5-react/templates/.lintstagedrc.js +8 -0
- package/generators/h5-react/templates/.prettierrc.json +7 -0
- package/generators/h5-react/templates/CICD/before_build.sh +76 -0
- package/generators/h5-react/templates/CICD/start_dev.sh +54 -0
- package/generators/h5-react/templates/CICD/start_local.sh +30 -0
- package/generators/h5-react/templates/CICD/start_prod.sh +53 -0
- package/generators/h5-react/templates/CICD/start_test.sh +55 -0
- package/generators/h5-react/templates/CICD/wangsu_fresh_dev.sh +19 -0
- package/generators/h5-react/templates/CICD/wangsu_fresh_prod.sh +19 -0
- package/generators/h5-react/templates/CICD/wangsu_fresh_test.sh +19 -0
- package/generators/h5-react/templates/README.md +301 -0
- package/generators/h5-react/templates/_gitignore +30 -0
- package/generators/h5-react/templates/_npmrc +6 -0
- package/generators/h5-react/templates/apps/.gitkeep +0 -0
- package/generators/h5-react/templates/dev.preset.json +10 -0
- package/generators/h5-react/templates/package.json +56 -0
- package/generators/h5-react/templates/packages/common-intl/README.md +180 -0
- package/generators/h5-react/templates/packages/common-intl/eslint.config.ts +12 -0
- package/generators/h5-react/templates/packages/common-intl/package.json +31 -0
- package/generators/h5-react/templates/packages/common-intl/src/index.ts +3 -0
- package/generators/h5-react/templates/packages/common-intl/src/intl.ts +100 -0
- package/generators/h5-react/templates/packages/common-intl/tsconfig.json +3 -0
- package/generators/h5-react/templates/packages/components/eslint.config.ts +12 -0
- package/generators/h5-react/templates/packages/components/package.json +32 -0
- package/generators/h5-react/templates/packages/components/src/Layout/ImmersiveHeader.tsx +126 -0
- package/generators/h5-react/templates/packages/components/src/Layout/LayoutFooter.tsx +72 -0
- package/generators/h5-react/templates/packages/components/src/Layout/index.tsx +121 -0
- package/generators/h5-react/templates/packages/components/src/assets/image/back.png +0 -0
- package/generators/h5-react/templates/packages/components/src/index.ts +0 -0
- package/generators/h5-react/templates/packages/components/tsconfig.json +13 -0
- package/generators/h5-react/templates/packages/components/typings.d.ts +1 -0
- package/generators/h5-react/templates/packages/constant/eslint.config.ts +12 -0
- package/generators/h5-react/templates/packages/constant/package.json +19 -0
- package/generators/h5-react/templates/packages/constant/src/index.ts +0 -0
- package/generators/h5-react/templates/packages/constant/src/member.ts +8 -0
- package/generators/h5-react/templates/packages/constant/tsconfig.json +3 -0
- package/generators/h5-react/templates/packages/deeplink/eslint.config.ts +12 -0
- package/generators/h5-react/templates/packages/deeplink/package.json +18 -0
- package/generators/h5-react/templates/packages/deeplink/src/index.ts +7 -0
- package/generators/h5-react/templates/packages/deeplink/tsconfig.json +3 -0
- package/generators/h5-react/templates/packages/domain/eslint.config.ts +12 -0
- package/generators/h5-react/templates/packages/domain/package.json +18 -0
- package/generators/h5-react/templates/packages/domain/src/index.ts +29 -0
- package/generators/h5-react/templates/packages/domain/tsconfig.json +3 -0
- package/generators/h5-react/templates/packages/domain/types.d.ts +11 -0
- package/generators/h5-react/templates/packages/eslint/eslint.config.base.ts +36 -0
- package/generators/h5-react/templates/packages/eslint/eslint.config.react.ts +33 -0
- package/generators/h5-react/templates/packages/eslint/package.json +22 -0
- package/generators/h5-react/templates/packages/js-bridge/eslint.config.ts +17 -0
- package/generators/h5-react/templates/packages/js-bridge/package.json +23 -0
- package/generators/h5-react/templates/packages/js-bridge/src/call.ts +126 -0
- package/generators/h5-react/templates/packages/js-bridge/src/closeH5Page.ts +9 -0
- package/generators/h5-react/templates/packages/js-bridge/src/getUserInfo.ts +96 -0
- package/generators/h5-react/templates/packages/js-bridge/src/index.ts +15 -0
- package/generators/h5-react/templates/packages/js-bridge/tsconfig.json +3 -0
- package/generators/h5-react/templates/packages/js-bridge/type.d.ts +24 -0
- package/generators/h5-react/templates/packages/request/axios.d.ts +42 -0
- package/generators/h5-react/templates/packages/request/eslint.config.ts +17 -0
- package/generators/h5-react/templates/packages/request/package.json +22 -0
- package/generators/h5-react/templates/packages/request/src/index.ts +165 -0
- package/generators/h5-react/templates/packages/request/src/interceptors.ts +126 -0
- package/generators/h5-react/templates/packages/request/src/types.ts +101 -0
- package/generators/h5-react/templates/packages/request/src/url-resolver.ts +66 -0
- package/generators/h5-react/templates/packages/request/src/utils.ts +12 -0
- package/generators/h5-react/templates/packages/request/tsconfig.json +3 -0
- package/generators/h5-react/templates/packages/request/umi.d.ts +94 -0
- package/generators/h5-react/templates/packages/typescript/package.json +11 -0
- package/generators/h5-react/templates/packages/typescript/tsconfig.base.json +23 -0
- package/generators/h5-react/templates/packages/typescript/tsconfig.react.json +7 -0
- package/generators/h5-react/templates/packages/umi-config/eslint.config.ts +12 -0
- package/generators/h5-react/templates/packages/umi-config/package.json +31 -0
- package/generators/h5-react/templates/packages/umi-config/src/config.dev.ts +34 -0
- package/generators/h5-react/templates/packages/umi-config/src/config.prod.development.ts +17 -0
- package/generators/h5-react/templates/packages/umi-config/src/config.prod.production.ts +42 -0
- package/generators/h5-react/templates/packages/umi-config/src/config.prod.testing.ts +17 -0
- package/generators/h5-react/templates/packages/umi-config/src/config.prod.ts +56 -0
- package/generators/h5-react/templates/packages/umi-config/src/config.ts +86 -0
- package/generators/h5-react/templates/packages/umi-config/src/index.ts +25 -0
- package/generators/h5-react/templates/packages/umi-config/src/plugins/apply-sentry-plugin.ts +57 -0
- package/generators/h5-react/templates/packages/umi-config/src/type.d.ts +3 -0
- package/generators/h5-react/templates/packages/umi-config/tsconfig.json +3 -0
- package/generators/h5-react/templates/packages/utils/eslint.config.ts +12 -0
- package/generators/h5-react/templates/packages/utils/package.json +27 -0
- package/generators/h5-react/templates/packages/utils/src/date.ts +21 -0
- package/generators/h5-react/templates/packages/utils/src/env.ts +40 -0
- package/generators/h5-react/templates/packages/utils/src/index.ts +3 -0
- package/generators/h5-react/templates/packages/utils/src/md5.ts +17 -0
- package/generators/h5-react/templates/packages/utils/src/mock.ts +83 -0
- package/generators/h5-react/templates/packages/utils/src/number.ts +23 -0
- package/generators/h5-react/templates/packages/utils/src/tailwind.ts +12 -0
- package/generators/h5-react/templates/packages/utils/src/url.ts +19 -0
- package/generators/h5-react/templates/packages/utils/tsconfig.json +9 -0
- package/generators/h5-react/templates/page.config.ts +1 -0
- package/generators/h5-react/templates/pnpm-workspace.yaml +17 -0
- package/generators/h5-react/templates/scripts/collect-dist.js +78 -0
- package/generators/h5-react/templates/scripts/dev-preset.js +265 -0
- package/generators/h5-react/templates/scripts/dev-preset.schema.json +39 -0
- package/generators/h5-react/templates/scripts/dev.js +133 -0
- package/generators/h5-react/templates/scripts/gateway.ts +241 -0
- package/generators/h5-react/templates/turbo.json +86 -0
- package/generators/micro-react/templates/apps/layout/config/config.dev.ts +2 -0
- package/generators/micro-react/templates/apps/layout/docs/feature-/345/244/264/351/203/250/344/270/200/347/272/247/345/257/274/350/210/252/345/270/203/345/261/200.md +166 -0
- package/generators/micro-react/templates/apps/layout/src/app.tsx +20 -2
- package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +36 -0
- package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +6 -0
- package/generators/micro-react/templates/apps/layout/src/common/request/index.ts +8 -2
- package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +128 -11
- package/generators/micro-react/templates/apps/layout/src/constants/index.ts +22 -0
- package/generators/micro-react/templates/apps/layout/src/hooks/useFilteredMenuItems.ts +29 -0
- package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.less +73 -0
- package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +51 -4
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +25 -18
- package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +4 -0
- package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +4 -0
- package/generators/micro-react/templates/apps/layout/src/services/config/index.ts +1 -0
- package/generators/micro-react/templates/packages/common-intl/src/intl.ts +20 -0
- package/generators/subapp-h5/ignore-list.json +1 -0
- package/generators/subapp-h5/index.js +424 -0
- package/generators/subapp-h5/meta.json +10 -0
- package/generators/subapp-h5/templates/.env +1 -0
- package/generators/subapp-h5/templates/.stylelintrc.js +22 -0
- package/generators/subapp-h5/templates/config/config.dev.ts +7 -0
- package/generators/subapp-h5/templates/config/config.prod.development.ts +7 -0
- package/generators/subapp-h5/templates/config/config.prod.production.ts +10 -0
- package/generators/subapp-h5/templates/config/config.prod.testing.ts +7 -0
- package/generators/subapp-h5/templates/config/config.prod.ts +7 -0
- package/generators/subapp-h5/templates/config/config.ts +6 -0
- package/generators/subapp-h5/templates/config/routes.ts +13 -0
- package/generators/subapp-h5/templates/eslint.config.ts +12 -0
- package/generators/subapp-h5/templates/mock/user.ts +34 -0
- package/generators/subapp-h5/templates/package.json +42 -0
- package/generators/subapp-h5/templates/src/app.tsx +14 -0
- package/generators/subapp-h5/templates/src/assets/yay.jpg +0 -0
- package/generators/subapp-h5/templates/src/intl.ts +37 -0
- package/generators/subapp-h5/templates/src/layouts/index.tsx +10 -0
- package/generators/subapp-h5/templates/src/pages/index.tsx +22 -0
- package/generators/subapp-h5/templates/src/services/user.ts +38 -0
- package/generators/subapp-h5/templates/tailwind.config.js +16 -0
- package/generators/subapp-h5/templates/tailwind.css +7 -0
- package/generators/subapp-h5/templates/tsconfig.json +3 -0
- package/generators/subapp-h5/templates/typings.d.ts +1 -0
- package/lib/setup-multica-desktop.js +154 -0
- package/lib/utils.js +25 -0
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ import { isNoAuthRoute, ROUTES } from '@/constants';
|
|
|
13
13
|
import { captureError } from '@common-web/sentry';
|
|
14
14
|
import { formatLayoutMessage } from '@/common/intl';
|
|
15
15
|
import { Modal } from '@mico-platform/ui';
|
|
16
|
+
import React from 'react';
|
|
16
17
|
import {
|
|
17
18
|
getTicketParam,
|
|
18
19
|
resolveAuthToken,
|
|
@@ -36,6 +37,38 @@ let tokenAcquiredAt: number | null = null;
|
|
|
36
37
|
/** 登录失败弹窗是否已显示,防止多个请求同时失败时重复弹出 */
|
|
37
38
|
let redirectLimitModalShown = false;
|
|
38
39
|
|
|
40
|
+
/** 是否正在执行 SSO 硬跳转,置位后用于阻断渲染与拦截在途请求 */
|
|
41
|
+
let redirecting = false;
|
|
42
|
+
|
|
43
|
+
/** 是否正在执行 SSO 硬跳转 */
|
|
44
|
+
export const isRedirecting = (): boolean => redirecting;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* ticket 换 token 阶段的网络层故障(Failed to fetch / NetworkError 等)。
|
|
48
|
+
* 由 ensureSsoSession() 在 catch 阶段抛出,供调用方识别是否为网络异常,
|
|
49
|
+
* 避免使用模块级 mutable 状态在函数间传递信号。
|
|
50
|
+
*/
|
|
51
|
+
export class SsoNetworkError extends Error {
|
|
52
|
+
constructor(
|
|
53
|
+
message: string,
|
|
54
|
+
public readonly cause?: unknown,
|
|
55
|
+
) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.name = 'SsoNetworkError';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 判断错误是否为网络层故障(无法建立连接,而非 HTTP 错误响应) */
|
|
62
|
+
const isNetworkError = (err: Error): boolean => {
|
|
63
|
+
const msg = err.message.toLowerCase();
|
|
64
|
+
return (
|
|
65
|
+
msg.includes('failed to fetch') ||
|
|
66
|
+
msg.includes('networkerror') ||
|
|
67
|
+
msg.includes('network error') ||
|
|
68
|
+
msg.includes('load failed')
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
39
72
|
/**
|
|
40
73
|
* 执行 SSO 重定向跳转,回跳地址中固定携带 redirect_count=1
|
|
41
74
|
*/
|
|
@@ -49,6 +82,9 @@ const performSsoRedirect = (): void => {
|
|
|
49
82
|
redirectUrl.searchParams.delete('ticket');
|
|
50
83
|
const serviceUrl = redirectUrl.toString();
|
|
51
84
|
|
|
85
|
+
// 在赋值 location.href 之前置位,浏览器导航期间用于阻断渲染与拦截在途请求
|
|
86
|
+
redirecting = true;
|
|
87
|
+
|
|
52
88
|
window.location.href = `${
|
|
53
89
|
externalLoginPath ?? ROUTES.LOGIN
|
|
54
90
|
}?service=${encodeURIComponent(serviceUrl)}`;
|
|
@@ -56,21 +92,28 @@ const performSsoRedirect = (): void => {
|
|
|
56
92
|
|
|
57
93
|
/**
|
|
58
94
|
* 处理认证失败后的重定向
|
|
95
|
+
*
|
|
96
|
+
* @param options.isNetworkError 是否由 ticket 换 token 阶段的网络异常触发,
|
|
97
|
+
* true 时弹窗展示 IOA/网络排障指引;其它认证失败入口(401 重试失败、登出等)
|
|
98
|
+
* 不传或传 false,使用默认「自动登录失败」文案。
|
|
59
99
|
*/
|
|
60
|
-
export const handleAuthFailureRedirect = (
|
|
61
|
-
|
|
100
|
+
export const handleAuthFailureRedirect = (
|
|
101
|
+
options: { isNetworkError?: boolean } = {},
|
|
102
|
+
): boolean => {
|
|
103
|
+
const { isNetworkError: isNetworkErr = false } = options;
|
|
104
|
+
if (typeof window === 'undefined') return false;
|
|
62
105
|
|
|
63
106
|
const authCheckPath = resolveAuthCheckPath(window.location.pathname);
|
|
64
107
|
|
|
65
108
|
// 与 getInitialState 一致:用 authCheckPath 判断,避免「/ + defaultPath」首屏仍按 / 误判为免认证
|
|
66
109
|
if (isNoAuthRoute(authCheckPath)) {
|
|
67
110
|
console.log('[SSO] 当前路由在免认证列表中,跳过 SSO 重定向', { authCheckPath });
|
|
68
|
-
return;
|
|
111
|
+
return false;
|
|
69
112
|
}
|
|
70
113
|
|
|
71
114
|
if (isPageAuthFree(authCheckPath)) {
|
|
72
115
|
console.log('[SSO] 页面 accessControlEnabled=false,跳过 SSO 重定向', { authCheckPath });
|
|
73
|
-
return;
|
|
116
|
+
return false;
|
|
74
117
|
}
|
|
75
118
|
|
|
76
119
|
// 从 URL 中获取当前 redirect 登录的次数
|
|
@@ -85,7 +128,7 @@ export const handleAuthFailureRedirect = (): void => {
|
|
|
85
128
|
// 如果 redirect 次数小于 1 次,则执行自动 redirect 跳转登录
|
|
86
129
|
if (redirectCount < 1) {
|
|
87
130
|
performSsoRedirect();
|
|
88
|
-
return;
|
|
131
|
+
return true;
|
|
89
132
|
}
|
|
90
133
|
|
|
91
134
|
// redirect 次数已达上限,停止自动重定向,记录日志并弹窗询问用户是否手动重试
|
|
@@ -105,18 +148,69 @@ export const handleAuthFailureRedirect = (): void => {
|
|
|
105
148
|
? new Date(tokenAcquiredAt).toISOString()
|
|
106
149
|
: null,
|
|
107
150
|
tokenAgeSeconds: tokenAge,
|
|
151
|
+
isNetworkError: isNetworkErr,
|
|
108
152
|
},
|
|
109
153
|
});
|
|
110
154
|
|
|
111
155
|
if (redirectLimitModalShown) {
|
|
112
156
|
console.log('[SSO] 登录失败弹窗已显示,跳过重复弹出');
|
|
113
|
-
return;
|
|
157
|
+
return false;
|
|
114
158
|
}
|
|
115
159
|
|
|
116
160
|
redirectLimitModalShown = true;
|
|
161
|
+
const secondaryStyle: React.CSSProperties = {
|
|
162
|
+
marginTop: 8,
|
|
163
|
+
color: 'var(--color-text-3, #86909c)',
|
|
164
|
+
fontSize: 13,
|
|
165
|
+
};
|
|
166
|
+
const modalContent = isNetworkErr
|
|
167
|
+
? React.createElement(
|
|
168
|
+
'div',
|
|
169
|
+
null,
|
|
170
|
+
React.createElement(
|
|
171
|
+
'div',
|
|
172
|
+
null,
|
|
173
|
+
formatLayoutMessage({
|
|
174
|
+
id: 'sso_auth_failure_network_primary',
|
|
175
|
+
defaultMessage: '网络异常,无法完成自动登录',
|
|
176
|
+
}),
|
|
177
|
+
),
|
|
178
|
+
React.createElement(
|
|
179
|
+
'div',
|
|
180
|
+
{ style: secondaryStyle },
|
|
181
|
+
React.createElement(
|
|
182
|
+
'div',
|
|
183
|
+
null,
|
|
184
|
+
formatLayoutMessage({
|
|
185
|
+
id: 'sso_auth_failure_network_hint_title',
|
|
186
|
+
defaultMessage: '请检查以下情况:',
|
|
187
|
+
}),
|
|
188
|
+
),
|
|
189
|
+
React.createElement(
|
|
190
|
+
'div',
|
|
191
|
+
null,
|
|
192
|
+
formatLayoutMessage({
|
|
193
|
+
id: 'sso_auth_failure_network_hint_ioa',
|
|
194
|
+
defaultMessage: '1. IOA 是否已连接',
|
|
195
|
+
}),
|
|
196
|
+
),
|
|
197
|
+
React.createElement(
|
|
198
|
+
'div',
|
|
199
|
+
null,
|
|
200
|
+
formatLayoutMessage({
|
|
201
|
+
id: 'sso_auth_failure_network_hint_network',
|
|
202
|
+
defaultMessage: '2. 当前网络是否可正常访问',
|
|
203
|
+
}),
|
|
204
|
+
),
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
: formatLayoutMessage({
|
|
208
|
+
id: 'sso_auth_failure_modal_content',
|
|
209
|
+
defaultMessage: '自动登录失败,是否重新尝试登录?',
|
|
210
|
+
});
|
|
117
211
|
Modal.confirm({
|
|
118
212
|
title: formatLayoutMessage({ id: 'sso_auth_failure_modal_title', defaultMessage: '登录提示' }),
|
|
119
|
-
content:
|
|
213
|
+
content: modalContent,
|
|
120
214
|
okText: formatLayoutMessage({ id: 'sso_auth_failure_modal_ok', defaultMessage: '重新登录' }),
|
|
121
215
|
cancelText: formatLayoutMessage({ id: 'sso_auth_failure_modal_cancel', defaultMessage: '取消' }),
|
|
122
216
|
onOk: () => {
|
|
@@ -128,6 +222,9 @@ export const handleAuthFailureRedirect = (): void => {
|
|
|
128
222
|
redirectLimitModalShown = false;
|
|
129
223
|
},
|
|
130
224
|
});
|
|
225
|
+
|
|
226
|
+
// 仅弹出 Modal 询问,未执行硬跳转
|
|
227
|
+
return false;
|
|
131
228
|
};
|
|
132
229
|
|
|
133
230
|
/**
|
|
@@ -180,12 +277,32 @@ export const ensureSsoSession = async (): Promise<void> => {
|
|
|
180
277
|
throw new Error('SSO 认证失败');
|
|
181
278
|
}
|
|
182
279
|
} catch (error) {
|
|
183
|
-
|
|
280
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
281
|
+
const networkError = isNetworkError(err);
|
|
282
|
+
console.error('[SSO] ticket 交换失败', err.message, err.stack);
|
|
283
|
+
captureError(err, {
|
|
284
|
+
tags: {
|
|
285
|
+
scene: 'sso_ticket_exchange_failed',
|
|
286
|
+
is_network_error: String(networkError),
|
|
287
|
+
},
|
|
288
|
+
extra: {
|
|
289
|
+
ticketParam,
|
|
290
|
+
message: err.message,
|
|
291
|
+
},
|
|
292
|
+
});
|
|
184
293
|
setFetchingToken(false);
|
|
185
294
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
295
|
+
const propagatedErr = networkError
|
|
296
|
+
? new SsoNetworkError(err.message, err)
|
|
297
|
+
: err;
|
|
298
|
+
rejectPendingRequests(propagatedErr);
|
|
299
|
+
|
|
300
|
+
// 仅在网络异常时向外抛出,让调用方据此切换 UI 文案;
|
|
301
|
+
// 其它失败(如 SSO 业务态失败)保持原有「静默」语义,由调用方
|
|
302
|
+
// 通过 token 是否就绪判断后续走向。
|
|
303
|
+
if (networkError) {
|
|
304
|
+
throw propagatedErr;
|
|
305
|
+
}
|
|
189
306
|
} finally {
|
|
190
307
|
removeParamFromUrl(ticketParam);
|
|
191
308
|
ticketPromise = null;
|
|
@@ -132,6 +132,28 @@ export const isAuthDisabled = (): boolean => {
|
|
|
132
132
|
return window.__MICO_CONFIG__?.disableAuth === true;
|
|
133
133
|
};
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* 布局模式
|
|
137
|
+
* - 'side':经典左侧完整目录树
|
|
138
|
+
* - 'mix':一级目录显示在头部 Tab,对应子级目录显示在侧边栏
|
|
139
|
+
*/
|
|
140
|
+
export type TLayoutMode = 'side' | 'mix';
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 获取布局模式
|
|
144
|
+
* 读取 window.__MICO_CONFIG__.layoutMode,未配置时默认 'side'(兼容旧版)
|
|
145
|
+
*/
|
|
146
|
+
export const getLayoutMode = (): TLayoutMode => {
|
|
147
|
+
return window.__MICO_CONFIG__?.layoutMode === 'mix' ? 'mix' : 'side';
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 判断是否为「头部一级 + 侧边二级」混合布局
|
|
152
|
+
*/
|
|
153
|
+
export const isMixLayout = (): boolean => {
|
|
154
|
+
return getLayoutMode() === 'mix';
|
|
155
|
+
};
|
|
156
|
+
|
|
135
157
|
/**
|
|
136
158
|
* 主题相关常量
|
|
137
159
|
*/
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ParsedMenuItem } from '@/common/menu';
|
|
2
|
+
import { filterMenuItems, parseMenuItems } from '@/common/menu';
|
|
3
|
+
import { getMenus } from '@/common/portal-data';
|
|
4
|
+
import { isAuthDisabled } from '@/constants';
|
|
5
|
+
import { useModel } from '@umijs/max';
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 权限过滤 + 解析后的菜单树
|
|
10
|
+
* 供侧边栏与头部一级导航共用,避免重复解析
|
|
11
|
+
*/
|
|
12
|
+
export const useFilteredMenuItems = (): ParsedMenuItem[] => {
|
|
13
|
+
const { initialState } = useModel('@@initialState');
|
|
14
|
+
const currentUser = initialState?.currentUser;
|
|
15
|
+
|
|
16
|
+
return useMemo(() => {
|
|
17
|
+
const menus = getMenus();
|
|
18
|
+
if (isAuthDisabled()) {
|
|
19
|
+
return parseMenuItems(menus);
|
|
20
|
+
}
|
|
21
|
+
const filteredMenus = filterMenuItems(menus, {
|
|
22
|
+
isSuperuser: currentUser?.is_superuser,
|
|
23
|
+
menuPerms: currentUser?.menu_perms || [],
|
|
24
|
+
});
|
|
25
|
+
return parseMenuItems(filteredMenus);
|
|
26
|
+
}, [currentUser?.is_superuser, currentUser?.menu_perms]);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default useFilteredMenuItems;
|
package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.less
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
.layout-header-logo {
|
|
41
41
|
display: flex;
|
|
42
42
|
align-items: center;
|
|
43
|
+
// side 布局保持原行为:Logo 居左,右侧内容靠右
|
|
43
44
|
margin-right: auto;
|
|
44
45
|
|
|
45
46
|
.logo-text {
|
|
@@ -47,12 +48,84 @@
|
|
|
47
48
|
font-weight: @font-weight-bold;
|
|
48
49
|
color: @Brand1-6;
|
|
49
50
|
letter-spacing: 1px;
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
white-space: nowrap;
|
|
53
|
+
text-overflow: ellipsis;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// mix 布局:Logo 列对齐侧边栏宽度,使一级导航起始 x 与内容区左边界对齐
|
|
58
|
+
// 左侧 padding(@spacing-lg) + 此宽度 = 侧边栏右边界(@sidebar-width)
|
|
59
|
+
.layout-header--mix .layout-header-logo {
|
|
60
|
+
width: calc(@sidebar-width - @spacing-lg);
|
|
61
|
+
margin-right: 0;
|
|
62
|
+
flex-shrink: 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 头部一级目录导航(mix 布局)
|
|
66
|
+
// 样式对齐 Figma 中台组件「水平 Tab」:14/22 PingFang SC,
|
|
67
|
+
// 激活态品牌紫 + Medium + 底部下划线,未激活 text/2 + Regular
|
|
68
|
+
.layout-header-nav {
|
|
69
|
+
flex: 1;
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
overflow: hidden;
|
|
73
|
+
margin-left: @spacing-xl;
|
|
74
|
+
|
|
75
|
+
.layout-header-tabs.arco-tabs {
|
|
76
|
+
width: 100%;
|
|
77
|
+
|
|
78
|
+
// 隐藏整条底部分隔线,仅保留激活态下划线,避免与头部阴影叠加
|
|
79
|
+
.arco-tabs-header-nav::before {
|
|
80
|
+
display: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.arco-tabs-header-title {
|
|
84
|
+
// 字号 14 / 行高 22,标题为「胶囊」形态,hover 显示浅灰圆角底
|
|
85
|
+
margin: 0 8px;
|
|
86
|
+
padding: 5px 12px;
|
|
87
|
+
font-size: 14px;
|
|
88
|
+
line-height: 22px;
|
|
89
|
+
font-weight: 400;
|
|
90
|
+
color: @color-text-2;
|
|
91
|
+
border-radius: @border-radius-md;
|
|
92
|
+
transition: background-color @animation-duration-fast,
|
|
93
|
+
color @animation-duration-fast;
|
|
94
|
+
|
|
95
|
+
&:first-child {
|
|
96
|
+
margin-left: 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 未选中 hover:仅加浅灰底(fill/2),文字保持灰色不变紫
|
|
100
|
+
&:hover {
|
|
101
|
+
color: @color-text-2;
|
|
102
|
+
background-color: @color-fill-2;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 选中态:品牌紫 + Medium
|
|
106
|
+
&-active {
|
|
107
|
+
color: @Brand1-6;
|
|
108
|
+
font-weight: 500;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 选中 + hover:品牌紫文字 + 浅灰圆角底
|
|
112
|
+
&-active:hover {
|
|
113
|
+
color: @Brand1-6;
|
|
114
|
+
background-color: @color-fill-2;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 激活下划线(ink bar):品牌紫
|
|
119
|
+
.arco-tabs-header-ink {
|
|
120
|
+
background-color: @Brand1-6;
|
|
121
|
+
}
|
|
50
122
|
}
|
|
51
123
|
}
|
|
52
124
|
|
|
53
125
|
.layout-header-right {
|
|
54
126
|
display: flex;
|
|
55
127
|
align-items: center;
|
|
128
|
+
margin-left: auto;
|
|
56
129
|
}
|
|
57
130
|
|
|
58
131
|
.layout-header-action {
|
package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx
CHANGED
|
@@ -1,14 +1,45 @@
|
|
|
1
|
+
import { findFirstLeaf, getActiveTopKey } from '@/common/menu';
|
|
1
2
|
import { AvatarDropdown, TenantDropdown } from '@/components/RightContent';
|
|
2
|
-
import { DEFAULT_NAME } from '@/constants';
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
3
|
+
import { DEFAULT_NAME, isMixLayout } from '@/constants';
|
|
4
|
+
import { useFilteredMenuItems } from '@/hooks/useFilteredMenuItems';
|
|
5
|
+
import { Layout, Space, Tabs } from '@mico-platform/ui';
|
|
6
|
+
import { history, useLocation } from '@umijs/max';
|
|
7
|
+
import React, { useMemo } from 'react';
|
|
5
8
|
import './index.less';
|
|
6
9
|
|
|
7
10
|
const Header = Layout.Header;
|
|
11
|
+
const TabPane = Tabs.TabPane;
|
|
8
12
|
|
|
9
13
|
const LayoutHeader: React.FC = () => {
|
|
14
|
+
const location = useLocation();
|
|
15
|
+
const menuItems = useFilteredMenuItems();
|
|
16
|
+
const mix = isMixLayout();
|
|
17
|
+
|
|
18
|
+
// 头部一级导航数据(仅 mix 布局使用顶层菜单项)
|
|
19
|
+
const topMenuItems = mix ? menuItems : [];
|
|
20
|
+
|
|
21
|
+
const activeTopKey = useMemo(() => {
|
|
22
|
+
if (!mix) return undefined;
|
|
23
|
+
return getActiveTopKey(topMenuItems, location.pathname);
|
|
24
|
+
}, [mix, topMenuItems, location.pathname]);
|
|
25
|
+
|
|
26
|
+
// 点击一级 Tab:跳转到该一级目录下第一个可跳转叶子
|
|
27
|
+
// - 外链(link):新窗口打开,避免外部 URL 被推进路由
|
|
28
|
+
// - 内部页面(page):路由跳转
|
|
29
|
+
const handleTopTabChange = (key: string) => {
|
|
30
|
+
const target = topMenuItems.find((item) => item.key === key);
|
|
31
|
+
if (!target) return;
|
|
32
|
+
const leaf = findFirstLeaf(target);
|
|
33
|
+
if (!leaf?.path) return;
|
|
34
|
+
if (leaf.type === 'link') {
|
|
35
|
+
window.open(leaf.path, '_blank');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
history.push(leaf.path);
|
|
39
|
+
};
|
|
40
|
+
|
|
10
41
|
return (
|
|
11
|
-
<Header className=
|
|
42
|
+
<Header className={`layout-header${mix ? ' layout-header--mix' : ''}`}>
|
|
12
43
|
{/* Logo */}
|
|
13
44
|
<div className="layout-header-logo">
|
|
14
45
|
<span className="logo-text">
|
|
@@ -16,6 +47,22 @@ const LayoutHeader: React.FC = () => {
|
|
|
16
47
|
</span>
|
|
17
48
|
</div>
|
|
18
49
|
|
|
50
|
+
{/* 一级目录导航(mix 布局) */}
|
|
51
|
+
{mix && topMenuItems.length > 0 && (
|
|
52
|
+
<div className="layout-header-nav">
|
|
53
|
+
<Tabs
|
|
54
|
+
className="layout-header-tabs"
|
|
55
|
+
type="line"
|
|
56
|
+
activeTab={activeTopKey}
|
|
57
|
+
onChange={handleTopTabChange}
|
|
58
|
+
>
|
|
59
|
+
{topMenuItems.map((item) => (
|
|
60
|
+
<TabPane key={item.key} title={item.label} />
|
|
61
|
+
))}
|
|
62
|
+
</Tabs>
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
|
|
19
66
|
{/* Right section */}
|
|
20
67
|
<div className="layout-header-right">
|
|
21
68
|
<Space size="medium">
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { ParsedMenuItem } from '@/common/menu';
|
|
2
|
-
import {
|
|
3
|
-
import { getMenus } from '@/common/portal-data';
|
|
2
|
+
import { getActiveTopKey } from '@/common/menu';
|
|
4
3
|
import IconFont from '@/components/IconFont';
|
|
5
|
-
import {
|
|
4
|
+
import { isMixLayout } from '@/constants';
|
|
5
|
+
import { useFilteredMenuItems } from '@/hooks/useFilteredMenuItems';
|
|
6
6
|
import { useMenuState } from '@/hooks/useMenuState';
|
|
7
7
|
import { useTheme } from '@/hooks/useTheme';
|
|
8
8
|
import { Layout, Menu } from '@mico-platform/ui';
|
|
9
9
|
import * as Icons from '@mico-platform/ui/icon';
|
|
10
|
-
import {
|
|
10
|
+
import { useLocation } from '@umijs/max';
|
|
11
11
|
import React, { useEffect, useMemo, useRef } from 'react';
|
|
12
12
|
import './index.less';
|
|
13
13
|
|
|
@@ -115,23 +115,25 @@ interface LayoutMenuProps {
|
|
|
115
115
|
const LayoutMenu: React.FC<LayoutMenuProps> = () => {
|
|
116
116
|
const siderRef = useRef<HTMLDivElement>(null);
|
|
117
117
|
const { isDark } = useTheme();
|
|
118
|
-
const
|
|
119
|
-
const currentUser = initialState?.currentUser;
|
|
118
|
+
const location = useLocation();
|
|
120
119
|
|
|
121
|
-
//
|
|
120
|
+
// 权限过滤 + 解析后的完整菜单树(与头部一级导航共用)
|
|
122
121
|
// getMenuItemFilterOutcome 内部已对每个菜单项单独检查 isNoPermissionRoute,
|
|
123
122
|
// 无需在此按当前页面路径全局跳过过滤,避免菜单可见性随页面变化
|
|
123
|
+
const allMenuItems = useFilteredMenuItems();
|
|
124
|
+
|
|
125
|
+
// mix 布局:侧边栏只渲染「当前激活一级目录」下的子树;side 布局:渲染完整树
|
|
124
126
|
const menuItems = useMemo(() => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return parseMenuItems(menus);
|
|
127
|
+
if (!isMixLayout()) {
|
|
128
|
+
return allMenuItems;
|
|
128
129
|
}
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
const activeTopKey = getActiveTopKey(allMenuItems, location.pathname);
|
|
131
|
+
const activeTop = allMenuItems.find((item) => item.key === activeTopKey);
|
|
132
|
+
return activeTop?.children ?? [];
|
|
133
|
+
}, [allMenuItems, location.pathname]);
|
|
134
|
+
|
|
135
|
+
// mix 布局下,当前一级目录没有子目录时隐藏侧边栏
|
|
136
|
+
const hideSider = isMixLayout() && menuItems.length === 0;
|
|
135
137
|
|
|
136
138
|
// 使用菜单状态 Hook
|
|
137
139
|
const {
|
|
@@ -150,10 +152,10 @@ const LayoutMenu: React.FC<LayoutMenuProps> = () => {
|
|
|
150
152
|
if (layoutContent) {
|
|
151
153
|
(layoutContent as HTMLElement).style.setProperty(
|
|
152
154
|
'--sider-width',
|
|
153
|
-
collapsed ? '48px' : '200px',
|
|
155
|
+
hideSider ? '0px' : collapsed ? '48px' : '200px',
|
|
154
156
|
);
|
|
155
157
|
}
|
|
156
|
-
}, [collapsed]);
|
|
158
|
+
}, [collapsed, hideSider]);
|
|
157
159
|
|
|
158
160
|
useEffect(() => {
|
|
159
161
|
const sider = siderRef.current;
|
|
@@ -166,6 +168,11 @@ const LayoutMenu: React.FC<LayoutMenuProps> = () => {
|
|
|
166
168
|
};
|
|
167
169
|
}, [handleMouseEnter, handleMouseLeave]);
|
|
168
170
|
|
|
171
|
+
// 一级目录无子目录:不渲染侧边栏(内容区占满,--sider-width 已置 0)
|
|
172
|
+
if (hideSider) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
169
176
|
// 点击触发按钮图标
|
|
170
177
|
const clickTriggerBtnIcon = collapsed
|
|
171
178
|
? 'webcs-outline_fold'
|
|
@@ -48,6 +48,10 @@ export default {
|
|
|
48
48
|
sso_auth_failure_modal_content: 'Auto login failed. Would you like to try logging in again?',
|
|
49
49
|
sso_auth_failure_modal_ok: 'Re-login',
|
|
50
50
|
sso_auth_failure_modal_cancel: 'Cancel',
|
|
51
|
+
sso_auth_failure_network_primary: 'Network error. Unable to complete auto login.',
|
|
52
|
+
sso_auth_failure_network_hint_title: 'Please check:',
|
|
53
|
+
sso_auth_failure_network_hint_ioa: '1. Whether IOA is connected',
|
|
54
|
+
sso_auth_failure_network_hint_network: '2. Whether your network can access the service',
|
|
51
55
|
|
|
52
56
|
// AvatarDropdown
|
|
53
57
|
'avatar.language': 'Language',
|
|
@@ -45,6 +45,10 @@ export default {
|
|
|
45
45
|
sso_auth_failure_modal_content: '自动登录失败,是否重新尝试登录?',
|
|
46
46
|
sso_auth_failure_modal_ok: '重新登录',
|
|
47
47
|
sso_auth_failure_modal_cancel: '取消',
|
|
48
|
+
sso_auth_failure_network_primary: '网络异常,无法完成自动登录',
|
|
49
|
+
sso_auth_failure_network_hint_title: '请检查以下情况:',
|
|
50
|
+
sso_auth_failure_network_hint_ioa: '1. IOA 是否已连接',
|
|
51
|
+
sso_auth_failure_network_hint_network: '2. 当前网络是否可正常访问',
|
|
48
52
|
|
|
49
53
|
// AvatarDropdown
|
|
50
54
|
'avatar.language': '语言',
|
|
@@ -16,6 +16,7 @@ const STATIC_TIMEZONE_LIST: ITimezone[] = [
|
|
|
16
16
|
{ region: 'DE', utc_offset: 1 },
|
|
17
17
|
{ region: 'FJ', utc_offset: 15 },
|
|
18
18
|
{ region: 'FR', utc_offset: 1 },
|
|
19
|
+
{ region: 'GB', utc_offset: 0 },
|
|
19
20
|
{ region: 'Global', utc_offset: 8 },
|
|
20
21
|
{ region: 'ID', utc_offset: 7 },
|
|
21
22
|
{ region: 'IN', utc_offset: 5 },
|
|
@@ -132,6 +132,26 @@ const intl = addIntl({
|
|
|
132
132
|
key: 'sso_auth_failure_modal_cancel',
|
|
133
133
|
defaultMessage: '取消',
|
|
134
134
|
}),
|
|
135
|
+
sso_auth_failure_network_primary: () =>
|
|
136
|
+
i18n({
|
|
137
|
+
key: 'sso_auth_failure_network_primary',
|
|
138
|
+
defaultMessage: '网络异常,无法完成自动登录',
|
|
139
|
+
}),
|
|
140
|
+
sso_auth_failure_network_hint_title: () =>
|
|
141
|
+
i18n({
|
|
142
|
+
key: 'sso_auth_failure_network_hint_title',
|
|
143
|
+
defaultMessage: '请检查以下情况:',
|
|
144
|
+
}),
|
|
145
|
+
sso_auth_failure_network_hint_ioa: () =>
|
|
146
|
+
i18n({
|
|
147
|
+
key: 'sso_auth_failure_network_hint_ioa',
|
|
148
|
+
defaultMessage: '1. IOA 是否已连接',
|
|
149
|
+
}),
|
|
150
|
+
sso_auth_failure_network_hint_network: () =>
|
|
151
|
+
i18n({
|
|
152
|
+
key: 'sso_auth_failure_network_hint_network',
|
|
153
|
+
defaultMessage: '2. 当前网络是否可正常访问',
|
|
154
|
+
}),
|
|
135
155
|
}, i18n);
|
|
136
156
|
|
|
137
157
|
export default intl;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
["node_modules", "dist", ".umi", ".umi-production", ".turbo"]
|