generator-mico-cli 0.2.28 → 0.2.30
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 +7 -20
- package/bin/mico.js +27 -62
- package/generators/micro-react/index.js +25 -1
- package/generators/micro-react/templates/.cursor/rules/always-read-docs.mdc +3 -0
- package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +1 -0
- package/generators/micro-react/templates/CICD/start_dev.sh +11 -0
- package/generators/micro-react/templates/CICD/start_local.sh +9 -0
- package/generators/micro-react/templates/CICD/start_prod.sh +13 -0
- package/generators/micro-react/templates/CICD/start_test.sh +11 -0
- package/generators/micro-react/templates/CLAUDE.md +1 -0
- package/generators/micro-react/templates/README.md +1 -1
- package/generators/micro-react/templates/apps/layout/config/config.dev.ts +13 -5
- package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +12 -0
- package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +12 -0
- package/generators/micro-react/templates/apps/layout/config/config.prod.ts +14 -0
- package/generators/micro-react/templates/apps/layout/docs/feature-PermissionFilter/346/214/211/351/222/256/346/235/203/351/231/220.md +116 -0
- package/generators/micro-react/templates/apps/layout/docs/feature-/345/233/275/351/231/205/345/214/226.md +121 -0
- 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 +8 -0
- 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 +83 -77
- 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 +50 -35
- package/generators/micro-react/templates/apps/layout/docs/feature-/350/267/257/347/224/261/346/235/203/351/231/220/346/227/245/345/277/227.md +162 -0
- package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +23 -31
- package/generators/micro-react/templates/apps/layout/mock/menus.ts +14 -0
- package/generators/micro-react/templates/apps/layout/mock/pages.ts +27 -8
- package/generators/micro-react/templates/apps/layout/package.json +2 -0
- package/generators/micro-react/templates/apps/layout/src/app.tsx +85 -4
- package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +3 -0
- package/generators/micro-react/templates/apps/layout/src/common/auth/tenant.ts +25 -0
- package/generators/micro-react/templates/apps/layout/src/common/auth/type.ts +41 -27
- package/generators/micro-react/templates/apps/layout/src/common/intl/formatLayoutMessage.ts +30 -0
- package/generators/micro-react/templates/apps/layout/src/common/intl/index.ts +6 -0
- package/generators/micro-react/templates/apps/layout/src/common/intl/intlRuntime.ts +14 -0
- package/generators/micro-react/templates/apps/layout/src/common/intl/localeMapping.ts +30 -0
- package/generators/micro-react/templates/apps/layout/src/common/intl/types.ts +14 -0
- package/generators/micro-react/templates/apps/layout/src/common/intl/useLayoutIntl.ts +40 -0
- package/generators/micro-react/templates/apps/layout/src/common/logger.ts +3 -4
- package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +148 -85
- package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +29 -6
- package/generators/micro-react/templates/apps/layout/src/common/micro/types.ts +23 -0
- package/generators/micro-react/templates/apps/layout/src/common/portal-data.ts +46 -2
- package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +74 -15
- package/generators/micro-react/templates/apps/layout/src/common/request/token-refresh.ts +2 -0
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +32 -6
- package/generators/micro-react/templates/apps/layout/src/components/PermissionFilter/index.tsx +51 -0
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +10 -1
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/TenantDropdown.tsx +76 -0
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/index.ts +1 -0
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/tenant-dropdown.less +48 -0
- package/generators/micro-react/templates/apps/layout/src/constants/index.ts +1 -0
- package/generators/micro-react/templates/apps/layout/src/hooks/index.ts +1 -0
- package/generators/micro-react/templates/apps/layout/src/hooks/useMenuState.ts +18 -0
- package/generators/micro-react/templates/apps/layout/src/hooks/useTenant.ts +41 -0
- package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +4 -1
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +21 -9
- package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +105 -60
- package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +28 -0
- package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +26 -0
- package/generators/micro-react/templates/apps/layout/src/pages/404/index.tsx +7 -3
- package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +32 -0
- package/generators/micro-react/templates/apps/layout/src/pages/Home/index.tsx +148 -4
- package/generators/micro-react/templates/apps/layout/src/requestErrorConfig.ts +2 -1
- package/generators/micro-react/templates/apps/layout/src/services/user.ts +79 -21
- package/generators/micro-react/templates/apps/layout/typings.d.ts +16 -0
- package/generators/micro-react/templates/docs/package-shared.md +189 -0
- package/generators/micro-react/templates/package.json +1 -1
- package/generators/micro-react/templates/packages/common-intl/README.md +78 -368
- package/generators/micro-react/templates/packages/common-intl/package.json +3 -13
- package/generators/micro-react/templates/packages/common-intl/src/index.ts +5 -6
- package/generators/micro-react/templates/packages/common-intl/src/intl.ts +115 -28
- package/generators/micro-react/templates/packages/common-intl/src/umiLocaleBridge.ts +101 -0
- package/generators/micro-react/templates/packages/common-intl/tsconfig.json +2 -4
- package/generators/micro-react/templates/packages/shared/README.md +120 -0
- package/generators/micro-react/templates/packages/shared/package.json +26 -0
- package/generators/micro-react/templates/packages/shared/services/common/index.ts +43 -0
- package/generators/micro-react/templates/packages/shared/services/index.ts +21 -0
- package/generators/micro-react/templates/packages/shared/services/request.ts +43 -0
- package/generators/micro-react/templates/packages/shared/timezone/index.ts +228 -0
- package/generators/micro-react/templates/packages/shared/tsconfig.json +20 -0
- package/generators/micro-react/templates/scripts/apply-sentry-plugin.ts +6 -1
- package/generators/micro-react/templates/turbo.json +9 -1
- package/generators/subapp-react/index.js +28 -22
- package/generators/subapp-react/templates/homepage/README.md +1 -0
- package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +1 -0
- package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +1 -0
- package/generators/subapp-react/templates/homepage/config/config.prod.ts +1 -0
- package/generators/subapp-react/templates/homepage/config/config.ts +10 -0
- package/generators/subapp-react/templates/homepage/docs/feature-PermissionFilter/346/214/211/351/222/256/346/235/203/351/231/220.md +35 -0
- package/generators/subapp-react/templates/homepage/docs/feature-/345/233/275/351/231/205/345/214/226.md +124 -0
- package/generators/subapp-react/templates/homepage/package.json +3 -1
- package/generators/subapp-react/templates/homepage/src/app.tsx +104 -2
- package/generators/subapp-react/templates/homepage/src/common/intl/index.ts +15 -0
- package/generators/subapp-react/templates/homepage/src/common/intl/intlRuntime.ts +14 -0
- package/generators/subapp-react/templates/homepage/src/common/intl/localeMapping.ts +24 -0
- package/generators/subapp-react/templates/homepage/src/common/intl/subappIntlConfig.ts +28 -0
- package/generators/subapp-react/templates/homepage/src/common/intl/subappLocale.ts +18 -0
- package/generators/subapp-react/templates/homepage/src/common/intl/subappOwnIntl.ts +63 -0
- package/generators/subapp-react/templates/homepage/src/common/intl/types.ts +14 -0
- package/generators/subapp-react/templates/homepage/src/common/intl/useSubappIntl.ts +61 -0
- package/generators/subapp-react/templates/homepage/src/common/locale.ts +80 -0
- package/generators/subapp-react/templates/homepage/src/common/mainApp.ts +41 -2
- package/generators/subapp-react/templates/homepage/src/components/PermissionFilter/index.tsx +48 -0
- package/generators/subapp-react/templates/homepage/src/locales/en-US.ts +6 -0
- package/generators/subapp-react/templates/homepage/src/locales/zh-CN.ts +6 -0
- package/generators/subapp-react/templates/homepage/src/pages/index.less +10 -0
- package/generators/subapp-react/templates/homepage/src/pages/index.tsx +86 -1
- package/generators/subapp-react/templates/homepage/typings.d.ts +12 -0
- package/lib/utils.js +0 -1
- package/package.json +2 -2
- package/generators/micro-react/templates/apps/layout/docs/common-intl.md +0 -372
- package/generators/micro-react/templates/packages/common-intl/src/indexedDBUtils.ts +0 -51
- package/generators/micro-react/templates/packages/common-intl/src/utils.ts +0 -482
- package/generators/micro-react/templates/packages/common-intl/vite.config.ts +0 -25
|
@@ -5,13 +5,115 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 注意:app.tsx 只能导出 Umi 规定的 API(qiankun、getInitialState 等)
|
|
7
7
|
* 自定义函数请放在 @/common/mainApp.ts 中
|
|
8
|
+
*
|
|
9
|
+
* 国际化使用:
|
|
10
|
+
* 子应用默认共享主应用的 intl(同 tag),直接导入即可:
|
|
11
|
+
* import { intl } from '<%= packageScope %>/common-intl';
|
|
12
|
+
* intl.common_hello();
|
|
13
|
+
* 独立 intl:在 `src/common/intl/subappIntlConfig.ts` 填写 `getSubappIntlInitOptions()`;
|
|
14
|
+
* 页面统一用 `useSubappIntl`(intl 优先,未命中走子应用 Umi locales)。
|
|
15
|
+
*
|
|
16
|
+
* common-intl 的 getLocale/setLocale 与 Umi locale / 主应用 props.locale 对齐:入口调用 configureLocale(见下)。
|
|
8
17
|
*/
|
|
9
18
|
|
|
10
|
-
import
|
|
19
|
+
import {
|
|
20
|
+
configureLocale,
|
|
21
|
+
fetchMultilingualData,
|
|
22
|
+
type ILang,
|
|
23
|
+
} from '<%= packageScope %>/common-intl';
|
|
24
|
+
import { Message } from '@mico-platform/ui';
|
|
25
|
+
import type { ReactNode } from 'react';
|
|
11
26
|
import { history } from '@umijs/max';
|
|
12
27
|
import { SentryErrorBoundary } from '@common-web/sentry';
|
|
13
28
|
import { appLogger } from './common/logger';
|
|
29
|
+
import { configureRequest } from '<%= packageScope %>/shared/services';
|
|
14
30
|
import { type IMicroAppProps, setMainAppProps } from './common/mainApp';
|
|
31
|
+
import { request } from './common/request';
|
|
32
|
+
import { setLocaleToStorage } from './common/locale';
|
|
33
|
+
import {
|
|
34
|
+
getSubappOwnFetchMultilingualData,
|
|
35
|
+
ilangToUmiLocale,
|
|
36
|
+
isCommonIntlEnabled,
|
|
37
|
+
umiLocaleToILang,
|
|
38
|
+
} from './common/intl';
|
|
39
|
+
import { getSubappCurrentLocale } from './common/intl/subappLocale';
|
|
40
|
+
|
|
41
|
+
// 子应用:将本地 request(自动桥接主应用 / 独立 umi)注入 shared 包,供共享服务层(getRequest)使用
|
|
42
|
+
// 注意:模块顶层注入,request 函数引用稳定;运行期由 request 内部根据是否处于 qiankun 选用 main / umi
|
|
43
|
+
configureRequest(request as <T = unknown>(url: string, options?: Record<string, unknown>) => Promise<T>);
|
|
44
|
+
|
|
45
|
+
// 子应用:common-intl initIntl 与 Umi `umi_locale` / 主应用注入 locale 对齐(与 layout app.tsx 同理)
|
|
46
|
+
configureLocale({
|
|
47
|
+
getLocale: () => umiLocaleToILang(getSubappCurrentLocale()),
|
|
48
|
+
setLocale: (lang: ILang) => {
|
|
49
|
+
setLocaleToStorage(ilangToUmiLocale(lang));
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @name 运行时国际化(Umi locale 插件)
|
|
55
|
+
* @see https://umijs.org/docs/max/i18n#%E8%BF%90%E8%A1%8C%E6%97%B6%E9%85%8D%E7%BD%AE
|
|
56
|
+
*/
|
|
57
|
+
export const locale = {
|
|
58
|
+
getLocale() {
|
|
59
|
+
return getSubappCurrentLocale();
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 中台文案预加载:默认 tag 与(可选)子应用独立 intl tag 并行拉取,失败仍渲染。
|
|
65
|
+
* @see generators/micro-react/templates/apps/layout/src/app.tsx render
|
|
66
|
+
*/
|
|
67
|
+
export function render(oldRender: () => void): void {
|
|
68
|
+
const ownFetch = getSubappOwnFetchMultilingualData();
|
|
69
|
+
const tasks: Promise<unknown>[] = [];
|
|
70
|
+
|
|
71
|
+
if (isCommonIntlEnabled()) {
|
|
72
|
+
tasks.push(
|
|
73
|
+
fetchMultilingualData({
|
|
74
|
+
requestInstance: request as (
|
|
75
|
+
url: string,
|
|
76
|
+
options?: unknown,
|
|
77
|
+
) => Promise<unknown>,
|
|
78
|
+
messageInstance: {
|
|
79
|
+
error: Message.error,
|
|
80
|
+
warning: Message.warning,
|
|
81
|
+
},
|
|
82
|
+
lang: umiLocaleToILang(getSubappCurrentLocale()) as ILang,
|
|
83
|
+
localeRequestUrl: process.env.LOCALE_REQUEST_URL,
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (ownFetch) {
|
|
89
|
+
tasks.push(
|
|
90
|
+
ownFetch({
|
|
91
|
+
requestInstance: request as (
|
|
92
|
+
url: string,
|
|
93
|
+
options?: unknown,
|
|
94
|
+
) => Promise<unknown>,
|
|
95
|
+
messageInstance: {
|
|
96
|
+
error: Message.error,
|
|
97
|
+
warning: Message.warning,
|
|
98
|
+
},
|
|
99
|
+
lang: umiLocaleToILang(getSubappCurrentLocale()) as ILang,
|
|
100
|
+
localeRequestUrl: process.env.LOCALE_REQUEST_URL,
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (tasks.length === 0) {
|
|
106
|
+
oldRender();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
Promise.all(tasks)
|
|
111
|
+
.then(() => oldRender())
|
|
112
|
+
.catch((error: Error) => {
|
|
113
|
+
console.error('获取多语言文案失败', error);
|
|
114
|
+
oldRender();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
15
117
|
|
|
16
118
|
/** 与 layout parser.findRouteByPath 一致:根路径 "/" 保持不变 */
|
|
17
119
|
function stripTrailingSlash(path: string): string {
|
|
@@ -103,6 +205,6 @@ export async function getInitialState() {
|
|
|
103
205
|
};
|
|
104
206
|
}
|
|
105
207
|
|
|
106
|
-
export function rootContainer(container:
|
|
208
|
+
export function rootContainer(container: ReactNode) {
|
|
107
209
|
return <SentryErrorBoundary>{container}</SentryErrorBoundary>;
|
|
108
210
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { isCommonIntlEnabled, getCommonIntlConfig } from './intlRuntime';
|
|
2
|
+
export type { IMicoConfigCommonIntl } from './types';
|
|
3
|
+
export {
|
|
4
|
+
getSubappIntlInitOptions,
|
|
5
|
+
type ISubappIntlInitOptions,
|
|
6
|
+
} from './subappIntlConfig';
|
|
7
|
+
export {
|
|
8
|
+
getSubappOwnFetchMultilingualData,
|
|
9
|
+
getSubappOwnI18n,
|
|
10
|
+
getSubappOwnIntlTag,
|
|
11
|
+
isSubappOwnIntlEnabled,
|
|
12
|
+
} from './subappOwnIntl';
|
|
13
|
+
export { useSubappIntl } from './useSubappIntl';
|
|
14
|
+
export { ilangToUmiLocale, umiLocaleToILang } from './localeMapping';
|
|
15
|
+
export { getSubappCurrentLocale } from './subappLocale';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { IMicoConfigCommonIntl } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 是否启用多语言中台拉取:以 __MICO_CONFIG__.intl 或 commonIntl 含有效 tag、app_name 为准
|
|
5
|
+
*/
|
|
6
|
+
export function isCommonIntlEnabled(): boolean {
|
|
7
|
+
const c = getCommonIntlConfig();
|
|
8
|
+
return !!(c?.tag && c?.app_name);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getCommonIntlConfig(): IMicoConfigCommonIntl | undefined {
|
|
12
|
+
const w = window.__MICO_CONFIG__;
|
|
13
|
+
return w?.intl ?? w?.commonIntl;
|
|
14
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { LANG, type ILang } from '<%= packageScope %>/common-intl';
|
|
2
|
+
import type { SupportedLocale } from '@/common/locale';
|
|
3
|
+
import { LOCALE } from '@/common/locale';
|
|
4
|
+
|
|
5
|
+
/** 与 packages/common-intl/src/umiLocaleBridge.ts、layout localeMapping 保持一致 */
|
|
6
|
+
const UMI_TO_ILANG: Record<SupportedLocale, ILang> = {
|
|
7
|
+
[LOCALE.ZH_CN]: LANG.ZH_CN,
|
|
8
|
+
[LOCALE.EN_US]: LANG.EN,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function umiLocaleToILang(locale: SupportedLocale): ILang {
|
|
12
|
+
return UMI_TO_ILANG[locale] ?? LANG.EN;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ILANG_TO_UMI: Partial<Record<ILang, SupportedLocale>> = {
|
|
16
|
+
[LANG.ZH_CN]: LOCALE.ZH_CN,
|
|
17
|
+
[LANG.EN]: LOCALE.EN_US,
|
|
18
|
+
[LANG.AR]: LOCALE.EN_US,
|
|
19
|
+
[LANG.TR]: LOCALE.EN_US,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function ilangToUmiLocale(lang: ILang): SupportedLocale {
|
|
23
|
+
return ILANG_TO_UMI[lang] ?? LOCALE.EN_US;
|
|
24
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 子应用独立 intl(与主应用不同 tag / 独立 IndexedDB)时,在此返回 initIntl 参数。
|
|
3
|
+
* 默认 `undefined`:使用 <%= packageScope %>/common-intl 包内默认实例(与主应用 externals 共享)。
|
|
4
|
+
*
|
|
5
|
+
* 启用方式:返回含有效 `tag`、`app_name` 的对象(与 packages/common-intl README 一致)。
|
|
6
|
+
*/
|
|
7
|
+
export interface ISubappIntlInitOptions {
|
|
8
|
+
tag: string;
|
|
9
|
+
app_name: string;
|
|
10
|
+
indexedDBParams?: {
|
|
11
|
+
dbName?: string;
|
|
12
|
+
dbVersion?: number;
|
|
13
|
+
storeName?: string;
|
|
14
|
+
keyPathKey?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getSubappIntlInitOptions():
|
|
19
|
+
| ISubappIntlInitOptions
|
|
20
|
+
| undefined {
|
|
21
|
+
return undefined;
|
|
22
|
+
// 如果需要启用独立 intl,请取消注释并修改 tag 和 app_name
|
|
23
|
+
// return {
|
|
24
|
+
// tag: 'cs_fe_mobile',
|
|
25
|
+
// app_name: 'middle',
|
|
26
|
+
// indexedDBParams: { dbName: 'mico_cs_mobile_i18n_db' },
|
|
27
|
+
// };
|
|
28
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getCurrentLocale, type SupportedLocale, SUPPORTED_LOCALES } from '@/common/locale';
|
|
2
|
+
import { getMainAppProps } from '@/common/mainApp';
|
|
3
|
+
|
|
4
|
+
function isSupportedLocale(value: string | undefined): value is SupportedLocale {
|
|
5
|
+
return !!value && (SUPPORTED_LOCALES as readonly string[]).includes(value);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 子应用当前 Umi 风格 locale:qiankun 下优先主应用 MicroAppLoader 注入的 `locale`,
|
|
10
|
+
* 否则与主应用相同规则(URL / umi_locale / 浏览器)。
|
|
11
|
+
*/
|
|
12
|
+
export function getSubappCurrentLocale(): SupportedLocale {
|
|
13
|
+
const fromMain = getMainAppProps()?.locale;
|
|
14
|
+
if (isSupportedLocale(fromMain)) {
|
|
15
|
+
return fromMain;
|
|
16
|
+
}
|
|
17
|
+
return getCurrentLocale();
|
|
18
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { initIntl, type ILang } from '<%= packageScope %>/common-intl';
|
|
2
|
+
import { setLocaleToStorage } from '@/common/locale';
|
|
3
|
+
import { getSubappIntlInitOptions } from './subappIntlConfig';
|
|
4
|
+
import { ilangToUmiLocale, umiLocaleToILang } from './localeMapping';
|
|
5
|
+
import { getSubappCurrentLocale } from './subappLocale';
|
|
6
|
+
|
|
7
|
+
type TI18nFn = (opts: {
|
|
8
|
+
key: string;
|
|
9
|
+
defaultMessage: string;
|
|
10
|
+
interpolations?: (string | number | boolean)[];
|
|
11
|
+
}) => string;
|
|
12
|
+
|
|
13
|
+
type TFetchFn = (params: {
|
|
14
|
+
requestInstance: (url: string, options?: unknown) => Promise<unknown>;
|
|
15
|
+
messageInstance: { error: (msg: string) => void; warning: (msg: string) => void };
|
|
16
|
+
lang: ILang;
|
|
17
|
+
localeRequestUrl?: string;
|
|
18
|
+
}) => Promise<unknown>;
|
|
19
|
+
|
|
20
|
+
function createSubappOwnIntl(): {
|
|
21
|
+
i18nFn: TI18nFn | null;
|
|
22
|
+
fetchFn: TFetchFn | null;
|
|
23
|
+
tag: string | undefined;
|
|
24
|
+
} {
|
|
25
|
+
const opts = getSubappIntlInitOptions();
|
|
26
|
+
if (!opts?.tag || !opts.app_name) {
|
|
27
|
+
return { i18nFn: null, fetchFn: null, tag: undefined };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { fetchMultilingualData, i18n } = initIntl({
|
|
31
|
+
...opts,
|
|
32
|
+
getLocale: () => umiLocaleToILang(getSubappCurrentLocale()),
|
|
33
|
+
setLocale: (lang: ILang) => {
|
|
34
|
+
setLocaleToStorage(ilangToUmiLocale(lang));
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
i18nFn: i18n as TI18nFn,
|
|
40
|
+
fetchFn: fetchMultilingualData as TFetchFn,
|
|
41
|
+
tag: opts.tag,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const subappOwnIntlRuntime = createSubappOwnIntl();
|
|
46
|
+
|
|
47
|
+
export function isSubappOwnIntlEnabled(): boolean {
|
|
48
|
+
return subappOwnIntlRuntime.i18nFn != null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getSubappOwnIntlTag(): string | undefined {
|
|
52
|
+
return subappOwnIntlRuntime.tag;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 独立 intl 实例的 i18n;未配置时为 `null` */
|
|
56
|
+
export function getSubappOwnI18n(): TI18nFn | null {
|
|
57
|
+
return subappOwnIntlRuntime.i18nFn;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 与默认 tag 的 fetch 相同入参;未配置独立 intl 时为 `null` */
|
|
61
|
+
export function getSubappOwnFetchMultilingualData(): TFetchFn | null {
|
|
62
|
+
return subappOwnIntlRuntime.fetchFn;
|
|
63
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 运行时 common-intl 配置(见 window.__MICO_CONFIG__.intl,兼容 commonIntl)
|
|
3
|
+
*/
|
|
4
|
+
export interface IMicoConfigCommonIntl {
|
|
5
|
+
/** 多语言中台 tag */
|
|
6
|
+
tag: string;
|
|
7
|
+
app_name: string;
|
|
8
|
+
indexedDBParams?: {
|
|
9
|
+
dbName?: string;
|
|
10
|
+
dbVersion?: number;
|
|
11
|
+
storeName?: string;
|
|
12
|
+
keyPathKey?: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { i18n } from '<%= packageScope %>/common-intl';
|
|
2
|
+
import { useIntl } from '@umijs/max';
|
|
3
|
+
import { getSubappCurrentLocale } from './subappLocale';
|
|
4
|
+
import { isCommonIntlEnabled } from './intlRuntime';
|
|
5
|
+
import { getSubappOwnI18n, isSubappOwnIntlEnabled, getSubappOwnIntlTag } from './subappOwnIntl';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 子应用统一国际化 Hook:
|
|
9
|
+
* - 配置了 {@link getSubappIntlInitOptions} 时:优先独立 intl 实例的 `i18n`,无命中再 Umi
|
|
10
|
+
* - 否则与 layout `useLayoutIntl` 一致:未启用 commonIntl 时仅 Umi;启用后包内 `i18n` 优先,无命中再 Umi
|
|
11
|
+
*/
|
|
12
|
+
export function useSubappIntl() {
|
|
13
|
+
const umiIntl = useIntl();
|
|
14
|
+
const commonIntlEnabled = isCommonIntlEnabled();
|
|
15
|
+
const locale = getSubappCurrentLocale();
|
|
16
|
+
const subappOwnIntl = isSubappOwnIntlEnabled();
|
|
17
|
+
const ownI18n = getSubappOwnI18n();
|
|
18
|
+
const subappOwnIntlTag = getSubappOwnIntlTag();
|
|
19
|
+
|
|
20
|
+
const formatMessage = (
|
|
21
|
+
descriptor: { id: string; defaultMessage?: string },
|
|
22
|
+
values?: Record<string, string | number | boolean | Date | null | undefined>,
|
|
23
|
+
): string => {
|
|
24
|
+
const dm = descriptor.defaultMessage ?? descriptor.id;
|
|
25
|
+
const fromUmi = umiIntl.formatMessage(descriptor, values);
|
|
26
|
+
|
|
27
|
+
if (subappOwnIntl && ownI18n) {
|
|
28
|
+
const fromOwn = ownI18n({
|
|
29
|
+
key: descriptor.id,
|
|
30
|
+
defaultMessage: dm,
|
|
31
|
+
});
|
|
32
|
+
if (fromOwn === dm && fromUmi !== fromOwn) {
|
|
33
|
+
return fromUmi;
|
|
34
|
+
}
|
|
35
|
+
return fromOwn;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!commonIntlEnabled) {
|
|
39
|
+
return fromUmi;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const fromIntl = i18n({
|
|
43
|
+
key: descriptor.id,
|
|
44
|
+
defaultMessage: dm,
|
|
45
|
+
});
|
|
46
|
+
if (fromIntl === dm && fromUmi !== fromIntl) {
|
|
47
|
+
return fromUmi;
|
|
48
|
+
}
|
|
49
|
+
return fromIntl;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
commonIntlEnabled,
|
|
54
|
+
formatMessage,
|
|
55
|
+
locale,
|
|
56
|
+
/** 是否启用子应用内独立 initIntl 配置 */
|
|
57
|
+
subappOwnIntl,
|
|
58
|
+
/** 独立 intl 的 tag(仅 subappOwnIntl 为 true 时有值) */
|
|
59
|
+
subappOwnIntlTag,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 语言管理(与主应用 layout `umi_locale` 规则一致:URL > localStorage > 浏览器)
|
|
3
|
+
* 独立运行子应用时使用;qiankun 下 {@link getSubappCurrentLocale} 优先主应用 props.locale
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const LOCALE_STORAGE_KEY = 'umi_locale';
|
|
7
|
+
const URL_PARAM_KEY = 'lang';
|
|
8
|
+
|
|
9
|
+
export const SUPPORTED_LOCALES = ['zh-CN', 'en-US'] as const;
|
|
10
|
+
export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
|
|
11
|
+
|
|
12
|
+
export const LOCALE = {
|
|
13
|
+
ZH_CN: 'zh-CN',
|
|
14
|
+
EN_US: 'en-US',
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
const DEFAULT_LOCALE: SupportedLocale = 'en-US';
|
|
18
|
+
|
|
19
|
+
function getLocaleFromUrl(): string | null {
|
|
20
|
+
if (typeof window === 'undefined') {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
24
|
+
return urlParams.get(URL_PARAM_KEY);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getLocaleFromStorage(): string | null {
|
|
28
|
+
if (typeof window === 'undefined') {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return localStorage.getItem(LOCALE_STORAGE_KEY);
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getBrowserLocale(): SupportedLocale {
|
|
39
|
+
if (typeof window === 'undefined') {
|
|
40
|
+
return DEFAULT_LOCALE;
|
|
41
|
+
}
|
|
42
|
+
const browserLang = navigator.language || (navigator as { userLanguage?: string }).userLanguage;
|
|
43
|
+
const locales = [...SUPPORTED_LOCALES];
|
|
44
|
+
if (locales.includes(browserLang as SupportedLocale)) {
|
|
45
|
+
return browserLang as SupportedLocale;
|
|
46
|
+
}
|
|
47
|
+
const langPrefix = browserLang.split('-')[0];
|
|
48
|
+
const matchedLocale = locales.find((locale) => locale.startsWith(langPrefix));
|
|
49
|
+
return matchedLocale || DEFAULT_LOCALE;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isValidLocale(locale: string | null): locale is SupportedLocale {
|
|
53
|
+
if (!locale) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return ([...SUPPORTED_LOCALES] as string[]).includes(locale);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getCurrentLocale(): SupportedLocale {
|
|
60
|
+
const urlLocale = getLocaleFromUrl();
|
|
61
|
+
if (isValidLocale(urlLocale)) {
|
|
62
|
+
return urlLocale;
|
|
63
|
+
}
|
|
64
|
+
const storageLocale = getLocaleFromStorage();
|
|
65
|
+
if (isValidLocale(storageLocale)) {
|
|
66
|
+
return storageLocale;
|
|
67
|
+
}
|
|
68
|
+
return getBrowserLocale();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function setLocaleToStorage(locale: SupportedLocale): void {
|
|
72
|
+
if (typeof window === 'undefined') {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
localStorage.setItem(LOCALE_STORAGE_KEY, locale);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('Failed to save locale to localStorage:', error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* @description 存储和获取主应用传递的 props
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { useSyncExternalStore } from 'react';
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* 主应用传递的 props 类型定义
|
|
8
10
|
*/
|
|
@@ -13,17 +15,56 @@ export interface IMicroAppProps {
|
|
|
13
15
|
request?: <T = any>(url: string, options?: any) => Promise<T>;
|
|
14
16
|
/** 当前路由路径(由主应用传递,用于子应用内部路由切换) */
|
|
15
17
|
routePath?: string;
|
|
18
|
+
/** 当前语言(与主应用 getCurrentLocale 一致,如 zh-CN / en-US),供国际化与 common-intl 对齐 */
|
|
19
|
+
locale?: string;
|
|
20
|
+
/**
|
|
21
|
+
* 按钮权限列表(与主应用 fetchUserInfo.button_perms 一致,由主应用 MicroAppLoader 注入)
|
|
22
|
+
*/
|
|
23
|
+
button_perms?: string[];
|
|
24
|
+
/** 是否超级用户(与主应用用户信息一致) */
|
|
25
|
+
is_superuser?: boolean;
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
/** 存储主应用传递的 props */
|
|
19
29
|
let mainAppProps: IMicroAppProps | null = null;
|
|
20
30
|
|
|
31
|
+
const propsListeners = new Set<() => void>();
|
|
32
|
+
|
|
33
|
+
const notifyPropsListeners = (): void => {
|
|
34
|
+
propsListeners.forEach((listener) => listener());
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 订阅主应用 props 变更(qiankun update 时触发,供 PermissionFilter 等订阅刷新)
|
|
39
|
+
*/
|
|
40
|
+
export const subscribeMainAppProps = (listener: () => void): (() => void) => {
|
|
41
|
+
propsListeners.add(listener);
|
|
42
|
+
return () => propsListeners.delete(listener);
|
|
43
|
+
};
|
|
44
|
+
|
|
21
45
|
/**
|
|
22
46
|
* 设置主应用 props(由 qiankun 生命周期调用)
|
|
23
47
|
* @internal
|
|
24
48
|
*/
|
|
25
49
|
export const setMainAppProps = (props: IMicroAppProps | null) => {
|
|
26
50
|
mainAppProps = props;
|
|
51
|
+
notifyPropsListeners();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 获取当前主应用注入的 props(非 React 场景)
|
|
56
|
+
*/
|
|
57
|
+
export const getMainAppProps = (): IMicroAppProps | null => mainAppProps;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* React Hook:订阅主应用 props,与 qiankun update 同步
|
|
61
|
+
*/
|
|
62
|
+
export const useMainAppProps = (): IMicroAppProps | null => {
|
|
63
|
+
return useSyncExternalStore(
|
|
64
|
+
subscribeMainAppProps,
|
|
65
|
+
() => mainAppProps,
|
|
66
|
+
() => null,
|
|
67
|
+
);
|
|
27
68
|
};
|
|
28
69
|
|
|
29
70
|
/**
|
|
@@ -51,5 +92,3 @@ export const isRunningInQiankun = () => !!mainAppProps;
|
|
|
51
92
|
* 获取主应用标识
|
|
52
93
|
*/
|
|
53
94
|
export const getMainAppName = () => mainAppProps?.mainApp;
|
|
54
|
-
|
|
55
|
-
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type IMicroAppProps, useMainAppProps } from '@/common/mainApp';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
export interface IPermissionFilterProps {
|
|
5
|
+
/** 按钮/操作权限标识,需在主应用传入的 `button_perms` 中 */
|
|
6
|
+
permissionKey: string;
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
/** 无权限时渲染内容;不传则渲染 null */
|
|
9
|
+
fallback?: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const isSuperuserUser = (value?: boolean | number): boolean => {
|
|
13
|
+
return value === true || value === 1;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function hasButtonPermission(
|
|
17
|
+
permissionKey: string,
|
|
18
|
+
props: IMicroAppProps | null,
|
|
19
|
+
): boolean {
|
|
20
|
+
if (!permissionKey?.trim()) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (isSuperuserUser(props?.is_superuser)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
const buttonPerms = props?.button_perms ?? [];
|
|
27
|
+
return buttonPerms.includes(permissionKey);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 按主应用通过 qiankun 传入的 `button_perms` / `is_superuser` 判断是否渲染子节点。
|
|
32
|
+
* 独立运行子应用时无主应用 props,默认无按钮权限。
|
|
33
|
+
*/
|
|
34
|
+
const PermissionFilter: React.FC<IPermissionFilterProps> = ({
|
|
35
|
+
permissionKey,
|
|
36
|
+
children,
|
|
37
|
+
fallback = null,
|
|
38
|
+
}) => {
|
|
39
|
+
const mainProps = useMainAppProps();
|
|
40
|
+
const allowed = hasButtonPermission(permissionKey, mainProps);
|
|
41
|
+
|
|
42
|
+
if (!allowed) {
|
|
43
|
+
return <>{fallback}</>;
|
|
44
|
+
}
|
|
45
|
+
return <>{children}</>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default PermissionFilter;
|
|
@@ -28,6 +28,16 @@
|
|
|
28
28
|
margin-bottom: @spacing-lg;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
&-intl-hint {
|
|
32
|
+
margin-bottom: @spacing-sm;
|
|
33
|
+
color: @color-text-2;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&-intl-meta {
|
|
37
|
+
font-size: @font-size-sm;
|
|
38
|
+
margin-top: @spacing-md;
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
&-card {
|
|
32
42
|
padding: @card-padding;
|
|
33
43
|
// 卡片背景色 - 使用 color-text-5(亮色模式为白色)
|