generator-mico-cli 0.2.20 → 0.2.22
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 +29 -0
- package/bin/mico.js +124 -5
- package/generators/micro-react/index.js +76 -17
- 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 +15 -7
- package/generators/micro-react/templates/_gitignore +2 -0
- package/generators/micro-react/templates/apps/layout/config/config.dev.ts +7 -3
- package/generators/micro-react/templates/apps/layout/config/config.ts +21 -0
- package/generators/micro-react/templates/apps/layout/config/routes.ts +0 -5
- package/generators/micro-react/templates/apps/layout/docs/common-intl.md +8 -6
- 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 +65 -37
- 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 +112 -48
- package/generators/micro-react/templates/apps/layout/docs/feature-/350/267/257/347/224/261/344/270/216/350/217/234/345/215/225/350/247/243/350/200/246.md +179 -0
- package/generators/micro-react/templates/apps/layout/docs/utils-timezone.md +4 -2
- package/generators/micro-react/templates/apps/layout/mock/menus.ts +89 -139
- package/generators/micro-react/templates/apps/layout/mock/pages.ts +83 -0
- package/generators/micro-react/templates/apps/layout/package.json +3 -2
- package/generators/micro-react/templates/apps/layout/src/app.tsx +10 -8
- package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +121 -58
- package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +35 -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 +20 -1
- 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/global.less +15 -3
- 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 +30 -3
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +15 -4
- 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 +2 -2
- package/generators/micro-react/templates/dev.preset.json +1 -1
- package/generators/micro-react/templates/package.json +2 -1
- package/generators/subapp-react/index.js +240 -14
- package/generators/subapp-react/templates/homepage/.env +2 -1
- package/generators/subapp-react/templates/homepage/config/config.dev.ts +9 -1
- package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +2 -1
- package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +2 -1
- package/generators/subapp-react/templates/homepage/config/config.prod.ts +2 -1
- package/generators/subapp-react/templates/homepage/config/config.ts +21 -0
- package/generators/subapp-react/templates/homepage/config/routes.ts +1 -1
- package/generators/subapp-react/templates/homepage/mock/api.mock.ts +2 -2
- package/generators/subapp-react/templates/homepage/package.json +3 -2
- package/generators/subapp-react/templates/homepage/src/app.tsx +1 -1
- package/generators/subapp-react/templates/homepage/src/common/request.ts +2 -2
- package/generators/subapp-react/templates/homepage/src/global.less +2 -1
- package/generators/subapp-react/templates/homepage/src/pages/index.less +1 -1
- package/generators/subapp-react/templates/homepage/src/pages/index.tsx +27 -27
- package/lib/utils.js +200 -2
- package/package.json +1 -1
|
@@ -1,14 +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
|
-
height:
|
|
12
|
-
|
|
23
|
+
min-height: max(100%, 800px);
|
|
24
|
+
// 这里是为了让子容器撑满高度,正常的height: 100%并不能撑满
|
|
13
25
|
display: flex;
|
|
14
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 {
|
|
@@ -135,8 +137,9 @@
|
|
|
135
137
|
// Sider styles
|
|
136
138
|
.<%= projectName %>-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;
|
|
140
143
|
|
|
141
144
|
.arco-layout-sider-trigger {
|
|
142
145
|
display: flex;
|
|
@@ -148,6 +151,10 @@
|
|
|
148
151
|
.click-trigger-btn {
|
|
149
152
|
cursor: pointer;
|
|
150
153
|
display: flex;
|
|
154
|
+
width: 24px;
|
|
155
|
+
height: 24px;
|
|
156
|
+
align-items: center;
|
|
157
|
+
justify-content: center;
|
|
151
158
|
|
|
152
159
|
// 图标颜色适配主题
|
|
153
160
|
.arco-icon {
|
|
@@ -163,4 +170,24 @@
|
|
|
163
170
|
.click-trigger-btn.collapsed {
|
|
164
171
|
justify-content: center;
|
|
165
172
|
}
|
|
173
|
+
|
|
174
|
+
.arco-menu-collapse .arco-menu-item .arco-icon,
|
|
175
|
+
.arco-menu-collapse .arco-menu-pop-header .arco-icon {
|
|
176
|
+
margin-left: 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.arco-menu .arco-menu-item,
|
|
180
|
+
.arco-menu .arco-menu-group-title,
|
|
181
|
+
.arco-menu .arco-menu-pop-header,
|
|
182
|
+
.arco-menu .arco-menu-inline-header .arco-menu-dark .arco-menu-item,
|
|
183
|
+
.arco-menu-dark .arco-menu-group-title,
|
|
184
|
+
.arco-menu-dark .arco-menu-pop-header,
|
|
185
|
+
.arco-menu-dark .arco-menu-inline-header {
|
|
186
|
+
background-color: @color-text-5;
|
|
187
|
+
color: @color-text-2;
|
|
188
|
+
|
|
189
|
+
&.arco-menu-selected {
|
|
190
|
+
color: @Brand1-6;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
166
193
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
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, isNoPermissionRoute } 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';
|
|
@@ -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;
|
|
@@ -112,7 +123,7 @@ const LayoutMenu: React.FC<LayoutMenuProps> = () => {
|
|
|
112
123
|
// Parse menu data
|
|
113
124
|
// disableAuth 或免权限校验路由时不过滤,显示全部菜单
|
|
114
125
|
const menuItems = useMemo(() => {
|
|
115
|
-
const menus =
|
|
126
|
+
const menus = getMenus();
|
|
116
127
|
if (isAuthDisabled() || isNoPermissionRoute(location.pathname)) {
|
|
117
128
|
return parseMenuItems(menus);
|
|
118
129
|
}
|
|
@@ -166,7 +177,7 @@ const LayoutMenu: React.FC<LayoutMenuProps> = () => {
|
|
|
166
177
|
onCollapse={handleCollapsed}
|
|
167
178
|
collapsible
|
|
168
179
|
breakpoint="xl"
|
|
169
|
-
className="
|
|
180
|
+
className="common-web-sider"
|
|
170
181
|
trigger={clickTriggerBtn}
|
|
171
182
|
theme={isDark ? 'dark' : 'light'}
|
|
172
183
|
>
|
|
@@ -2,9 +2,12 @@ import { layoutLogger } from '@/common/logger';
|
|
|
2
2
|
import {
|
|
3
3
|
extractRoutes,
|
|
4
4
|
filterMenuItems,
|
|
5
|
+
findPageByPath,
|
|
5
6
|
findRouteByPath,
|
|
6
|
-
|
|
7
|
+
getDynamicRoutes,
|
|
8
|
+
isSuperuserUser,
|
|
7
9
|
} from '@/common/menu';
|
|
10
|
+
import { getMenus, getPages, hasPages } from '@/common/portal-data';
|
|
8
11
|
import { getAppNameFromEntry } from '@/common/micro';
|
|
9
12
|
import AppTabs from '@/components/AppTabs';
|
|
10
13
|
import MicroAppLoader from '@/components/MicroAppLoader';
|
|
@@ -46,57 +49,89 @@ const BasicLayout: React.FC = () => {
|
|
|
46
49
|
[currentUser?.is_superuser, currentUser?.side_menus],
|
|
47
50
|
);
|
|
48
51
|
|
|
49
|
-
//
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
return extractRoutes(menus);
|
|
52
|
+
// 所有页面路由(优先 PAGES,降级 MENUS)— 用于路由匹配和渲染
|
|
53
|
+
const allPageRoutes = useMemo(() => {
|
|
54
|
+
return getDynamicRoutes();
|
|
53
55
|
}, []);
|
|
54
56
|
|
|
55
|
-
//
|
|
56
|
-
const
|
|
57
|
+
// 菜单路由(从 MENUS)— 用于权限交叉引用
|
|
58
|
+
const allMenuRoutes = useMemo(() => {
|
|
59
|
+
return extractRoutes(getMenus());
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
const allowedMenuRoutes = useMemo(() => {
|
|
57
63
|
if (isAuthDisabled()) {
|
|
58
|
-
return
|
|
64
|
+
return allMenuRoutes;
|
|
59
65
|
}
|
|
60
|
-
const
|
|
61
|
-
const filteredMenus = filterMenuItems(menus, filterOptions);
|
|
66
|
+
const filteredMenus = filterMenuItems(getMenus(), filterOptions);
|
|
62
67
|
return extractRoutes(filteredMenus);
|
|
63
|
-
}, [filterOptions,
|
|
68
|
+
}, [filterOptions, allMenuRoutes]);
|
|
64
69
|
|
|
65
|
-
//
|
|
70
|
+
// 当前路由配置(从所有页面路由中查找)
|
|
66
71
|
const currentRoute = useMemo(() => {
|
|
67
|
-
return findRouteByPath(
|
|
68
|
-
}, [
|
|
72
|
+
return findRouteByPath(allPageRoutes, location.pathname);
|
|
73
|
+
}, [allPageRoutes, location.pathname]);
|
|
69
74
|
|
|
70
|
-
//
|
|
75
|
+
// 权限判断:菜单交叉引用 + 隐藏页面级兜底
|
|
71
76
|
const isForbidden = useMemo(() => {
|
|
72
|
-
// 关闭权限控制时,不校验权限
|
|
73
77
|
if (isAuthDisabled()) return false;
|
|
74
|
-
// 免权限校验路由,不检查菜单权限
|
|
75
78
|
if (isNoPermissionRoute(location.pathname)) return false;
|
|
76
|
-
//
|
|
77
|
-
if (currentRoute) return false;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
79
|
+
// 非动态路由,交给 Umi 处理(404 等)
|
|
80
|
+
if (!currentRoute) return false;
|
|
81
|
+
if (isSuperuserUser(currentUser?.is_superuser)) return false;
|
|
82
|
+
|
|
83
|
+
// Tier 1: 菜单权限交叉引用
|
|
84
|
+
const inAllMenu = findRouteByPath(allMenuRoutes, location.pathname);
|
|
85
|
+
if (inAllMenu) {
|
|
86
|
+
const inAllowed = findRouteByPath(allowedMenuRoutes, location.pathname);
|
|
87
|
+
const forbidden = !inAllowed;
|
|
88
|
+
|
|
89
|
+
layoutLogger.log('isForbidden (menu check):', {
|
|
90
|
+
pathname: location.pathname,
|
|
91
|
+
inAllMenu: true,
|
|
92
|
+
inAllowed: !!inAllowed,
|
|
93
|
+
forbidden,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return forbidden;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Tier 2: 隐藏页面级权限(仅在 PAGES 数据可用时生效)
|
|
100
|
+
if (!hasPages()) return false;
|
|
101
|
+
|
|
102
|
+
const page = findPageByPath(getPages(), location.pathname);
|
|
103
|
+
if (!page) return false;
|
|
104
|
+
|
|
105
|
+
if (page.adminOnly) {
|
|
106
|
+
layoutLogger.log('isForbidden (hidden page adminOnly):', {
|
|
107
|
+
pathname: location.pathname,
|
|
108
|
+
pageId: page.id,
|
|
109
|
+
});
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (page.accessControlEnabled) {
|
|
114
|
+
const sideMenus = (currentUser?.side_menus || []) as string[];
|
|
115
|
+
const forbidden = !page.routeKey || !sideMenus.includes(page.routeKey);
|
|
116
|
+
|
|
117
|
+
layoutLogger.log('isForbidden (hidden page accessControl):', {
|
|
118
|
+
pathname: location.pathname,
|
|
119
|
+
pageId: page.id,
|
|
120
|
+
routeKey: page.routeKey,
|
|
121
|
+
hasSideMenuMatch: !forbidden,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return forbidden;
|
|
125
|
+
}
|
|
92
126
|
|
|
93
|
-
return
|
|
127
|
+
return false;
|
|
94
128
|
}, [
|
|
95
129
|
currentRoute,
|
|
96
|
-
|
|
97
|
-
|
|
130
|
+
allMenuRoutes,
|
|
131
|
+
allowedMenuRoutes,
|
|
98
132
|
location.pathname,
|
|
99
|
-
|
|
133
|
+
currentUser?.is_superuser,
|
|
134
|
+
currentUser?.side_menus,
|
|
100
135
|
]);
|
|
101
136
|
|
|
102
137
|
// 判断是否需要显示布局
|
|
@@ -195,7 +230,9 @@ const BasicLayout: React.FC = () => {
|
|
|
195
230
|
<AppTabs />
|
|
196
231
|
<Content className="layout-content-outlet">
|
|
197
232
|
<Suspense
|
|
198
|
-
fallback={
|
|
233
|
+
fallback={
|
|
234
|
+
<Spin dot style={{ width: '100%', marginTop: 100 }} />
|
|
235
|
+
}
|
|
199
236
|
>
|
|
200
237
|
{renderContent()}
|
|
201
238
|
</Suspense>
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
filterMenuItems,
|
|
4
|
-
getWindowMenus,
|
|
5
|
-
type MenuFilterOptions,
|
|
6
|
-
} from '@/common/menu';
|
|
1
|
+
import { extractRoutes, filterMenuItems, type MenuFilterOptions } from '@/common/menu';
|
|
2
|
+
import { getMenus } from '@/common/portal-data';
|
|
7
3
|
import { isAuthDisabled } from '@/constants';
|
|
8
4
|
import { Button, Result, Space } from '@mico-platform/ui';
|
|
9
5
|
import { history, useModel } from '@umijs/max';
|
|
@@ -13,7 +9,7 @@ import React, { useMemo } from 'react';
|
|
|
13
9
|
* 获取第一个可访问的路由路径
|
|
14
10
|
*/
|
|
15
11
|
const getFirstAvailablePath = (filterOptions: MenuFilterOptions): string => {
|
|
16
|
-
const menus =
|
|
12
|
+
const menus = getMenus();
|
|
17
13
|
|
|
18
14
|
// 根据权限过滤菜单
|
|
19
15
|
const filteredMenus = isAuthDisabled()
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"lint-staged": "pnpm exec lint-staged --quiet",
|
|
27
27
|
"prepare": "husky",
|
|
28
28
|
"test": "dotenv -e .env -e .env.local -- turbo run test",
|
|
29
|
-
"create:umi-app": "./scripts/create-umi-app.sh"
|
|
29
|
+
"create:umi-app": "./scripts/create-umi-app.sh",
|
|
30
|
+
"upgrade:mico": "npx @mico-platform/mico-up"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@commitlint/cli": "^19.5.0",
|