generator-mico-cli 0.2.8 → 0.2.10

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.
Files changed (18) hide show
  1. package/generators/micro-react/index.js +8 -3
  2. package/generators/micro-react/templates/apps/layout/config/config.dev.ts +1 -1
  3. 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 +187 -53
  4. package/generators/micro-react/templates/apps/layout/src/app.tsx +16 -4
  5. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +15 -0
  6. package/generators/micro-react/templates/apps/layout/src/common/request/index.ts +2 -2
  7. package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.tsx +2 -2
  8. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +8 -2
  9. package/generators/micro-react/templates/apps/layout/src/constants/index.ts +108 -5
  10. package/generators/micro-react/templates/apps/layout/src/hooks/useRoutePermissionRefresh.ts +2 -2
  11. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +1 -1
  12. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +8 -2
  13. package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +16 -5
  14. package/generators/micro-react/templates/apps/layout/src/services/user.ts +1 -0
  15. package/generators/subapp-react/index.js +10 -1
  16. package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +0 -1
  17. package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +0 -1
  18. package/package.json +1 -1
@@ -139,15 +139,20 @@ module.exports = class extends Generator {
139
139
  }
140
140
  }
141
141
 
142
+ install() {
143
+ this.log('');
144
+ this.log('📦 正在安装依赖...');
145
+ this.spawnCommandSync('pnpm', ['install'], {
146
+ cwd: this.destDir
147
+ });
148
+ }
149
+
142
150
  end() {
143
151
  this.log('');
144
152
  this.log('✅ 项目创建成功!');
145
153
  this.log('');
146
154
  this.log(' 后续步骤:');
147
155
  this.log('');
148
- this.log(' # 安装依赖');
149
- this.log(' pnpm install');
150
- this.log('');
151
156
  this.log(' # 启动开发服务器');
152
157
  this.log(' pnpm dev');
153
158
  this.log('');
@@ -17,7 +17,7 @@ const config: ReturnType<typeof defineConfig> = {
17
17
  content: `
18
18
  window.__MICO_MENUS__ = ${JSON.stringify(mockMenus)};
19
19
  window.__MICO_CONFIG__ = {
20
- appName: 'Mico Center',
20
+ appName: '<%= projectName %>',
21
21
  apiBaseUrl: '',
22
22
  defaultPath: '',
23
23
  };
@@ -1,11 +1,32 @@
1
1
  # 菜单权限控制
2
2
 
3
3
  > 创建时间:2026-01-24
4
+ > 更新时间:2026-01-26
4
5
 
5
6
  ## 功能概述
6
7
 
7
8
  基于用户信息中的 `side_menus` 字段实现菜单和路由的白名单权限控制。非超级用户只能看到和访问 `side_menus` 中配置的菜单项,访问无权限路由时显示 403 页面。
8
9
 
10
+ **v2 更新**:支持认证(Authentication)与授权(Authorization)分离配置,可独立控制"跳过 SSO 登录"和"跳过菜单权限校验"。
11
+
12
+ ## 核心概念
13
+
14
+ ### 认证 vs 授权
15
+
16
+ | 概念 | 英文 | 作用 | 配置项 |
17
+ | --- | --- | --- | --- |
18
+ | 认证 | Authentication | 确认用户身份(是否登录) | `noAuthRouteList` |
19
+ | 授权 | Authorization | 确认用户权限(能否访问) | `noPermissionRouteList` |
20
+
21
+ ### 四种路由场景
22
+
23
+ | 场景 | noAuthRouteList | noPermissionRouteList | 示例 |
24
+ | --- | --- | --- | --- |
25
+ | 公开页面 | ✅ | ✅ | 首页、活动页 |
26
+ | 登录后公共页面 | ❌ | ✅ | 个人设置、帮助中心 |
27
+ | 需要权限的页面 | ❌ | ❌ | 后台管理、业务功能 |
28
+ | 登录页等特殊页面 | ✅ | ✅ | /user/login、/403 |
29
+
9
30
  ## 技术方案
10
31
 
11
32
  ### 技术栈
@@ -14,24 +35,68 @@
14
35
  - UI 组件:Arco Design (Result 组件)
15
36
  - 状态管理:Umi initialState
16
37
 
17
- ### 核心实现
38
+ ### 权限控制流程
18
39
 
19
- 1. 用户信息接口返回 `side_menus` 字段(白名单)
20
- 2. `filterMenuItems` 根据白名单过滤菜单项
21
- 3. Layout 组件对比"所有路由"和"有权限路由"判断 403
22
- 4. 无权限时在 Layout 内直接渲染 403 组件(无跳转)
40
+ ```
41
+ 用户访问路由
42
+
43
+
44
+ ┌─────────────────────────────────┐
45
+ │ 1. SSO 认证检查 (app.tsx) │
46
+ │ isNoAuthRoute(pathname)? │
47
+ │ ├── 是 → 跳过 SSO │
48
+ │ └── 否 → 执行 ensureSsoSession()
49
+ └─────────────────────────────────┘
50
+
51
+
52
+ ┌─────────────────────────────────┐
53
+ │ 2. 权限校验 (layouts/index.tsx) │
54
+ │ isNoPermissionRoute(pathname)?
55
+ │ ├── 是 → 跳过权限校验 │
56
+ │ └── 否 → 检查 side_menus │
57
+ │ ├── 有权限 → 正常渲染
58
+ │ └── 无权限 → 显示 403
59
+ └─────────────────────────────────┘
60
+
61
+
62
+ ┌─────────────────────────────────┐
63
+ │ 3. 菜单渲染 (menu/index.tsx) │
64
+ │ isNoPermissionRoute(pathname)?
65
+ │ ├── 是 → 显示全部菜单 │
66
+ │ └── 否 → 按 side_menus 过滤 │
67
+ └─────────────────────────────────┘
68
+
69
+
70
+ ┌─────────────────────────────────┐
71
+ │ 4. 子应用加载 (MicroAppLoader) │
72
+ │ isNoPermissionRoute(pathname)?
73
+ │ ├── 是 → 直接加载 │
74
+ │ └── 否 → 等待 currentUser │
75
+ └─────────────────────────────────┘
76
+ ```
23
77
 
24
- ### 权限判断逻辑
78
+ ### 权限判断逻辑(详细)
25
79
 
26
80
  ```
27
81
  用户访问 /some-path
28
-
82
+
83
+
84
+ 是否 disableAuth === true?
85
+ ├── 是 → 允许所有访问(调试模式)
86
+
87
+
88
+ 是否在 noPermissionRouteList 中?
89
+ ├── 是 → 允许访问,显示全部菜单
90
+
91
+
29
92
  是否是超级用户?
30
93
  ├── 是 → 允许访问所有菜单
31
- └── 否 → 检查 side_menus 白名单
32
- ├── 精确匹配:menuPath === side_menus[i]
33
- ├── 前缀匹配:side_menus[i].startsWith(menuPath + '.')
34
- └── 都不匹配 禁止访问,显示 403
94
+
95
+
96
+ 检查 side_menus 白名单
97
+ ├── 精确匹配:menuPath === side_menus[i]
98
+ ├── 前缀匹配:side_menus[i].startsWith(menuPath + '.')
99
+ └── 都不匹配 → 禁止访问,显示 403
35
100
  ```
36
101
 
37
102
  ## 文件清单
@@ -39,20 +104,59 @@
39
104
  ### 新增文件
40
105
 
41
106
  | 文件路径 | 说明 |
42
- |----------|------|
107
+ | --- | --- |
43
108
  | `src/pages/403/index.tsx` | 403 无权限页面组件 |
44
109
 
45
110
  ### 修改文件
46
111
 
47
112
  | 文件路径 | 修改内容 |
48
- |----------|----------|
113
+ | --- | --- |
114
+ | `src/common/menu/types.ts` | 新增 `noPermissionRouteList` 类型定义 |
115
+ | `src/constants/index.ts` | 新增 `isNoPermissionRoute`、`getNoPermissionRouteList` 函数 |
49
116
  | `src/common/menu/parser.ts` | 新增 `filterMenuItems`、`isMenuAllowed` 等权限过滤函数 |
50
- | `src/layouts/index.tsx` | 新增 `isForbidden` 判断和 403 渲染逻辑 |
51
- | `src/layouts/components/menu/index.tsx` | 集成菜单过滤 |
117
+ | `src/layouts/index.tsx` | 新增 `isForbidden` 判断,集成 `isNoPermissionRoute` |
118
+ | `src/layouts/components/menu/index.tsx` | 集成菜单过滤,支持免权限校验路由 |
119
+ | `src/components/MicroAppLoader/index.tsx` | 集成 `isNoPermissionRoute`,免权限路由直接加载 |
120
+ | `src/app.tsx` | SSO 认证检查,使用 `isNoAuthRoute` |
52
121
  | `config/routes.ts` | 新增 `/403` 路由 |
53
122
 
54
123
  ## API / 组件接口
55
124
 
125
+ ### Window 配置
126
+
127
+ ```typescript
128
+ interface Window {
129
+ __MICO_CONFIG__?: {
130
+ /**
131
+ * 免认证路由列表(跳过 SSO 登录)
132
+ * 支持精确匹配和前缀匹配(以 /* 结尾)
133
+ */
134
+ noAuthRouteList?: string[];
135
+ /**
136
+ * 免权限校验路由列表(跳过菜单权限检查)
137
+ * 支持精确匹配和前缀匹配(以 /* 结尾)
138
+ * 注意:如果同时需要跳过 SSO,需同时配置 noAuthRouteList
139
+ */
140
+ noPermissionRouteList?: string[];
141
+ /** 关闭权限控制(菜单全部显示,路由不校验权限) */
142
+ disableAuth?: boolean;
143
+ };
144
+ }
145
+ ```
146
+
147
+ ### 路由判断函数
148
+
149
+ ```typescript
150
+ // 判断是否为免认证路由(跳过 SSO)
151
+ function isNoAuthRoute(pathname: string): boolean;
152
+
153
+ // 判断是否为免权限校验路由(跳过菜单权限检查)
154
+ function isNoPermissionRoute(pathname: string): boolean;
155
+
156
+ // 判断是否关闭权限控制
157
+ function isAuthDisabled(): boolean;
158
+ ```
159
+
56
160
  ### MenuFilterOptions
57
161
 
58
162
  ```typescript
@@ -77,26 +181,6 @@ function filterMenuItems(
77
181
  ): MenuItem[];
78
182
  ```
79
183
 
80
- **参数说明**:
81
-
82
- | 参数 | 类型 | 必填 | 说明 |
83
- |------|------|------|------|
84
- | items | MenuItem[] | 是 | 原始菜单数据 |
85
- | options | MenuFilterOptions | 否 | 过滤选项 |
86
- | parentPath | string | 否 | 父级路径(递归用) |
87
-
88
- ### isRouteAllowed
89
-
90
- ```typescript
91
- /**
92
- * 检查路由是否允许访问
93
- */
94
- function isRouteAllowed(
95
- menuPath: string,
96
- options?: MenuFilterOptions,
97
- ): boolean;
98
- ```
99
-
100
184
  ### 用户信息相关字段
101
185
 
102
186
  ```typescript
@@ -111,14 +195,45 @@ interface IUserInfo {
111
195
 
112
196
  ## 使用示例
113
197
 
114
- ### 用户信息接口返回
198
+ ### 配置免认证+免权限路由
199
+
200
+ ```javascript
201
+ // config/config.dev.ts
202
+ window.__MICO_CONFIG__ = {
203
+ // 免认证路由(跳过 SSO 登录)
204
+ noAuthRouteList: [
205
+ '/',
206
+ '/subapp',
207
+ '/public/*', // 前缀匹配
208
+ ],
209
+ // 免权限校验路由(跳过菜单权限检查)
210
+ noPermissionRouteList: [
211
+ '/',
212
+ '/subapp',
213
+ '/settings', // 登录后的公共页面
214
+ ],
215
+ disableAuth: false,
216
+ };
217
+ ```
115
218
 
116
- ```json
117
- {
118
- "is_superuser": false,
119
- "side_menus": ["列队管理.配置队列", "工作台"],
120
- "miss_permissions": ["列队管理.配置队列.查看"]
121
- }
219
+ ### 静态配置(代码中)
220
+
221
+ ```typescript
222
+ // src/constants/index.ts
223
+
224
+ // 静态免认证路由(始终生效)
225
+ export const NO_AUTH_ROUTE_LIST: string[] = [
226
+ '/user/login',
227
+ '/user/register',
228
+ '/403',
229
+ '/404',
230
+ ];
231
+
232
+ // 静态免权限校验路由(始终生效)
233
+ export const NO_PERMISSION_ROUTE_LIST: string[] = [
234
+ '/403',
235
+ '/404',
236
+ ];
122
237
  ```
123
238
 
124
239
  ### 菜单过滤结果
@@ -138,38 +253,57 @@ interface IUserInfo {
138
253
  ```tsx
139
254
  // layouts/index.tsx
140
255
  const isForbidden = useMemo(() => {
141
- if (currentRoute) return false; // 在有权限的路由中找到了
256
+ // 关闭权限控制时,不校验权限
257
+ if (isAuthDisabled()) return false;
258
+ // 免权限校验路由,不检查菜单权限
259
+ if (isNoPermissionRoute(location.pathname)) return false;
260
+ // 如果在有权限的路由中找到了,说明有权限
261
+ if (currentRoute) return false;
262
+ // 在所有路由中存在但无权限
142
263
  const routeInAll = findRouteByPath(allRoutes, location.pathname);
143
- return !!routeInAll; // 在所有路由中存在但无权限
264
+ return !!routeInAll;
144
265
  }, [currentRoute, allRoutes, location.pathname]);
266
+ ```
145
267
 
146
- const renderContent = () => {
147
- if (isForbidden) {
148
- return <ForbiddenPage />;
149
- }
150
- // ... 正常渲染
151
- };
268
+ ### MicroAppLoader 中的认证判断
269
+
270
+ ```tsx
271
+ // components/MicroAppLoader/index.tsx
272
+ const isAuthReady =
273
+ isAuthDisabled() ||
274
+ isNoPermissionRoute(location.pathname) ||
275
+ !!initialState?.currentUser;
276
+
277
+ // 未准备好时不加载子应用
278
+ if (!isAuthReady) {
279
+ return; // 继续等待
280
+ }
152
281
  ```
153
282
 
154
283
  ## 设计决策
155
284
 
156
285
  | 决策点 | 选择 | 理由 |
157
- |--------|------|------|
286
+ | --- | --- | --- |
287
+ | 认证与授权分离 | 两个独立配置项 | 不同场景需要不同组合,如"需要登录但不需要权限"的个人设置页 |
158
288
  | 权限模型 | 白名单 (`side_menus`) | 后端返回的 `side_menus` 是允许列表,比黑名单更安全 |
159
289
  | 403 处理 | 原地渲染组件 | 保持 URL 不变,用户体验更好,便于分享链接 |
160
290
  | 父级菜单显示 | 前缀匹配 | 子菜单有权限时,父级菜单需要作为容器显示 |
161
291
  | 超级用户 | 跳过所有检查 | 管理员需要完整访问权限 |
162
- | 权限管理菜单 | 硬编码禁止 | 非超级用户不应看到权限管理入口 |
292
+ | 免权限路由的菜单 | 显示全部 | 用户访问公开页面时应能看到所有导航选项 |
293
+ | 免权限路由的子应用 | 直接加载 | 不等待 currentUser,避免加载卡住 |
163
294
 
164
295
  ## 注意事项
165
296
 
297
+ - `noAuthRouteList` 和 `noPermissionRouteList` 是独立的,需要根据场景分别配置
298
+ - 两个列表都支持 `/*` 后缀进行前缀匹配
166
299
  - `side_menus` 为空时,非超级用户没有任何菜单权限
167
300
  - `side_menus` 格式为菜单路径,如 `"列队管理.配置队列"`
168
301
  - `miss_permissions` 用于按钮级别权限控制,不影响菜单显示
169
302
  - 403 页面在 Layout 内渲染,不会触发路由跳转
170
303
  - 调试时可在控制台搜索 `isForbidden check` 查看权限判断日志
304
+ - **常见问题**:配置了 `noAuthRouteList` 但页面仍显示 403,需同时配置 `noPermissionRouteList`
171
305
 
172
306
  ## 相关文档
173
307
 
174
308
  - [微前端模式](./feature-微前端模式.md) - 路由和菜单解析
175
- - [用户信息接口](./feature-用户信息接口.md) - 用户数据结构
309
+ - [日志与常量](./arch-日志与常量.md) - 常量管理
@@ -10,15 +10,18 @@ import { getStoredAuthToken } from './common/auth/auth-manager';
10
10
  import type { IUserInfo } from './common/auth/type';
11
11
  import { fetchUserInfo } from './services/user';
12
12
  import { extractRoutes, getWindowMenus } from './common/menu';
13
- import { ensureSsoSession } from './common/request/sso';
14
13
  import {
15
14
  clearMicroAppProps,
16
15
  type IMicroAppProps,
17
16
  setMicroAppProps,
18
17
  } from './common/micro';
18
+ import {
19
+ ensureSsoSession,
20
+ handleAuthFailureRedirect,
21
+ } from './common/request/sso';
19
22
  import { initTheme } from './common/theme';
20
23
  import MicroAppLoader from './components/MicroAppLoader';
21
- import { NO_AUTH_ROUTE_LIST } from '@/constants';
24
+ import { isNoAuthRoute } from '@/constants';
22
25
  import './global.less';
23
26
 
24
27
  // ==================== qiankun 全局错误处理 ====================
@@ -110,10 +113,10 @@ export async function getInitialState(): Promise<{
110
113
  };
111
114
 
112
115
  const { location } = history;
113
- const isNoAuthRoute = NO_AUTH_ROUTE_LIST.includes(location.pathname);
116
+ const noAuthRoute = isNoAuthRoute(location.pathname);
114
117
 
115
118
  // 非免认证路由:走 SSO 流程
116
- if (!isNoAuthRoute) {
119
+ if (!noAuthRoute) {
117
120
  await ensureSsoSession();
118
121
  }
119
122
 
@@ -128,6 +131,15 @@ export async function getInitialState(): Promise<{
128
131
  }
129
132
  }
130
133
 
134
+ // 非免认证路由且没有 token,跳转到 SSO 登录
135
+ if (!noAuthRoute) {
136
+ handleAuthFailureRedirect();
137
+ // 返回空状态,页面会被重定向
138
+ return {
139
+ fetchUserInfo: fetchUserInfoFn,
140
+ };
141
+ }
142
+
131
143
  return {
132
144
  fetchUserInfo: fetchUserInfoFn,
133
145
  };
@@ -157,6 +157,21 @@ declare global {
157
157
  apiBaseUrl?: string;
158
158
  /** 默认重定向路径,访问 "/" 时自动跳转到此路径 */
159
159
  defaultPath?: string;
160
+ /**
161
+ * 免认证路由列表(跳过 SSO 登录)
162
+ * 支持精确匹配和前缀匹配(以 /* 结尾)
163
+ */
164
+ noAuthRouteList?: string[];
165
+ /**
166
+ * 免权限校验路由列表(跳过菜单权限检查)
167
+ * 支持精确匹配和前缀匹配(以 /* 结尾)
168
+ * 注意:如果同时需要跳过 SSO,需同时配置 noAuthRouteList
169
+ */
170
+ noPermissionRouteList?: string[];
171
+ /** 动态配置的不显示布局路由列表,支持精确匹配和前缀匹配(以 /* 结尾) */
172
+ noLayoutRouteList?: string[];
173
+ /** 关闭权限控制(菜单全部显示,路由不校验权限) */
174
+ disableAuth?: boolean;
160
175
  [key: string]: unknown;
161
176
  };
162
177
  __MICO_WORKSPACE__?: WorkspaceConfig | null;
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { request as rawRequest } from '@umijs/max';
18
18
  import { setStoredAuthToken } from '../auth/auth-manager';
19
- import { NO_AUTH_ROUTE_LIST } from '@/constants';
19
+ import { isNoAuthRoute } from '@/constants';
20
20
 
21
21
  // 配置相关
22
22
  import {
@@ -64,7 +64,7 @@ initDefaultInterceptors(isFetchingToken, addToPendingQueue);
64
64
  * 判断当前路由是否跳过认证
65
65
  */
66
66
  const shouldSkipAuth = (): boolean => {
67
- return NO_AUTH_ROUTE_LIST.includes(location.pathname);
67
+ return isNoAuthRoute(location.pathname);
68
68
  };
69
69
 
70
70
  /**
@@ -1,5 +1,5 @@
1
1
  import { findRouteByPath } from '@/common/menu';
2
- import { NO_AUTH_ROUTE_LIST } from '@/constants';
2
+ import { isNoLayoutRoute } from '@/constants';
3
3
  import useMenu from '@/hooks/useMenu';
4
4
  import { Tabs } from '@arco-design/web-react';
5
5
  import { history, useLocation } from '@umijs/max';
@@ -47,7 +47,7 @@ function AppTabs() {
47
47
  const [tabs, setTabs] = useState<TabItem[]>([]);
48
48
 
49
49
  const showTabs = useMemo(() => {
50
- return !NO_AUTH_ROUTE_LIST.includes(location.pathname);
50
+ return !isNoLayoutRoute(location.pathname);
51
51
  }, [location.pathname]);
52
52
 
53
53
  const microAppPrefixes = useMemo(() => {
@@ -1,8 +1,9 @@
1
1
  import { getAuthInfo } from '@/common/auth/auth-manager';
2
2
  import { EEnv, getEnv } from '@/common/env';
3
3
  import { request } from '@/common/request';
4
+ import { isAuthDisabled, isNoPermissionRoute } from '@/constants';
4
5
  import { Spin } from '@arco-design/web-react';
5
- import { useModel } from '@umijs/max';
6
+ import { useLocation, useModel } from '@umijs/max';
6
7
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
7
8
  import './index.less';
8
9
  import { microAppManager, type MicroAppState } from './micro-app-manager';
@@ -45,8 +46,13 @@ const MicroAppLoader: React.FC<MicroAppLoaderProps> = ({
45
46
  });
46
47
 
47
48
  // 获取 initialState,等待用户信息准备好再加载子应用
49
+ // disableAuth 模式或免权限校验路由下跳过等待
48
50
  const { initialState } = useModel('@@initialState');
49
- const isAuthReady = !!initialState?.currentUser;
51
+ const location = useLocation();
52
+ const isAuthReady =
53
+ isAuthDisabled() ||
54
+ isNoPermissionRoute(location.pathname) ||
55
+ !!initialState?.currentUser;
50
56
 
51
57
  const appName = sanitizeId(name);
52
58
 
@@ -19,7 +19,7 @@ export const ROUTES = {
19
19
  } as const;
20
20
 
21
21
  /**
22
- * 无需认证的路由列表
22
+ * 无需认证的路由列表(静态配置)
23
23
  */
24
24
  export const NO_AUTH_ROUTE_LIST: string[] = [
25
25
  ROUTES.LOGIN,
@@ -29,12 +29,115 @@ export const NO_AUTH_ROUTE_LIST: string[] = [
29
29
  ROUTES.NOT_FOUND,
30
30
  ];
31
31
 
32
+ /**
33
+ * 获取合并后的免鉴权路由列表
34
+ * 合并静态常量 + window.__MICO_CONFIG__.noAuthRouteList
35
+ */
36
+ export const getNoAuthRouteList = (): string[] => {
37
+ const dynamicRoutes = window.__MICO_CONFIG__?.noAuthRouteList ?? [];
38
+ return [...new Set([...NO_AUTH_ROUTE_LIST, ...dynamicRoutes])];
39
+ };
40
+
41
+ /**
42
+ * 判断路径是否匹配路由列表(支持精确匹配和前缀匹配)
43
+ * @param pathname - 当前路由路径
44
+ * @param routes - 路由列表
45
+ * @returns 是否匹配
46
+ */
47
+ const matchRouteList = (pathname: string, routes: string[]): boolean => {
48
+ return routes.some((route) => {
49
+ // 前缀匹配:/public/* 匹配 /public/xxx
50
+ if (route.endsWith('/*')) {
51
+ const prefix = route.slice(0, -1); // 去掉末尾的 *,保留 /
52
+ return pathname.startsWith(prefix) || pathname === prefix.slice(0, -1);
53
+ }
54
+ // 精确匹配
55
+ return pathname === route;
56
+ });
57
+ };
58
+
59
+ /**
60
+ * 判断指定路径是否为免认证路由(跳过 SSO 登录)
61
+ * 支持精确匹配和前缀匹配(以 /* 结尾的模式)
62
+ * @param pathname - 当前路由路径
63
+ * @returns 是否免认证
64
+ */
65
+ export const isNoAuthRoute = (pathname: string): boolean => {
66
+ return matchRouteList(pathname, getNoAuthRouteList());
67
+ };
68
+
69
+ /**
70
+ * 免权限校验的路由列表(静态配置)
71
+ * 这些路由不检查菜单权限,但仍可能需要登录
72
+ */
73
+ export const NO_PERMISSION_ROUTE_LIST: string[] = [
74
+ ROUTES.FORBIDDEN,
75
+ ROUTES.NOT_FOUND,
76
+ ];
77
+
78
+ /**
79
+ * 获取合并后的免权限校验路由列表
80
+ * 合并静态常量 + window.__MICO_CONFIG__.noPermissionRouteList
81
+ */
82
+ export const getNoPermissionRouteList = (): string[] => {
83
+ const dynamicRoutes = window.__MICO_CONFIG__?.noPermissionRouteList ?? [];
84
+ return [...new Set([...NO_PERMISSION_ROUTE_LIST, ...dynamicRoutes])];
85
+ };
86
+
87
+ /**
88
+ * 判断指定路径是否为免权限校验路由(跳过菜单权限检查)
89
+ * 支持精确匹配和前缀匹配(以 /* 结尾的模式)
90
+ * @param pathname - 当前路由路径
91
+ * @returns 是否免权限校验
92
+ */
93
+ export const isNoPermissionRoute = (pathname: string): boolean => {
94
+ return matchRouteList(pathname, getNoPermissionRouteList());
95
+ };
96
+
97
+ /**
98
+ * 不显示布局的路由列表(静态配置)
99
+ * 注意:403/404 保留布局,方便用户通过导航返回
100
+ */
101
+ export const NO_LAYOUT_ROUTE_LIST: string[] = [
102
+ ROUTES.LOGIN,
103
+ ROUTES.REGISTER,
104
+ ROUTES.REGISTER_RESULT,
105
+ ];
106
+
107
+ /**
108
+ * 获取合并后的不显示布局路由列表
109
+ * 合并静态常量 + window.__MICO_CONFIG__.noLayoutRouteList
110
+ */
111
+ export const getNoLayoutRouteList = (): string[] => {
112
+ const dynamicRoutes = window.__MICO_CONFIG__?.noLayoutRouteList ?? [];
113
+ return [...new Set([...NO_LAYOUT_ROUTE_LIST, ...dynamicRoutes])];
114
+ };
115
+
116
+ /**
117
+ * 判断指定路径是否不显示布局
118
+ * 支持精确匹配和前缀匹配(以 /* 结尾的模式)
119
+ * @param pathname - 当前路由路径
120
+ * @returns 是否不显示布局
121
+ */
122
+ export const isNoLayoutRoute = (pathname: string): boolean => {
123
+ return matchRouteList(pathname, getNoLayoutRouteList());
124
+ };
125
+
126
+ /**
127
+ * 判断是否关闭权限控制
128
+ * 关闭后:菜单全部显示,路由不校验权限
129
+ * @returns 是否关闭权限控制
130
+ */
131
+ export const isAuthDisabled = (): boolean => {
132
+ return window.__MICO_CONFIG__?.disableAuth === true;
133
+ };
134
+
32
135
  /**
33
136
  * 主题相关常量
34
137
  */
35
138
  export const THEME = {
36
139
  /** localStorage 存储键 */
37
- STORAGE_KEY: 'audit-center-theme',
140
+ STORAGE_KEY: '<%= ProjectName %>-theme',
38
141
  /** 默认主题 */
39
142
  DEFAULT: 'light' as const,
40
143
  /** 可选主题值 */
@@ -46,9 +149,9 @@ export const THEME = {
46
149
  */
47
150
  export const TIMEZONE = {
48
151
  /** localStorage 存储键(IANA 时区,如 Asia/Shanghai) */
49
- STORAGE_KEY: 'audit-center-timezone',
152
+ STORAGE_KEY: '<%= ProjectName %>-timezone',
50
153
  /** localStorage 存储键(用于展示的地区/名称,可选) */
51
- REGION_STORAGE_KEY: 'audit-center-timezone-region',
154
+ REGION_STORAGE_KEY: '<%= ProjectName %>-timezone-region',
52
155
  } as const;
53
156
 
54
157
  /**
@@ -56,7 +159,7 @@ export const TIMEZONE = {
56
159
  */
57
160
  export const PRESENCE = {
58
161
  /** localStorage 存储键 */
59
- STORAGE_KEY: 'audit-center-presence-status',
162
+ STORAGE_KEY: '<%= ProjectName %>-presence-status',
60
163
  } as const;
61
164
 
62
165
  /**
@@ -1,7 +1,7 @@
1
1
  import { useLocation, useModel } from '@umijs/max';
2
2
  import { useEffect, useRef, useState } from 'react';
3
3
  import { layoutLogger } from '@/common/logger';
4
- import { NO_AUTH_ROUTE_LIST } from '@/constants';
4
+ import { isNoAuthRoute } from '@/constants';
5
5
 
6
6
  /**
7
7
  * 路由切换时自动刷新用户权限
@@ -39,7 +39,7 @@ export function useRoutePermissionRefresh() {
39
39
  prevPathRef.current = location.pathname;
40
40
 
41
41
  // 免认证路由不需要刷新
42
- if (NO_AUTH_ROUTE_LIST.includes(location.pathname)) {
42
+ if (isNoAuthRoute(location.pathname)) {
43
43
  return;
44
44
  }
45
45
 
@@ -57,7 +57,7 @@ const LayoutHeader: React.FC = () => {
57
57
  {/* Logo */}
58
58
  <div className="layout-header-logo">
59
59
  <span className="logo-text">
60
- {window.__MICO_MENUS__?.appName || 'Mico CENTER'}
60
+ {window.__MICO_MENUS__?.appName || '<%= projectName %>'}
61
61
  </span>
62
62
  </div>
63
63
 
@@ -1,11 +1,12 @@
1
1
  import type { ParsedMenuItem } from '@/common/menu';
2
2
  import { filterMenuItems, getWindowMenus, parseMenuItems } from '@/common/menu';
3
+ import { isAuthDisabled, isNoPermissionRoute } from '@/constants';
3
4
  import IconFont from '@/components/IconFont';
4
5
  import { useMenuState } from '@/hooks/useMenuState';
5
6
  import { useTheme } from '@/hooks/useTheme';
6
7
  import { Layout, Menu } from '@arco-design/web-react';
7
8
  import * as Icons from '@arco-design/web-react/icon';
8
- import { useModel } from '@umijs/max';
9
+ import { useLocation, useModel } from '@umijs/max';
9
10
  import React, { useEffect, useMemo, useRef } from 'react';
10
11
  import './index.less';
11
12
 
@@ -103,19 +104,24 @@ interface LayoutMenuProps {
103
104
  const LayoutMenu: React.FC<LayoutMenuProps> = () => {
104
105
  const siderRef = useRef<HTMLDivElement>(null);
105
106
  const { isDark } = useTheme();
107
+ const location = useLocation();
106
108
 
107
109
  const { initialState } = useModel('@@initialState');
108
110
  const currentUser = initialState?.currentUser;
109
111
 
110
112
  // Parse menu data
113
+ // disableAuth 或免权限校验路由时不过滤,显示全部菜单
111
114
  const menuItems = useMemo(() => {
112
115
  const menus = getWindowMenus();
116
+ if (isAuthDisabled() || isNoPermissionRoute(location.pathname)) {
117
+ return parseMenuItems(menus);
118
+ }
113
119
  const filteredMenus = filterMenuItems(menus, {
114
120
  isSuperuser: currentUser?.is_superuser,
115
121
  sideMenus: (currentUser?.side_menus || []) as string[],
116
122
  });
117
123
  return parseMenuItems(filteredMenus);
118
- }, [currentUser?.is_superuser, currentUser?.side_menus]);
124
+ }, [currentUser?.is_superuser, currentUser?.side_menus, location.pathname]);
119
125
 
120
126
  // 使用菜单状态 Hook
121
127
  const {
@@ -8,7 +8,11 @@ import {
8
8
  import { getAppNameFromEntry } from '@/common/micro';
9
9
  import AppTabs from '@/components/AppTabs';
10
10
  import MicroAppLoader from '@/components/MicroAppLoader';
11
- import { NO_AUTH_ROUTE_LIST } from '@/constants';
11
+ import {
12
+ isAuthDisabled,
13
+ isNoLayoutRoute,
14
+ isNoPermissionRoute,
15
+ } from '@/constants';
12
16
  import { useRoutePermissionRefresh } from '@/hooks/useRoutePermissionRefresh';
13
17
  import ForbiddenPage from '@/pages/403';
14
18
  import { Layout, Spin } from '@arco-design/web-react';
@@ -48,20 +52,27 @@ const BasicLayout: React.FC = () => {
48
52
  return extractRoutes(menus);
49
53
  }, []);
50
54
 
51
- // 有权限的路由
55
+ // 有权限的路由(disableAuth 时不过滤,显示全部)
52
56
  const allowedRoutes = useMemo(() => {
57
+ if (isAuthDisabled()) {
58
+ return allRoutes;
59
+ }
53
60
  const menus = getWindowMenus();
54
61
  const filteredMenus = filterMenuItems(menus, filterOptions);
55
62
  return extractRoutes(filteredMenus);
56
- }, [filterOptions]);
63
+ }, [filterOptions, allRoutes]);
57
64
 
58
65
  // 查找当前路由配置(优先从有权限的路由中查找)
59
66
  const currentRoute = useMemo(() => {
60
67
  return findRouteByPath(allowedRoutes, location.pathname);
61
68
  }, [allowedRoutes, location.pathname]);
62
69
 
63
- // 判断是否是动态路由但无权限
70
+ // 判断是否是动态路由但无权限(disableAuth 时始终返回 false)
64
71
  const isForbidden = useMemo(() => {
72
+ // 关闭权限控制时,不校验权限
73
+ if (isAuthDisabled()) return false;
74
+ // 免权限校验路由,不检查菜单权限
75
+ if (isNoPermissionRoute(location.pathname)) return false;
65
76
  // 如果在有权限的路由中找到了,说明有权限
66
77
  if (currentRoute) return false;
67
78
  // 如果在所有路由中也找不到,说明不是动态路由,交给 Umi 处理
@@ -83,7 +94,7 @@ const BasicLayout: React.FC = () => {
83
94
  }, [currentRoute, allRoutes, allowedRoutes, location.pathname, filterOptions]);
84
95
 
85
96
  // 判断是否需要显示布局
86
- const showLayout = !NO_AUTH_ROUTE_LIST.includes(location.pathname);
97
+ const showLayout = !isNoLayoutRoute(location.pathname);
87
98
 
88
99
  // 渲染页面内容
89
100
  const renderContent = () => {
@@ -17,6 +17,7 @@ export interface IUserInfoResponse {
17
17
  export async function fetchUserInfo(): Promise<IUserInfo> {
18
18
  const response = await request<IUserInfoResponse>(USER_INFO_API, {
19
19
  method: 'GET',
20
+ skipProxy: true,
20
21
  });
21
22
  if (response.code === 200 && response.data) {
22
23
  return response.data;
@@ -177,12 +177,21 @@ module.exports = class extends Generator {
177
177
  }
178
178
  }
179
179
 
180
+ install() {
181
+ this.log('');
182
+ this.log('📦 正在安装依赖...');
183
+ this.spawnCommandSync('pnpm', ['install'], {
184
+ cwd: this.monorepoRoot
185
+ });
186
+ }
187
+
180
188
  end() {
181
189
  this.log('');
182
190
  this.log('✅ 子应用创建成功!');
183
191
  this.log('');
192
+ this.log(' 后续步骤:');
193
+ this.log('');
184
194
  this.log(` cd apps/${this.appName}`);
185
- this.log(' pnpm install');
186
195
  this.log(' pnpm dev');
187
196
  this.log('');
188
197
  }
@@ -1,7 +1,6 @@
1
1
  // https://umijs.org/config/
2
2
 
3
3
  import { defineConfig } from '@umijs/max';
4
- const { CDN_PUBLIC_PATH } = process.env;
5
4
 
6
5
 
7
6
  const config: ReturnType<typeof defineConfig> = {
@@ -1,7 +1,6 @@
1
1
  // https://umijs.org/config/
2
2
 
3
3
  import { defineConfig } from '@umijs/max';
4
- const { CDN_PUBLIC_PATH } = process.env;
5
4
 
6
5
 
7
6
  const config: ReturnType<typeof defineConfig> = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generator-mico-cli",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Yeoman generator for Mico CLI projects",
5
5
  "keywords": [
6
6
  "yeoman-generator",