docs: 添加前后端智能协作方案设计文档
This commit is contained in:
@@ -0,0 +1,483 @@
|
||||
# 前后端智能协作方案设计文档
|
||||
|
||||
> **文档版本**: v1.0
|
||||
> **创建日期**: 2026-06-06
|
||||
> **适用范围**: rui-frontend 所有前端项目(admin-ui、cashier-mobile、cashier-customer)
|
||||
|
||||
---
|
||||
|
||||
## 一、问题背景
|
||||
|
||||
### 1.1 当前痛点
|
||||
|
||||
admin-ui 项目中存在**表单字段与后台 API 不同步**的问题:
|
||||
|
||||
1. **表单字段硬编码** - 19 个 FormDialog 组件全部采用手动定义字段(如 `UserFormDialog.vue` 中的 `username`, `password`, `userType` 等)
|
||||
2. **缺少 TypeScript 类型** - Service 层大量使用 `any` 类型,没有与后端 DTO 对应的接口定义
|
||||
3. **BaseService 泛型未充分利用** - `BaseService<T>` 定义了泛型,但子类没有传入具体类型
|
||||
4. **API 文档未利用** - 后端已提供 `/v3/api-docs`(SpringDoc OpenAPI),但前端未使用
|
||||
|
||||
### 1.2 影响
|
||||
|
||||
- 后端字段变更时,前端需要手动修改所有相关表单
|
||||
- 缺少编译时类型检查,运行时容易出错
|
||||
- 前后端协作效率低,沟通成本高
|
||||
|
||||
---
|
||||
|
||||
## 二、设计目标
|
||||
|
||||
1. **类型安全** - 前端类型与后端 API 自动同步
|
||||
2. **减少样板代码** - 表单配置化,减少 70% 重复代码
|
||||
3. **支持自定义扩展** - 复杂表单可自定义字段和逻辑
|
||||
4. **多模块支持** - 适配微服务架构(聚合启动器 + 独立服务)
|
||||
5. **自动化检测** - 防止前后端不同步上线
|
||||
|
||||
---
|
||||
|
||||
## 三、架构设计
|
||||
|
||||
### 3.1 整体架构
|
||||
|
||||
```
|
||||
API设计规范.md (单一事实来源)
|
||||
│
|
||||
├── 包含所有 api-docs 地址
|
||||
└── 由后端团队维护
|
||||
|
||||
↓ 自动解析
|
||||
|
||||
scripts/parse-api-config.ts
|
||||
│
|
||||
└── 读取并解析 Markdown 中的 URL
|
||||
└── 识别聚合启动器和独立服务
|
||||
|
||||
↓ 动态配置
|
||||
|
||||
scripts/generate-api.ts
|
||||
│
|
||||
├── 聚合启动器模块(通过 group 参数获取)
|
||||
├── 独立服务模块(直接访问)
|
||||
├── 生成类型定义文件
|
||||
├── 生成统一导出
|
||||
└── 生成模块映射
|
||||
|
||||
↓ 类型文件
|
||||
|
||||
src/types/
|
||||
├── system-api.d.ts # 系统服务类型
|
||||
├── user-api.d.ts # 用户服务类型
|
||||
├── cashier-api.d.ts # 收银服务类型
|
||||
├── api.d.ts # 统一导出
|
||||
└── api-modules.json # 模块映射
|
||||
|
||||
↓ 类型驱动
|
||||
|
||||
前端代码
|
||||
│
|
||||
├── Service 层(类型安全)
|
||||
├── useApiForm() 组合式函数
|
||||
├── ApiFormDialog 组件
|
||||
└── 自定义扩展(插槽机制)
|
||||
```
|
||||
|
||||
### 3.2 核心组件
|
||||
|
||||
| 组件 | 职责 | 说明 |
|
||||
|------|------|------|
|
||||
| `parse-api-config.ts` | 解析 API 规范文档 | 从 Markdown 读取 api-docs 地址 |
|
||||
| `generate-api.ts` | 生成类型定义 | 调用 openapi-typescript 生成 .d.ts |
|
||||
| `useApiForm()` | 类型驱动表单管理 | 组合式函数,自动生成表单配置 |
|
||||
| `ApiFormDialog` | 配置化表单组件 | 根据字段配置自动渲染表单 |
|
||||
| `BaseService<T>` | 类型安全 Service 基类 | 保留现有架构,增强类型 |
|
||||
|
||||
---
|
||||
|
||||
## 四、详细设计
|
||||
|
||||
### 4.1 API 配置解析(parse-api-config.ts)
|
||||
|
||||
**核心功能**:从 `API设计规范.md` 自动读取 api-docs 地址
|
||||
|
||||
**聚合启动器识别规则**:
|
||||
- 端口 9399 → 聚合启动器,包含多个模块(/user, /system)
|
||||
- 通过 `?group=xxx` 参数获取子模块
|
||||
- 其他端口 → 独立服务
|
||||
|
||||
**配置结构**:
|
||||
|
||||
```typescript
|
||||
interface ApiModuleConfig {
|
||||
name: string // 模块名:user, system, cashier
|
||||
url: string // api-docs URL
|
||||
output: string // 输出路径
|
||||
prefix: string // API 路径前缀
|
||||
description: string // 模块描述
|
||||
aggregator?: string // 所属聚合器(如果有)
|
||||
}
|
||||
```
|
||||
|
||||
**解析示例**:
|
||||
|
||||
```markdown
|
||||
# API设计规范.md
|
||||
|
||||
http://localhost:9399/v3/api-docs # 聚合启动器 API 文档(开发调试)
|
||||
http://localhost:9601/v3/api-docs # 收银服务 API 文档
|
||||
```
|
||||
|
||||
解析结果:
|
||||
- `9399` → 聚合启动器 → 展开为 user、system 两个模块
|
||||
- `9601` → 独立服务 → cashier 模块
|
||||
|
||||
### 4.2 类型生成(generate-api.ts)
|
||||
|
||||
**生成流程**:
|
||||
|
||||
1. 读取 API设计规范.md
|
||||
2. 识别聚合启动器和独立服务
|
||||
3. 为每个模块生成类型文件
|
||||
4. 生成统一导出文件
|
||||
5. 生成模块映射文件
|
||||
|
||||
**输出文件结构**:
|
||||
|
||||
```
|
||||
src/types/
|
||||
├── user-api.d.ts # 用户服务类型(来自聚合启动器)
|
||||
├── system-api.d.ts # 系统服务类型(来自聚合启动器)
|
||||
├── cashier-api.d.ts # 收银服务类型(独立服务)
|
||||
├── api.d.ts # 统一导出
|
||||
│ └── export type * from './user-api'
|
||||
│ └── export type * from './system-api'
|
||||
│ └── export type * from './cashier-api'
|
||||
└── api-modules.json # 模块映射(用于运行时)
|
||||
└── {
|
||||
└── "aggregator": [
|
||||
└── { "name": "user", "prefix": "/user" },
|
||||
└── { "name": "system", "prefix": "/system" }
|
||||
└── ]
|
||||
└── }
|
||||
```
|
||||
|
||||
### 4.3 类型驱动表单(useApiForm)
|
||||
|
||||
**核心功能**:根据 TypeScript 类型自动生成表单配置
|
||||
|
||||
**接口设计**:
|
||||
|
||||
```typescript
|
||||
interface FieldConfig<T = any> {
|
||||
key: keyof T // 字段名(类型安全)
|
||||
label: string // 字段标签
|
||||
type: FieldType // 字段类型
|
||||
required?: boolean // 是否必填
|
||||
rules?: any[] // 验证规则
|
||||
options?: Option[] // 选项(select/radio/checkbox)
|
||||
disabled?: boolean | ((form: T) => boolean)
|
||||
placeholder?: string
|
||||
props?: Record<string, any>
|
||||
}
|
||||
|
||||
interface FormConfig<T> {
|
||||
initial?: Partial<T> // 初始值
|
||||
fields: FieldConfig<T>[] // 字段配置
|
||||
onSubmit: (data: T) => Promise<void>
|
||||
beforeSubmit?: (data: T) => boolean | string
|
||||
}
|
||||
|
||||
function useApiForm<T extends Record<string, any>>(
|
||||
config: FormConfig<T>
|
||||
): {
|
||||
formRef: Ref<FormInstance>
|
||||
form: Ref<Partial<T>>
|
||||
rules: ComputedRef<Record<string, any[]>>
|
||||
loading: Ref<boolean>
|
||||
handleSubmit: () => Promise<boolean>
|
||||
resetForm: (initial?: Partial<T>) => void
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 配置化表单组件(ApiFormDialog)
|
||||
|
||||
**核心功能**:根据字段配置自动渲染表单元素
|
||||
|
||||
**支持的字段类型**:
|
||||
|
||||
| 类型 | 组件 | 说明 |
|
||||
|------|------|------|
|
||||
| `input` | ElInput | 文本输入 |
|
||||
| `textarea` | ElInput(type="textarea") | 多行文本 |
|
||||
| `select` | ElSelect | 下拉选择 |
|
||||
| `radio` | ElRadioGroup | 单选按钮 |
|
||||
| `checkbox` | ElCheckboxGroup | 多选框 |
|
||||
| `number` | ElInputNumber | 数字输入 |
|
||||
| `tree-select` | ElTreeSelect | 树形选择 |
|
||||
| `date` | ElDatePicker | 日期选择 |
|
||||
| `datetime` | ElDatePicker | 日期时间选择 |
|
||||
|
||||
**自定义扩展机制**:
|
||||
|
||||
```vue
|
||||
<ApiFormDialog
|
||||
v-model:visible="visible"
|
||||
v-model:form="form"
|
||||
:fields="fields"
|
||||
:rules="rules"
|
||||
@submit="handleSubmit"
|
||||
>
|
||||
<!-- 自定义字段插槽 -->
|
||||
<template #custom-fields="{ form }">
|
||||
<el-form-item label="自定义部门">
|
||||
<el-tree-select v-model="customDeptIds" :data="deptTree" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</ApiFormDialog>
|
||||
```
|
||||
|
||||
### 4.5 Service 层增强
|
||||
|
||||
**保留现有 BaseService 架构**,增强类型安全:
|
||||
|
||||
```typescript
|
||||
// 现有架构保持不变
|
||||
class BaseService<T = any> {
|
||||
protected baseUrl: string
|
||||
|
||||
async page(params: PageParams & Record<string, any>): Promise<PageResult<T>>
|
||||
async list(params?: Record<string, any>): Promise<T[]>
|
||||
async getById(id: number | string): Promise<T>
|
||||
async add(data: Partial<T>): Promise<T>
|
||||
async update(data: Partial<T> & { id: number | string }): Promise<boolean>
|
||||
async remove(id: number | string): Promise<boolean>
|
||||
}
|
||||
|
||||
// 使用时传入具体类型
|
||||
class UserService extends BaseService<UserDTO> {
|
||||
constructor() {
|
||||
super('/user/admin/user')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、使用示例
|
||||
|
||||
### 5.1 标准表单(完全配置化)
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { useApiForm } from '@/composables/useApiForm'
|
||||
import { userService } from '@/service/user/userService'
|
||||
import type { components } from '@/types/user-api'
|
||||
|
||||
type UserDTO = components['schemas']['UserDTO']
|
||||
|
||||
const { formRef, form, rules, loading, handleSubmit, resetForm } = useApiForm<UserDTO>({
|
||||
initial: { userType: 1, status: 1 },
|
||||
fields: [
|
||||
{ key: 'username', label: '用户名', type: 'input', required: true },
|
||||
{ key: 'password', label: '密码', type: 'input', required: true, props: { type: 'password' } },
|
||||
{ key: 'userType', label: '用户类型', type: 'select', options: [
|
||||
{ label: '普通用户', value: 1 },
|
||||
{ label: '管理员', value: 2 }
|
||||
]},
|
||||
{ key: 'status', label: '状态', type: 'radio', options: [
|
||||
{ label: '启用', value: 1 },
|
||||
{ label: '禁用', value: 0 }
|
||||
]}
|
||||
],
|
||||
onSubmit: async (data) => {
|
||||
await userService.add(data)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ApiFormDialog
|
||||
v-model:visible="visible"
|
||||
v-model:form="form"
|
||||
title="新增用户"
|
||||
:fields="fields"
|
||||
:rules="rules"
|
||||
:loading="loading"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 5.2 自定义扩展表单
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useApiForm } from '@/composables/useApiForm'
|
||||
import { roleService } from '@/service/system/roleService'
|
||||
import type { components } from '@/types/system-api'
|
||||
|
||||
type RoleDTO = components['schemas']['RoleDTO']
|
||||
|
||||
const selectedDeptIds = ref<number[]>([])
|
||||
const showCustomDept = computed(() => form.value.dataScope === 5)
|
||||
|
||||
const { form, fields, rules, handleSubmit } = useApiForm<RoleDTO>({
|
||||
fields: [
|
||||
{ key: 'roleCode', label: '角色编码', type: 'input', required: true },
|
||||
{ key: 'roleName', label: '角色名称', type: 'input', required: true },
|
||||
{ key: 'dataScope', label: '数据范围', type: 'select', options: [
|
||||
{ label: '全部', value: 1 },
|
||||
{ label: '自定义', value: 5 }
|
||||
]}
|
||||
],
|
||||
onSubmit: async (data) => {
|
||||
// 自定义验证
|
||||
if (data.dataScope === 5 && selectedDeptIds.value.length === 0) {
|
||||
throw new Error('请选择部门')
|
||||
}
|
||||
await roleService.add(data)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ApiFormDialog
|
||||
v-model:visible="visible"
|
||||
v-model:form="form"
|
||||
:fields="fields"
|
||||
:rules="rules"
|
||||
@submit="handleSubmit"
|
||||
>
|
||||
<template #custom-fields>
|
||||
<el-form-item v-if="showCustomDept" label="选择部门" required>
|
||||
<el-tree-select v-model="selectedDeptIds" :data="deptTree" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</ApiFormDialog>
|
||||
</template>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、自动化机制
|
||||
|
||||
### 6.1 Git Hook(提交前检查)
|
||||
|
||||
```typescript
|
||||
// scripts/check-api-changes.ts
|
||||
function checkApiChanges() {
|
||||
const currentHash = execSync('git hash-object src/types/*.d.ts').toString()
|
||||
|
||||
try {
|
||||
execSync('pnpm api:generate', { stdio: 'pipe' })
|
||||
const newHash = execSync('git hash-object src/types/*.d.ts').toString()
|
||||
|
||||
if (currentHash !== newHash) {
|
||||
console.error('❌ API types have changed!')
|
||||
console.error('Please run "pnpm api:generate" and commit the changes.')
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Could not check API changes')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 脚本命令
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"api:generate": "tsx scripts/generate-api.ts",
|
||||
"api:check": "tsx scripts/check-api-changes.ts",
|
||||
"api:watch": "nodemon --watch ../docs/standards/API设计规范.md --exec 'pnpm api:generate'"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、扩展性设计
|
||||
|
||||
### 7.1 新增模块
|
||||
|
||||
当后端新增模块时,只需在 `API设计规范.md` 中添加:
|
||||
|
||||
```markdown
|
||||
http://localhost:9399/v3/api-docs?group=订单服务 # 订单服务 API 文档
|
||||
http://localhost:9701/v3/api-docs # 新独立服务 API 文档
|
||||
```
|
||||
|
||||
前端自动识别:
|
||||
- 端口 9399 → 聚合启动器,通过 `group` 参数获取
|
||||
- 新端口 9701 → 独立服务,直接访问
|
||||
|
||||
### 7.2 新增字段类型
|
||||
|
||||
在 `ApiFormDialog.vue` 中添加新的字段类型渲染逻辑即可。
|
||||
|
||||
---
|
||||
|
||||
## 八、实施计划
|
||||
|
||||
### Phase 1: 基础搭建(1-2 天)
|
||||
|
||||
1. 安装依赖:`openapi-typescript`, `tsx`, `nodemon`
|
||||
2. 创建 `scripts/parse-api-config.ts` - 解析 API 规范
|
||||
3. 创建 `scripts/generate-api.ts` - 生成类型
|
||||
4. 配置 package.json 脚本
|
||||
|
||||
### Phase 2: 类型生成(1 天)
|
||||
|
||||
1. 运行 `pnpm api:generate` 生成初始类型
|
||||
2. 验证类型正确性
|
||||
3. 提交生成的类型文件
|
||||
|
||||
### Phase 3: 表单工具(2-3 天)
|
||||
|
||||
1. 创建 `useApiForm()` 组合式函数
|
||||
2. 创建 `ApiFormDialog.vue` 组件
|
||||
3. 编写单元测试
|
||||
|
||||
### Phase 4: 试点迁移(2-3 天)
|
||||
|
||||
1. 选择 2-3 个简单表单进行迁移
|
||||
2. 验证方案可行性
|
||||
3. 收集反馈优化
|
||||
|
||||
### Phase 5: 全面推广(1-2 周)
|
||||
|
||||
1. 迁移所有标准表单
|
||||
2. 保留自定义表单的特殊逻辑
|
||||
3. 编写迁移文档
|
||||
|
||||
### Phase 6: 自动化(1 天)
|
||||
|
||||
1. 配置 Git Hook
|
||||
2. 配置 CI/CD 检查
|
||||
3. 编写使用文档
|
||||
|
||||
---
|
||||
|
||||
## 九、风险评估
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|---------|
|
||||
| 后端 API 文档不准确 | 生成的类型错误 | 建立 API 文档审核机制 |
|
||||
| 聚合启动器不可用 | 无法生成类型 | 支持独立服务直接访问 |
|
||||
| 复杂表单迁移困难 | 影响进度 | 保留自定义扩展机制 |
|
||||
| 团队成员学习成本 | 初期效率下降 | 提供详细文档和示例 |
|
||||
|
||||
---
|
||||
|
||||
## 十、成功标准
|
||||
|
||||
1. ✅ 所有 Service 层使用具体类型替代 `any`
|
||||
2. ✅ 标准表单代码量减少 70%
|
||||
3. ✅ 后端字段变更时,前端编译期即可发现
|
||||
4. ✅ 新增模块时,类型自动生成
|
||||
5. ✅ 提交时自动检测 API 变更
|
||||
|
||||
---
|
||||
|
||||
> **下一步**: 编写实现计划(implementation plan)
|
||||
Reference in New Issue
Block a user