# 家族圈动态接口接入 Implementation Plan > **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:** 将 `PC.openapi2.json` 中已交付的 12 个家族圈动态接口接入既有动态页面,并保持未开发的家谱业务不调用不存在的接口。 **Architecture:** `PC.openapi2.json` 是接口唯一依据;`ApiClient` 提供一一对应的私有请求封装,`feed-pages.js` 只协调页面事件和已定义的接口方法。动态页从 URL 查询参数读取真实 `genealogyId`,不写入示例家谱 ID。 **Tech Stack:** 原生 JavaScript、Axios、Node LTS `node:test`、静态 HTML/CSS。 ## Global Constraints - 所有新增或修改的注释使用清晰中文。 - 不删除现有页面和样式;不接入尚未出现在 `PC.openapi2.json` 的业务。 - 不提交当前工作区:其中含用户已有的未提交修改。 - 动态读取响应目前只有通用 `ListResult`/`PageResult`,页面仅使用 `feedId` 与 `feedContent`;后端返回缺少这两个字段时展示明确错误,不猜测字段名。 --- ### Task 1: 更新接口边界测试和动态请求测试 **Files:** - Modify: `tests/api-client-contract.test.js` - Modify: `tests/pc-scope.test.js` - Create: `tests/feed-pages.test.js` **Interfaces:** - Consumes: `PC.openapi2.json` 的 12 个 `/genealogy/pc/genealogies/{genealogyId}/feeds` 操作。 - Produces: 对客户端路径、HTTP 方法、请求体和页面请求体转换的自动化约束。 - [ ] **Step 1: 写入失败测试** ```js test('家族圈动态方法使用文档定义的路径和请求体', async () => { const calls = []; const client = createClientWithRecorder(calls); await client.createFeed(900001001, { feedContent: '今天上传一张老照片。' }); await client.feedCommentsPage(900001001, 7001, { pageNum: 1, pageSize: 10 }); assert.deepEqual(calls.map((call) => [call.method, call.url]), [ ['post', '/genealogy/pc/genealogies/900001001/feeds'], ['get', '/genealogy/pc/genealogies/900001001/feeds/7001/comments/page'] ]); }); test('动态表单只构造 FamilyFeedBody 字段', () => { assert.deepEqual(FeedPages.buildFeedBody({ feedContent: '家谱动态', mediaOssIds: '101,102', status: '0' }), { feedType: 'text', feedContent: '家谱动态', mediaOssIds: '101,102', status: '0' }); }); ``` - [ ] **Step 2: 运行失败测试** Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test` Expected: 动态方法或 `feed-pages.js` 尚不存在导致失败。 - [ ] **Step 3: 调整范围检查** ```js assert.match(contract, /\/genealogy\/pc\/genealogies\/\{genealogyId\}\/feeds/); assert.equal(read('profile-feed.html').includes('src="public/js/feed-pages.js"'), true); assert.equal(read('profile-feed-edit.html').includes('src="public/js/feed-pages.js"'), true); ``` - [ ] **Step 4: 重新运行测试,确认仍处于预期失败状态** Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test` Expected: 仅动态实现相关断言失败。 ### Task 2: 增加 12 个家族圈 API 方法 **Files:** - Modify: `utils/ApiClient.js` - Modify: `tests/api-client-contract.test.js` **Interfaces:** - Consumes: `genealogyId:number|string`、`feedId:number|string`、`commentId:number|string`。 - Produces: `feeds`、`feedsPage`、`feedDetail`、`createFeed`、`updateFeed`、`deleteFeed`、`likeFeed`、`unlikeFeed`、`feedComments`、`feedCommentsPage`、`createFeedComment`、`deleteFeedComment`。 - [ ] **Step 1: 保持失败测试不变** ```js await client.createFeed(900001001, { feedContent: '家谱动态' }); assert.equal(calls[0].url, '/genealogy/pc/genealogies/900001001/feeds'); assert.deepEqual(calls[0].data, { feedContent: '家谱动态' }); ``` - [ ] **Step 2: 实现最小路径构造** ```js function feedPath(genealogyId, suffix) { return '/genealogy/pc/genealogies/' + encodeURIComponent(genealogyId) + '/feeds' + suffix; } createFeed: function (genealogyId, body) { return request('POST', feedPath(genealogyId, ''), { body: body }); } ``` - [ ] **Step 3: 运行测试,确认通过** Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test` Expected: 动态客户端路径和请求体断言通过。 ### Task 3: 恢复动态页的真实交互 **Files:** - Create: `public/js/feed-pages.js` - Modify: `profile-feed.html` - Modify: `profile-feed-edit.html` - Modify: `tests/feed-pages.test.js` **Interfaces:** - Consumes: `GenealogyApi.defaultClient` 的 12 个动态方法和 URL 中的 `genealogyId`、可选 `feedId`。 - Produces: 动态列表、发布/编辑、点赞/取消点赞、评论查看/发布/删除、动态删除事件。 - [ ] **Step 1: 使页面测试失败** ```js assert.equal(FeedPages.getCurrentGenealogyId('?genealogyId=900001001'), '900001001'); assert.equal(FeedPages.getCurrentGenealogyId(''), ''); assert.equal(FeedPages.getFeedId({ feedId: 7001 }), '7001'); ``` - [ ] **Step 2: 实现页面数据转换和事件** ```js function buildFeedBody(values) { return { feedType: 'text', feedContent: String(values.feedContent || '').trim(), mediaOssIds: trimOrUndefined(values.mediaOssIds), status: trimOrUndefined(values.status) }; } ``` 列表只读取 `feedId`、`feedContent`,并在缺少其中任一字段时显示“动态响应缺少 feedId 或 feedContent,请联系后端补充 DTO”。编辑页以 `feedId` 查询详情并调用 `updateFeed`;列表操作调用点赞、取消点赞、评论、删除接口后重新加载当前页。 - [ ] **Step 3: 挂载脚本和契约字段** ```html ``` 移除未在 `FamilyFeedBody` 中定义的“可见范围”提交字段;保持页面布局与既有视觉类名。 - [ ] **Step 4: 运行测试,确认通过** Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test` Expected: `feed-pages.test.js` 和已有测试全部通过。 ### Task 4: 更新文档来源与最终验证 **Files:** - Modify: `tests/pc-scope.test.js` - Modify: `PC.openapi.yaml` **Interfaces:** - Consumes: `PC.openapi2.json` 的接口路径。 - Produces: 明确标记旧 YAML 不再为接口源,并使静态检查以 JSON 为准。 - [ ] **Step 1: 写入失败检查** ```js const contract = JSON.parse(read('PC.openapi2.json')); assert.ok(contract.paths['/genealogy/pc/genealogies/{genealogyId}/feeds']); ``` - [ ] **Step 2: 标记旧 YAML 失效** 在 `PC.openapi.yaml` 文件首行写入中文说明:该文件不再作为代码或测试接口源,正式接口源为 `PC.openapi2.json`。不复制或手改 JSON 中未定义的动态响应 DTO。 - [ ] **Step 3: 全量验证** Run: `$env:Path = 'C:\\Program Files\\nodejs;' + $env:Path; npm.cmd test` Expected: 全部测试通过,且所有动态页面加载的脚本文件都存在。 ## Self-review - 范围只覆盖最新文档已交付的家族圈动态接口,没有加入家谱创建、成员或世系树接口。 - 所有请求体字段来自 `FamilyFeedBody` 或 `FamilyFeedCommentBody`。 - 读取响应不使用历史字段兜底,不把未知字段猜成有效数据。 - 任何 URL 缺少 `genealogyId` 的页面都会明确提示,不使用硬编码示例 ID。