# 家谱主页 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:** 将 `profile-family-home.html` 从静态预览开放为真实 PC 家谱主页,展示当前家谱概览和世系树预览,并保留已接入业务页入口。 **Architecture:** `profile-common.js` 继续唯一拥有当前 `genealogyId`;页面只调用已有 `genealogyOverview(genealogyId)` 和 `lineageTree(genealogyId)`。`lineage-pages.js` 继续唯一拥有世系树节点规范化与 HTML 生成,新建 `family-home-pages.js` 只负责概览 DTO、权限显隐、并行读取和页面状态。 **Tech Stack:** 静态 HTML、原生 JavaScript UMD、Axios、Node `node:test`。 **Status:** 2026-07-29 已按 Task 1–4 实施;自动化与无家谱真实账号分支已验证,有家谱数据态等待具备真实家谱的账号复核。 ## Global Constraints - 后端 `D:\WorkSpace\Java\Genealogy` 全程只读。 - 只使用 PC 接口;不得调用 `/genealogy/dashboard/overview`、APP 或管理后台接口。 - `genealogyId` 只能来自 `ProfileUI.getGenealogyId()`,始终按字符串处理。 - `GET /genealogy/pc/genealogies/{genealogyId}/overview` 返回 `AppGenealogyVo`,它不是内容统计接口;不得制造文章、相册、视频或活动数量。 - 世系树只读调用 `GET /genealogy/pc/genealogies/{genealogyId}/lineage/tree`。 - 家谱管理入口仅在响应 `canManage=true` 时显示;内容页自身继续负责更细权限。 - PC 没有成员邀请创建/分享接口;主页不得开放“邀请家人”操作。 - 本批次没有写接口,不创建、修改或删除任何真实业务数据。 --- ### Task 1: 家谱主页 DTO 与页面契约 **Files:** - Create: `public/js/family-home-pages.js` - Create: `tests/family-home-pages.test.js` **Interfaces:** - Consumes: `ProfileUI.getGenealogyId()`、`GenealogyApi.defaultClient` - Produces: - `normalizeFamilyOverview(item, expectedGenealogyId)` - `renderFamilyOverview(overview)` - `shouldShowFamilyManagement(overview)` - `shouldRedirectToLogin(api, error)` - [ ] **Step 1: 写失败数据边界测试** ```js const overview = FamilyHomePages.normalizeFamilyOverview({ genealogyId: '2062179707935264769', genealogyNo: 'G20260729001', genealogyName: '叶氏家谱', surname: '叶', ancestralHall: '南阳堂', originPlace: '四川成都', regionFullName: '四川省 成都市', memberCount: 12, personCount: 36, status: '0', canManage: true, canEditContent: true, ownerUserId: 'must-not-render', coverOssId: 'must-not-render' }, '2062179707935264769'); assert.deepEqual(overview, { genealogyId: '2062179707935264769', genealogyNo: 'G20260729001', genealogyName: '叶氏家谱', surname: '叶', ancestralHall: '南阳堂', originPlace: '四川成都', regionFullName: '四川省 成都市', memberCount: 12, personCount: 36, canManage: true, canEditContent: true }); assert.equal( FamilyHomePages.normalizeFamilyOverview( { genealogyId: Number.MAX_SAFE_INTEGER + 1, genealogyName: '非法', status: '0' }, '2062179707935264769' ), null ); assert.equal( FamilyHomePages.normalizeFamilyOverview( { genealogyId: '2', genealogyName: '串谱', status: '0' }, '2062179707935264769' ), null ); ``` 同时断言: - `genealogyName` 为空、`status!='0'`、负数/非安全计数均拒绝; - 渲染转义全部文本,不出现 `ownerUserId`、`coverOssId` 或原始 JSON; - 只有布尔值 `canManage===true` 才开放管理入口; - 401 清理登录态,403 不清理登录态。 - [ ] **Step 2: 运行 RED** Run: `node --test tests/family-home-pages.test.js` Expected: FAIL,`family-home-pages.js` 不存在。 - [ ] **Step 3: 最小实现概览规范化** ```js function normalizeFamilyOverview(item, expectedGenealogyId) { var source = item || {}; var genealogyId = normalizeId(source.genealogyId); var expectedId = normalizeId(expectedGenealogyId); var memberCount = normalizeCount(source.memberCount); var personCount = normalizeCount(source.personCount); if (!genealogyId || genealogyId !== expectedId || !text(source.genealogyName) || String(source.status) !== '0' || memberCount === null || personCount === null) return null; return { genealogyId: genealogyId, genealogyNo: text(source.genealogyNo), genealogyName: text(source.genealogyName), surname: text(source.surname), ancestralHall: text(source.ancestralHall), originPlace: text(source.originPlace), regionFullName: text(source.regionFullName), memberCount: memberCount, personCount: personCount, canManage: source.canManage === true, canEditContent: source.canEditContent === true }; } ``` - [ ] **Step 4: 运行 GREEN** Run: ```powershell node --test tests/family-home-pages.test.js node --check public/js/family-home-pages.js ``` Expected: PASS。 --- ### Task 2: 复用世系树唯一渲染 owner **Files:** - Modify: `public/js/lineage-pages.js` - Modify: `tests/lineage-pages.test.js` - Modify: `tests/family-home-pages.test.js` **Interfaces:** - Produces: `LineagePages.renderLineageTreeHtml(data)` - Consumes: 现有 `normalizeLineagePerson(item)`、`renderTreeNode(item, ancestry)` - [ ] **Step 1: 写失败共享渲染测试** ```js const tree = [{ personId: '2062179707935264770', genealogyId: '2062179707935264769', name: '<始祖>', status: '0', spouses: [], children: [] }]; const html = LineagePages.renderLineageTreeHtml(tree); assert.match(html, /<始祖>/); assert.match(html, /data-lineage-person="2062179707935264770"/); assert.doesNotMatch(html, /2062179707935264769/); assert.match(LineagePages.renderLineageTreeHtml([]), /暂无世系树/); ``` - [ ] **Step 2: 运行 RED** Run: `node --test tests/lineage-pages.test.js tests/family-home-pages.test.js` Expected: FAIL,`renderLineageTreeHtml` 未导出。 - [ ] **Step 3: 从现有 `renderTree` 提取纯 HTML owner** ```js function renderLineageTreeHtml(data) { var nodes = normalizeList(data) .map(function (item) { return renderTreeNode(item, {}); }) .filter(Boolean); return nodes.length ? '