# 前后端智能协作方案实现计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers-subagent-driven-development (recommended) or superpowers-executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 建立前后端智能协作机制,通过 OpenAPI 自动生成 TypeScript 类型,实现类型安全的表单开发,减少前后端字段不一致问题。 **Architecture:** 保留现有 BaseService + Service 层架构,引入 openapi-typescript 生成类型,构建 useApiForm() 组合式函数和 ApiFormDialog 组件实现配置化表单,支持聚合启动器多模块架构。 **Tech Stack:** Vue 3 + TypeScript + Vite + openapi-typescript + Element Plus --- ## 文件结构 ### 新增文件 | 文件 | 职责 | |------|------| | `scripts/parse-api-config.ts` | 解析 API设计规范.md,提取 api-docs 地址 | | `scripts/generate-api.ts` | 调用 openapi-typescript 生成类型定义 | | `scripts/check-api-changes.ts` | Git Hook 脚本,提交前检查 API 变更 | | `src/composables/useApiForm.ts` | 类型驱动的表单管理组合式函数 | | `src/components/ApiFormDialog.vue` | 配置化表单对话框组件 | | `src/types/api.d.ts` | 统一类型导出(自动生成) | | `src/types/api-modules.json` | 模块映射配置(自动生成) | ### 修改文件 | 文件 | 修改内容 | |------|---------| | `package.json` | 添加 api:generate、api:check、api:watch 脚本 | | `src/service/BaseService.ts` | 增强类型安全,完善泛型使用 | | `.env` / `.env.development` | 添加 API_GENERATE_URL 配置 | --- ## Task 1: 安装依赖 **Files:** - Modify: `admin-ui/package.json` - [ ] **Step 1: 安装类型生成依赖** ```bash cd /Users/zhangsheng/rui/rui-frontend/admin-ui pnpm add -D openapi-typescript tsx nodemon cross-env ``` - [ ] **Step 2: 验证安装** ```bash pnpm list openapi-typescript tsx nodemon cross-env ``` Expected: 显示已安装版本 - [ ] **Step 3: Commit** ```bash git add package.json pnpm-lock.yaml git commit -m "chore: 安装 openapi-typescript 类型生成依赖" ``` --- ## Task 2: 创建 API 配置解析脚本 **Files:** - Create: `admin-ui/scripts/parse-api-config.ts` - [ ] **Step 1: 创建解析脚本** ```typescript import fs from 'fs' import path from 'path' /** * API 模块配置 */ export interface ApiModuleConfig { /** 模块名称 */ name: string /** OpenAPI JSON URL */ url: string /** 类型输出路径 */ output: string /** API 路径前缀 */ prefix: string /** 模块描述 */ description: string /** 所属聚合器(如果有) */ aggregator?: string } /** * 聚合器配置 */ export interface AggregatorConfig { /** 聚合器名称 */ name: string /** 基础 URL */ baseUrl: string /** 包含的模块 */ modules: Array<{ name: string prefix: string description: string }> } /** * 从 API设计规范.md 解析配置 * * 聚合启动器结构: * http://localhost:9399/v3/api-docs # 聚合启动器(包含 /user, /system) * http://localhost:9601/v3/api-docs # 收银服务(独立) * * 通过 group 参数获取子模块: * http://localhost:9399/v3/api-docs?group=用户服务 * http://localhost:9399/v3/api-docs?group=系统服务 */ export function parseApiConfigFromMarkdown(filePath: string): { aggregators: AggregatorConfig[] standalone: ApiModuleConfig[] } { const content = fs.readFileSync(filePath, 'utf-8') // 查找 OpenAPI JSON 接口 部分 const openapiSection = content.match(/####\s+OpenAPI\s+JSON\s+接口\s*\n([\s\S]*?)(?=\n#{1,4}\s|\n---\s*$|$)/) if (!openapiSection) { throw new Error('未在 API设计规范.md 中找到 OpenAPI JSON 接口部分') } const lines = openapiSection[1].trim().split('\n') const aggregators: Map = new Map() const standalone: ApiModuleConfig[] = [] for (const line of lines) { const match = line.match(/(http:\/\/[^\s]+)\s+#\s+(.+)/) if (!match) continue const url = match[1] const description = match[2].trim() // 解析 URL 结构 const urlInfo = parseUrl(url, description) if (urlInfo.isAggregator) { // 聚合启动器 - 包含多个模块 const aggregator: AggregatorConfig = { name: urlInfo.name, baseUrl: url, modules: urlInfo.modules.map(m => ({ name: m.name, prefix: m.prefix, description: m.description })) } aggregators.set(urlInfo.name, aggregator) } else { // 独立服务 standalone.push({ name: urlInfo.name, url, output: `src/types/${urlInfo.name}-api.d.ts`, prefix: urlInfo.prefix, description }) } } return { aggregators: Array.from(aggregators.values()), standalone } } /** * 解析 URL 信息 */ function parseUrl(url: string, description: string): { name: string isAggregator: boolean prefix: string modules: Array<{ name: string; prefix: string; description: string }> } { const portMatch = url.match(/:(\d+)/) const port = portMatch ? portMatch[1] : '' // 聚合启动器识别规则 const aggregatorPorts = ['9399'] const isAggregator = aggregatorPorts.includes(port) // 端口映射 const portMapping: Record }> = { '9300': { name: 'gateway', prefix: '', isAggregator: false }, '9301': { name: 'auth', prefix: '/auth', isAggregator: false }, '9399': { name: 'aggregator', prefix: '', isAggregator: true, modules: [ { name: 'user', prefix: '/user', description: '用户服务 API' }, { name: 'system', prefix: '/system', description: '系统服务 API' } ] }, '9601': { name: 'cashier', prefix: '/cashier', isAggregator: false } } const mapping = portMapping[port] if (!mapping) { console.warn(`⚠️ 未知端口 ${port},作为独立服务处理`) return { name: `service-${port}`, isAggregator: false, prefix: '', modules: [] } } return { name: mapping.name, isAggregator: mapping.isAggregator, prefix: mapping.prefix, modules: mapping.modules || [] } } /** * 生成完整的模块配置列表(展开聚合器) */ export function expandModules(config: { aggregators: AggregatorConfig[] standalone: ApiModuleConfig[] }): ApiModuleConfig[] { const modules: ApiModuleConfig[] = [...config.standalone] // 展开聚合器中的模块 for (const aggregator of config.aggregators) { for (const module of aggregator.modules) { modules.push({ name: module.name, url: `${aggregator.baseUrl}?group=${encodeURIComponent(module.description)}`, output: `src/types/${module.name}-api.d.ts`, prefix: module.prefix, description: module.description, aggregator: aggregator.name }) } } return modules } /** * 默认配置(当无法解析规范文件时使用) */ export const defaultApiConfig = { aggregators: [ { name: 'aggregator', baseUrl: 'http://localhost:9399/v3/api-docs', modules: [ { name: 'user', prefix: '/user', description: '用户服务 API' }, { name: 'system', prefix: '/system', description: '系统服务 API' } ] } ], standalone: [ { name: 'cashier', url: 'http://localhost:9601/v3/api-docs', output: 'src/types/cashier-api.d.ts', prefix: '/cashier', description: '收银服务 API 文档' } ] } /** * 获取 API 模块配置 */ export function getApiModules(specPath?: string): ApiModuleConfig[] { const mdPath = specPath || path.resolve(process.cwd(), '../docs/standards/API设计规范.md') try { if (fs.existsSync(mdPath)) { console.log('📖 从 API设计规范.md 读取配置...') const config = parseApiConfigFromMarkdown(mdPath) const modules = expandModules(config) if (modules.length > 0) { console.log(`✅ 成功解析 ${modules.length} 个模块:`) // 按聚合器分组显示 const aggregatorModules = modules.filter(m => m.aggregator) const standaloneModules = modules.filter(m => !m.aggregator) if (aggregatorModules.length > 0) { console.log(' 📦 聚合启动器:') aggregatorModules.forEach(m => console.log(` - ${m.name}: ${m.url}`) ) } if (standaloneModules.length > 0) { console.log(' 🔗 独立服务:') standaloneModules.forEach(m => console.log(` - ${m.name}: ${m.url}`) ) } return modules } } } catch (error) { console.warn('⚠️ 解析 API设计规范.md 失败:', error) } console.log('📋 使用默认配置') return expandModules(defaultApiConfig) } ``` - [ ] **Step 2: 验证解析脚本** ```bash cd /Users/zhangsheng/rui/rui-frontend/admin-ui npx tsx scripts/parse-api-config.ts ``` Expected: 显示解析的模块列表 - [ ] **Step 3: Commit** ```bash git add scripts/parse-api-config.ts git commit -m "feat: 添加 API 配置解析脚本" ``` --- ## Task 3: 创建类型生成脚本 **Files:** - Create: `admin-ui/scripts/generate-api.ts` - [ ] **Step 1: 创建生成脚本** ```typescript import { execSync } from 'child_process' import fs from 'fs' import path from 'path' import { getApiModules, type ApiModuleConfig } from './parse-api-config' /** * 生成单个模块的类型定义 */ async function generateModuleTypes(module: ApiModuleConfig): Promise { console.log(`\n🔄 生成 ${module.name} 模块类型...`) if (module.aggregator) { console.log(` 📦 来自聚合器: ${module.aggregator}`) } try { // 确保输出目录存在 const outputDir = path.dirname(module.output) if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }) } // 使用 openapi-typescript 生成类型 execSync( `npx openapi-typescript "${module.url}" -o ${module.output}`, { stdio: 'inherit' } ) // 添加模块元数据注释 const content = fs.readFileSync(module.output, 'utf-8') const header = `/** * ${module.description} * * @module ${module.name} * @prefix ${module.prefix || '/'} * @source ${module.url} * ${module.aggregator ? `@aggregator ${module.aggregator}` : '@standalone'} * @generated ${new Date().toISOString()} * * ⚠️ 此文件由脚本自动生成,请勿手动修改 * 如需更新,请运行: pnpm api:generate */ ` fs.writeFileSync(module.output, header + content) console.log(`✅ ${module.name} 生成成功 -> ${module.output}`) return true } catch (error) { console.error(`❌ ${module.name} 生成失败:`, error) return false } } /** * 生成统一导出文件 */ function generateIndex(modules: ApiModuleConfig[]) { const indexPath = 'src/types/api.d.ts' const imports = modules.map(m => `// ${m.description}\nexport type * from './${m.name}-api'` ).join('\n\n') const content = `/** * API 类型统一导出 * * ⚠️ 此文件由脚本自动生成,请勿手动修改 */ ${imports} ` fs.writeFileSync(indexPath, content) console.log(`\n📦 统一导出文件已生成: ${indexPath}`) } /** * 生成聚合器映射文件 */ function generateAggregatorMap(modules: ApiModuleConfig[]) { const mapPath = 'src/types/aggregator-modules.json' const aggregators = modules .filter(m => m.aggregator) .reduce((acc, m) => { if (!acc[m.aggregator!]) { acc[m.aggregator!] = [] } acc[m.aggregator!].push({ name: m.name, prefix: m.prefix, description: m.description }) return acc }, {} as Record) fs.writeFileSync(mapPath, JSON.stringify(aggregators, null, 2)) console.log(`🗺️ 聚合器映射文件已生成: ${mapPath}`) } /** * 主函数 */ async function main() { console.log('🚀 开始生成 API 类型定义...\n') // 从 API设计规范.md 读取配置 const modules = getApiModules() if (modules.length === 0) { console.error('❌ 未找到任何 API 模块配置') process.exit(1) } // 并行生成所有模块 const results = await Promise.all( modules.map(m => generateModuleTypes(m)) ) const successCount = results.filter(Boolean).length if (successCount === 0) { console.error('\n❌ 所有模块生成失败') process.exit(1) } // 生成辅助文件 generateIndex(modules) generateAggregatorMap(modules) console.log(`\n✨ 完成! ${successCount}/${modules.length} 个模块生成成功`) if (successCount < modules.length) { console.warn('⚠️ 部分模块生成失败,请检查服务是否启动') } } // 执行 main().catch(error => { console.error('💥 生成过程出错:', error) process.exit(1) }) ``` - [ ] **Step 2: 配置 package.json 脚本** 修改 `admin-ui/package.json`: ```json { "scripts": { "dev": "vite --port 3000", "build": "vite build", "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'" } } ``` - [ ] **Step 3: 测试生成** ```bash pnpm api:generate ``` Expected: 生成 src/types/user-api.d.ts, src/types/system-api.d.ts, src/types/cashier-api.d.ts - [ ] **Step 4: Commit** ```bash git add scripts/generate-api.ts package.json git commit -m "feat: 添加 API 类型生成脚本" ``` --- ## Task 4: 创建 API 变更检查脚本 **Files:** - Create: `admin-ui/scripts/check-api-changes.ts` - [ ] **Step 1: 创建检查脚本** ```typescript import { execSync } from 'child_process' import fs from 'fs' function checkApiChanges() { console.log('🔍 检查 API 变更...') // 获取当前分支的 API 类型文件 hash const currentHash = execSync('git hash-object src/types/*.d.ts 2>/dev/null || echo ""').toString() if (!currentHash.trim()) { console.log('⚠️ 未找到现有类型文件,跳过检查') return } // 尝试重新生成 API 类型 try { execSync('pnpm api:generate', { stdio: 'pipe' }) // 比较生成后的 hash const newHash = execSync('git hash-object src/types/*.d.ts').toString() if (currentHash !== newHash) { console.error('❌ API 类型已变更!') console.error('') console.error('请运行以下命令更新类型:') console.error(' pnpm api:generate') console.error('') console.error('然后提交变更:') console.error(' git add src/types/') console.error(' git commit -m "chore: 更新 API 类型"') console.error('') process.exit(1) } console.log('✅ API 类型已是最新') } catch (error) { console.warn('⚠️ 无法检查 API 变更:', error) // 不阻断提交,仅警告 } } checkApiChanges() ``` - [ ] **Step 2: 配置 Git Hook(可选)** 在项目根目录创建 `.husky/pre-commit`: ```bash #!/bin/sh . "$(dirname "$0")/_/husky.sh" cd admin-ui pnpm api:check ``` - [ ] **Step 3: Commit** ```bash git add scripts/check-api-changes.ts git commit -m "feat: 添加 API 变更检查脚本" ``` --- ## Task 5: 创建 useApiForm 组合式函数 **Files:** - Create: `admin-ui/src/composables/useApiForm.ts` - [ ] **Step 1: 创建组合式函数** ```typescript import { ref, computed } from 'vue' import type { Ref, ComputedRef } from 'vue' /** * 字段类型 */ export type FieldType = | 'input' | 'textarea' | 'select' | 'radio' | 'checkbox' | 'number' | 'tree-select' | 'date' | 'datetime' /** * 选项配置 */ export interface Option { label: string value: any } /** * 字段配置接口 */ export interface FieldConfig { /** 字段名 */ key: keyof T /** 字段标签 */ label: string /** 字段类型 */ type: FieldType /** 是否必填 */ required?: boolean /** 验证规则 */ rules?: any[] /** 选项(用于 select/radio/checkbox) */ options?: Option[] /** 是否禁用 */ disabled?: boolean | ((form: T) => boolean) /** 占位符 */ placeholder?: string /** 额外属性 */ props?: Record } /** * 表单配置接口 */ export interface FormConfig> { /** 初始数据 */ initial?: Partial /** 字段配置列表 */ fields: FieldConfig[] /** 提交回调 */ onSubmit: (data: T) => Promise /** 提交前验证 */ beforeSubmit?: (data: T) => boolean | string } /** * 类型驱动的表单组合式函数 * * @example * const { formRef, form, rules, loading, handleSubmit, resetForm } = useApiForm({ * fields: [ * { key: 'username', label: '用户名', type: 'input', required: true }, * { key: 'status', label: '状态', type: 'radio', options: [...] } * ], * onSubmit: async (data) => { * await userService.add(data) * } * }) */ export function useApiForm>(config: FormConfig) { const formRef = ref() const loading = ref(false) // 生成响应式表单数据 const form = ref>({ ...config.initial } as Partial) // 自动生成验证规则 const rules = computed(() => { const result: Record = {} for (const field of config.fields) { if (field.required) { result[field.key as string] = [ { required: true, message: `请输入${field.label}`, trigger: 'blur' }, ...(field.rules || []) ] } else if (field.rules) { result[field.key as string] = field.rules } } return result }) // 字段配置列表(响应式) const fields = computed(() => config.fields) /** * 重置表单 */ function resetForm(initial?: Partial) { form.value = { ...(initial || config.initial) } as Partial formRef.value?.resetFields() } /** * 提交表单 */ async function handleSubmit(): Promise { const valid = await formRef.value?.validate().catch(() => false) if (!valid) return false // 自定义验证 if (config.beforeSubmit) { const result = config.beforeSubmit(form.value as T) if (result === false) return false if (typeof result === 'string') { console.error(result) return false } } loading.value = true try { await config.onSubmit(form.value as T) return true } catch (error) { return false } finally { loading.value = false } } return { formRef, form, rules, fields, loading, handleSubmit, resetForm } } export default useApiForm ``` - [ ] **Step 2: Commit** ```bash git add src/composables/useApiForm.ts git commit -m "feat: 添加 useApiForm 类型驱动表单组合式函数" ``` --- ## Task 6: 创建 ApiFormDialog 组件 **Files:** - Create: `admin-ui/src/components/ApiFormDialog.vue` - [ ] **Step 1: 创建组件** ```vue ``` - [ ] **Step 2: Commit** ```bash git add src/components/ApiFormDialog.vue git commit -m "feat: 添加 ApiFormDialog 配置化表单组件" ``` --- ## Task 7: 增强 BaseService 类型安全 **Files:** - Modify: `admin-ui/src/service/BaseService.ts` - [ ] **Step 1: 增强类型定义** 在现有 BaseService 基础上,添加更严格的类型约束: ```typescript // 在 BaseService.ts 中添加 /** * 类型安全的 Service 工厂函数 * 用于新模块,完全类型安全 */ export function createTypedService(basePath: string) { return { async getById(id: number | string): Promise { const res: any = await request({ url: `${basePath}/${id}`, method: 'get', }) return res.data }, async add(data: Omit): Promise { const res: any = await request({ url: basePath, method: 'post', data, }) return res.data }, async update(data: T): Promise { const res: any = await request({ url: basePath, method: 'put', data, }) return res.data === true }, async remove(id: number | string): Promise { const res: any = await request({ url: `${basePath}/${id}`, method: 'delete', }) return res.data === true } } } ``` - [ ] **Step 2: Commit** ```bash git add src/service/BaseService.ts git commit -m "feat: 增强 BaseService 类型安全" ``` --- ## Task 8: 试点迁移 - UserFormDialog **Files:** - Modify: `admin-ui/src/views/user/info/UserFormDialog.vue` - [ ] **Step 1: 重写 UserFormDialog** ```vue ``` - [ ] **Step 2: 测试表单功能** 1. 打开用户管理页面 2. 点击"新增用户" 3. 验证表单字段是否正确显示 4. 提交表单,验证数据是否正确 - [ ] **Step 3: Commit** ```bash git add src/views/user/info/UserFormDialog.vue git commit -m "refactor: 使用类型驱动表单重写 UserFormDialog" ``` --- ## Task 9: 文档和配置 **Files:** - Create: `admin-ui/docs/api-collaboration.md` - [ ] **Step 1: 创建使用文档** ```markdown # 前后端智能协作使用指南 ## 快速开始 ### 1. 生成 API 类型 ```bash # 生成所有模块类型 pnpm api:generate # 监视 API 规范变更(开发时) pnpm api:watch ``` ### 2. 在 Service 中使用类型 ```typescript import type { components } from '@/types/user-api' type UserDTO = components['schemas']['UserDTO'] class UserService extends BaseService { constructor() { super('/user/admin/user') } } ``` ### 3. 创建类型驱动表单 ```typescript import { useApiForm } from '@/composables/useApiForm' const { form, fields, rules, handleSubmit } = useApiForm({ fields: [ { key: 'username', label: '用户名', type: 'input', required: true }, { key: 'status', label: '状态', type: 'radio', options: [...] } ], onSubmit: async (data) => { await userService.add(data) } }) ``` ## 自定义扩展 ### 添加自定义字段 ```vue ``` ## 添加新模块 1. 在 `API设计规范.md` 中添加 api-docs 地址 2. 运行 `pnpm api:generate` 3. 类型文件自动生成 ## 故障排除 ### 类型生成失败 - 检查后端服务是否启动 - 检查 `API设计规范.md` 中的 URL 是否正确 ### 类型不匹配 - 运行 `pnpm api:generate` 重新生成 - 检查后端 API 是否变更 ``` - [ ] **Step 2: Commit** ```bash git add docs/api-collaboration.md git commit -m "docs: 添加前后端智能协作使用指南" ``` --- ## 实施检查清单 - [ ] Task 1: 安装依赖 - [ ] Task 2: 创建 API 配置解析脚本 - [ ] Task 3: 创建类型生成脚本 - [ ] Task 4: 创建 API 变更检查脚本 - [ ] Task 5: 创建 useApiForm 组合式函数 - [ ] Task 6: 创建 ApiFormDialog 组件 - [ ] Task 7: 增强 BaseService 类型安全 - [ ] Task 8: 试点迁移 UserFormDialog - [ ] Task 9: 文档和配置 --- ## 成功标准 1. ✅ `pnpm api:generate` 成功生成所有模块类型 2. ✅ UserFormDialog 使用新方案正常运行 3. ✅ 表单字段与后端 API 类型一致 4. ✅ 提交时自动检测 API 变更 5. ✅ 团队可以使用新方案开发新功能 --- > **下一步**: 执行计划(使用 superpowers-subagent-driven-development 或 superpowers-executing-plans)