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.
- package/README.md +84 -0
- package/bin/mico.js +316 -0
- package/generators/micro-react/ignore-list.json +8 -0
- package/generators/micro-react/index.js +158 -0
- package/generators/micro-react/templates/.commitlintrc.js +6 -0
- package/generators/micro-react/templates/.cursor/rules/always-read-docs.mdc +129 -0
- package/generators/micro-react/templates/.cursor/rules/cicd-deploy.mdc +143 -0
- package/generators/micro-react/templates/.cursor/rules/coding-conventions.mdc +206 -0
- package/generators/micro-react/templates/.cursor/rules/commit-conventions.mdc +111 -0
- package/generators/micro-react/templates/.cursor/rules/development-guide.mdc +295 -0
- package/generators/micro-react/templates/.cursor/rules/layout-app.mdc +275 -0
- package/generators/micro-react/templates/.cursor/rules/micro-frontend.mdc +196 -0
- package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +128 -0
- package/generators/micro-react/templates/.cursor/rules/request-auth.mdc +220 -0
- package/generators/micro-react/templates/.cursor/rules/theme-system.mdc +206 -0
- package/generators/micro-react/templates/.editorconfig +16 -0
- package/generators/micro-react/templates/.env +3 -0
- package/generators/micro-react/templates/.eslintrc.js +30 -0
- package/generators/micro-react/templates/.husky/commit-msg +2 -0
- package/generators/micro-react/templates/.husky/pre-commit +2 -0
- package/generators/micro-react/templates/.lintstagedrc +5 -0
- package/generators/micro-react/templates/.stylelintrc.js +25 -0
- package/generators/micro-react/templates/AGENTS.md +39 -0
- package/generators/micro-react/templates/CICD/start_dev.sh +30 -0
- package/generators/micro-react/templates/CICD/start_local.sh +30 -0
- package/generators/micro-react/templates/CICD/start_prod.sh +30 -0
- package/generators/micro-react/templates/CICD/start_test.sh +30 -0
- package/generators/micro-react/templates/CICD/wangsu_fresh_dev.sh +19 -0
- package/generators/micro-react/templates/CICD/wangsu_fresh_prod.sh +19 -0
- package/generators/micro-react/templates/CICD/wangsu_fresh_test.sh +19 -0
- package/generators/micro-react/templates/CLAUDE.md +106 -0
- package/generators/micro-react/templates/README.md +84 -0
- package/generators/micro-react/templates/_gitignore +57 -0
- package/generators/micro-react/templates/_npmrc +2 -0
- package/generators/micro-react/templates/apps/layout/.env +4 -0
- package/generators/micro-react/templates/apps/layout/.eslintrc.js +10 -0
- package/generators/micro-react/templates/apps/layout/.lintstagedrc +17 -0
- package/generators/micro-react/templates/apps/layout/.prettierignore +3 -0
- package/generators/micro-react/templates/apps/layout/.prettierrc +8 -0
- package/generators/micro-react/templates/apps/layout/.stylelintrc.js +20 -0
- package/generators/micro-react/templates/apps/layout/README.md +37 -0
- package/generators/micro-react/templates/apps/layout/config/config.dev.ts +54 -0
- package/generators/micro-react/templates/apps/layout/config/config.prod.ts +37 -0
- package/generators/micro-react/templates/apps/layout/config/config.testing.ts +27 -0
- package/generators/micro-react/templates/apps/layout/config/config.ts +132 -0
- package/generators/micro-react/templates/apps/layout/config/routes.ts +13 -0
- package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +78 -0
- package/generators/micro-react/templates/apps/layout/mock/menus.json +100 -0
- package/generators/micro-react/templates/apps/layout/mock/menus.ts +11 -0
- package/generators/micro-react/templates/apps/layout/mock/user.mock.ts +20 -0
- package/generators/micro-react/templates/apps/layout/package.json +45 -0
- package/generators/micro-react/templates/apps/layout/public/font/ar-SA.js +54 -0
- package/generators/micro-react/templates/apps/layout/public/font/default.js +54 -0
- package/generators/micro-react/templates/apps/layout/src/app.tsx +123 -0
- package/generators/micro-react/templates/apps/layout/src/assets/.gitkeep +0 -0
- package/generators/micro-react/templates/apps/layout/src/common/auth/cs-auth-manager.ts +220 -0
- package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +41 -0
- package/generators/micro-react/templates/apps/layout/src/common/auth/tool.ts +3 -0
- package/generators/micro-react/templates/apps/layout/src/common/auth/type.ts +6 -0
- package/generators/micro-react/templates/apps/layout/src/common/constants.ts +38 -0
- package/generators/micro-react/templates/apps/layout/src/common/env.ts +73 -0
- package/generators/micro-react/templates/apps/layout/src/common/helpers.ts +69 -0
- package/generators/micro-react/templates/apps/layout/src/common/locale.ts +123 -0
- package/generators/micro-react/templates/apps/layout/src/common/logger.ts +45 -0
- package/generators/micro-react/templates/apps/layout/src/common/menu/index.ts +2 -0
- package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +143 -0
- package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +92 -0
- package/generators/micro-react/templates/apps/layout/src/common/request/config.ts +73 -0
- package/generators/micro-react/templates/apps/layout/src/common/request/index.ts +188 -0
- package/generators/micro-react/templates/apps/layout/src/common/request/interceptors.ts +186 -0
- package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +132 -0
- package/generators/micro-react/templates/apps/layout/src/common/request/token-refresh.ts +136 -0
- package/generators/micro-react/templates/apps/layout/src/common/request/types.ts +44 -0
- package/generators/micro-react/templates/apps/layout/src/common/request/url-resolver.ts +75 -0
- package/generators/micro-react/templates/apps/layout/src/common/theme.ts +107 -0
- package/generators/micro-react/templates/apps/layout/src/common/types.ts +7 -0
- package/generators/micro-react/templates/apps/layout/src/common/upload/index.ts +2 -0
- package/generators/micro-react/templates/apps/layout/src/common/upload/oss.ts +401 -0
- package/generators/micro-react/templates/apps/layout/src/common/upload/types.ts +47 -0
- package/generators/micro-react/templates/apps/layout/src/common/uploadFiles.ts +35 -0
- package/generators/micro-react/templates/apps/layout/src/components/IconFont/index.tsx +25 -0
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.less +44 -0
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +121 -0
- package/generators/micro-react/templates/apps/layout/src/constants/index.ts +15 -0
- package/generators/micro-react/templates/apps/layout/src/global.less +13 -0
- package/generators/micro-react/templates/apps/layout/src/hooks/index.ts +3 -0
- package/generators/micro-react/templates/apps/layout/src/hooks/useAuth.ts +75 -0
- package/generators/micro-react/templates/apps/layout/src/hooks/useMenu.ts +35 -0
- package/generators/micro-react/templates/apps/layout/src/hooks/useMenuState.ts +112 -0
- package/generators/micro-react/templates/apps/layout/src/hooks/useTheme.ts +124 -0
- package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.less +109 -0
- package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +97 -0
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +164 -0
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +165 -0
- package/generators/micro-react/templates/apps/layout/src/layouts/index.less +71 -0
- package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +91 -0
- package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +20 -0
- package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +19 -0
- package/generators/micro-react/templates/apps/layout/src/models/global.ts +13 -0
- package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +3 -0
- package/generators/micro-react/templates/apps/layout/src/pages/Home/index.tsx +7 -0
- package/generators/micro-react/templates/apps/layout/src/requestErrorConfig.ts +171 -0
- package/generators/micro-react/templates/apps/layout/src/services/auth.ts +37 -0
- package/generators/micro-react/templates/apps/layout/src/services/oss.ts +40 -0
- package/generators/micro-react/templates/apps/layout/src/styles/arco-override.less +78 -0
- package/generators/micro-react/templates/apps/layout/src/styles/themes/dark/custom-var.less +244 -0
- package/generators/micro-react/templates/apps/layout/src/styles/themes/normal/custom-var.less +195 -0
- package/generators/micro-react/templates/apps/layout/src/styles/variables.less +5 -0
- package/generators/micro-react/templates/apps/layout/src/utils/format.ts +4 -0
- package/generators/micro-react/templates/apps/layout/tailwind.config.js +7 -0
- package/generators/micro-react/templates/apps/layout/tailwind.css +3 -0
- package/generators/micro-react/templates/apps/layout/tsconfig.json +3 -0
- package/generators/micro-react/templates/apps/layout/typings.d.ts +1 -0
- package/generators/micro-react/templates/deployDesc.md +60 -0
- package/generators/micro-react/templates/docs/commit-message.md +98 -0
- package/generators/micro-react/templates/package.json +35 -0
- package/generators/micro-react/templates/packages/shared-styles/README.md +125 -0
- package/generators/micro-react/templates/packages/shared-styles/arco-override.less +78 -0
- package/generators/micro-react/templates/packages/shared-styles/index.less +14 -0
- package/generators/micro-react/templates/packages/shared-styles/package.json +27 -0
- package/generators/micro-react/templates/packages/shared-styles/theme-inject.less +10 -0
- package/generators/micro-react/templates/packages/shared-styles/themes/dark/custom-var.less +246 -0
- package/generators/micro-react/templates/packages/shared-styles/themes/normal/custom-var.less +195 -0
- package/generators/micro-react/templates/packages/shared-styles/variables-only.less +301 -0
- package/generators/micro-react/templates/packages/shared-styles/variables.less +363 -0
- package/generators/micro-react/templates/pnpm-workspace.yaml +9 -0
- package/generators/micro-react/templates/scripts/collect-dist.js +68 -0
- package/generators/micro-react/templates/scripts/create-umi-app.sh +61 -0
- package/generators/micro-react/templates/scripts/dev.js +133 -0
- package/generators/micro-react/templates/turbo.json +68 -0
- package/generators/subapp-react/ignore-list.json +7 -0
- package/generators/subapp-react/index.js +189 -0
- package/generators/subapp-react/templates/homepage/.env +4 -0
- package/generators/subapp-react/templates/homepage/README.md +116 -0
- package/generators/subapp-react/templates/homepage/_gitignore +9 -0
- package/generators/subapp-react/templates/homepage/config/config.dev.ts +59 -0
- package/generators/subapp-react/templates/homepage/config/config.prod.ts +41 -0
- package/generators/subapp-react/templates/homepage/config/config.testing.ts +40 -0
- package/generators/subapp-react/templates/homepage/config/config.ts +102 -0
- package/generators/subapp-react/templates/homepage/config/routes.ts +7 -0
- package/generators/subapp-react/templates/homepage/mock/api.mock.ts +59 -0
- package/generators/subapp-react/templates/homepage/package.json +30 -0
- package/generators/subapp-react/templates/homepage/src/app.tsx +80 -0
- package/generators/subapp-react/templates/homepage/src/assets/yay.jpg +0 -0
- package/generators/subapp-react/templates/homepage/src/common/logger.ts +42 -0
- package/generators/subapp-react/templates/homepage/src/common/mainApp.ts +53 -0
- package/generators/subapp-react/templates/homepage/src/common/request.ts +49 -0
- package/generators/subapp-react/templates/homepage/src/global.less +26 -0
- package/generators/subapp-react/templates/homepage/src/pages/index.less +139 -0
- package/generators/subapp-react/templates/homepage/src/pages/index.tsx +342 -0
- package/generators/subapp-react/templates/homepage/src/styles/theme.less +6 -0
- package/generators/subapp-react/templates/homepage/tsconfig.json +3 -0
- package/generators/subapp-react/templates/homepage/typings.d.ts +17 -0
- package/lib/utils.js +165 -0
- package/package.json +31 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { ParsedMenuItem } from '@/common/menu';
|
|
2
|
+
import { getWindowMenus, parseMenuItems } from '@/common/menu';
|
|
3
|
+
import IconFont from '@/components/IconFont';
|
|
4
|
+
import { useMenuState } from '@/hooks/useMenuState';
|
|
5
|
+
import { useTheme } from '@/hooks/useTheme';
|
|
6
|
+
import { Layout, Menu } from '@arco-design/web-react';
|
|
7
|
+
import * as Icons from '@arco-design/web-react/icon';
|
|
8
|
+
import React, { useMemo, useRef } from 'react';
|
|
9
|
+
import './index.less';
|
|
10
|
+
|
|
11
|
+
const MenuItem = Menu.Item;
|
|
12
|
+
const SubMenu = Menu.SubMenu;
|
|
13
|
+
const Sider = Layout.Sider;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 图标名称到 Arco Icon 组件的映射
|
|
17
|
+
* 用于处理自定义图标名称与 Arco 图标名称的差异
|
|
18
|
+
*/
|
|
19
|
+
const ICON_ALIAS_MAP: Record<string, keyof typeof Icons> = {
|
|
20
|
+
Package: 'IconApps',
|
|
21
|
+
// 可以在这里添加更多别名映射
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 根据图标名称获取对应的 Icon 组件
|
|
26
|
+
* 优先使用 Icon${name} 格式,然后查找别名映射
|
|
27
|
+
*/
|
|
28
|
+
const getIconComponent = (iconName: string): React.ReactNode => {
|
|
29
|
+
if (!iconName) return null;
|
|
30
|
+
|
|
31
|
+
// 尝试直接匹配 Icon${name} 格式
|
|
32
|
+
const iconKey = `Icon${iconName}` as keyof typeof Icons;
|
|
33
|
+
const IconComponent = Icons[iconKey] as React.ComponentType | undefined;
|
|
34
|
+
|
|
35
|
+
if (IconComponent) {
|
|
36
|
+
return <IconComponent />;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 尝试别名映射
|
|
40
|
+
const aliasKey = ICON_ALIAS_MAP[iconName];
|
|
41
|
+
if (aliasKey) {
|
|
42
|
+
const AliasIcon = Icons[aliasKey] as React.ComponentType | undefined;
|
|
43
|
+
if (AliasIcon) {
|
|
44
|
+
return <AliasIcon />;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Recursively render menu items
|
|
53
|
+
*/
|
|
54
|
+
const renderMenuItems = (items: ParsedMenuItem[]): React.ReactNode => {
|
|
55
|
+
return items.map((item) => {
|
|
56
|
+
const icon = getIconComponent(item.icon);
|
|
57
|
+
|
|
58
|
+
// External link type
|
|
59
|
+
if (item.type === 'link' && item.path) {
|
|
60
|
+
return (
|
|
61
|
+
<MenuItem
|
|
62
|
+
key={item.key}
|
|
63
|
+
onClick={() => window.open(item.path, '_blank')}
|
|
64
|
+
>
|
|
65
|
+
{icon}
|
|
66
|
+
{item.label}
|
|
67
|
+
</MenuItem>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Has sub-menu
|
|
72
|
+
if (item.children && item.children.length > 0) {
|
|
73
|
+
return (
|
|
74
|
+
<SubMenu
|
|
75
|
+
key={item.key}
|
|
76
|
+
title={
|
|
77
|
+
<>
|
|
78
|
+
{icon}
|
|
79
|
+
{item.label}
|
|
80
|
+
</>
|
|
81
|
+
}
|
|
82
|
+
>
|
|
83
|
+
{renderMenuItems(item.children)}
|
|
84
|
+
</SubMenu>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Page type
|
|
89
|
+
return (
|
|
90
|
+
<MenuItem key={item.key}>
|
|
91
|
+
{icon}
|
|
92
|
+
{item.label}
|
|
93
|
+
</MenuItem>
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
interface LayoutMenuProps {
|
|
99
|
+
collapsed?: boolean;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const LayoutMenu: React.FC<LayoutMenuProps> = () => {
|
|
103
|
+
const siderRef = useRef<HTMLDivElement>(null);
|
|
104
|
+
const { isDark } = useTheme();
|
|
105
|
+
|
|
106
|
+
// Parse menu data
|
|
107
|
+
const menuItems = useMemo(() => {
|
|
108
|
+
const menus = getWindowMenus();
|
|
109
|
+
return parseMenuItems(menus);
|
|
110
|
+
}, []);
|
|
111
|
+
|
|
112
|
+
// 使用菜单状态 Hook
|
|
113
|
+
const {
|
|
114
|
+
selectedKeys,
|
|
115
|
+
openKeys,
|
|
116
|
+
collapsed,
|
|
117
|
+
handleClickMenuItem,
|
|
118
|
+
handleCollapsed,
|
|
119
|
+
setOpenKeys,
|
|
120
|
+
} = useMenuState({ menuItems });
|
|
121
|
+
|
|
122
|
+
// 点击触发按钮图标
|
|
123
|
+
const clickTriggerBtnIcon = collapsed
|
|
124
|
+
? 'webcs-outline_fold'
|
|
125
|
+
: 'webcs-outline_unfold';
|
|
126
|
+
|
|
127
|
+
// 点击触发按钮
|
|
128
|
+
const clickTriggerBtn = (
|
|
129
|
+
<div
|
|
130
|
+
className={`click-trigger-btn ${collapsed ? 'collapsed' : ''} mr-3`}
|
|
131
|
+
onClick={() => handleCollapsed(collapsed, 'clickTrigger')}
|
|
132
|
+
>
|
|
133
|
+
<IconFont type={clickTriggerBtnIcon} fontSize={24} />
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<Sider
|
|
139
|
+
ref={siderRef}
|
|
140
|
+
collapsed={collapsed}
|
|
141
|
+
onCollapse={handleCollapsed}
|
|
142
|
+
collapsible
|
|
143
|
+
breakpoint="xl"
|
|
144
|
+
className="<%= projectName %>-sider"
|
|
145
|
+
trigger={clickTriggerBtn}
|
|
146
|
+
theme={isDark ? 'dark' : 'light'}
|
|
147
|
+
>
|
|
148
|
+
<Menu
|
|
149
|
+
className="layout-menu"
|
|
150
|
+
mode="vertical"
|
|
151
|
+
collapse={collapsed}
|
|
152
|
+
selectedKeys={selectedKeys}
|
|
153
|
+
openKeys={openKeys}
|
|
154
|
+
onClickMenuItem={handleClickMenuItem}
|
|
155
|
+
onClickSubMenu={(_, newOpenKeys) => setOpenKeys(newOpenKeys)}
|
|
156
|
+
style={{ width: '100%', height: '100%' }}
|
|
157
|
+
theme={isDark ? 'dark' : 'light'}
|
|
158
|
+
>
|
|
159
|
+
{renderMenuItems(menuItems)}
|
|
160
|
+
</Menu>
|
|
161
|
+
</Sider>
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export default LayoutMenu;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
@import '@/styles/variables.less';
|
|
2
|
+
|
|
3
|
+
.layout-container {
|
|
4
|
+
min-width: 100vw;
|
|
5
|
+
background: @color-fill-1;
|
|
6
|
+
overflow-x: auto;
|
|
7
|
+
overflow-y: hidden;
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
|
|
11
|
+
.layout-content {
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
flex: 1;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: row;
|
|
16
|
+
|
|
17
|
+
.layout-content-right {
|
|
18
|
+
padding: @spacing-md @spacing-lg;
|
|
19
|
+
flex: 1;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
|
|
24
|
+
.layout-content-outlet {
|
|
25
|
+
flex: 1;
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
background: none;
|
|
28
|
+
border-radius: @border-radius-lg;
|
|
29
|
+
position: relative;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.arco-spin-children::after {
|
|
35
|
+
background-color: color-mix(in srgb, var(--color-text-5) 60%, transparent);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.arco-form {
|
|
40
|
+
.arco-form-label-item {
|
|
41
|
+
label {
|
|
42
|
+
span,
|
|
43
|
+
strong > svg {
|
|
44
|
+
display: inline-block;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.<%= projectName %>-sider {
|
|
51
|
+
box-shadow: none;
|
|
52
|
+
|
|
53
|
+
.arco-layout-sider-trigger {
|
|
54
|
+
display: flex;
|
|
55
|
+
justify-content: flex-end;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.click-trigger-btn {
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
display: flex;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.click-trigger-btn:not(.collapsed) {
|
|
64
|
+
background-color: @color-fill-1;
|
|
65
|
+
border-radius: @border-radius-lg;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.click-trigger-btn.collapsed {
|
|
69
|
+
justify-content: center;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { layoutLogger } from '@/common/logger';
|
|
2
|
+
import { extractRoutes, findRouteByPath, getWindowMenus } from '@/common/menu';
|
|
3
|
+
import MicroAppLoader from '@/components/MicroAppLoader';
|
|
4
|
+
import { NO_AUTH_ROUTE_LIST } from '@/constants';
|
|
5
|
+
import { Layout, Spin } from '@arco-design/web-react';
|
|
6
|
+
import { Outlet, useLocation } from '@umijs/max';
|
|
7
|
+
import React, { Suspense, useMemo } from 'react';
|
|
8
|
+
import LayoutHeader from './components/header';
|
|
9
|
+
import LayoutMenu from './components/menu';
|
|
10
|
+
import './index.less';
|
|
11
|
+
|
|
12
|
+
const { Content } = Layout;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 主布局组件
|
|
16
|
+
* 注意:Umi Max 使用 React Router 6,应该使用 <Outlet /> 渲染子路由,而不是 children
|
|
17
|
+
*/
|
|
18
|
+
const BasicLayout: React.FC = () => {
|
|
19
|
+
const location = useLocation();
|
|
20
|
+
|
|
21
|
+
// 解析路由配置
|
|
22
|
+
const routes = useMemo(() => {
|
|
23
|
+
const menus = getWindowMenus();
|
|
24
|
+
return extractRoutes(menus);
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
// 查找当前路由配置
|
|
28
|
+
const currentRoute = useMemo(() => {
|
|
29
|
+
return findRouteByPath(routes, location.pathname);
|
|
30
|
+
}, [routes, location.pathname]);
|
|
31
|
+
|
|
32
|
+
// 判断是否需要显示布局
|
|
33
|
+
const showLayout = !NO_AUTH_ROUTE_LIST.includes(location.pathname);
|
|
34
|
+
|
|
35
|
+
// 渲染页面内容
|
|
36
|
+
const renderContent = () => {
|
|
37
|
+
layoutLogger.log('renderContent:', {
|
|
38
|
+
pathname: location.pathname,
|
|
39
|
+
currentRoute,
|
|
40
|
+
routes,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// 如果有匹配的动态路由配置且需要加载微应用
|
|
44
|
+
if (currentRoute?.loadType === 'microapp' && currentRoute.entry) {
|
|
45
|
+
layoutLogger.log('Loading microapp:', currentRoute);
|
|
46
|
+
return (
|
|
47
|
+
<MicroAppLoader
|
|
48
|
+
entry={currentRoute.entry}
|
|
49
|
+
// 使用 path 作为微应用唯一标识(如 /homepage)
|
|
50
|
+
name={currentRoute.path}
|
|
51
|
+
// 显示名称用于 loading 提示
|
|
52
|
+
displayName={currentRoute.name}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 默认:使用 Outlet 渲染内部路由
|
|
58
|
+
return <Outlet />;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// 不需要布局的页面
|
|
62
|
+
if (!showLayout) {
|
|
63
|
+
return (
|
|
64
|
+
<Suspense
|
|
65
|
+
fallback={<Spin dot style={{ width: '100%', marginTop: 100 }} />}
|
|
66
|
+
>
|
|
67
|
+
<Outlet />
|
|
68
|
+
</Suspense>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Layout className="layout-container">
|
|
74
|
+
<LayoutHeader />
|
|
75
|
+
<Layout className="layout-content">
|
|
76
|
+
<LayoutMenu />
|
|
77
|
+
<Layout className="layout-content-right">
|
|
78
|
+
<Content className="layout-content-outlet">
|
|
79
|
+
<Suspense
|
|
80
|
+
fallback={<Spin dot style={{ width: '100%', marginTop: 100 }} />}
|
|
81
|
+
>
|
|
82
|
+
{renderContent()}
|
|
83
|
+
</Suspense>
|
|
84
|
+
</Content>
|
|
85
|
+
</Layout>
|
|
86
|
+
</Layout>
|
|
87
|
+
</Layout>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export default BasicLayout;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
'menu.home': 'Home',
|
|
3
|
+
'menu.settings': 'Settings',
|
|
4
|
+
'menu.logout': 'Logout',
|
|
5
|
+
|
|
6
|
+
'header.theme.light': 'Switch to Light Mode',
|
|
7
|
+
'header.theme.dark': 'Switch to Dark Mode',
|
|
8
|
+
|
|
9
|
+
'error.404.title': 'Page Not Found',
|
|
10
|
+
'error.404.description': 'Sorry, the page you visited does not exist.',
|
|
11
|
+
'error.404.back': 'Back to Home',
|
|
12
|
+
|
|
13
|
+
'error.403.title': 'Access Denied',
|
|
14
|
+
'error.403.description':
|
|
15
|
+
'Sorry, you do not have permission to access this page.',
|
|
16
|
+
'error.403.back': 'Back to Home',
|
|
17
|
+
|
|
18
|
+
'loading.text': 'Loading...',
|
|
19
|
+
'loading.error': 'Failed to load',
|
|
20
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
'menu.home': '首页',
|
|
3
|
+
'menu.settings': '设置',
|
|
4
|
+
'menu.logout': '退出登录',
|
|
5
|
+
|
|
6
|
+
'header.theme.light': '切换到亮色模式',
|
|
7
|
+
'header.theme.dark': '切换到暗色模式',
|
|
8
|
+
|
|
9
|
+
'error.404.title': '页面不存在',
|
|
10
|
+
'error.404.description': '抱歉,您访问的页面不存在。',
|
|
11
|
+
'error.404.back': '返回首页',
|
|
12
|
+
|
|
13
|
+
'error.403.title': '无权限访问',
|
|
14
|
+
'error.403.description': '抱歉,您没有权限访问此页面。',
|
|
15
|
+
'error.403.back': '返回首页',
|
|
16
|
+
|
|
17
|
+
'loading.text': '加载中...',
|
|
18
|
+
'loading.error': '加载失败',
|
|
19
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAuthError,
|
|
3
|
+
createBusinessError,
|
|
4
|
+
notifyHostError,
|
|
5
|
+
} from '@/common/micro';
|
|
6
|
+
import { Message, Notification } from '@arco-design/web-react';
|
|
7
|
+
import type { RequestConfig } from '@umijs/max';
|
|
8
|
+
|
|
9
|
+
// 错误处理方案: 错误类型
|
|
10
|
+
enum ErrorShowType {
|
|
11
|
+
SILENT = 0,
|
|
12
|
+
WARN_MESSAGE = 1,
|
|
13
|
+
ERROR_MESSAGE = 2,
|
|
14
|
+
NOTIFICATION = 3,
|
|
15
|
+
REDIRECT = 9,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 与后端约定的响应数据格式
|
|
19
|
+
interface ResponseStructure {
|
|
20
|
+
success: boolean;
|
|
21
|
+
data: unknown;
|
|
22
|
+
errorCode?: number;
|
|
23
|
+
errorMessage?: string;
|
|
24
|
+
showType?: ErrorShowType;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 业务错误类型
|
|
28
|
+
interface BizError extends Error {
|
|
29
|
+
name: 'BizError';
|
|
30
|
+
info: ResponseStructure;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Axios 错误类型
|
|
34
|
+
interface AxiosLikeError extends Error {
|
|
35
|
+
response?: {
|
|
36
|
+
status: number;
|
|
37
|
+
data?: unknown;
|
|
38
|
+
};
|
|
39
|
+
request?: unknown;
|
|
40
|
+
config?: {
|
|
41
|
+
url?: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 错误处理选项
|
|
46
|
+
interface ErrorHandlerOptions {
|
|
47
|
+
skipErrorHandler?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 类型守卫:判断是否为业务错误
|
|
52
|
+
*/
|
|
53
|
+
const isBizError = (error: unknown): error is BizError => {
|
|
54
|
+
return error instanceof Error && error.name === 'BizError' && 'info' in error;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 类型守卫:判断是否为 Axios 类错误
|
|
59
|
+
*/
|
|
60
|
+
const isAxiosLikeError = (error: unknown): error is AxiosLikeError => {
|
|
61
|
+
return error instanceof Error && ('response' in error || 'request' in error);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @name 错误处理
|
|
66
|
+
* pro 自带的错误处理, 可以在这里做自己的改动
|
|
67
|
+
* @doc https://umijs.org/docs/max/request#配置
|
|
68
|
+
*/
|
|
69
|
+
export const errorConfig: RequestConfig = {
|
|
70
|
+
// 错误处理: umi@3 的错误处理方案。
|
|
71
|
+
errorConfig: {
|
|
72
|
+
// 错误抛出
|
|
73
|
+
errorThrower: (res) => {
|
|
74
|
+
const { success, data, errorCode, errorMessage, showType } =
|
|
75
|
+
res as unknown as ResponseStructure;
|
|
76
|
+
if (!success) {
|
|
77
|
+
const error = new Error(errorMessage) as BizError;
|
|
78
|
+
error.name = 'BizError';
|
|
79
|
+
error.info = { success, errorCode, errorMessage, showType, data };
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
// 错误接收及处理
|
|
84
|
+
errorHandler: (error: unknown, opts: unknown) => {
|
|
85
|
+
const options = opts as ErrorHandlerOptions | undefined;
|
|
86
|
+
if (options?.skipErrorHandler) throw error;
|
|
87
|
+
|
|
88
|
+
// 我们的 errorThrower 抛出的错误
|
|
89
|
+
if (isBizError(error)) {
|
|
90
|
+
const errorInfo = error.info;
|
|
91
|
+
const { errorMessage, errorCode } = errorInfo;
|
|
92
|
+
|
|
93
|
+
// 通知主应用业务错误
|
|
94
|
+
notifyHostError(
|
|
95
|
+
createBusinessError(errorMessage ?? '业务错误', errorCode, 'http', {
|
|
96
|
+
showType: errorInfo.showType,
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
switch (errorInfo.showType) {
|
|
101
|
+
case ErrorShowType.SILENT:
|
|
102
|
+
// do nothing
|
|
103
|
+
break;
|
|
104
|
+
case ErrorShowType.WARN_MESSAGE:
|
|
105
|
+
Message.warning(errorMessage ?? 'Warning');
|
|
106
|
+
break;
|
|
107
|
+
case ErrorShowType.ERROR_MESSAGE:
|
|
108
|
+
Message.error(errorMessage ?? 'Error');
|
|
109
|
+
break;
|
|
110
|
+
case ErrorShowType.NOTIFICATION:
|
|
111
|
+
Notification.normal({
|
|
112
|
+
title: errorMessage,
|
|
113
|
+
content: errorCode,
|
|
114
|
+
});
|
|
115
|
+
break;
|
|
116
|
+
case ErrorShowType.REDIRECT:
|
|
117
|
+
// TODO: redirect
|
|
118
|
+
break;
|
|
119
|
+
default:
|
|
120
|
+
Message.error(errorMessage ?? 'Error');
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (isAxiosLikeError(error)) {
|
|
126
|
+
if (error.response) {
|
|
127
|
+
// Axios 的错误
|
|
128
|
+
// 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
|
|
129
|
+
const status = error.response.status;
|
|
130
|
+
const errorMessage = `Response status:${status}`;
|
|
131
|
+
|
|
132
|
+
// 根据 HTTP 状态码区分鉴权错误和业务错误
|
|
133
|
+
if (status === 401 || status === 403) {
|
|
134
|
+
// 鉴权失败
|
|
135
|
+
notifyHostError(createAuthError(errorMessage, status, 'http'));
|
|
136
|
+
} else {
|
|
137
|
+
// 其他业务错误
|
|
138
|
+
notifyHostError(
|
|
139
|
+
createBusinessError(errorMessage, status, 'http', {
|
|
140
|
+
url: error.config?.url,
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
Message.error(errorMessage);
|
|
146
|
+
} else if (error.request) {
|
|
147
|
+
// 请求已经成功发起,但没有收到响应
|
|
148
|
+
const errorMessage = 'None response! Please retry.';
|
|
149
|
+
notifyHostError(
|
|
150
|
+
createBusinessError(errorMessage, 'NO_RESPONSE', 'http', {
|
|
151
|
+
url: error.config?.url,
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
Message.error(errorMessage);
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 发送请求时出了点问题
|
|
160
|
+
const errorMessage = 'Request error, please retry.';
|
|
161
|
+
const errorObj =
|
|
162
|
+
error instanceof Error ? error : new Error(String(error));
|
|
163
|
+
notifyHostError(
|
|
164
|
+
createBusinessError(errorMessage, 'REQUEST_ERROR', 'http', {
|
|
165
|
+
message: errorObj.message,
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
Message.error(errorMessage);
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { request } from '@/common/request';
|
|
2
|
+
|
|
3
|
+
const AUTH_CHECK = '/api/authentication/check/';
|
|
4
|
+
|
|
5
|
+
const AUTH_LOGIN = '/api/authentication/login/';
|
|
6
|
+
|
|
7
|
+
export interface ILoginReq {
|
|
8
|
+
email: string;
|
|
9
|
+
password: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ILoginRsp {
|
|
13
|
+
code: number;
|
|
14
|
+
msg?: string;
|
|
15
|
+
data?: {
|
|
16
|
+
access: string;
|
|
17
|
+
refresh?: string;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function checkStaffAuth(): Promise<any> {
|
|
23
|
+
const response = await request(AUTH_CHECK);
|
|
24
|
+
console.warn('checkStaffAuth response', JSON.stringify(response));
|
|
25
|
+
return response;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 员工登录,仅用于测试环境测试使用
|
|
30
|
+
*/
|
|
31
|
+
export async function loginStaff(payload: ILoginReq): Promise<ILoginRsp> {
|
|
32
|
+
const response = await request<ILoginRsp>(AUTH_LOGIN, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
data: payload,
|
|
35
|
+
});
|
|
36
|
+
return response;
|
|
37
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { request } from '@/common/request';
|
|
2
|
+
|
|
3
|
+
export interface SignUrlRequestPayload {
|
|
4
|
+
md5_content: string;
|
|
5
|
+
file_extension: string;
|
|
6
|
+
dir_category: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SignUrlResponse {
|
|
10
|
+
upload_url: string;
|
|
11
|
+
file_url: string;
|
|
12
|
+
file_key: string;
|
|
13
|
+
content_type?: string;
|
|
14
|
+
content_md5?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 获取 OSS 上传签名 URL
|
|
19
|
+
*
|
|
20
|
+
* 端点路径由 globalConfig.api.endpoints.ossSignUrl 配置:
|
|
21
|
+
* - CS 模式:默认使用 '/im_crm_platform/internal/workbench/file/signUrl'
|
|
22
|
+
* - CRM 模式:可配置不同的端点路径
|
|
23
|
+
*
|
|
24
|
+
* URL 的完整解析(baseUrl + proxyPath)由 request 模块统一处理。
|
|
25
|
+
*
|
|
26
|
+
* @param payload 请求参数
|
|
27
|
+
* @returns 签名 URL 响应
|
|
28
|
+
*/
|
|
29
|
+
export async function fetchUploadSignedUrl(
|
|
30
|
+
payload: SignUrlRequestPayload,
|
|
31
|
+
): Promise<SignUrlResponse> {
|
|
32
|
+
const response = await request<SignUrlResponse>(
|
|
33
|
+
'/im_crm_platform/internal/workbench/file/signUrl',
|
|
34
|
+
{
|
|
35
|
+
method: 'POST',
|
|
36
|
+
data: payload,
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
return response;
|
|
40
|
+
}
|