@morningfast/create-ui 0.0.6 → 0.0.8
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 +13 -13
- package/dist/index.js +68 -11
- package/dist/templates/main-app/README.md +44 -13
- package/dist/templates/main-app/package.json +1 -1
- package/dist/templates/main-app/src/micro-apps/demo-apps.ts +12 -0
- package/dist/templates/main-app/src/router/mock-routes.ts +35 -45
- package/dist/templates/main-app/src/setup.ts +5 -0
- package/dist/templates/main-app/vite.config.ts +4 -5
- package/dist/templates/sub-app/README.md +12 -13
- package/dist/templates/sub-app/package.json +1 -1
- package/dist/templates/sub-app/src/pages/error/Forbidden.vue +2 -3
- package/dist/templates/sub-app/src/pages/error/NotFound.vue +1 -2
- package/dist/templates/sub-app/vite.config.ts +1 -1
- package/dist/templates/workspace/.morningfast-ui.json +1 -0
- package/dist/templates/workspace/README.md +76 -2
- package/dist/templates/workspace/package.json +6 -6
- package/dist/templates/workspace/scripts/run-sub-app.mjs +23 -0
- package/package.json +1 -1
- package/dist/templates/main-app/src/pages/demo/MenuLeaf.vue +0 -12
- package/dist/templates/main-app/src/pages/demo/MenuLevelOne.vue +0 -12
- package/dist/templates/main-app/src/pages/demo/MenuLevelTwo.vue +0 -12
- package/dist/templates/main-app/src/pages/demo/StandaloneMenu.vue +0 -12
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ packages/create-ui/dist/templates/workspace
|
|
|
26
26
|
可以在任意希望放置新项目的目录执行:
|
|
27
27
|
|
|
28
28
|
```sh
|
|
29
|
-
|
|
29
|
+
pnpm dlx @morningfast/create-ui
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
脚手架会依次询问:
|
|
@@ -46,43 +46,43 @@ node /Users/chenjie/Desktop/code/BFI-frontend/packages/create-ui/dist/index.js
|
|
|
46
46
|
生成默认子应用:
|
|
47
47
|
|
|
48
48
|
```sh
|
|
49
|
-
|
|
49
|
+
pnpm dlx @morningfast/create-ui my-sub
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
生成主应用:
|
|
53
53
|
|
|
54
54
|
```sh
|
|
55
|
-
|
|
55
|
+
pnpm dlx @morningfast/create-ui my-main --template main-app
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
生成主应用并移除 demo:
|
|
59
59
|
|
|
60
60
|
```sh
|
|
61
|
-
|
|
61
|
+
pnpm dlx @morningfast/create-ui my-main --template main-app --no-demo
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
生成子应用:
|
|
65
65
|
|
|
66
66
|
```sh
|
|
67
|
-
|
|
67
|
+
pnpm dlx @morningfast/create-ui my-sub --template sub-app
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
生成业务 monorepo:
|
|
71
71
|
|
|
72
72
|
```sh
|
|
73
|
-
|
|
73
|
+
pnpm dlx @morningfast/create-ui my-platform --template workspace
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
在已生成项目根目录更新:
|
|
77
77
|
|
|
78
78
|
```sh
|
|
79
|
-
|
|
79
|
+
pnpm dlx @morningfast/create-ui --update
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
也可以在项目外指定 workspace 根目录更新:
|
|
83
83
|
|
|
84
84
|
```sh
|
|
85
|
-
|
|
85
|
+
pnpm dlx @morningfast/create-ui my-platform --update
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
`--update` 必须在 workspace 根目录执行,或显式传入 workspace 根目录。它会读取根目录 `.morningfast-ui.json`,
|
|
@@ -91,19 +91,19 @@ node /Users/chenjie/Desktop/code/BFI-frontend/packages/create-ui/dist/index.js m
|
|
|
91
91
|
默认更新整个 monorepo:
|
|
92
92
|
|
|
93
93
|
```sh
|
|
94
|
-
|
|
94
|
+
pnpm dlx @morningfast/create-ui --update
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
只更新主应用:
|
|
98
98
|
|
|
99
99
|
```sh
|
|
100
|
-
|
|
100
|
+
pnpm dlx @morningfast/create-ui --update --update-target main
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
只更新所有子应用:
|
|
104
104
|
|
|
105
105
|
```sh
|
|
106
|
-
|
|
106
|
+
pnpm dlx @morningfast/create-ui --update --update-target sub-apps
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
更新只覆盖 `.morningfast-ui.json` 里声明的 `managedRoots`。业务页面、业务接口模块和业务路由默认不覆盖;
|
|
@@ -139,7 +139,7 @@ hash 是按文件完整内容计算的,空格、换行和 CRLF/LF 变化都会
|
|
|
139
139
|
指定系统初始名:
|
|
140
140
|
|
|
141
141
|
```sh
|
|
142
|
-
|
|
142
|
+
pnpm dlx @morningfast/create-ui my-main --system-name "FMS 管理系统"
|
|
143
143
|
```
|
|
144
144
|
|
|
145
145
|
生成完成后进入新项目:
|
|
@@ -157,7 +157,7 @@ pnpm dev
|
|
|
157
157
|
- `main-app` 的 nginx 静态资源目录会按项目名替换。
|
|
158
158
|
- `main-app` 可选替换 nginx 的 `proxy_pass`。
|
|
159
159
|
- 默认模板是 `sub-app`,单独主应用需要显式传 `--template main-app`。
|
|
160
|
-
- `--no-demo` 会删除 `src/pages/demo
|
|
160
|
+
- `--no-demo` 会删除 `src/pages/demo`、对应示例路由和示例子应用配置,`main-app` / `sub-app` 都适用。
|
|
161
161
|
- `--update` 只能在 workspace 根目录执行,支持 `all`、`main`、`sub-apps` 三种更新目标。
|
|
162
162
|
|
|
163
163
|
## 不处理的内容
|
package/dist/index.js
CHANGED
|
@@ -808,8 +808,11 @@ async function customizeAppTemplate(appRoot, variables) {
|
|
|
808
808
|
}
|
|
809
809
|
if (!variables.keepDemo) {
|
|
810
810
|
await rm(path.join(appRoot, "src", "pages", "demo"), { recursive: true, force: true });
|
|
811
|
-
await
|
|
811
|
+
await rm(path.join(appRoot, "src", "micro-apps", "demo-apps.ts"), { force: true });
|
|
812
|
+
await removeDemoRoute(path.join(appRoot, "src", "router", "mock-routes.ts"));
|
|
813
|
+
await removeSubAppDemoMockRoute(path.join(appRoot, "src", "router", "mock-routes.ts"));
|
|
812
814
|
await removeSubAppDemoRoute(path.join(appRoot, "src", "router", "index.ts"));
|
|
815
|
+
await removeMainAppDemoSetup(path.join(appRoot, "src", "setup.ts"));
|
|
813
816
|
}
|
|
814
817
|
}
|
|
815
818
|
function getTemplatePackageNameForAppRoot(appRoot, variables) {
|
|
@@ -824,14 +827,17 @@ async function updateWorkspaceScripts(targetDir, variables) {
|
|
|
824
827
|
packageJson.scripts = {
|
|
825
828
|
...packageJson.scripts ?? {},
|
|
826
829
|
"dev:main": `pnpm --filter ${variables.mainAppDir} dev`,
|
|
827
|
-
"dev:sub": `
|
|
828
|
-
|
|
830
|
+
"dev:sub": `node scripts/run-sub-app.mjs dev ${variables.subAppDir}`,
|
|
831
|
+
"dev:sub-app": `node scripts/run-sub-app.mjs dev ${variables.subAppDir}`,
|
|
832
|
+
[`dev:${variables.subAppDir}`]: `node scripts/run-sub-app.mjs dev ${variables.subAppDir}`,
|
|
829
833
|
"check:main": `pnpm --filter ${variables.mainAppDir} check`,
|
|
830
|
-
"check:sub": `
|
|
831
|
-
|
|
834
|
+
"check:sub": `node scripts/run-sub-app.mjs check ${variables.subAppDir}`,
|
|
835
|
+
"check:sub-app": `node scripts/run-sub-app.mjs check ${variables.subAppDir}`,
|
|
836
|
+
[`check:${variables.subAppDir}`]: `node scripts/run-sub-app.mjs check ${variables.subAppDir}`,
|
|
832
837
|
"build:main": `pnpm --filter ${variables.mainAppDir} build`,
|
|
833
|
-
"build:sub": `
|
|
834
|
-
|
|
838
|
+
"build:sub": `node scripts/run-sub-app.mjs build ${variables.subAppDir}`,
|
|
839
|
+
"build:sub-app": `node scripts/run-sub-app.mjs build ${variables.subAppDir}`,
|
|
840
|
+
[`build:${variables.subAppDir}`]: `node scripts/run-sub-app.mjs build ${variables.subAppDir}`,
|
|
835
841
|
"build:all": "pnpm build:main && pnpm build:sub"
|
|
836
842
|
};
|
|
837
843
|
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
|
|
@@ -876,16 +882,56 @@ async function removeDemoRoute(filePath) {
|
|
|
876
882
|
return;
|
|
877
883
|
}
|
|
878
884
|
const source = await readFile(filePath, "utf8");
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
885
|
+
let updated = removeRouteObject(source, "path: 'demo'");
|
|
886
|
+
updated = removeRouteObject(updated, "path: 'micro'");
|
|
887
|
+
updated = updated.replace(/\n\s+'demo-user-management': \(\) => import\('@\/pages\/demo\/user-management\/index\.vue'\),/g, "").replace(/\n\s+'demo-mapping-config': \(\) => import\('@\/pages\/demo\/mapping-config\/index\.vue'\),/g, "").replace(/\n\s+'demo-table-page': \(\) => import\('@\/pages\/demo\/table-page-demo\/index\.vue'\),/g, "").replace(/\n\s+'demo-upload': \(\) => import\('@\/pages\/demo\/upload-demo\/index\.vue'\),/g, "");
|
|
888
|
+
if (updated !== source) {
|
|
889
|
+
await writeFile(filePath, updated, "utf8");
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
async function removeMainAppDemoSetup(filePath) {
|
|
893
|
+
if (!existsSync(filePath)) {
|
|
882
894
|
return;
|
|
883
895
|
}
|
|
884
|
-
const
|
|
896
|
+
const source = await readFile(filePath, "utf8");
|
|
897
|
+
const updated = source.replace(/\nimport \{ setDefaultAppConfigs \} from '@morningfast\/platform\/main\/micro-apps\/config';\n/, "\n").replace(/\nimport \{ demoAppConfigs \} from '\.\/micro-apps\/demo-apps';\n/, "\n").replace(/\n\s+setDefaultAppConfigs\(demoAppConfigs\);\n/, "\n").replace(/\n{3,}/g, "\n\n").replace(/\{\n\n\s+\/\//g, "{\n //");
|
|
885
898
|
if (updated !== source) {
|
|
886
899
|
await writeFile(filePath, updated, "utf8");
|
|
887
900
|
}
|
|
888
901
|
}
|
|
902
|
+
function removeRouteObject(source, marker) {
|
|
903
|
+
const markerIndex = source.indexOf(marker);
|
|
904
|
+
if (markerIndex < 0) {
|
|
905
|
+
return source;
|
|
906
|
+
}
|
|
907
|
+
const objectStart = source.lastIndexOf(" {", markerIndex);
|
|
908
|
+
if (objectStart < 0) {
|
|
909
|
+
return source;
|
|
910
|
+
}
|
|
911
|
+
let index = objectStart;
|
|
912
|
+
let depth = 0;
|
|
913
|
+
let objectEnd = -1;
|
|
914
|
+
for (; index < source.length; index += 1) {
|
|
915
|
+
const char = source[index];
|
|
916
|
+
if (char === "{") {
|
|
917
|
+
depth += 1;
|
|
918
|
+
} else if (char === "}") {
|
|
919
|
+
depth -= 1;
|
|
920
|
+
if (depth === 0) {
|
|
921
|
+
objectEnd = index + 1;
|
|
922
|
+
break;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
if (objectEnd < 0) {
|
|
927
|
+
return source;
|
|
928
|
+
}
|
|
929
|
+
const trailingCommaEnd = source.slice(objectEnd).match(/^,\n?/)?.[0].length ?? 0;
|
|
930
|
+
const leadingCommaStart = trailingCommaEnd ? objectStart : source.lastIndexOf(",", objectStart);
|
|
931
|
+
const removeStart = leadingCommaStart >= 0 && source.slice(leadingCommaStart, objectStart).trim() === "" ? leadingCommaStart : objectStart;
|
|
932
|
+
const removeEnd = objectEnd + trailingCommaEnd;
|
|
933
|
+
return source.slice(0, removeStart) + source.slice(removeEnd);
|
|
934
|
+
}
|
|
889
935
|
async function removeSubAppDemoRoute(filePath) {
|
|
890
936
|
if (!existsSync(filePath)) {
|
|
891
937
|
return;
|
|
@@ -902,6 +948,17 @@ async function removeSubAppDemoRoute(filePath) {
|
|
|
902
948
|
await writeFile(filePath, updated, "utf8");
|
|
903
949
|
}
|
|
904
950
|
}
|
|
951
|
+
async function removeSubAppDemoMockRoute(filePath) {
|
|
952
|
+
if (!existsSync(filePath)) {
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
const source = await readFile(filePath, "utf8");
|
|
956
|
+
let updated = removeRouteObject(source, "path: '/demo/orders'");
|
|
957
|
+
updated = updated.replace(/\n\s+'demo-orders': \(\) => import\('@\/pages\/demo\/orders\/index\.vue'\),/g, "");
|
|
958
|
+
if (updated !== source) {
|
|
959
|
+
await writeFile(filePath, updated, "utf8");
|
|
960
|
+
}
|
|
961
|
+
}
|
|
905
962
|
async function replaceText(filePath, searchValue, replaceValue) {
|
|
906
963
|
if (!existsSync(filePath)) {
|
|
907
964
|
return;
|
|
@@ -45,10 +45,10 @@ pnpm format
|
|
|
45
45
|
|
|
46
46
|
## 本地联调主子应用
|
|
47
47
|
|
|
48
|
-
主应用已预留 `qiankun`
|
|
48
|
+
主应用已预留 `qiankun` 微前端入口。保留 demo 时,模板会带一个本地测试子应用配置:
|
|
49
49
|
|
|
50
50
|
```txt
|
|
51
|
-
http://localhost:
|
|
51
|
+
http://localhost:8082
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
本地联调时分别启动主应用和子应用:
|
|
@@ -61,25 +61,56 @@ pnpm --filter __MORNINGFAST_MAIN_APP_DIR__ dev
|
|
|
61
61
|
pnpm --filter __MORNINGFAST_SUB_APP_DIR__ dev
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
保留 demo 时,然后访问主应用里的“微前端 / 订单管理”。
|
|
65
|
+
|
|
66
|
+
如果生成项目时使用了 `--no-demo`,不会带默认子应用配置和 demo 菜单,需要在业务路由里添加自己的微前端菜单,再在“系统 / 应用配置”页面维护子应用入口。
|
|
65
67
|
|
|
66
68
|
如果子应用地址不同,在主应用“系统 / 应用配置”页面维护子应用:
|
|
67
69
|
|
|
68
70
|
- `应用名称`:展示名。
|
|
69
71
|
- `路由前缀`:qiankun `activeRule`,例如 `/micro/__MORNINGFAST_SUB_APP_DIR__`。
|
|
70
|
-
- `入口地址`:qiankun `entry`,例如 `http://localhost:
|
|
72
|
+
- `入口地址`:qiankun `entry`,例如 `http://localhost:8082`。每个子应用单独维护自己的入口地址。
|
|
71
73
|
- `应用说明`:仅用于配置页展示。
|
|
72
74
|
|
|
73
75
|
应用配置当前先存在浏览器 `localStorage`,key 是 `system:apps`。保存后刷新主应用,qiankun 会重新读取本地配置并注册子应用。
|
|
74
76
|
|
|
75
77
|
## 配置一个子应用
|
|
76
78
|
|
|
77
|
-
1. 启动子应用,确认能直接打开它的本地地址,例如 `http://localhost:
|
|
79
|
+
1. 启动子应用,确认能直接打开它的本地地址,例如 `http://localhost:8082`。
|
|
78
80
|
2. 打开主应用“系统 / 应用配置”。
|
|
79
81
|
3. 点击新增应用,填写应用名称、路由前缀、入口地址和应用说明。
|
|
80
82
|
4. 保存后刷新主应用。
|
|
81
83
|
5. 通过主应用菜单进入该子应用路径。
|
|
82
84
|
|
|
85
|
+
子应用菜单路由复用同一个平台承载页,不需要为每个子应用新增 `componentKey` 或组件映射。完全新增一个子应用菜单时,在 `src/router/mock-routes.ts` 里写 `microApp: true` 即可:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
{
|
|
89
|
+
path: 'fms',
|
|
90
|
+
name: 'fms',
|
|
91
|
+
title: 'FMS 系统',
|
|
92
|
+
icon: 'Connection',
|
|
93
|
+
redirect: '/fms/orders',
|
|
94
|
+
children: [
|
|
95
|
+
{
|
|
96
|
+
path: 'orders',
|
|
97
|
+
name: 'fms-orders',
|
|
98
|
+
title: '订单管理',
|
|
99
|
+
microApp: true,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
path: ':pathMatch(.*)*',
|
|
103
|
+
name: 'fms-fallback',
|
|
104
|
+
title: 'FMS 页面',
|
|
105
|
+
hidden: true,
|
|
106
|
+
microApp: true,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
上面菜单对应的应用配置 `路由前缀` 应填写 `/fms`,入口地址填写该子应用的 dev server 或部署地址。隐藏的 `:pathMatch(.*)*` 用来兜住子应用深层路径刷新或跳转。
|
|
113
|
+
|
|
83
114
|
配置字段对应 qiankun 注册字段:
|
|
84
115
|
|
|
85
116
|
- `应用名称` 会成为 qiankun `name` 和主应用展示名。
|
|
@@ -97,7 +128,6 @@ pnpm --filter __MORNINGFAST_SUB_APP_DIR__ dev
|
|
|
97
128
|
{
|
|
98
129
|
basePath,
|
|
99
130
|
app,
|
|
100
|
-
auth,
|
|
101
131
|
runtime,
|
|
102
132
|
theme,
|
|
103
133
|
}
|
|
@@ -107,18 +137,17 @@ pnpm --filter __MORNINGFAST_SUB_APP_DIR__ dev
|
|
|
107
137
|
|
|
108
138
|
- `basePath`:子应用路由基座,来自子应用配置的路由前缀。
|
|
109
139
|
- `app`:当前子应用信息,包含 `id`、`name`、`title`、`entry`、`activeRule`。
|
|
110
|
-
- `auth`:主应用登录上下文,包含 `token`、`user`、`roles`、`permissions`。
|
|
111
140
|
- `runtime`:运行时配置,当前包含 `env` 和 `apiBaseUrl`。
|
|
112
141
|
- `theme`:主应用当前 Element Plus 主题变量和明暗模式。
|
|
113
142
|
|
|
114
143
|
子应用业务页面不直接读主应用 store,也不自己重新维护一套登录态。主应用只负责把必要上下文传进去,子应用通过自己的
|
|
115
|
-
`useSubAppContext()`
|
|
144
|
+
`useSubAppContext()` 读取。登录 token、用户和权限通过 `@morningfast/platform/auth` 共享包读取。
|
|
116
145
|
|
|
117
146
|
后续接菜单、权限时,继续沿用这个边界:
|
|
118
147
|
|
|
119
148
|
- 菜单树属于某个应用,先在应用配置中选应用,再维护该应用菜单。
|
|
120
149
|
- 权限点属于某个应用,接口按应用维度返回。
|
|
121
|
-
-
|
|
150
|
+
- 主应用负责维护当前登录用户的权限集合,子应用通过共享认证包读取权限后按自己的菜单和权限点渲染页面。
|
|
122
151
|
|
|
123
152
|
子应用通过 `morningfast-plus/components` 使用 `MpSearchForm`、`MpTable` 等公共组件。
|
|
124
153
|
|
|
@@ -152,12 +181,14 @@ const apiBaseUrl = runtime.apiBaseUrl;
|
|
|
152
181
|
|
|
153
182
|
### 子应用怎么取用户信息?
|
|
154
183
|
|
|
155
|
-
|
|
184
|
+
主应用登录后会把 token 持久化到 `localStorage`,并把用户、角色和权限写入共享认证包的内存缓存。子应用从共享认证包读取:
|
|
156
185
|
|
|
157
186
|
```ts
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
187
|
+
import { getAccessToken, getAuthProfile } from '@morningfast/platform/auth';
|
|
188
|
+
|
|
189
|
+
const token = getAccessToken();
|
|
190
|
+
const profile = getAuthProfile();
|
|
191
|
+
const permissions = profile?.permissions ?? [];
|
|
161
192
|
```
|
|
162
193
|
|
|
163
194
|
### 子应用之间怎么通信?
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AppConfigItem } from '@morningfast/platform/main/micro-apps/config';
|
|
2
|
+
|
|
3
|
+
export const demoAppConfigs: AppConfigItem[] = [
|
|
4
|
+
{
|
|
5
|
+
id: 'sub-orders',
|
|
6
|
+
name: '订单子应用',
|
|
7
|
+
type: 'sub',
|
|
8
|
+
description: '独立业务子应用,先在本地配置里测试子应用结构和菜单入口。',
|
|
9
|
+
basePath: '/micro/sub-app',
|
|
10
|
+
entry: 'http://localhost:8082',
|
|
11
|
+
},
|
|
12
|
+
];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RouteRecordRaw } from 'vue-router';
|
|
2
|
+
import { MicroAppView } from '@morningfast/platform/main';
|
|
2
3
|
|
|
3
4
|
export interface MockRouteItem {
|
|
4
5
|
path: string;
|
|
@@ -9,6 +10,7 @@ export interface MockRouteItem {
|
|
|
9
10
|
hidden?: boolean;
|
|
10
11
|
redirect?: string;
|
|
11
12
|
componentKey?: string;
|
|
13
|
+
microApp?: boolean;
|
|
12
14
|
children?: MockRouteItem[];
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -34,48 +36,8 @@ export const mockRouteItems: MockRouteItem[] = [
|
|
|
34
36
|
name: 'demo',
|
|
35
37
|
title: '示例模块',
|
|
36
38
|
icon: 'Setting',
|
|
37
|
-
redirect: '/demo/
|
|
39
|
+
redirect: '/demo/user-management',
|
|
38
40
|
children: [
|
|
39
|
-
{
|
|
40
|
-
path: 'menu',
|
|
41
|
-
name: 'demo-menu',
|
|
42
|
-
title: '示例分组一',
|
|
43
|
-
redirect: '/demo/menu/one',
|
|
44
|
-
children: [
|
|
45
|
-
{
|
|
46
|
-
path: 'one',
|
|
47
|
-
name: 'demo-menu-one',
|
|
48
|
-
title: '菜单示例一',
|
|
49
|
-
componentKey: 'demo-menu-one',
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
path: 'two',
|
|
53
|
-
name: 'demo-menu-two',
|
|
54
|
-
title: '菜单示例二',
|
|
55
|
-
componentKey: 'demo-menu-two',
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
path: 'another',
|
|
61
|
-
name: 'demo-another',
|
|
62
|
-
title: '示例分组二',
|
|
63
|
-
redirect: '/demo/another/leaf',
|
|
64
|
-
children: [
|
|
65
|
-
{
|
|
66
|
-
path: 'leaf',
|
|
67
|
-
name: 'demo-another-leaf',
|
|
68
|
-
title: '菜单示例三',
|
|
69
|
-
componentKey: 'demo-another-leaf',
|
|
70
|
-
},
|
|
71
|
-
],
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
path: 'standalone',
|
|
75
|
-
name: 'demo-standalone',
|
|
76
|
-
title: '独立菜单示例',
|
|
77
|
-
componentKey: 'demo-standalone',
|
|
78
|
-
},
|
|
79
41
|
{
|
|
80
42
|
path: 'user-management',
|
|
81
43
|
name: 'demo-user-management',
|
|
@@ -103,14 +65,38 @@ export const mockRouteItems: MockRouteItem[] = [
|
|
|
103
65
|
},
|
|
104
66
|
],
|
|
105
67
|
},
|
|
68
|
+
{
|
|
69
|
+
path: 'micro',
|
|
70
|
+
name: 'micro',
|
|
71
|
+
title: '微前端',
|
|
72
|
+
icon: 'Connection',
|
|
73
|
+
redirect: '/micro/sub-app/demo/orders',
|
|
74
|
+
children: [
|
|
75
|
+
{
|
|
76
|
+
path: 'sub-app/demo/orders',
|
|
77
|
+
name: 'micro-sub-app-demo-orders',
|
|
78
|
+
title: '订单管理',
|
|
79
|
+
microApp: true,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
path: ':pathMatch(.*)*',
|
|
83
|
+
name: 'micro-sub-app-fallback',
|
|
84
|
+
title: '子应用页面',
|
|
85
|
+
hidden: true,
|
|
86
|
+
microApp: true,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
path: 'micro2/sub-app-2/demo/orders',
|
|
92
|
+
name: 'micro2-sub-app2-demo-orders',
|
|
93
|
+
title: '订单管理',
|
|
94
|
+
microApp: true,
|
|
95
|
+
},
|
|
106
96
|
];
|
|
107
97
|
|
|
108
98
|
const componentMap: Record<string, RouteRecordRaw['component']> = {
|
|
109
99
|
dashboard: () => import('@/pages/dashboard/Workplace.vue'),
|
|
110
|
-
'demo-menu-one': () => import('@/pages/demo/MenuLevelOne.vue'),
|
|
111
|
-
'demo-menu-two': () => import('@/pages/demo/MenuLevelTwo.vue'),
|
|
112
|
-
'demo-another-leaf': () => import('@/pages/demo/MenuLeaf.vue'),
|
|
113
|
-
'demo-standalone': () => import('@/pages/demo/StandaloneMenu.vue'),
|
|
114
100
|
'demo-user-management': () => import('@/pages/demo/user-management/index.vue'),
|
|
115
101
|
'demo-mapping-config': () => import('@/pages/demo/mapping-config/index.vue'),
|
|
116
102
|
'demo-table-page': () => import('@/pages/demo/table-page-demo/index.vue'),
|
|
@@ -141,6 +127,10 @@ function createRouteFromMock(item: MockRouteItem): RouteRecordRaw {
|
|
|
141
127
|
route.component = componentMap[item.componentKey];
|
|
142
128
|
}
|
|
143
129
|
|
|
130
|
+
if (item.microApp) {
|
|
131
|
+
route.component = MicroAppView;
|
|
132
|
+
}
|
|
133
|
+
|
|
144
134
|
if (item.children?.length) {
|
|
145
135
|
route.children = createRoutesFromMock(item.children);
|
|
146
136
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { MountMorningfastAppContext } from '@morningfast/platform/main';
|
|
2
|
+
import { setDefaultAppConfigs } from '@morningfast/platform/main/micro-apps/config';
|
|
3
|
+
|
|
4
|
+
import { demoAppConfigs } from './micro-apps/demo-apps';
|
|
2
5
|
|
|
3
6
|
export async function setupBusinessApp({ app, router, pinia, http }: MountMorningfastAppContext) {
|
|
7
|
+
setDefaultAppConfigs(demoAppConfigs);
|
|
8
|
+
|
|
4
9
|
// 业务插件、全局组件、指令、监控 SDK 等启动扩展写在这里。
|
|
5
10
|
void app;
|
|
6
11
|
void router;
|
|
@@ -38,10 +38,9 @@ export default defineConfig((configEnv) => {
|
|
|
38
38
|
},
|
|
39
39
|
],
|
|
40
40
|
},
|
|
41
|
-
server:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
: undefined,
|
|
41
|
+
server: {
|
|
42
|
+
port: Number(env.VITE_MAIN_APP_PORT || 8081),
|
|
43
|
+
...(Object.keys(proxy).length ? { proxy } : {}),
|
|
44
|
+
},
|
|
46
45
|
}, defineBusinessViteConfig(configEnv))
|
|
47
46
|
})
|
|
@@ -12,7 +12,7 @@ Morningfast UI 微前端子应用模板。
|
|
|
12
12
|
pnpm dev
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
作为主应用子应用联调时,默认端口是 `
|
|
15
|
+
作为主应用子应用联调时,默认端口是 `8082`,主应用默认入口也是 `http://localhost:8082`。
|
|
16
16
|
|
|
17
17
|
如果需要开启 `vite-plugin-qiankun` 的 dev 子应用调试模式:
|
|
18
18
|
|
|
@@ -37,9 +37,9 @@ VITE_QIANKUN_DEV_MODE=true pnpm dev
|
|
|
37
37
|
|
|
38
38
|
- 子应用名:`morningfast-__MORNINGFAST_SUB_APP_DIR__`。
|
|
39
39
|
- 主应用默认挂载路径:`/micro/__MORNINGFAST_SUB_APP_DIR__`。
|
|
40
|
-
- 主应用应用配置里的 `入口地址` 要指向子应用访问地址,例如 `http://localhost:
|
|
40
|
+
- 主应用应用配置里的 `入口地址` 要指向子应用访问地址,例如 `http://localhost:8082`。
|
|
41
41
|
- 主应用应用配置里的 `路由前缀` 要和子应用挂载路径一致,例如 `/micro/__MORNINGFAST_SUB_APP_DIR__`。
|
|
42
|
-
- 子应用默认保留一个基础 `home`
|
|
42
|
+
- 子应用默认保留一个基础 `home` 页面。保留 demo 时,业务示例放在 `src/pages/demo/orders`,对应路由是 `/demo/orders`。
|
|
43
43
|
- 子应用独立运行时,`html`、`body`、`#sub-app` 必须形成 `height: 100%; min-height: 0;` 的闭合高度链,否则
|
|
44
44
|
`MpTablePage` / `MpMainContainerDefault` / `MpTable` 的固定高度表格无法计算内容高度。
|
|
45
45
|
公共组件统一从 `morningfast-plus/components` 引入:
|
|
@@ -66,7 +66,6 @@ import { MpSearchForm, MpTable } from 'morningfast-plus/components';
|
|
|
66
66
|
{
|
|
67
67
|
basePath,
|
|
68
68
|
app,
|
|
69
|
-
auth,
|
|
70
69
|
runtime,
|
|
71
70
|
theme,
|
|
72
71
|
}
|
|
@@ -81,14 +80,12 @@ import { useSubAppContext } from '@/composables/useSubAppContext';
|
|
|
81
80
|
|
|
82
81
|
const subAppContext = useSubAppContext();
|
|
83
82
|
const apiBaseUrl = subAppContext.runtime.apiBaseUrl;
|
|
84
|
-
const token = subAppContext.auth?.token;
|
|
85
83
|
```
|
|
86
84
|
|
|
87
85
|
常用字段:
|
|
88
86
|
|
|
89
87
|
- `basePath`:子应用路由基座,独立运行时默认 `/`,被主应用挂载时例如 `/micro/sub-app/`。
|
|
90
88
|
- `app`:当前子应用配置,包含应用 id、名称、入口地址和激活路由前缀。
|
|
91
|
-
- `auth`:主应用登录态,包含 token、用户、角色和权限。
|
|
92
89
|
- `runtime`:运行时配置,当前包含 `env` 和 `apiBaseUrl`。请求封装应优先从这里拿接口前缀。
|
|
93
90
|
- `theme`:当前主题快照。
|
|
94
91
|
|
|
@@ -103,9 +100,9 @@ runtime: {
|
|
|
103
100
|
|
|
104
101
|
后续真实接菜单和权限时,建议保持边界:
|
|
105
102
|
|
|
106
|
-
-
|
|
103
|
+
- 主应用只通过 qiankun props 传当前应用运行时上下文,不通过 props 传 token 或权限快照。
|
|
107
104
|
- 子应用维护自己的路由、菜单和权限点定义。
|
|
108
|
-
- 子应用通过
|
|
105
|
+
- 子应用通过 `@morningfast/platform/auth` 共享认证包读取 token、用户和权限,并根据自身菜单配置决定展示内容。
|
|
109
106
|
|
|
110
107
|
## 子应用接收主应用消息
|
|
111
108
|
|
|
@@ -172,13 +169,15 @@ fetch(`${runtime.apiBaseUrl}/orders`, {
|
|
|
172
169
|
### 子应用怎么取当前用户和权限?
|
|
173
170
|
|
|
174
171
|
```ts
|
|
175
|
-
|
|
172
|
+
import { getAccessToken, getAuthProfile } from '@morningfast/platform/auth';
|
|
176
173
|
|
|
177
|
-
const
|
|
178
|
-
const
|
|
174
|
+
const token = getAccessToken();
|
|
175
|
+
const profile = getAuthProfile();
|
|
176
|
+
const user = profile?.user;
|
|
177
|
+
const permissions = profile?.permissions ?? [];
|
|
179
178
|
```
|
|
180
179
|
|
|
181
|
-
业务页面用 `permissions`
|
|
180
|
+
业务页面用 `permissions` 控制按钮和功能入口。token 保存在 `localStorage`,刷新后仍可读取;用户和权限在共享包内存中,主应用刷新初始化后会重新写入。
|
|
182
181
|
|
|
183
182
|
### 子应用怎么知道自己挂在哪个路径?
|
|
184
183
|
|
|
@@ -227,7 +226,7 @@ window.dispatchEvent(
|
|
|
227
226
|
|
|
228
227
|
- 基础页面放 `src/pages/<module>/index.vue`。
|
|
229
228
|
- 演示页面放 `src/pages/demo/<module>/index.vue`。
|
|
230
|
-
-
|
|
229
|
+
- 保留 demo 时,当前订单示例是 `src/pages/demo/orders/index.vue`。
|
|
231
230
|
|
|
232
231
|
## 后续建设方向
|
|
233
232
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<span class="result-page__eyebrow">ACCESS CONTROL</span>
|
|
16
16
|
<h1>当前子应用页面暂未开放</h1>
|
|
17
17
|
<p class="result-page__desc">
|
|
18
|
-
|
|
18
|
+
登录状态正常,但当前角色还没有这个子应用页面或操作权限。请返回上一页,或联系管理员核对权限配置。
|
|
19
19
|
</p>
|
|
20
20
|
<div class="result-page__meta">
|
|
21
21
|
<span>状态码 403</span>
|
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
25
|
<div class="result-page__actions">
|
|
26
|
-
<el-button @click="router.back()">返回上一页</el-button>
|
|
27
|
-
<el-button type="primary" @click="router.push('/home')">返回工作台</el-button>
|
|
26
|
+
<el-button type="primary" @click="router.back()">返回上一页</el-button>
|
|
28
27
|
</div>
|
|
29
28
|
</div>
|
|
30
29
|
</div>
|
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
25
|
<div class="result-page__actions">
|
|
26
|
-
<el-button @click="router.back()">返回上一页</el-button>
|
|
27
|
-
<el-button type="primary" @click="router.push('/home')">返回工作台</el-button>
|
|
26
|
+
<el-button type="primary" @click="router.back()">返回上一页</el-button>
|
|
28
27
|
</div>
|
|
29
28
|
</div>
|
|
30
29
|
</div>
|
|
@@ -14,23 +14,97 @@ packages/
|
|
|
14
14
|
micro-contract/
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## 快速启动
|
|
18
|
+
|
|
19
|
+
进入生成后的项目根目录,先安装依赖:
|
|
18
20
|
|
|
19
21
|
```sh
|
|
20
22
|
pnpm install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
只运行主应用:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
21
28
|
pnpm dev:main
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
只运行子应用:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
22
34
|
pnpm dev:sub
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
默认会启动生成时的第一个子应用。多个子应用时,可以把子应用目录名或 package name 作为参数传进去:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
pnpm dev:sub order-app
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
主子应用本地联调时,需要开两个终端同时运行:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
pnpm dev:main
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
pnpm dev:sub
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
默认主应用使用 Vite 默认端口,通常是 `http://localhost:8081`。
|
|
54
|
+
默认子应用端口是 `http://localhost:8082`。保留 demo 时,主应用示例子应用入口也指向这个地址。
|
|
55
|
+
|
|
56
|
+
保留 demo 时,启动后先访问主应用地址,再从主应用菜单进入“微前端 / 订单管理”。如果子应用端口或部署地址不同,在主应用“系统 / 应用配置”里维护子应用入口地址。
|
|
57
|
+
|
|
58
|
+
如果生成项目时使用了 `--no-demo`,不会带默认子应用配置和 demo 菜单,需要在业务路由里添加自己的微前端菜单。
|
|
59
|
+
|
|
60
|
+
## 检查和构建
|
|
61
|
+
|
|
62
|
+
检查主应用:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
23
65
|
pnpm check:main
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
检查子应用:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
24
71
|
pnpm check:sub
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
检查指定子应用:
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
pnpm check:sub order-app
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
构建主应用:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
25
83
|
pnpm build:main
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
构建子应用:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
26
89
|
pnpm build:sub
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
构建指定子应用:
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
pnpm build:sub order-app
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
构建全部应用:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
27
101
|
pnpm build:all
|
|
28
102
|
```
|
|
29
103
|
|
|
30
104
|
`build:*` 会先执行 `check:*`,校验通过后再使用 `vite build --mode pro` 做正式环境构建。
|
|
31
105
|
|
|
32
106
|
`apps/main-app` 是微前端主应用,`apps/sub-app` 是默认子应用。后续新增业务子应用时,建议继续放在 `apps/` 下。
|
|
33
|
-
|
|
107
|
+
主应用不需要复制子应用启动命令,只需要在“系统 / 应用配置”维护对应子应用的路由前缀和入口地址。
|
|
34
108
|
|
|
35
109
|
初始化时可以自定义应用目录名:
|
|
36
110
|
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev:main": "pnpm --filter main-app dev",
|
|
8
|
-
"dev:sub": "
|
|
9
|
-
"dev:sub-app": "
|
|
8
|
+
"dev:sub": "node scripts/run-sub-app.mjs dev sub-app",
|
|
9
|
+
"dev:sub-app": "node scripts/run-sub-app.mjs dev sub-app",
|
|
10
10
|
"check:main": "pnpm --filter main-app check",
|
|
11
|
-
"check:sub": "
|
|
12
|
-
"check:sub-app": "
|
|
11
|
+
"check:sub": "node scripts/run-sub-app.mjs check sub-app",
|
|
12
|
+
"check:sub-app": "node scripts/run-sub-app.mjs check sub-app",
|
|
13
13
|
"build:main": "pnpm --filter main-app build",
|
|
14
|
-
"build:sub": "
|
|
15
|
-
"build:sub-app": "
|
|
14
|
+
"build:sub": "node scripts/run-sub-app.mjs build sub-app",
|
|
15
|
+
"build:sub-app": "node scripts/run-sub-app.mjs build sub-app",
|
|
16
16
|
"build:all": "pnpm build:main && pnpm build:sub"
|
|
17
17
|
},
|
|
18
18
|
"engines": {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
const [script, defaultApp, appArg] = process.argv.slice(2);
|
|
4
|
+
const app = appArg || defaultApp;
|
|
5
|
+
|
|
6
|
+
if (!script || !app) {
|
|
7
|
+
console.error('用法:node scripts/run-sub-app.mjs <dev|check|build> <默认子应用> [子应用目录或包名]');
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const child = spawn('pnpm', ['--filter', app, script], {
|
|
12
|
+
stdio: 'inherit',
|
|
13
|
+
shell: process.platform === 'win32',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
child.on('exit', (code, signal) => {
|
|
17
|
+
if (signal) {
|
|
18
|
+
process.kill(process.pid, signal);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
process.exit(code ?? 1);
|
|
23
|
+
});
|
package/package.json
CHANGED