111
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 1m8s

This commit is contained in:
2026-07-29 14:58:28 +08:00
parent eea19f2a5b
commit 5ba227eff5
5 changed files with 1027 additions and 0 deletions

View File

@@ -0,0 +1,213 @@
# 前端选型与代码组织
> 状态:推荐方案,尚未创建脚手架或安装依赖。
## 一、推荐结论
推荐使用 Vue 3 + TypeScript + Vite并采用 pnpm workspace 单仓库、三个独立应用:
```text
frontend/
├── apps/
│ ├── admin-web/ # 管理 Web
│ ├── admin-h5/ # 管理 H5
│ └── client-h5/ # 独立 C 端 H5
└── packages/
└── api/ # OpenAPI 生成类型和请求客户端
```
推荐组合:
| 领域 | 选择 | 理由 |
| --- | --- | --- |
| 框架 | Vue 3 + TypeScript | 管理后台和 H5 生态成熟,单文件组件适合页面型业务 |
| 构建 | Vite | 配置少,三应用可独立构建和发布 |
| 工作区 | pnpm workspace | 只解决多应用依赖和共享 API 包,不引入额外编排平台 |
| 管理 Web UI | Element Plus | 表格、表单、树、分页、弹窗等后台能力完整 |
| 管理 H5/C 端 UI | Vant | 移动表单、列表、弹层、支付结果页等 H5 交互成熟 |
| 路由 | Vue Router | 三个应用分别维护静态路由表 |
| 客户端状态 | Pinia | 只保存登录态、当前用户、菜单和按钮权限 |
| 接口类型 | openapi-typescript | 从现有 OpenAPI 生成 TypeScript 类型 |
| 请求客户端 | openapi-fetch + 原生 fetch | 类型直接复用,减少手写接口和额外封装 |
不在首期引入Nx、Turborepo、大型后台模板、服务端状态缓存库、自研组件库、微前端。
## 二、为什么建议三个应用
管理 Web 和管理 H5 使用同一套后端业务与权限,但它们是两个真实的交互界面:
- Web 以高密度表格、批量操作、复杂配置为主。
- 管理 H5 以资产查询、现场操作、审批、资金和通知为主。
- 两端登录分别传 `device=web``device=h5`,后端会返回不同菜单。
- Element Plus 和 Vant 的布局与交互模型不同,强行在一个应用混用会增加条件渲染和包体积。
- C 端 H5 使用完全不同的个人客户认证、微信 OAuth、支付回跳和资产绑定流程必须独立。
三个应用放在同一仓库,可以统一 TypeScript、Lint、构建和 OpenAPI 类型,又不会把三种页面体验绑在一个发布包中。
## 三、何时可以改成两个应用
如果管理 H5 最终只保留以下少量页面,可以使用“自适应管理端 + C 端 H5”两应用方案
- 登录和个人中心。
- 通知。
- 统一资产搜索和只读详情。
- 少量订单、退款、换货审批。
一旦管理 H5 需要独立导航、设备控制、企业授权、资金、上传或大量移动表单,就应使用独立 `admin-h5`。当前页面矩阵已经超过纯只读壳层,因此默认采用三个应用。
## 四、共享边界
首期只共享 `packages/api`
```text
Go 路由和 DTO
docs/admin-openapi.yaml
openapi-typescript
packages/api 类型与客户端
admin-web / admin-h5 / client-h5
```
暂不共享:
- 页面组件。
- 登录页面。
- 业务状态管理。
- Web/H5 表单组件。
- 业务流程组合函数。
等两个应用出现真实、稳定、完全相同的重复后再提取。不要先创建 `shared-business``shared-components` 等空泛包。
## 五、推荐仓库结构
```text
frontend/
├── apps/
│ ├── admin-web/
│ │ └── src/
│ │ ├── app/
│ │ ├── pages/
│ │ ├── routes/
│ │ └── stores/
│ ├── admin-h5/
│ │ └── src/
│ │ ├── app/
│ │ ├── pages/
│ │ ├── routes/
│ │ └── stores/
│ └── client-h5/
│ └── src/
│ ├── app/
│ ├── pages/
│ ├── routes/
│ └── stores/
├── packages/
│ └── api/
│ ├── generated/
│ ├── client.ts
│ └── index.ts
├── package.json
├── pnpm-workspace.yaml
└── tsconfig.base.json
```
目录保持扁平。页面内部先就近放置组件,不按 `api/service/model/controller` 重建一套前端分层。
## 六、请求模块的唯一职责
`packages/api` 只负责:
1. OpenAPI 生成类型。
2. Base URL。
3. Bearer Token 注入。
4. 管理端 Refresh Token 单次刷新队列。
5. `{code,msg,data,timestamp}` 解包。
6. 401、403、429、5xx 统一错误事件。
7. Request ID 提取。
它不负责:
- 页面跳转。
- Toast 文案拼接。
- 将订单状态改成本地状态机。
- 缓存所有列表和详情。
- 替页面吞掉错误。
这使请求模块保持为一个小接口、深实现的公共模块,三应用只需要学习同一套调用方式。
## 七、动态菜单实现
后端菜单只决定“哪些本地路由可见和可进入”,不能直接决定加载哪个源代码文件。
正确方式:
1. 每个管理应用维护静态路由表:`routeKey/url → 页面组件`
2. 登录后读取 `menus`
3. 将后端菜单与本地静态路由表求交集。
4. 生成导航和路由白名单。
5. 未匹配到本地页面时记录告警并隐藏,不允许字符串动态 import 任意组件。
6. 使用 `buttons` 控制操作按钮。
## 八、状态管理边界
Pinia 首期只保存:
- Access Token、Refresh Token。
- 当前用户。
- 菜单、按钮和权限码。
- 当前终端类型。
- 少量跨页面 UI 状态。
订单列表、资产详情、套餐列表等服务端数据由页面直接请求并局部刷新。等出现跨页面共享缓存、复杂失效和后台自动刷新需求后,再评估服务端状态库。
## 九、OpenAPI 工作流
建议建立固定流水线:
```text
go run ./cmd/gendocs
更新 docs/admin-openapi.yaml
生成 packages/api/generated
TypeScript 类型检查
构建三个应用
```
生成文件不手改。字段不正确时修改 Go DTO、RouteSpec 或文档生成器,然后重新生成。
当前 `admin-openapi.yaml` 同时包含管理端、C 端、开放接口和回调,文件名虽然偏旧,但首期无需为前端拆分多份 OpenAPI。
## 十、方案对比
| 方案 | 优点 | 风险 | 结论 |
| --- | --- | --- | --- |
| Vue 三应用单仓库 | Web/H5 体验清晰;共享接口类型;独立发布 | 有少量工程重复 | 推荐 |
| Vue 自适应管理端 + C端H5 | 少一个应用,首期代码更少 | Web/H5 组件混用、条件页面和包体增长 | 仅管理 H5 很小时使用 |
| React 三应用 | 也能满足需求,团队成熟时可选 | 当前没有团队技术偏好,初始约定和库选择更多 | 团队明显更熟 React 时替换推荐方案 |
如果实施团队已有稳定 React 经验,应优先服从团队能力,使用 React + TypeScript + Vite、Ant Design、Ant Design Mobile仍保持“三应用单仓库只共享 OpenAPI 包”的结构,不必为追求 Vue 统一而增加学习成本。
## 十一、创建脚手架前需要确认
1. 前端团队更熟 Vue 还是 React。
2. 管理 H5 是否作为独立正式产品发布。
3. 三个应用的域名、Base URL 和环境配置。
4. 微信公众号 OAuth 回调域名和支付回跳地址。
5. 首期页面范围及权限种子数据。
6. 后端企业数据范围和逐接口权限门禁是否完成验证。
## 十二、参考资料
- [Vue TypeScript](https://vuejs.org/guide/typescript/overview.html)
- [Pinia](https://pinia.vuejs.org/)
- [Element Plus](https://element-plus.org/)
- [Vant](https://vant-ui.github.io/vant/)
- [openapi-typescript](https://openapi-ts.dev/introduction)
- [openapi-fetch](https://openapi-ts.dev/openapi-fetch/)
- [pnpm Workspace](https://pnpm.io/workspaces)