generator-mico-cli 0.2.1 → 0.2.2

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.
@@ -23,6 +23,7 @@ const config: ReturnType<typeof defineConfig> = {
23
23
  window.__MICO_CONFIG__ = {
24
24
  appName: 'Mico Center',
25
25
  apiBaseUrl: '',
26
+ defaultPath: '',
26
27
  };
27
28
  `,
28
29
  },
@@ -1,5 +1,5 @@
1
- import { history, type RequestConfig } from '@umijs/max';
2
- import { prefetchApps } from 'qiankun';
1
+ import { history, Navigate, type RequestConfig } from '@umijs/max';
2
+ import { addGlobalUncaughtErrorHandler, prefetchApps } from 'qiankun';
3
3
  import { errorConfig } from './requestErrorConfig';
4
4
  // 解决「React19 中无法使用 Message/Notification」的问题。 @see https://github.com/arco-design/arco-design/issues/2900#issuecomment-2796571653
5
5
  import * as arco from '@arco-design/web-react';
@@ -21,6 +21,57 @@ import MicroAppLoader from './components/MicroAppLoader';
21
21
  import { NO_AUTH_ROUTE_LIST } from './constants';
22
22
  import './global.less';
23
23
 
24
+ // ==================== qiankun 全局错误处理 ====================
25
+ // 捕获子应用运行时未捕获的异常,防止页面崩溃
26
+
27
+ /**
28
+ * 注册 qiankun 全局未捕获错误处理器
29
+ * 处理以下场景:
30
+ * 1. 子应用 JS 运行时错误
31
+ * 2. 子应用生命周期钩子抛出的异常
32
+ * 3. 子应用资源加载失败
33
+ */
34
+ if (typeof window !== 'undefined') {
35
+ addGlobalUncaughtErrorHandler((event: Event | string) => {
36
+ // 提取错误信息
37
+ const error =
38
+ event instanceof ErrorEvent
39
+ ? event.error
40
+ : event instanceof PromiseRejectionEvent
41
+ ? event.reason
42
+ : event;
43
+
44
+ const errorMessage =
45
+ error instanceof Error
46
+ ? error.message
47
+ : typeof error === 'string'
48
+ ? error
49
+ : 'Unknown micro-app error';
50
+
51
+ console.error('[qiankun] Global uncaught error:', {
52
+ error,
53
+ message: errorMessage,
54
+ type: event instanceof Event ? event.type : 'unknown',
55
+ });
56
+
57
+ // 检查是否是子应用加载失败(资源 404 等)
58
+ const isLoadError =
59
+ errorMessage.includes('Failed to fetch') ||
60
+ errorMessage.includes('Loading chunk') ||
61
+ errorMessage.includes('load') ||
62
+ errorMessage.includes('Script error');
63
+
64
+ if (isLoadError) {
65
+ console.error(
66
+ '[qiankun] Micro-app resource loading failed. Please check if the micro-app server is running.',
67
+ );
68
+ }
69
+
70
+ // 注意:这里不阻止错误冒泡,让控制台仍能显示原始错误
71
+ // 如果需要阻止,可以 return true
72
+ });
73
+ }
74
+
24
75
  // ==================== 微应用预加载 ====================
25
76
  // 预加载所有微应用资源,避免快速切换时的竞态条件
26
77
  // 当资源已预加载时,loadMicroApp 的异步加载会快速完成,减少容器不存在的错误
@@ -226,6 +277,23 @@ export function patchClientRoutes({ routes }: { routes: UmiRoute[] }) {
226
277
  });
227
278
  }
228
279
 
280
+
281
+ /**
282
+ * @name 路由变化处理
283
+ * @description 处理 defaultPath 重定向:访问 "/" 时自动跳转到配置的默认路径
284
+ * @doc https://umijs.org/docs/api/runtime-config#onroutechange
285
+ */
286
+ export function onRouteChange({
287
+ location,
288
+ }: {
289
+ location: { pathname: string };
290
+ }) {
291
+ const defaultPath = window.__MICO_CONFIG__?.defaultPath;
292
+ if (location.pathname === '/' && defaultPath && defaultPath !== '/') {
293
+ history.replace(defaultPath);
294
+ }
295
+ }
296
+
229
297
  /**
230
298
  * @name qiankun 微前端子应用生命周期
231
299
  * @description 当应用作为 qiankun 子应用运行时,这些生命周期会被调用
@@ -155,6 +155,8 @@ declare global {
155
155
  __MICO_CONFIG__?: {
156
156
  appName?: string;
157
157
  apiBaseUrl?: string;
158
+ /** 默认重定向路径,访问 "/" 时自动跳转到此路径 */
159
+ defaultPath?: string;
158
160
  [key: string]: unknown;
159
161
  };
160
162
  __MICO_WORKSPACE__?: WorkspaceConfig | null;
@@ -5,6 +5,8 @@
5
5
  * 1. 容器在 document.body 中创建,与 React 生命周期解耦
6
6
  * 2. 激活时移动到占位元素内,参与正常文档流
7
7
  * 3. 停用时移回 body 并隐藏
8
+ * 4. 加载状态锁防止并发加载冲突
9
+ * 5. unmount 超时机制防止子应用卡死
8
10
  *
9
11
  * 这样既避免了 React unmount 时删除容器导致的竞态问题,
10
12
  * 又能让容器在激活时参与正常的 CSS 布局。
@@ -19,12 +21,18 @@ import type { MicroApp } from 'qiankun';
19
21
  // ============================================================================
20
22
 
21
23
  type ContainerStatus = 'active' | 'hidden' | 'pending-delete';
24
+ type LoadingStatus = 'idle' | 'loading' | 'mounted' | 'unmounting';
22
25
 
23
26
  interface ContainerEntry {
24
27
  container: HTMLElement;
25
28
  microApp: MicroApp | null;
26
29
  status: ContainerStatus;
30
+ loadingStatus: LoadingStatus;
27
31
  deleteTimer: ReturnType<typeof setTimeout> | null;
32
+ /** 当前加载会话 ID,用于取消过期的加载操作 */
33
+ loadSessionId: number;
34
+ /** 等待卸载完成的 Promise */
35
+ unmountPromise: Promise<void> | null;
28
36
  }
29
37
 
30
38
  // ============================================================================
@@ -34,6 +42,9 @@ interface ContainerEntry {
34
42
  /** 容器删除延迟(毫秒)- 给 qiankun 足够时间完成清理 */
35
43
  const DELETE_DELAY = 5000;
36
44
 
45
+ /** unmount 超时时间(毫秒)- 防止子应用卡死 */
46
+ const UNMOUNT_TIMEOUT = 10000;
47
+
37
48
  /** CSS 类名 */
38
49
  const CSS_CLASS = {
39
50
  base: 'micro-app-container-managed',
@@ -47,6 +58,40 @@ const CSS_CLASS = {
47
58
 
48
59
  const containers = new Map<string, ContainerEntry>();
49
60
 
61
+ /** 全局会话 ID 计数器 */
62
+ let globalSessionId = 0;
63
+
64
+ // ============================================================================
65
+ // 工具函数
66
+ // ============================================================================
67
+
68
+ /**
69
+ * 带超时的 Promise
70
+ */
71
+ function withTimeout<T>(
72
+ promise: Promise<T>,
73
+ ms: number,
74
+ timeoutMessage: string,
75
+ ): Promise<T> {
76
+ return Promise.race([
77
+ promise,
78
+ new Promise<T>((_, reject) => {
79
+ setTimeout(() => reject(new Error(timeoutMessage)), ms);
80
+ }),
81
+ ]);
82
+ }
83
+
84
+ /**
85
+ * 安全执行 Promise,忽略错误
86
+ */
87
+ async function safeAwait(promise: Promise<unknown>): Promise<void> {
88
+ try {
89
+ await promise;
90
+ } catch {
91
+ // 忽略
92
+ }
93
+ }
94
+
50
95
  // ============================================================================
51
96
  // 私有方法
52
97
  // ============================================================================
@@ -59,6 +104,18 @@ function createContainer(appName: string): HTMLElement {
59
104
  return container;
60
105
  }
61
106
 
107
+ function createEntry(container: HTMLElement): ContainerEntry {
108
+ return {
109
+ container,
110
+ microApp: null,
111
+ status: 'hidden',
112
+ loadingStatus: 'idle',
113
+ deleteTimer: null,
114
+ loadSessionId: 0,
115
+ unmountPromise: null,
116
+ };
117
+ }
118
+
62
119
  // ============================================================================
63
120
  // 公开 API
64
121
  // ============================================================================
@@ -81,12 +138,7 @@ export function getContainer(appName: string): HTMLElement {
81
138
 
82
139
  // 创建新容器
83
140
  const container = createContainer(appName);
84
- entry = {
85
- container,
86
- microApp: null,
87
- status: 'hidden',
88
- deleteTimer: null,
89
- };
141
+ entry = createEntry(container);
90
142
  containers.set(appName, entry);
91
143
 
92
144
  return container;
@@ -130,6 +182,59 @@ export function deactivateContainer(appName: string): void {
130
182
  entry.status = 'hidden';
131
183
  }
132
184
 
185
+ /**
186
+ * 开始加载会话
187
+ * 返回会话 ID,用于后续检查该会话是否仍然有效
188
+ */
189
+ export function startLoadSession(appName: string): number {
190
+ const entry = containers.get(appName);
191
+ if (!entry) return 0;
192
+
193
+ const sessionId = ++globalSessionId;
194
+ entry.loadSessionId = sessionId;
195
+ entry.loadingStatus = 'loading';
196
+
197
+ return sessionId;
198
+ }
199
+
200
+ /**
201
+ * 检查加载会话是否仍然有效
202
+ * 如果返回 false,说明已经有新的加载请求,当前加载应该取消
203
+ */
204
+ export function isLoadSessionValid(appName: string, sessionId: number): boolean {
205
+ const entry = containers.get(appName);
206
+ return entry?.loadSessionId === sessionId;
207
+ }
208
+
209
+ /**
210
+ * 标记加载完成
211
+ */
212
+ export function markLoadComplete(appName: string, sessionId: number): boolean {
213
+ const entry = containers.get(appName);
214
+ if (!entry || entry.loadSessionId !== sessionId) {
215
+ return false;
216
+ }
217
+ entry.loadingStatus = 'mounted';
218
+ return true;
219
+ }
220
+
221
+ /**
222
+ * 获取当前加载状态
223
+ */
224
+ export function getLoadingStatus(appName: string): LoadingStatus {
225
+ return containers.get(appName)?.loadingStatus ?? 'idle';
226
+ }
227
+
228
+ /**
229
+ * 等待当前卸载操作完成(如果有)
230
+ */
231
+ export async function waitForUnmount(appName: string): Promise<void> {
232
+ const entry = containers.get(appName);
233
+ if (entry?.unmountPromise) {
234
+ await entry.unmountPromise;
235
+ }
236
+ }
237
+
133
238
  /**
134
239
  * 设置微应用实例
135
240
  */
@@ -149,30 +254,57 @@ export function getMicroApp(appName: string): MicroApp | null {
149
254
 
150
255
  /**
151
256
  * 安全卸载微应用并安排容器延迟删除
257
+ * 带超时机制,防止子应用 unmount 卡死
152
258
  */
153
259
  export async function unmountApp(appName: string): Promise<void> {
154
260
  const entry = containers.get(appName);
155
261
  if (!entry) return;
156
262
 
263
+ // 如果已经在卸载中,等待卸载完成
264
+ if (entry.unmountPromise) {
265
+ await entry.unmountPromise;
266
+ return;
267
+ }
268
+
157
269
  const { microApp } = entry;
158
270
 
159
271
  if (microApp) {
272
+ entry.loadingStatus = 'unmounting';
160
273
  entry.microApp = null;
161
274
 
162
- // 等待各阶段完成再卸载
163
- try {
164
- await microApp.loadPromise;
165
- await microApp.bootstrapPromise;
166
- await microApp.mountPromise;
167
- } catch {
168
- // 忽略
169
- }
170
-
171
- try {
172
- await microApp.unmount();
173
- } catch {
174
- // 忽略
175
- }
275
+ // 创建卸载 Promise 供其他调用方等待
276
+ entry.unmountPromise = (async () => {
277
+ // 等待各阶段完成再卸载(带超时)
278
+ try {
279
+ await withTimeout(
280
+ Promise.all([
281
+ safeAwait(microApp.loadPromise),
282
+ safeAwait(microApp.bootstrapPromise),
283
+ safeAwait(microApp.mountPromise),
284
+ ]),
285
+ UNMOUNT_TIMEOUT / 2,
286
+ 'Waiting for micro-app lifecycle timeout',
287
+ );
288
+ } catch (err) {
289
+ console.warn(`[container-manager] ${appName} lifecycle wait timeout:`, err);
290
+ }
291
+
292
+ // 执行卸载(带超时)
293
+ try {
294
+ await withTimeout(
295
+ microApp.unmount(),
296
+ UNMOUNT_TIMEOUT / 2,
297
+ 'Micro-app unmount timeout',
298
+ );
299
+ } catch (err) {
300
+ console.warn(`[container-manager] ${appName} unmount error/timeout:`, err);
301
+ }
302
+
303
+ entry.loadingStatus = 'idle';
304
+ entry.unmountPromise = null;
305
+ })();
306
+
307
+ await entry.unmountPromise;
176
308
  }
177
309
 
178
310
  // 安排延迟删除
@@ -9,8 +9,12 @@ import {
9
9
  deactivateContainer,
10
10
  getContainer,
11
11
  getMicroApp,
12
+ isLoadSessionValid,
13
+ markLoadComplete,
12
14
  setMicroApp,
15
+ startLoadSession,
13
16
  unmountApp,
17
+ waitForUnmount,
14
18
  } from './container-manager';
15
19
  import './index.less';
16
20
 
@@ -39,7 +43,6 @@ const MicroAppLoader: React.FC<MicroAppLoaderProps> = ({
39
43
  routePath,
40
44
  }) => {
41
45
  const wrapperRef = useRef<HTMLDivElement>(null);
42
- const mountIdRef = useRef(0);
43
46
  const [loading, setLoading] = useState(true);
44
47
  const [error, setError] = useState<string | null>(null);
45
48
  const isMountedRef = useRef(false);
@@ -73,7 +76,11 @@ const MicroAppLoader: React.FC<MicroAppLoaderProps> = ({
73
76
  };
74
77
  };
75
78
 
76
- // 当路由等"无需重载"的状态更新时,通过 update() 通知子应用
79
+ // 记录最新的 props 值,用于加载完成后应用
80
+ const latestPropsRef = useRef({ routePath });
81
+ latestPropsRef.current = { routePath };
82
+
83
+ // 当时区、路由等"无需重载"的状态更新时,通过 update() 通知子应用
77
84
  useEffect(() => {
78
85
  if (!isMountedRef.current) return;
79
86
 
@@ -85,7 +92,8 @@ const MicroAppLoader: React.FC<MicroAppLoaderProps> = ({
85
92
  const wrapper = wrapperRef.current;
86
93
  if (!wrapper) return;
87
94
 
88
- const currentMountId = ++mountIdRef.current;
95
+ // 开始新的加载会话,获取会话 ID
96
+ const sessionId = startLoadSession(appName);
89
97
  let isCancelled = false;
90
98
 
91
99
  setLoading(true);
@@ -95,7 +103,15 @@ const MicroAppLoader: React.FC<MicroAppLoaderProps> = ({
95
103
  activateContainer(appName, wrapper);
96
104
 
97
105
  const loadApp = async () => {
98
- // 卸载旧实例
106
+ // 1. 先等待之前的卸载操作完成(如果有)
107
+ await waitForUnmount(appName);
108
+
109
+ // 检查会话是否仍然有效(可能在等待期间被新的加载请求覆盖)
110
+ if (!isLoadSessionValid(appName, sessionId) || isCancelled) {
111
+ return;
112
+ }
113
+
114
+ // 2. 卸载旧实例(如果存在)
99
115
  const existingApp = getMicroApp(appName);
100
116
  if (existingApp) {
101
117
  try {
@@ -105,12 +121,15 @@ const MicroAppLoader: React.FC<MicroAppLoaderProps> = ({
105
121
  }
106
122
  }
107
123
 
108
- if (isCancelled) return;
124
+ if (!isLoadSessionValid(appName, sessionId) || isCancelled) {
125
+ return;
126
+ }
109
127
 
110
128
  try {
129
+ // 3. 加载新微应用
111
130
  const microApp = loadMicroApp(
112
131
  {
113
- name: `${appName}_${currentMountId}`,
132
+ name: `${appName}_${sessionId}`,
114
133
  entry,
115
134
  container,
116
135
  props: buildPropsRef.current(),
@@ -142,7 +161,7 @@ const MicroAppLoader: React.FC<MicroAppLoaderProps> = ({
142
161
  // 关键:必须同时处理 loadPromise 和 bootstrapPromise/mountPromise 的错误
143
162
  // qiankun 内部会产生多个 Promise 链,任何一个未处理的 rejection 都会导致页面崩溃
144
163
  const handlePromiseError = (err: unknown) => {
145
- if (!isCancelled) {
164
+ if (isLoadSessionValid(appName, sessionId) && !isCancelled) {
146
165
  const message =
147
166
  err instanceof Error ? err.message : 'Unknown error';
148
167
  setError(message);
@@ -155,28 +174,45 @@ const MicroAppLoader: React.FC<MicroAppLoaderProps> = ({
155
174
  microApp.bootstrapPromise.catch(handlePromiseError);
156
175
  microApp.mountPromise.catch(handlePromiseError);
157
176
 
158
- if (isCancelled) {
177
+ if (!isLoadSessionValid(appName, sessionId) || isCancelled) {
178
+ // 会话已过期,卸载刚加载的应用
159
179
  unmountApp(appName);
160
180
  return;
161
181
  }
162
182
 
163
- // 等待加载完成,这里的错误会被上层 catch 捕获
183
+ // 4. 等待加载完成
164
184
  await microApp.loadPromise;
165
185
 
166
- if (isCancelled) {
186
+ if (!isLoadSessionValid(appName, sessionId) || isCancelled) {
167
187
  unmountApp(appName);
168
188
  return;
169
189
  }
170
190
 
171
- // 等待挂载完成
191
+ // 5. 等待挂载完成
172
192
  await microApp.mountPromise;
173
193
 
174
- if (!isCancelled) {
194
+ if (!isLoadSessionValid(appName, sessionId) || isCancelled) {
195
+ unmountApp(appName);
196
+ return;
197
+ }
198
+
199
+ // 6. 标记加载完成
200
+ if (markLoadComplete(appName, sessionId)) {
175
201
  isMountedRef.current = true;
176
202
  setLoading(false);
203
+
204
+ // 7. 加载完成后,检查是否有 props 变化需要同步
205
+ // 这解决了加载期间 locale/timezone 等变化被跳过的问题
206
+ queueMicrotask(() => {
207
+ if (!isLoadSessionValid(appName, sessionId) || isCancelled) return;
208
+ const currentMicroApp = getMicroApp(appName);
209
+ if (currentMicroApp?.update) {
210
+ currentMicroApp.update(buildPropsRef.current());
211
+ }
212
+ });
177
213
  }
178
214
  } catch (err) {
179
- if (!isCancelled) {
215
+ if (isLoadSessionValid(appName, sessionId) && !isCancelled) {
180
216
  const message = err instanceof Error ? err.message : 'Unknown error';
181
217
  // 提取更友好的错误信息
182
218
  const friendlyMessage = message.includes('Failed to fetch')
@@ -197,8 +233,10 @@ const MicroAppLoader: React.FC<MicroAppLoaderProps> = ({
197
233
  // 同步执行,确保在下一次 activate 前移走容器
198
234
  deactivateContainer(appName);
199
235
 
200
- // 异步卸载微应用,给 qiankun 足够时间完成清理
201
- requestAnimationFrame(() => {
236
+ // 使用 queueMicrotask 替代 requestAnimationFrame
237
+ // queueMicrotask 在当前事件循环末尾执行,比 rAF 更快更可靠
238
+ // 这确保了卸载操作在下一个加载操作之前执行
239
+ queueMicrotask(() => {
202
240
  unmountApp(appName);
203
241
  });
204
242
  };
@@ -0,0 +1,69 @@
1
+ import { useLocation, useModel } from '@umijs/max';
2
+ import debounce from 'lodash-es/debounce';
3
+ import { useEffect, useMemo, useRef, useState } from 'react';
4
+ import { layoutLogger } from '@/common/logger';
5
+ import { NO_AUTH_ROUTE_LIST } from '@/constants';
6
+
7
+ /**
8
+ * 路由切换时自动刷新用户权限
9
+ *
10
+ * 功能:
11
+ * - 路由变化时自动刷新 initialState(用户信息和权限)
12
+ * - 防抖处理,避免频繁切换路由时重复请求
13
+ * - 跳过首次渲染(getInitialState 已获取过)
14
+ * - 跳过免认证路由
15
+ * - 只响应 pathname 变化,忽略 search/hash
16
+ */
17
+ export function useRoutePermissionRefresh() {
18
+ const location = useLocation();
19
+ const { refresh } = useModel('@@initialState');
20
+ const [isRefreshing, setIsRefreshing] = useState(false);
21
+
22
+ const isFirstRender = useRef(true);
23
+ const prevPathRef = useRef(location.pathname);
24
+ // 使用 ref 持久化 refresh,避免 debounce 实例重建
25
+ const refreshRef = useRef(refresh);
26
+ refreshRef.current = refresh;
27
+
28
+ const debouncedRefresh = useMemo(
29
+ () =>
30
+ debounce(async (pathname: string) => {
31
+ layoutLogger.log('Route changed, refreshing user info:', pathname);
32
+ try {
33
+ await refreshRef.current?.();
34
+ } finally {
35
+ setIsRefreshing(false);
36
+ }
37
+ }, 300),
38
+ [],
39
+ );
40
+
41
+ useEffect(() => {
42
+ // 跳过首次渲染(getInitialState 已经获取过用户信息)
43
+ if (isFirstRender.current) {
44
+ isFirstRender.current = false;
45
+ return;
46
+ }
47
+
48
+ // 只有路径真正变化时才刷新(避免 search/hash 变化触发)
49
+ if (prevPathRef.current === location.pathname) {
50
+ return;
51
+ }
52
+ prevPathRef.current = location.pathname;
53
+
54
+ // 免认证路由不需要刷新用户信息
55
+ if (NO_AUTH_ROUTE_LIST.includes(location.pathname)) {
56
+ return;
57
+ }
58
+
59
+ // 立即设置 loading 状态,阻止页面渲染和业务请求
60
+ setIsRefreshing(true);
61
+ debouncedRefresh(location.pathname);
62
+
63
+ return () => {
64
+ debouncedRefresh.cancel();
65
+ };
66
+ }, [location.pathname, debouncedRefresh]);
67
+
68
+ return { isRefreshing };
69
+ }
@@ -3,6 +3,7 @@ import { extractRoutes, filterMenuItems, findRouteByPath, getWindowMenus } from
3
3
  import AppTabs from '@/components/AppTabs';
4
4
  import MicroAppLoader from '@/components/MicroAppLoader';
5
5
  import { NO_AUTH_ROUTE_LIST } from '@/constants';
6
+ import { useRoutePermissionRefresh } from '@/hooks/useRoutePermissionRefresh';
6
7
  import ForbiddenPage from '@/pages/403';
7
8
  import { Layout, Spin } from '@arco-design/web-react';
8
9
  import { Outlet, useLocation, useModel } from '@umijs/max';
@@ -41,6 +42,8 @@ const BasicLayout: React.FC = () => {
41
42
  const location = useLocation();
42
43
  const { initialState } = useModel('@@initialState');
43
44
  const currentUser = initialState?.currentUser;
45
+ // 路由切换时自动刷新用户权限
46
+ const { isRefreshing } = useRoutePermissionRefresh();
44
47
 
45
48
  const filterOptions = useMemo(
46
49
  () => ({
@@ -49,6 +52,7 @@ const BasicLayout: React.FC = () => {
49
52
  }),
50
53
  [currentUser?.is_superuser, currentUser?.side_menus],
51
54
  );
55
+
52
56
  // 所有路由(不过滤权限,用于判断路径是否存在)
53
57
  const allRoutes = useMemo(() => {
54
58
  const menus = getWindowMenus();
@@ -98,8 +102,14 @@ const BasicLayout: React.FC = () => {
98
102
  pathname: location.pathname,
99
103
  currentRoute,
100
104
  isForbidden,
105
+ isRefreshing,
101
106
  });
102
107
 
108
+ // 权限刷新中,显示 loading 阻止用户操作
109
+ if (isRefreshing) {
110
+ return <Spin dot style={{ width: '100%', marginTop: 100, textAlign: 'center' }} />;
111
+ }
112
+
103
113
  // 无权限,显示 403
104
114
  if (isForbidden) {
105
115
  return <ForbiddenPage />;
@@ -31,5 +31,10 @@
31
31
  "husky": "^9.1.7",
32
32
  "lint-staged": "^16.1.2",
33
33
  "turbo": "^2.5.8"
34
+ },
35
+ "dependencies": {
36
+ "@types/lodash-es": "^4.17.12",
37
+ "classnames": "^2.5.1",
38
+ "lodash-es": "^4.17.23"
34
39
  }
35
40
  }
@@ -3,9 +3,6 @@
3
3
  import { defineConfig } from '@umijs/max';
4
4
  const { CDN_PUBLIC_PATH } = process.env;
5
5
 
6
- const PUBLIC_PATH: string = CDN_PUBLIC_PATH
7
- ? `${CDN_PUBLIC_PATH.replace(/\/?$/, '/')}homepage/`
8
- : '/homepage/';
9
6
 
10
7
  const config: ReturnType<typeof defineConfig> = {
11
8
  // 测试环境:将所有代码打包到一个文件
@@ -19,7 +16,6 @@ const config: ReturnType<typeof defineConfig> = {
19
16
  memo.optimization.runtimeChunk(false);
20
17
  return memo;
21
18
  },
22
- publicPath: PUBLIC_PATH,
23
19
 
24
20
  /**
25
21
  * @name 外部依赖配置
@@ -3,9 +3,6 @@
3
3
  import { defineConfig } from '@umijs/max';
4
4
  const { CDN_PUBLIC_PATH } = process.env;
5
5
 
6
- const PUBLIC_PATH: string = CDN_PUBLIC_PATH
7
- ? `${CDN_PUBLIC_PATH.replace(/\/?$/, '/')}homepage/`
8
- : '/homepage/';
9
6
 
10
7
  const config: ReturnType<typeof defineConfig> = {
11
8
  // 测试环境:将所有代码打包到一个文件
@@ -19,7 +16,6 @@ const config: ReturnType<typeof defineConfig> = {
19
16
  memo.optimization.runtimeChunk(false);
20
17
  return memo;
21
18
  },
22
- publicPath: PUBLIC_PATH,
23
19
 
24
20
  /**
25
21
  * @name 外部依赖配置
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generator-mico-cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Yeoman generator for Mico CLI projects",
5
5
  "keywords": [
6
6
  "yeoman-generator",