diff --git a/superpowers/plans/2026-06-06-api-collaboration-plan.md b/superpowers/plans/2026-06-06-api-collaboration-plan.md new file mode 100644 index 0000000..5b6dffd --- /dev/null +++ b/superpowers/plans/2026-06-06-api-collaboration-plan.md @@ -0,0 +1,1322 @@ +# 前后端智能协作方案实现计划 + +> **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)