generator-mico-cli 0.1.18

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 (155) hide show
  1. package/README.md +84 -0
  2. package/bin/mico.js +316 -0
  3. package/generators/micro-react/ignore-list.json +8 -0
  4. package/generators/micro-react/index.js +158 -0
  5. package/generators/micro-react/templates/.commitlintrc.js +6 -0
  6. package/generators/micro-react/templates/.cursor/rules/always-read-docs.mdc +129 -0
  7. package/generators/micro-react/templates/.cursor/rules/cicd-deploy.mdc +143 -0
  8. package/generators/micro-react/templates/.cursor/rules/coding-conventions.mdc +206 -0
  9. package/generators/micro-react/templates/.cursor/rules/commit-conventions.mdc +111 -0
  10. package/generators/micro-react/templates/.cursor/rules/development-guide.mdc +295 -0
  11. package/generators/micro-react/templates/.cursor/rules/layout-app.mdc +275 -0
  12. package/generators/micro-react/templates/.cursor/rules/micro-frontend.mdc +196 -0
  13. package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +128 -0
  14. package/generators/micro-react/templates/.cursor/rules/request-auth.mdc +220 -0
  15. package/generators/micro-react/templates/.cursor/rules/theme-system.mdc +206 -0
  16. package/generators/micro-react/templates/.editorconfig +16 -0
  17. package/generators/micro-react/templates/.env +3 -0
  18. package/generators/micro-react/templates/.eslintrc.js +30 -0
  19. package/generators/micro-react/templates/.husky/commit-msg +2 -0
  20. package/generators/micro-react/templates/.husky/pre-commit +2 -0
  21. package/generators/micro-react/templates/.lintstagedrc +5 -0
  22. package/generators/micro-react/templates/.stylelintrc.js +25 -0
  23. package/generators/micro-react/templates/AGENTS.md +39 -0
  24. package/generators/micro-react/templates/CICD/start_dev.sh +30 -0
  25. package/generators/micro-react/templates/CICD/start_local.sh +30 -0
  26. package/generators/micro-react/templates/CICD/start_prod.sh +30 -0
  27. package/generators/micro-react/templates/CICD/start_test.sh +30 -0
  28. package/generators/micro-react/templates/CICD/wangsu_fresh_dev.sh +19 -0
  29. package/generators/micro-react/templates/CICD/wangsu_fresh_prod.sh +19 -0
  30. package/generators/micro-react/templates/CICD/wangsu_fresh_test.sh +19 -0
  31. package/generators/micro-react/templates/CLAUDE.md +106 -0
  32. package/generators/micro-react/templates/README.md +84 -0
  33. package/generators/micro-react/templates/_gitignore +57 -0
  34. package/generators/micro-react/templates/_npmrc +2 -0
  35. package/generators/micro-react/templates/apps/layout/.env +4 -0
  36. package/generators/micro-react/templates/apps/layout/.eslintrc.js +10 -0
  37. package/generators/micro-react/templates/apps/layout/.lintstagedrc +17 -0
  38. package/generators/micro-react/templates/apps/layout/.prettierignore +3 -0
  39. package/generators/micro-react/templates/apps/layout/.prettierrc +8 -0
  40. package/generators/micro-react/templates/apps/layout/.stylelintrc.js +20 -0
  41. package/generators/micro-react/templates/apps/layout/README.md +37 -0
  42. package/generators/micro-react/templates/apps/layout/config/config.dev.ts +54 -0
  43. package/generators/micro-react/templates/apps/layout/config/config.prod.ts +37 -0
  44. package/generators/micro-react/templates/apps/layout/config/config.testing.ts +27 -0
  45. package/generators/micro-react/templates/apps/layout/config/config.ts +132 -0
  46. package/generators/micro-react/templates/apps/layout/config/routes.ts +13 -0
  47. package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +78 -0
  48. package/generators/micro-react/templates/apps/layout/mock/menus.json +100 -0
  49. package/generators/micro-react/templates/apps/layout/mock/menus.ts +11 -0
  50. package/generators/micro-react/templates/apps/layout/mock/user.mock.ts +20 -0
  51. package/generators/micro-react/templates/apps/layout/package.json +45 -0
  52. package/generators/micro-react/templates/apps/layout/public/font/ar-SA.js +54 -0
  53. package/generators/micro-react/templates/apps/layout/public/font/default.js +54 -0
  54. package/generators/micro-react/templates/apps/layout/src/app.tsx +123 -0
  55. package/generators/micro-react/templates/apps/layout/src/assets/.gitkeep +0 -0
  56. package/generators/micro-react/templates/apps/layout/src/common/auth/cs-auth-manager.ts +220 -0
  57. package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +41 -0
  58. package/generators/micro-react/templates/apps/layout/src/common/auth/tool.ts +3 -0
  59. package/generators/micro-react/templates/apps/layout/src/common/auth/type.ts +6 -0
  60. package/generators/micro-react/templates/apps/layout/src/common/constants.ts +38 -0
  61. package/generators/micro-react/templates/apps/layout/src/common/env.ts +73 -0
  62. package/generators/micro-react/templates/apps/layout/src/common/helpers.ts +69 -0
  63. package/generators/micro-react/templates/apps/layout/src/common/locale.ts +123 -0
  64. package/generators/micro-react/templates/apps/layout/src/common/logger.ts +45 -0
  65. package/generators/micro-react/templates/apps/layout/src/common/menu/index.ts +2 -0
  66. package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +143 -0
  67. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +92 -0
  68. package/generators/micro-react/templates/apps/layout/src/common/request/config.ts +73 -0
  69. package/generators/micro-react/templates/apps/layout/src/common/request/index.ts +188 -0
  70. package/generators/micro-react/templates/apps/layout/src/common/request/interceptors.ts +186 -0
  71. package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +132 -0
  72. package/generators/micro-react/templates/apps/layout/src/common/request/token-refresh.ts +136 -0
  73. package/generators/micro-react/templates/apps/layout/src/common/request/types.ts +44 -0
  74. package/generators/micro-react/templates/apps/layout/src/common/request/url-resolver.ts +75 -0
  75. package/generators/micro-react/templates/apps/layout/src/common/theme.ts +107 -0
  76. package/generators/micro-react/templates/apps/layout/src/common/types.ts +7 -0
  77. package/generators/micro-react/templates/apps/layout/src/common/upload/index.ts +2 -0
  78. package/generators/micro-react/templates/apps/layout/src/common/upload/oss.ts +401 -0
  79. package/generators/micro-react/templates/apps/layout/src/common/upload/types.ts +47 -0
  80. package/generators/micro-react/templates/apps/layout/src/common/uploadFiles.ts +35 -0
  81. package/generators/micro-react/templates/apps/layout/src/components/IconFont/index.tsx +25 -0
  82. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.less +44 -0
  83. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +121 -0
  84. package/generators/micro-react/templates/apps/layout/src/constants/index.ts +15 -0
  85. package/generators/micro-react/templates/apps/layout/src/global.less +13 -0
  86. package/generators/micro-react/templates/apps/layout/src/hooks/index.ts +3 -0
  87. package/generators/micro-react/templates/apps/layout/src/hooks/useAuth.ts +75 -0
  88. package/generators/micro-react/templates/apps/layout/src/hooks/useMenu.ts +35 -0
  89. package/generators/micro-react/templates/apps/layout/src/hooks/useMenuState.ts +112 -0
  90. package/generators/micro-react/templates/apps/layout/src/hooks/useTheme.ts +124 -0
  91. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.less +109 -0
  92. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +97 -0
  93. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +164 -0
  94. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +165 -0
  95. package/generators/micro-react/templates/apps/layout/src/layouts/index.less +71 -0
  96. package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +91 -0
  97. package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +20 -0
  98. package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +19 -0
  99. package/generators/micro-react/templates/apps/layout/src/models/global.ts +13 -0
  100. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +3 -0
  101. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.tsx +7 -0
  102. package/generators/micro-react/templates/apps/layout/src/requestErrorConfig.ts +171 -0
  103. package/generators/micro-react/templates/apps/layout/src/services/auth.ts +37 -0
  104. package/generators/micro-react/templates/apps/layout/src/services/oss.ts +40 -0
  105. package/generators/micro-react/templates/apps/layout/src/styles/arco-override.less +78 -0
  106. package/generators/micro-react/templates/apps/layout/src/styles/themes/dark/custom-var.less +244 -0
  107. package/generators/micro-react/templates/apps/layout/src/styles/themes/normal/custom-var.less +195 -0
  108. package/generators/micro-react/templates/apps/layout/src/styles/variables.less +5 -0
  109. package/generators/micro-react/templates/apps/layout/src/utils/format.ts +4 -0
  110. package/generators/micro-react/templates/apps/layout/tailwind.config.js +7 -0
  111. package/generators/micro-react/templates/apps/layout/tailwind.css +3 -0
  112. package/generators/micro-react/templates/apps/layout/tsconfig.json +3 -0
  113. package/generators/micro-react/templates/apps/layout/typings.d.ts +1 -0
  114. package/generators/micro-react/templates/deployDesc.md +60 -0
  115. package/generators/micro-react/templates/docs/commit-message.md +98 -0
  116. package/generators/micro-react/templates/package.json +35 -0
  117. package/generators/micro-react/templates/packages/shared-styles/README.md +125 -0
  118. package/generators/micro-react/templates/packages/shared-styles/arco-override.less +78 -0
  119. package/generators/micro-react/templates/packages/shared-styles/index.less +14 -0
  120. package/generators/micro-react/templates/packages/shared-styles/package.json +27 -0
  121. package/generators/micro-react/templates/packages/shared-styles/theme-inject.less +10 -0
  122. package/generators/micro-react/templates/packages/shared-styles/themes/dark/custom-var.less +246 -0
  123. package/generators/micro-react/templates/packages/shared-styles/themes/normal/custom-var.less +195 -0
  124. package/generators/micro-react/templates/packages/shared-styles/variables-only.less +301 -0
  125. package/generators/micro-react/templates/packages/shared-styles/variables.less +363 -0
  126. package/generators/micro-react/templates/pnpm-workspace.yaml +9 -0
  127. package/generators/micro-react/templates/scripts/collect-dist.js +68 -0
  128. package/generators/micro-react/templates/scripts/create-umi-app.sh +61 -0
  129. package/generators/micro-react/templates/scripts/dev.js +133 -0
  130. package/generators/micro-react/templates/turbo.json +68 -0
  131. package/generators/subapp-react/ignore-list.json +7 -0
  132. package/generators/subapp-react/index.js +189 -0
  133. package/generators/subapp-react/templates/homepage/.env +4 -0
  134. package/generators/subapp-react/templates/homepage/README.md +116 -0
  135. package/generators/subapp-react/templates/homepage/_gitignore +9 -0
  136. package/generators/subapp-react/templates/homepage/config/config.dev.ts +59 -0
  137. package/generators/subapp-react/templates/homepage/config/config.prod.ts +41 -0
  138. package/generators/subapp-react/templates/homepage/config/config.testing.ts +40 -0
  139. package/generators/subapp-react/templates/homepage/config/config.ts +102 -0
  140. package/generators/subapp-react/templates/homepage/config/routes.ts +7 -0
  141. package/generators/subapp-react/templates/homepage/mock/api.mock.ts +59 -0
  142. package/generators/subapp-react/templates/homepage/package.json +30 -0
  143. package/generators/subapp-react/templates/homepage/src/app.tsx +80 -0
  144. package/generators/subapp-react/templates/homepage/src/assets/yay.jpg +0 -0
  145. package/generators/subapp-react/templates/homepage/src/common/logger.ts +42 -0
  146. package/generators/subapp-react/templates/homepage/src/common/mainApp.ts +53 -0
  147. package/generators/subapp-react/templates/homepage/src/common/request.ts +49 -0
  148. package/generators/subapp-react/templates/homepage/src/global.less +26 -0
  149. package/generators/subapp-react/templates/homepage/src/pages/index.less +139 -0
  150. package/generators/subapp-react/templates/homepage/src/pages/index.tsx +342 -0
  151. package/generators/subapp-react/templates/homepage/src/styles/theme.less +6 -0
  152. package/generators/subapp-react/templates/homepage/tsconfig.json +3 -0
  153. package/generators/subapp-react/templates/homepage/typings.d.ts +17 -0
  154. package/lib/utils.js +165 -0
  155. package/package.json +31 -0
@@ -0,0 +1,75 @@
1
+ import { getAuthInfo } from '@/common/auth/tool';
2
+ import type { AuthInfo } from '@/common/auth/type';
3
+ import { handleAuthFailureRedirect, resolveAuthToken } from '@/common/request';
4
+ import { useCallback, useEffect, useState } from 'react';
5
+
6
+ interface UseAuthOptions {
7
+ /** 是否跳过认证检查 */
8
+ skipAuthCheck?: boolean;
9
+ }
10
+
11
+ /**
12
+ * 认证管理 Hook
13
+ */
14
+ export const useAuth = (options: UseAuthOptions = {}) => {
15
+ const { skipAuthCheck = false } = options;
16
+ const [authInfo, setAuthInfo] = useState<AuthInfo | null>(null);
17
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
18
+ const [loading, setLoading] = useState(true);
19
+
20
+ // 检查认证状态
21
+ const checkAuth = useCallback(() => {
22
+ if (skipAuthCheck) {
23
+ setLoading(false);
24
+ return;
25
+ }
26
+
27
+ const token = resolveAuthToken();
28
+ if (token) {
29
+ const info = getAuthInfo();
30
+ setAuthInfo(info);
31
+ setIsAuthenticated(true);
32
+ } else {
33
+ setIsAuthenticated(false);
34
+ }
35
+ setLoading(false);
36
+ }, [skipAuthCheck]);
37
+
38
+ // 初始化时检查认证
39
+ useEffect(() => {
40
+ checkAuth();
41
+ }, [checkAuth]);
42
+
43
+ // 登出
44
+ const logout = useCallback(() => {
45
+ // 清除本地存储的认证信息
46
+ localStorage.removeItem('authtoken');
47
+ localStorage.removeItem('refreshtoken');
48
+ localStorage.removeItem('uid');
49
+ localStorage.removeItem('nickname');
50
+ localStorage.removeItem('avatar');
51
+
52
+ setAuthInfo(null);
53
+ setIsAuthenticated(false);
54
+
55
+ // 跳转到登录页
56
+ handleAuthFailureRedirect();
57
+ }, []);
58
+
59
+ // 刷新认证信息
60
+ const refreshAuthInfo = useCallback(() => {
61
+ const info = getAuthInfo();
62
+ setAuthInfo(info);
63
+ }, []);
64
+
65
+ return {
66
+ authInfo,
67
+ isAuthenticated,
68
+ loading,
69
+ logout,
70
+ refreshAuthInfo,
71
+ checkAuth,
72
+ };
73
+ };
74
+
75
+ export default useAuth;
@@ -0,0 +1,35 @@
1
+ import type { MenuItem, ParsedMenuItem, ParsedRoute } from '@/common/menu';
2
+ import { extractRoutes, getWindowMenus, parseMenuItems } from '@/common/menu';
3
+ import { useMemo } from 'react';
4
+
5
+ /**
6
+ * 菜单管理 Hook
7
+ */
8
+ export const useMenu = () => {
9
+ // 获取原始菜单数据
10
+ const rawMenus = useMemo<MenuItem[]>(() => {
11
+ return getWindowMenus();
12
+ }, []);
13
+
14
+ // 解析后的菜单项(用于渲染菜单组件)
15
+ const menuItems = useMemo<ParsedMenuItem[]>(() => {
16
+ return parseMenuItems(rawMenus);
17
+ }, [rawMenus]);
18
+
19
+ // 解析后的路由配置(用于路由匹配)
20
+ const routes = useMemo<ParsedRoute[]>(() => {
21
+ return extractRoutes(rawMenus);
22
+ }, [rawMenus]);
23
+
24
+ // 是否有菜单数据
25
+ const hasMenus = rawMenus.length > 0;
26
+
27
+ return {
28
+ rawMenus,
29
+ menuItems,
30
+ routes,
31
+ hasMenus,
32
+ };
33
+ };
34
+
35
+ export default useMenu;
@@ -0,0 +1,112 @@
1
+ import type { ParsedMenuItem } from '@/common/menu';
2
+ import { findMenuKeyByPath } from '@/common/menu';
3
+ import { history, useLocation } from '@umijs/max';
4
+ import { useCallback, useEffect, useState } from 'react';
5
+
6
+ export enum ETriggerMode {
7
+ CLICK_TRIGGER = 'clickTrigger',
8
+ HOVER_TRIGGER = 'hoverTrigger',
9
+ }
10
+
11
+ interface UseMenuStateOptions {
12
+ menuItems: ParsedMenuItem[];
13
+ }
14
+
15
+ interface UseMenuStateReturn {
16
+ selectedKeys: string[];
17
+ openKeys: string[];
18
+ collapsed: boolean;
19
+ triggerMode: ETriggerMode;
20
+ handleClickMenuItem: (key: string) => void;
21
+ handleCollapsed: (
22
+ isCollapsed: boolean,
23
+ type: 'clickTrigger' | 'responsive',
24
+ ) => void;
25
+ setOpenKeys: (keys: string[]) => void;
26
+ }
27
+
28
+ /**
29
+ * 菜单状态管理 Hook
30
+ * 分离菜单的状态逻辑,使组件更简洁
31
+ */
32
+ export const useMenuState = ({
33
+ menuItems,
34
+ }: UseMenuStateOptions): UseMenuStateReturn => {
35
+ const location = useLocation();
36
+ const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
37
+ const [openKeys, setOpenKeys] = useState<string[]>([]);
38
+ const [collapsed, setCollapsed] = useState<boolean>(false);
39
+ const [triggerMode, setTriggerMode] = useState<ETriggerMode>(
40
+ ETriggerMode.CLICK_TRIGGER,
41
+ );
42
+
43
+ // 根据当前路径更新选中状态
44
+ useEffect(() => {
45
+ const result = findMenuKeyByPath(menuItems, location.pathname);
46
+ if (result) {
47
+ setSelectedKeys([result.key]);
48
+ if (!collapsed && result.openKeys.length > 0) {
49
+ setOpenKeys((prev) => [...new Set([...prev, ...result.openKeys])]);
50
+ }
51
+ }
52
+ }, [location.pathname, menuItems, collapsed]);
53
+
54
+ // 查找菜单项
55
+ const findItem = useCallback(
56
+ (items: ParsedMenuItem[], targetKey: string): ParsedMenuItem | null => {
57
+ for (const item of items) {
58
+ if (item.key === targetKey) return item;
59
+ if (item.children) {
60
+ const found = findItem(item.children, targetKey);
61
+ if (found) return found;
62
+ }
63
+ }
64
+ return null;
65
+ },
66
+ [],
67
+ );
68
+
69
+ // 处理菜单项点击
70
+ const handleClickMenuItem = useCallback(
71
+ (key: string) => {
72
+ const item = findItem(menuItems, key);
73
+ if (item?.path && item.type !== 'link') {
74
+ history.push(item.path);
75
+ }
76
+ },
77
+ [menuItems, findItem],
78
+ );
79
+
80
+ // 处理侧边栏折叠
81
+ const handleCollapsed = useCallback(
82
+ (isCollapsed: boolean, type: 'clickTrigger' | 'responsive') => {
83
+ // 点击触发时切换到 hover 模式
84
+ if (
85
+ type === 'clickTrigger' &&
86
+ triggerMode === ETriggerMode.CLICK_TRIGGER
87
+ ) {
88
+ setTriggerMode(ETriggerMode.HOVER_TRIGGER);
89
+ setCollapsed(isCollapsed);
90
+ return;
91
+ }
92
+
93
+ // hover 模式下点击切换回 click 模式
94
+ if (triggerMode === ETriggerMode.HOVER_TRIGGER) {
95
+ setTriggerMode(ETriggerMode.CLICK_TRIGGER);
96
+ }
97
+ },
98
+ [triggerMode],
99
+ );
100
+
101
+ return {
102
+ selectedKeys,
103
+ openKeys,
104
+ collapsed,
105
+ triggerMode,
106
+ handleClickMenuItem,
107
+ handleCollapsed,
108
+ setOpenKeys,
109
+ };
110
+ };
111
+
112
+ export default useMenuState;
@@ -0,0 +1,124 @@
1
+ import {
2
+ applyTheme,
3
+ getStoredTheme,
4
+ THEME,
5
+ type ThemeMode,
6
+ } from '@/common/theme';
7
+ import { useCallback, useEffect, useSyncExternalStore } from 'react';
8
+
9
+ // 主题变化订阅者列表
10
+ const subscribers = new Set<() => void>();
11
+
12
+ // 当前主题值(模块级别的共享状态)
13
+ let currentTheme: ThemeMode | null = null;
14
+
15
+ /**
16
+ * 通知所有订阅者主题已变化
17
+ */
18
+ const notifySubscribers = (): void => {
19
+ subscribers.forEach((callback) => callback());
20
+ };
21
+
22
+ /**
23
+ * 获取当前主题(用于 useSyncExternalStore)
24
+ */
25
+ const getSnapshot = (): ThemeMode => {
26
+ if (currentTheme === null) {
27
+ currentTheme = getStoredTheme();
28
+ }
29
+ return currentTheme;
30
+ };
31
+
32
+ /**
33
+ * 订阅主题变化(用于 useSyncExternalStore)
34
+ */
35
+ const subscribe = (callback: () => void): (() => void) => {
36
+ subscribers.add(callback);
37
+ return () => {
38
+ subscribers.delete(callback);
39
+ };
40
+ };
41
+
42
+ /**
43
+ * 设置主题(内部方法)
44
+ */
45
+ const setThemeInternal = (theme: ThemeMode, persist = true): void => {
46
+ if (currentTheme === theme) return;
47
+
48
+ currentTheme = theme;
49
+ applyTheme(theme);
50
+
51
+ if (persist) {
52
+ localStorage.setItem(THEME.STORAGE_KEY, theme);
53
+ }
54
+
55
+ // 通知所有使用 useTheme 的组件
56
+ notifySubscribers();
57
+ };
58
+
59
+ /**
60
+ * 主题管理 Hook
61
+ *
62
+ * 使用 useSyncExternalStore 实现跨组件状态同步,
63
+ * 解决多个组件各自调用 useTheme 时状态不同步的问题。
64
+ */
65
+ export const useTheme = () => {
66
+ // 使用 useSyncExternalStore 订阅共享的主题状态
67
+ const theme = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
68
+
69
+ // 首次挂载时应用主题
70
+ useEffect(() => {
71
+ applyTheme(theme);
72
+ }, []);
73
+
74
+ // 监听系统主题变化
75
+ useEffect(() => {
76
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
77
+
78
+ const handleChange = (e: MediaQueryListEvent): void => {
79
+ // 只有在没有手动设置主题时才跟随系统
80
+ if (!localStorage.getItem(THEME.STORAGE_KEY)) {
81
+ const newTheme: ThemeMode = e.matches ? 'dark' : 'light';
82
+ setThemeInternal(newTheme, false);
83
+ }
84
+ };
85
+
86
+ mediaQuery.addEventListener('change', handleChange);
87
+ return () => mediaQuery.removeEventListener('change', handleChange);
88
+ }, []);
89
+
90
+ // 监听 storage 事件(其他标签页的主题变化)
91
+ useEffect(() => {
92
+ const handleStorage = (e: StorageEvent): void => {
93
+ if (e.key === THEME.STORAGE_KEY && e.newValue) {
94
+ const newTheme = e.newValue as ThemeMode;
95
+ if (newTheme === 'light' || newTheme === 'dark') {
96
+ setThemeInternal(newTheme, false);
97
+ }
98
+ }
99
+ };
100
+
101
+ window.addEventListener('storage', handleStorage);
102
+ return () => window.removeEventListener('storage', handleStorage);
103
+ }, []);
104
+
105
+ // 切换主题
106
+ const toggleTheme = useCallback((): void => {
107
+ const next: ThemeMode = currentTheme === 'light' ? 'dark' : 'light';
108
+ setThemeInternal(next, true);
109
+ }, []);
110
+
111
+ // 设置指定主题
112
+ const setThemeValue = useCallback((newTheme: ThemeMode): void => {
113
+ setThemeInternal(newTheme, true);
114
+ }, []);
115
+
116
+ return {
117
+ theme,
118
+ toggleTheme,
119
+ setTheme: setThemeValue,
120
+ isDark: theme === 'dark',
121
+ };
122
+ };
123
+
124
+ export default useTheme;
@@ -0,0 +1,109 @@
1
+ @import '@/styles/variables.less';
2
+
3
+ // Common mixins
4
+ .header-gray-btn-mixin() {
5
+ background: @color-fill-2;
6
+ border-radius: @border-radius-button;
7
+ }
8
+
9
+ .center-mixin() {
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ }
14
+
15
+ .round-mixin(@size) {
16
+ width: @size;
17
+ height: @size;
18
+ border-radius: @border-radius-round;
19
+ }
20
+
21
+ .layout-header {
22
+ z-index: @header-z-index;
23
+ height: @header-height;
24
+ line-height: @header-height;
25
+ padding: 0 @spacing-lg;
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: space-between;
29
+ background: var(--color-bg-3);
30
+ box-shadow: 0 4px 4px 0 rgba(134, 144, 156, 8%);
31
+ flex-shrink: 0;
32
+
33
+ .trigger {
34
+ margin-left: 20px;
35
+ }
36
+ }
37
+
38
+ .layout-header-logo {
39
+ display: flex;
40
+ align-items: center;
41
+ margin-right: auto;
42
+
43
+ .logo-text {
44
+ font-size: @font-size-lg;
45
+ font-weight: @font-weight-bold;
46
+ color: @Brand1-6;
47
+ letter-spacing: 1px;
48
+ }
49
+ }
50
+
51
+ .layout-header-right {
52
+ display: flex;
53
+ align-items: center;
54
+ }
55
+
56
+ .layout-header-action {
57
+ width: 32px;
58
+ height: 32px;
59
+ .center-mixin();
60
+
61
+ border-radius: @border-radius-round;
62
+ cursor: pointer;
63
+ transition: background-color @animation-duration-fast;
64
+
65
+ &:hover {
66
+ background: @color-fill-2;
67
+ }
68
+
69
+ svg {
70
+ font-size: 18px;
71
+ color: @color-text-2;
72
+ }
73
+ }
74
+
75
+ .layout-header-user {
76
+ display: flex;
77
+ align-items: center;
78
+ padding: @spacing-xs @spacing-sm;
79
+ border-radius: @border-radius-md;
80
+ cursor: pointer;
81
+ transition: background-color @animation-duration-fast;
82
+
83
+ &:hover {
84
+ background: @color-fill-2;
85
+ }
86
+
87
+ .user-name {
88
+ color: @color-text-1;
89
+ font-size: @font-size-base;
90
+ }
91
+ }
92
+
93
+ .split-line {
94
+ width: 1px;
95
+ height: 16px;
96
+ background: var(--color-border-2);
97
+ margin: 0 @spacing-xs;
98
+ }
99
+
100
+ .notification-btn {
101
+ .header-gray-btn-mixin();
102
+ .center-mixin();
103
+
104
+ color: @color-text-1;
105
+
106
+ .unread-count {
107
+ background-color: @Danger-6;
108
+ }
109
+ }
@@ -0,0 +1,97 @@
1
+ import { getAuthInfo } from '@/common/auth/tool';
2
+ import { useTheme } from '@/hooks/useTheme';
3
+ import {
4
+ Avatar,
5
+ Divider,
6
+ Dropdown,
7
+ Layout,
8
+ Menu,
9
+ Space,
10
+ } from '@arco-design/web-react';
11
+ import {
12
+ IconMoonFill,
13
+ IconPoweroff,
14
+ IconSettings,
15
+ IconSunFill,
16
+ IconUser,
17
+ } from '@arco-design/web-react/icon';
18
+ import { useModel } from '@umijs/max';
19
+ import React, { useCallback } from 'react';
20
+ import './index.less';
21
+
22
+ const Header = Layout.Header;
23
+
24
+ const LayoutHeader: React.FC = () => {
25
+ const { initialState } = useModel('@@initialState');
26
+ const { theme, toggleTheme } = useTheme();
27
+ const authInfo = getAuthInfo();
28
+
29
+ const handleMenuClick = useCallback((key: string) => {
30
+ if (key === 'logout') {
31
+ // TODO: Implement logout logic
32
+ console.log('logout');
33
+ } else if (key === 'settings') {
34
+ // TODO: Navigate to settings page
35
+ console.log('settings');
36
+ }
37
+ }, []);
38
+
39
+ const droplist = (
40
+ <Menu onClickMenuItem={handleMenuClick}>
41
+ <Menu.Item key="settings">
42
+ <IconSettings style={{ marginRight: 8 }} />
43
+ 设置
44
+ </Menu.Item>
45
+ <Divider style={{ margin: '4px 0' }} />
46
+ <Menu.Item key="logout">
47
+ <IconPoweroff style={{ marginRight: 8 }} />
48
+ 退出登录
49
+ </Menu.Item>
50
+ </Menu>
51
+ );
52
+
53
+ return (
54
+ <Header className="layout-header">
55
+ {/* Logo */}
56
+ <div className="layout-header-logo">
57
+ <span className="logo-text">
58
+ {window.__MICO_MENUS__?.appName || 'AUDIT CENTER'}
59
+ </span>
60
+ </div>
61
+
62
+ {/* Right section */}
63
+ <div className="layout-header-right">
64
+ <Space size="medium">
65
+ {/* Theme toggle */}
66
+ <div
67
+ className="layout-header-action"
68
+ onClick={toggleTheme}
69
+ title={theme === 'dark' ? '切换到亮色模式' : '切换到暗色模式'}
70
+ >
71
+ {theme === 'dark' ? <IconSunFill /> : <IconMoonFill />}
72
+ </div>
73
+
74
+ <span className="split-line" />
75
+
76
+ {/* User info */}
77
+ <Dropdown droplist={droplist} position="br" trigger="click">
78
+ <div className="layout-header-user">
79
+ <Avatar size={32} style={{ marginRight: 8 }}>
80
+ {authInfo.avatar ? (
81
+ <img src={authInfo.avatar} alt="avatar" />
82
+ ) : (
83
+ <IconUser />
84
+ )}
85
+ </Avatar>
86
+ <span className="user-name">
87
+ {authInfo.nickname || initialState?.currentUser?.name || '用户'}
88
+ </span>
89
+ </div>
90
+ </Dropdown>
91
+ </Space>
92
+ </div>
93
+ </Header>
94
+ );
95
+ };
96
+
97
+ export default LayoutHeader;
@@ -0,0 +1,164 @@
1
+ @import '@/styles/variables.less';
2
+
3
+ .arco-layout-sider-collapsed {
4
+ .layout-menu {
5
+ .layout-menu-level-1 {
6
+ // Handle safari text ellipsis in collapsed state
7
+ text-overflow: clip;
8
+ }
9
+ }
10
+ }
11
+
12
+ .layout-menu {
13
+ font-size: @font-size-base;
14
+
15
+ // Base menu item styles
16
+ .arco-menu-item {
17
+ height: 40px;
18
+ line-height: 40px;
19
+ padding: 0 12px;
20
+
21
+ &:hover {
22
+ background-color: @Brand1-1;
23
+ }
24
+ }
25
+
26
+ .arco-menu-inline-header {
27
+ &:hover {
28
+ background-color: @Brand1-1;
29
+ }
30
+ }
31
+
32
+ .arco-menu-inline-header.arco-menu-selected {
33
+ &:hover {
34
+ background-color: @Brand1-1;
35
+ }
36
+ }
37
+
38
+ .arco-menu-item.arco-menu-selected {
39
+ background-color: @Brand1-1;
40
+ }
41
+
42
+ .arco-menu-pop-header.arco-menu-selected {
43
+ background-color: @Brand1-1;
44
+ }
45
+
46
+ .arco-menu-pop-header:hover {
47
+ background-color: @Brand1-1;
48
+ }
49
+
50
+ .arco-menu-indent {
51
+ width: 32px;
52
+ }
53
+
54
+ .layout-menu-level-3 {
55
+ margin-bottom: 0;
56
+
57
+ .arco-menu-indent {
58
+ width: 24px;
59
+ }
60
+ }
61
+
62
+ .arco-icon {
63
+ font-size: 16px;
64
+ }
65
+
66
+ .arco-menu-inline-header .arco-icon {
67
+ margin-right: 16px;
68
+ }
69
+
70
+ .arco-menu-icon-suffix {
71
+ .arco-icon {
72
+ font-size: 14px;
73
+ }
74
+ }
75
+
76
+ // Level 1 menu spacing
77
+ &-level-1 {
78
+ margin-bottom: @spacing-sm;
79
+
80
+ &:last-child {
81
+ margin-bottom: 0;
82
+ }
83
+ }
84
+
85
+ // Level 2 menu styles
86
+ .arco-menu-inner {
87
+ .arco-menu-sub-menu {
88
+ // Level 2 menu item spacing
89
+ .arco-menu-item {
90
+ margin-bottom: @spacing-xs;
91
+
92
+ &:last-child {
93
+ margin-bottom: 0;
94
+ }
95
+ }
96
+
97
+ // Spacing between level 1 and level 2 menus
98
+ > .arco-menu-item {
99
+ margin-top: @spacing-xs;
100
+ }
101
+ }
102
+
103
+ // Level 3 menu styles
104
+ .arco-menu-sub-menu .arco-menu-sub-menu {
105
+ // Level 3 menu item spacing is 0
106
+ .arco-menu-item {
107
+ margin-bottom: 0;
108
+ }
109
+
110
+ // Level 3 menu indent relative to level 2
111
+ .arco-menu-inner {
112
+ padding-left: 56px;
113
+ }
114
+ }
115
+ }
116
+
117
+ // Submenu expanded styles
118
+ .arco-menu-sub-menu {
119
+ .arco-menu-sub-menu-title {
120
+ height: 40px;
121
+ line-height: 40px;
122
+ padding: 0 16px;
123
+ }
124
+ }
125
+
126
+ // Limit to max 3 levels
127
+ .arco-menu-sub-menu .arco-menu-sub-menu .arco-menu-sub-menu {
128
+ // Hide level 4+ menus
129
+ .arco-menu-sub-menu {
130
+ display: none;
131
+ }
132
+ }
133
+ }
134
+
135
+ // Sider styles
136
+ .<%= projectName %>-sider {
137
+ box-shadow: none;
138
+
139
+ .arco-layout-sider-trigger {
140
+ display: flex;
141
+ justify-content: flex-end;
142
+ // 使用主题变量适配亮/暗主题
143
+ background-color: @color-text-5;
144
+ }
145
+
146
+ .click-trigger-btn {
147
+ cursor: pointer;
148
+ display: flex;
149
+
150
+ // 图标颜色适配主题
151
+ .arco-icon {
152
+ color: @color-text-2;
153
+ }
154
+ }
155
+
156
+ .click-trigger-btn:not(.collapsed) {
157
+ background-color: @color-fill-1;
158
+ border-radius: @border-radius-lg;
159
+ }
160
+
161
+ .click-trigger-btn.collapsed {
162
+ justify-content: center;
163
+ }
164
+ }