generator-mico-cli 0.2.21 → 0.2.23
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 +33 -0
- package/bin/mico.js +15 -2
- package/generators/micro-react/index.js +44 -6
- package/generators/micro-react/meta.json +2 -1
- package/generators/micro-react/templates/.cursor/rules/always-read-docs.mdc +14 -4
- package/generators/micro-react/templates/.cursor/rules/layout-app.mdc +36 -26
- package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +5 -2
- package/generators/micro-react/templates/CLAUDE.md +4 -2
- package/generators/micro-react/templates/_gitignore +3 -1
- package/generators/micro-react/templates/_npmrc +1 -0
- package/generators/micro-react/templates/apps/layout/config/config.dev.ts +7 -3
- package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +5 -0
- package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +5 -0
- package/generators/micro-react/templates/apps/layout/config/config.prod.ts +12 -0
- package/generators/micro-react/templates/apps/layout/config/routes.ts +0 -5
- 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 +4 -2
- 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 +116 -56
- 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/mock/api.mock.ts +81 -61
- package/generators/micro-react/templates/apps/layout/mock/menus.ts +89 -144
- package/generators/micro-react/templates/apps/layout/mock/pages.ts +83 -0
- package/generators/micro-react/templates/apps/layout/package.json +1 -0
- package/generators/micro-react/templates/apps/layout/src/app.tsx +13 -8
- package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +118 -43
- package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +31 -4
- package/generators/micro-react/templates/apps/layout/src/common/micro-prefetch.ts +3 -2
- 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 +49 -10
- package/generators/micro-react/templates/apps/layout/src/common/request/interceptors.ts +1 -1
- package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +6 -0
- package/generators/micro-react/templates/apps/layout/src/common/theme.ts +0 -2
- package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.less +0 -1
- package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.tsx +4 -4
- package/generators/micro-react/templates/apps/layout/src/components/IconFont/index.tsx +4 -5
- 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 +4 -3
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/micro-app-manager.ts +7 -1
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +12 -16
- package/generators/micro-react/templates/apps/layout/src/global.less +15 -2
- package/generators/micro-react/templates/apps/layout/src/hooks/useMenu.ts +3 -2
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +32 -4
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +20 -10
- package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +75 -38
- package/generators/micro-react/templates/apps/layout/src/pages/404/index.tsx +3 -7
- package/generators/micro-react/templates/apps/layout/src/services/user.ts +7 -3
- package/generators/micro-react/templates/dev.preset.json +1 -1
- package/generators/micro-react/templates/package.json +1 -0
- package/generators/micro-react/templates/scripts/apply-sentry-plugin.ts +45 -0
- package/generators/subapp-react/index.js +206 -6
- package/generators/subapp-react/templates/homepage/.env +2 -1
- 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 +8 -0
- package/generators/subapp-react/templates/homepage/package.json +1 -0
- package/generators/subapp-react/templates/homepage/src/app.tsx +6 -0
- package/generators/subapp-umd/ignore-list.json +5 -0
- package/generators/subapp-umd/index.js +325 -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 +68 -0
- package/lib/utils.js +2 -1
- package/package.json +1 -1
|
@@ -30,6 +30,10 @@ export interface PageConfig {
|
|
|
30
30
|
routeMode: PageRouteMode;
|
|
31
31
|
/** 是否仅超级管理员可见 */
|
|
32
32
|
adminOnly?: boolean;
|
|
33
|
+
/** 是否开启权限控制 */
|
|
34
|
+
accessControlEnabled: boolean;
|
|
35
|
+
/** 路由权限标识(用于匹配 sideMenus) */
|
|
36
|
+
routeKey: string | null;
|
|
33
37
|
/** 关联的主文档 ID */
|
|
34
38
|
mainDocumentId: number;
|
|
35
39
|
/** 所属工作空间子域名 */
|
|
@@ -46,6 +50,15 @@ export interface PageConfig {
|
|
|
46
50
|
updatedAt: string;
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
/**
|
|
54
|
+
* 公共页面配置 - 从 window.__MICO_PAGES__ 获取
|
|
55
|
+
* PageConfig 的子集,用于动态路由注册和页面级权限控制
|
|
56
|
+
*/
|
|
57
|
+
export type PublicPageItem = Omit<
|
|
58
|
+
PageConfig,
|
|
59
|
+
'workspaceSubdomain' | 'publishedBy' | 'versions' | 'createdAt' | 'updatedAt'
|
|
60
|
+
>;
|
|
61
|
+
|
|
49
62
|
/**
|
|
50
63
|
* 菜单项类型
|
|
51
64
|
*/
|
|
@@ -67,8 +80,6 @@ export interface MenuItem {
|
|
|
67
80
|
enabled: boolean;
|
|
68
81
|
sortOrder: number;
|
|
69
82
|
pageId: number | null;
|
|
70
|
-
/** 页面配置(type=page 时存在) */
|
|
71
|
-
page: PageConfig | null;
|
|
72
83
|
/** 是否仅超级管理员可见 */
|
|
73
84
|
adminOnly?: boolean;
|
|
74
85
|
children: MenuItem[];
|
|
@@ -91,7 +102,7 @@ export interface ParsedRoute {
|
|
|
91
102
|
/** 微应用入口 URL (htmlUrl 或 jsUrls[0]) */
|
|
92
103
|
entry?: string;
|
|
93
104
|
/** 原始页面配置 */
|
|
94
|
-
pageConfig?:
|
|
105
|
+
pageConfig?: PublicPageItem;
|
|
95
106
|
}
|
|
96
107
|
|
|
97
108
|
/**
|
|
@@ -111,7 +122,7 @@ export interface ParsedMenuItem {
|
|
|
111
122
|
*/
|
|
112
123
|
export interface MicroAppProps {
|
|
113
124
|
/** 主应用标识 */
|
|
114
|
-
mainApp: '
|
|
125
|
+
mainApp: 'common-web';
|
|
115
126
|
/** 共享的 request 实例,子应用可直接使用 */
|
|
116
127
|
request: <T = any>(url: string, options?: any) => Promise<T>;
|
|
117
128
|
}
|
|
@@ -132,6 +143,8 @@ export type WorkspaceStatus = 'active' | 'inactive' | 'pending';
|
|
|
132
143
|
export interface WorkspaceConfig {
|
|
133
144
|
/** 关联的应用 ID(可能为空) */
|
|
134
145
|
appId: number | null;
|
|
146
|
+
/** CAS 服务端登录 URL,用于 SSO 外部登录跳转 */
|
|
147
|
+
casServerLoginUrl?: string;
|
|
135
148
|
/** 创建时间 */
|
|
136
149
|
createdAt: string;
|
|
137
150
|
/** 创建者 ID */
|
|
@@ -165,10 +178,24 @@ export interface WorkspaceConfig {
|
|
|
165
178
|
*/
|
|
166
179
|
declare global {
|
|
167
180
|
interface Window {
|
|
181
|
+
__MICO_PAGES__?: PublicPageItem[];
|
|
168
182
|
__MICO_MENUS__?: MenuItem[];
|
|
169
183
|
__MICO_CONFIG__?: {
|
|
170
184
|
appName?: string;
|
|
185
|
+
/** 站点/产品标题,用于登录页标题文案、logo alt 等 */
|
|
186
|
+
title?: string;
|
|
187
|
+
/** 登录页 logo 图片地址,不配置时使用应用内置 logo */
|
|
188
|
+
logo?: string;
|
|
189
|
+
appId?: string;
|
|
171
190
|
apiBaseUrl?: string;
|
|
191
|
+
proxySuffix?: string;
|
|
192
|
+
loginEndpoint?: string;
|
|
193
|
+
refreshEndpoint?: string;
|
|
194
|
+
externalLoginPath?: string;
|
|
195
|
+
/** 请求客户端配置覆盖,与 config 中 defaultClientOptions 同名字段 */
|
|
196
|
+
defaultClientOptions?: Partial<
|
|
197
|
+
import('../request/types').RequestClientOptions
|
|
198
|
+
>;
|
|
172
199
|
/** 默认重定向路径,访问 "/" 时自动跳转到此路径 */
|
|
173
200
|
defaultPath?: string;
|
|
174
201
|
/**
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { prefetchApps } from 'qiankun';
|
|
9
|
-
import { extractRoutes
|
|
9
|
+
import { extractRoutes } from './menu';
|
|
10
|
+
import { getMenus } from './portal-data';
|
|
10
11
|
import { getAppNameFromEntry } from './micro';
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -45,7 +46,7 @@ export const getMicroAppsForPrefetch = (
|
|
|
45
46
|
excludeEntry?: string,
|
|
46
47
|
): Array<{ name: string; entry: string }> => {
|
|
47
48
|
try {
|
|
48
|
-
const menus =
|
|
49
|
+
const menus = getMenus();
|
|
49
50
|
const routes = extractRoutes(menus);
|
|
50
51
|
|
|
51
52
|
return routes
|
|
@@ -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,59 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 请求客户端配置管理
|
|
3
|
+
* appId 优先从 window.__MICO_WORKSPACE__ 获取;
|
|
4
|
+
* externalLoginPath 优先从 window.__MICO_WORKSPACE__.casServerLoginUrl 获取;
|
|
5
|
+
* 其余 defaultClientOptions / apiBaseUrl / proxySuffix / loginEndpoint / refreshEndpoint 优先从 window.__MICO_CONFIG__ 同名字段获取。
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
8
|
import { getStoredAuthToken } from '../auth/auth-manager';
|
|
6
9
|
import type { RequestClientOptions, TokenResolver } from './types';
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
function getMicoConfig(): Window['__MICO_CONFIG__'] {
|
|
12
|
+
if (typeof window === 'undefined') return undefined;
|
|
13
|
+
return window.__MICO_CONFIG__;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getMicoWorkspace(): Window['__MICO_WORKSPACE__'] {
|
|
17
|
+
if (typeof window === 'undefined') return undefined;
|
|
18
|
+
return window.__MICO_WORKSPACE__ ?? undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildDefaultClientOptions(): RequestClientOptions {
|
|
22
|
+
const envDefaults: RequestClientOptions = {
|
|
23
|
+
appId: process.env.APP_ID ?? '',
|
|
24
|
+
ticketParam: 'ticket',
|
|
25
|
+
logoutPath: '/logout',
|
|
26
|
+
apiBaseUrl: process.env.API_BASE_URL ?? '',
|
|
27
|
+
proxySuffix: process.env.PROXY_SUFFIX ?? '',
|
|
28
|
+
loginEndpoint: process.env.LOGIN_ENDPOINT ?? '',
|
|
29
|
+
refreshEndpoint: process.env.REFRESH_ENDPOINT ?? '',
|
|
30
|
+
externalLoginPath: process.env.EXTERNAL_LOGIN_PATH ?? '',
|
|
31
|
+
};
|
|
32
|
+
const mico = getMicoConfig();
|
|
33
|
+
const workspace = getMicoWorkspace();
|
|
34
|
+
const appIdFromWorkspace =
|
|
35
|
+
workspace?.appId !== undefined && workspace?.appId !== null
|
|
36
|
+
? String(workspace.appId)
|
|
37
|
+
: undefined;
|
|
38
|
+
const externalLoginFromWorkspace = workspace?.casServerLoginUrl;
|
|
39
|
+
if (!mico && !appIdFromWorkspace && !externalLoginFromWorkspace)
|
|
40
|
+
return envDefaults;
|
|
41
|
+
return {
|
|
42
|
+
...envDefaults,
|
|
43
|
+
...(mico?.defaultClientOptions ?? {}),
|
|
44
|
+
appId: appIdFromWorkspace ?? mico?.appId ?? envDefaults.appId,
|
|
45
|
+
apiBaseUrl: mico?.apiBaseUrl ?? envDefaults.apiBaseUrl,
|
|
46
|
+
proxySuffix: mico?.proxySuffix ?? envDefaults.proxySuffix,
|
|
47
|
+
loginEndpoint: mico?.loginEndpoint ?? envDefaults.loginEndpoint,
|
|
48
|
+
refreshEndpoint: mico?.refreshEndpoint ?? envDefaults.refreshEndpoint,
|
|
49
|
+
externalLoginPath:
|
|
50
|
+
externalLoginFromWorkspace ??
|
|
51
|
+
mico?.externalLoginPath ??
|
|
52
|
+
envDefaults.externalLoginPath,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const defaultClientOptions: RequestClientOptions = buildDefaultClientOptions();
|
|
18
57
|
|
|
19
58
|
let clientOptions: RequestClientOptions = { ...defaultClientOptions };
|
|
20
59
|
|
|
@@ -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
|
};
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
maybePersistTokens,
|
|
8
8
|
setStoredAuthToken,
|
|
9
9
|
} from '@/common/auth/auth-manager';
|
|
10
|
+
import { isPageAuthFree } from '@/common/menu';
|
|
10
11
|
import { isNoAuthRoute, ROUTES } from '@/constants';
|
|
11
12
|
import {
|
|
12
13
|
getTicketParam,
|
|
@@ -37,6 +38,11 @@ export const handleAuthFailureRedirect = (): void => {
|
|
|
37
38
|
return;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
if (isPageAuthFree(window.location.pathname)) {
|
|
42
|
+
console.log('[SSO] 页面 accessControlEnabled=false,跳过 SSO 重定向');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
40
46
|
// 从 URL 中获取当前 redirect 登录的次数
|
|
41
47
|
const currentUrl = new URL(window.location.href);
|
|
42
48
|
const redirectCountParam = currentUrl.searchParams.get('redirect_count');
|
|
@@ -50,10 +50,8 @@ export function applyTheme(theme: ThemeMode): void {
|
|
|
50
50
|
if (typeof document === 'undefined') return;
|
|
51
51
|
|
|
52
52
|
if (theme === 'dark') {
|
|
53
|
-
document.body.setAttribute('arco-theme', 'dark');
|
|
54
53
|
document.body.setAttribute('data-theme', 'dark');
|
|
55
54
|
} else {
|
|
56
|
-
document.body.removeAttribute('arco-theme');
|
|
57
55
|
document.body.setAttribute('data-theme', 'normal');
|
|
58
56
|
}
|
|
59
57
|
}
|
|
@@ -188,10 +188,10 @@ function AppTabs() {
|
|
|
188
188
|
>
|
|
189
189
|
{tabs.map((tab) => {
|
|
190
190
|
const isChinese = isChineseLocale();
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
const label = getMenuLabel(
|
|
192
|
+
{ name: tab.title, nameEn: tab.titleEn, nameKey: tab.nameKey },
|
|
193
|
+
isChinese,
|
|
194
|
+
);
|
|
195
195
|
return <TabPane destroyOnHide key={tab.key} title={label} />;
|
|
196
196
|
})}
|
|
197
197
|
</Tabs>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getIconFontPath } from '@/common/locale';
|
|
2
2
|
import { Icon } from '@mico-platform/ui';
|
|
3
3
|
|
|
4
4
|
// 该组件用于自定义的图标,用法见https://arco.design/react/components/icon#%E6%B7%BB%E5%8A%A0-iconbox-%E9%A1%B9%E7%9B%AE
|
|
@@ -7,13 +7,12 @@ import { Icon } from '@mico-platform/ui';
|
|
|
7
7
|
// 字体库地址:https://arco.design/iconbox/mime/lib/1945/0
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* 根据当前语言加载对应的图标库
|
|
10
|
+
* 根据当前语言加载对应的图标库(当前只有中文)
|
|
11
11
|
* 语言切换时会重新加载页面,所以这里只需要在初始化时根据当前语言加载
|
|
12
12
|
*/
|
|
13
13
|
function createIconFont() {
|
|
14
|
-
//
|
|
15
|
-
const
|
|
16
|
-
const iconPath = getIconFontPath(currentLocale);
|
|
14
|
+
// TODO: 后续支持多语言时,需要根据当前语言加载对应的图标库
|
|
15
|
+
const iconPath = getIconFontPath();
|
|
17
16
|
|
|
18
17
|
// 创建 IconFont 实例
|
|
19
18
|
return Icon.addFromIconFontCn({ src: iconPath });
|
package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.less
CHANGED
|
@@ -4,20 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
.micro-app-container {
|
|
6
6
|
width: 100%;
|
|
7
|
-
height: 100%;
|
|
8
7
|
position: relative;
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
9
10
|
overflow: auto;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
.micro-app-loading {
|
|
13
|
-
position: absolute;
|
|
14
|
-
top: 0;
|
|
15
|
-
left: 0;
|
|
16
|
-
right: 0;
|
|
17
|
-
bottom: 0;
|
|
18
14
|
display: flex;
|
|
19
15
|
align-items: center;
|
|
20
16
|
justify-content: center;
|
|
17
|
+
min-height: 600px;
|
|
21
18
|
background: var(--color-bg-1);
|
|
22
19
|
z-index: 10;
|
|
23
20
|
}
|
|
@@ -48,10 +45,28 @@
|
|
|
48
45
|
// 激活时在文档流中,继承占位元素的尺寸
|
|
49
46
|
width: 100%;
|
|
50
47
|
height: 100%;
|
|
48
|
+
display: flex;
|
|
49
|
+
flex: 1;
|
|
50
|
+
flex-direction: column;
|
|
51
51
|
overflow: auto;
|
|
52
|
+
min-height: calc(100vh - 134px);
|
|
53
|
+
|
|
52
54
|
|
|
53
55
|
> div {
|
|
56
|
+
flex: 1;
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
> div > div {
|
|
54
62
|
height: 100%;
|
|
63
|
+
display: flex;
|
|
64
|
+
flex: 1 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
> div > div > div {
|
|
68
|
+
width: 100%;
|
|
69
|
+
flex: 1 1;
|
|
55
70
|
}
|
|
56
71
|
|
|
57
72
|
// 隐藏状态(在 body 中)
|
package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx
CHANGED
|
@@ -2,7 +2,8 @@ import { getAuthInfo } from '@/common/auth/auth-manager';
|
|
|
2
2
|
import { EEnv, getEnv } from '@/common/env';
|
|
3
3
|
import { getCurrentLocale } from '@/common/locale';
|
|
4
4
|
import { request } from '@/common/request';
|
|
5
|
-
import {
|
|
5
|
+
import { isPageAuthFree } from '@/common/menu/parser';
|
|
6
|
+
import { isAuthDisabled, isNoAuthRoute, isNoPermissionRoute } from '@/constants';
|
|
6
7
|
import { Spin } from '@mico-platform/ui';
|
|
7
8
|
import { useLocation, useModel } from '@umijs/max';
|
|
8
9
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
@@ -46,12 +47,12 @@ const MicroAppLoader: React.FC<MicroAppLoaderProps> = ({
|
|
|
46
47
|
mounted: false,
|
|
47
48
|
});
|
|
48
49
|
|
|
49
|
-
// 获取 initialState,等待用户信息准备好再加载子应用
|
|
50
|
-
// disableAuth 模式或免权限校验路由下跳过等待
|
|
51
50
|
const { initialState } = useModel('@@initialState');
|
|
52
51
|
const location = useLocation();
|
|
53
52
|
const isAuthReady =
|
|
54
53
|
isAuthDisabled() ||
|
|
54
|
+
isPageAuthFree(location.pathname) ||
|
|
55
|
+
isNoAuthRoute(location.pathname) ||
|
|
55
56
|
isNoPermissionRoute(location.pathname) ||
|
|
56
57
|
!!initialState?.currentUser;
|
|
57
58
|
|
|
@@ -391,6 +391,12 @@ class MicroAppManager {
|
|
|
391
391
|
activateContainer(container, request.target);
|
|
392
392
|
|
|
393
393
|
console.log('🔍[路由调试] 调用 loadMicroApp', { name: request.name, qiankunName, entry: request.entry });
|
|
394
|
+
|
|
395
|
+
if (process.env.NODE_ENV === 'development') {
|
|
396
|
+
// 重置标志,使子应用能初始化自己的 react-refresh 运行时以支持 HMR
|
|
397
|
+
(window as any).__reactRefreshInjected = false;
|
|
398
|
+
}
|
|
399
|
+
|
|
394
400
|
const microApp = loadMicroApp(
|
|
395
401
|
{
|
|
396
402
|
name: qiankunName,
|
|
@@ -399,7 +405,7 @@ class MicroAppManager {
|
|
|
399
405
|
props: request.props,
|
|
400
406
|
},
|
|
401
407
|
{
|
|
402
|
-
sandbox: { strictStyleIsolation: false, experimentalStyleIsolation: true },
|
|
408
|
+
sandbox: { strictStyleIsolation: false, experimentalStyleIsolation: false, loose: true },
|
|
403
409
|
},
|
|
404
410
|
);
|
|
405
411
|
|
package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logout, STORAGE_KEYS } from '@/common/auth';
|
|
2
|
+
import type { IUserInfo } from '@/common/auth/type';
|
|
2
3
|
import {
|
|
3
4
|
getCurrentTimezone,
|
|
4
5
|
getTimezoneRegion,
|
|
@@ -39,13 +40,10 @@ interface IMenuItem {
|
|
|
39
40
|
children?: IMenuItem[];
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
const DEFAULT_AVATAR =
|
|
43
|
-
'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png';
|
|
44
|
-
|
|
45
43
|
// TODO: 临时假数据,后续接入真实登录态
|
|
46
|
-
const MOCK_USER = {
|
|
44
|
+
const MOCK_USER: Partial<IUserInfo> = {
|
|
47
45
|
user_name: 'Test User',
|
|
48
|
-
|
|
46
|
+
username: 'test@micous.com',
|
|
49
47
|
};
|
|
50
48
|
|
|
51
49
|
export const AvatarName: React.FC<{ userName?: string }> = ({ userName }) => {
|
|
@@ -100,11 +98,11 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
|
|
|
100
98
|
|
|
101
99
|
// 管理头像 URL
|
|
102
100
|
const [avatarSrc, setAvatarSrc] = useState<string>(
|
|
103
|
-
currentUser?.avatar ||
|
|
101
|
+
currentUser?.avatar || '',
|
|
104
102
|
);
|
|
105
103
|
|
|
106
104
|
useEffect(() => {
|
|
107
|
-
setAvatarSrc(currentUser?.avatar ||
|
|
105
|
+
setAvatarSrc(currentUser?.avatar || '');
|
|
108
106
|
}, [currentUser?.avatar]);
|
|
109
107
|
|
|
110
108
|
// 加载时区列表
|
|
@@ -142,10 +140,6 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
|
|
|
142
140
|
};
|
|
143
141
|
}, []);
|
|
144
142
|
|
|
145
|
-
const handleAvatarError = () => {
|
|
146
|
-
setAvatarSrc(DEFAULT_AVATAR);
|
|
147
|
-
};
|
|
148
|
-
|
|
149
143
|
const loginOut = async () => {
|
|
150
144
|
logout();
|
|
151
145
|
const { search, pathname } = window.location;
|
|
@@ -370,11 +364,13 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
|
|
|
370
364
|
}
|
|
371
365
|
>
|
|
372
366
|
<div className="flex items-center avatar-dropdown-trigger">
|
|
373
|
-
|
|
374
|
-
<
|
|
375
|
-
<
|
|
376
|
-
|
|
377
|
-
|
|
367
|
+
{avatarSrc && (
|
|
368
|
+
<div className="flex items-center">
|
|
369
|
+
<Avatar size={24} shape="circle">
|
|
370
|
+
<img src={avatarSrc} alt="avatar" />
|
|
371
|
+
</Avatar>
|
|
372
|
+
</div>
|
|
373
|
+
)}
|
|
378
374
|
<AvatarName userName={currentUser?.user_name} />
|
|
379
375
|
<IconFont
|
|
380
376
|
type="webcs-outline_down1"
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
|
|
2
2
|
// 再导入 @mico-platform/theme 主题变量(CSS Variables + Less Variables)
|
|
3
3
|
// 这样项目的变量定义会在 UI 库之后,可以正确覆盖
|
|
4
|
-
@import '@mico-platform/theme';
|
|
4
|
+
@import '@mico-platform/theme/variables';
|
|
5
5
|
|
|
6
6
|
* {
|
|
7
7
|
box-sizing: border-box;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
// 禁用 Mac 触摸板双指左右滑动触发的浏览器前进/后退手势
|
|
11
|
+
html {
|
|
12
|
+
overscroll-behavior-x: none;
|
|
13
|
+
height: 100%;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
body {
|
|
17
|
+
overscroll-behavior-x: none;
|
|
18
|
+
height: 100%;
|
|
19
|
+
background-color: @color-fill-1;
|
|
20
|
+
}
|
|
21
|
+
|
|
10
22
|
#root {
|
|
11
|
-
min-height:
|
|
23
|
+
min-height: max(100%, 800px);
|
|
24
|
+
// 这里是为了让子容器撑满高度,正常的height: 100%并不能撑满
|
|
12
25
|
display: flex;
|
|
13
26
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { MenuItem, ParsedMenuItem, ParsedRoute } from '@/common/menu';
|
|
2
|
-
import { extractRoutes,
|
|
2
|
+
import { extractRoutes, parseMenuItems } from '@/common/menu';
|
|
3
|
+
import { getMenus } from '@/common/portal-data';
|
|
3
4
|
import { useMemo } from 'react';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -8,7 +9,7 @@ import { useMemo } from 'react';
|
|
|
8
9
|
export const useMenu = () => {
|
|
9
10
|
// 获取原始菜单数据
|
|
10
11
|
const rawMenus = useMemo<MenuItem[]>(() => {
|
|
11
|
-
return
|
|
12
|
+
return getMenus();
|
|
12
13
|
}, []);
|
|
13
14
|
|
|
14
15
|
// 解析后的菜单项(用于渲染菜单组件)
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
.layout-menu
|
|
12
|
+
.layout-menu.arco-menu-dark,
|
|
13
|
+
.layout-menu.arco-menu {
|
|
13
14
|
font-size: @font-size-base;
|
|
15
|
+
background-color: @color-text-5;
|
|
14
16
|
|
|
15
17
|
// Base menu item styles
|
|
16
18
|
.arco-menu-item {
|
|
@@ -48,7 +50,7 @@
|
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
.arco-menu-indent {
|
|
51
|
-
width:
|
|
53
|
+
width: 12px;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
.layout-menu-level-3 {
|
|
@@ -133,10 +135,12 @@
|
|
|
133
135
|
}
|
|
134
136
|
|
|
135
137
|
// Sider styles
|
|
136
|
-
|
|
138
|
+
.layout-web-sider {
|
|
137
139
|
box-shadow: none;
|
|
140
|
+
background-color: @color-text-5 !important;
|
|
138
141
|
position: fixed !important;
|
|
139
|
-
height: calc(100vh -
|
|
142
|
+
height: calc(100vh - @header-height) !important;
|
|
143
|
+
z-index: 999;
|
|
140
144
|
|
|
141
145
|
.arco-layout-sider-trigger {
|
|
142
146
|
display: flex;
|
|
@@ -148,6 +152,10 @@
|
|
|
148
152
|
.click-trigger-btn {
|
|
149
153
|
cursor: pointer;
|
|
150
154
|
display: flex;
|
|
155
|
+
width: 24px;
|
|
156
|
+
height: 24px;
|
|
157
|
+
align-items: center;
|
|
158
|
+
justify-content: center;
|
|
151
159
|
|
|
152
160
|
// 图标颜色适配主题
|
|
153
161
|
.arco-icon {
|
|
@@ -163,4 +171,24 @@
|
|
|
163
171
|
.click-trigger-btn.collapsed {
|
|
164
172
|
justify-content: center;
|
|
165
173
|
}
|
|
174
|
+
|
|
175
|
+
.arco-menu-collapse .arco-menu-item .arco-icon,
|
|
176
|
+
.arco-menu-collapse .arco-menu-pop-header .arco-icon {
|
|
177
|
+
margin-left: 0;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.arco-menu .arco-menu-item,
|
|
181
|
+
.arco-menu .arco-menu-group-title,
|
|
182
|
+
.arco-menu .arco-menu-pop-header,
|
|
183
|
+
.arco-menu .arco-menu-inline-header .arco-menu-dark .arco-menu-item,
|
|
184
|
+
.arco-menu-dark .arco-menu-group-title,
|
|
185
|
+
.arco-menu-dark .arco-menu-pop-header,
|
|
186
|
+
.arco-menu-dark .arco-menu-inline-header {
|
|
187
|
+
background-color: @color-text-5;
|
|
188
|
+
color: @color-text-2;
|
|
189
|
+
|
|
190
|
+
&.arco-menu-selected {
|
|
191
|
+
color: @Brand1-6;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
166
194
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { ParsedMenuItem } from '@/common/menu';
|
|
2
|
-
import { filterMenuItems,
|
|
3
|
-
import {
|
|
2
|
+
import { filterMenuItems, parseMenuItems } from '@/common/menu';
|
|
3
|
+
import { getMenus } from '@/common/portal-data';
|
|
4
4
|
import IconFont from '@/components/IconFont';
|
|
5
|
+
import { isAuthDisabled } from '@/constants';
|
|
5
6
|
import { useMenuState } from '@/hooks/useMenuState';
|
|
6
7
|
import { useTheme } from '@/hooks/useTheme';
|
|
7
8
|
import { Layout, Menu } from '@mico-platform/ui';
|
|
8
9
|
import * as Icons from '@mico-platform/ui/icon';
|
|
9
|
-
import {
|
|
10
|
+
import { useModel } from '@umijs/max';
|
|
10
11
|
import React, { useEffect, useMemo, useRef } from 'react';
|
|
11
12
|
import './index.less';
|
|
12
13
|
|
|
@@ -30,6 +31,16 @@ const ICON_ALIAS_MAP: Record<string, keyof typeof Icons> = {
|
|
|
30
31
|
const getIconComponent = (iconName: string): React.ReactNode => {
|
|
31
32
|
if (!iconName) return null;
|
|
32
33
|
|
|
34
|
+
// 从中台配置的 icon 是有 Icon 前缀的。类似 IconMenu 这种
|
|
35
|
+
const directIconKey = iconName as keyof typeof Icons;
|
|
36
|
+
const DirectIconComponent = Icons[directIconKey] as
|
|
37
|
+
| React.ComponentType
|
|
38
|
+
| undefined;
|
|
39
|
+
|
|
40
|
+
if (DirectIconComponent) {
|
|
41
|
+
return <DirectIconComponent />;
|
|
42
|
+
}
|
|
43
|
+
|
|
33
44
|
// 尝试直接匹配 Icon${name} 格式
|
|
34
45
|
const iconKey = `Icon${iconName}` as keyof typeof Icons;
|
|
35
46
|
const IconComponent = Icons[iconKey] as React.ComponentType | undefined;
|
|
@@ -104,16 +115,15 @@ interface LayoutMenuProps {
|
|
|
104
115
|
const LayoutMenu: React.FC<LayoutMenuProps> = () => {
|
|
105
116
|
const siderRef = useRef<HTMLDivElement>(null);
|
|
106
117
|
const { isDark } = useTheme();
|
|
107
|
-
const location = useLocation();
|
|
108
|
-
|
|
109
118
|
const { initialState } = useModel('@@initialState');
|
|
110
119
|
const currentUser = initialState?.currentUser;
|
|
111
120
|
|
|
112
121
|
// Parse menu data
|
|
113
|
-
//
|
|
122
|
+
// isMenuAllowed 内部已对每个菜单项单独检查 isNoPermissionRoute,
|
|
123
|
+
// 无需在此按当前页面路径全局跳过过滤,避免菜单可见性随页面变化
|
|
114
124
|
const menuItems = useMemo(() => {
|
|
115
|
-
const menus =
|
|
116
|
-
if (isAuthDisabled()
|
|
125
|
+
const menus = getMenus();
|
|
126
|
+
if (isAuthDisabled()) {
|
|
117
127
|
return parseMenuItems(menus);
|
|
118
128
|
}
|
|
119
129
|
const filteredMenus = filterMenuItems(menus, {
|
|
@@ -121,7 +131,7 @@ const LayoutMenu: React.FC<LayoutMenuProps> = () => {
|
|
|
121
131
|
sideMenus: (currentUser?.side_menus || []) as string[],
|
|
122
132
|
});
|
|
123
133
|
return parseMenuItems(filteredMenus);
|
|
124
|
-
}, [currentUser?.is_superuser, currentUser?.side_menus
|
|
134
|
+
}, [currentUser?.is_superuser, currentUser?.side_menus]);
|
|
125
135
|
|
|
126
136
|
// 使用菜单状态 Hook
|
|
127
137
|
const {
|
|
@@ -166,7 +176,7 @@ const LayoutMenu: React.FC<LayoutMenuProps> = () => {
|
|
|
166
176
|
onCollapse={handleCollapsed}
|
|
167
177
|
collapsible
|
|
168
178
|
breakpoint="xl"
|
|
169
|
-
className="
|
|
179
|
+
className="layout-web-sider"
|
|
170
180
|
trigger={clickTriggerBtn}
|
|
171
181
|
theme={isDark ? 'dark' : 'light'}
|
|
172
182
|
>
|