Files
jiapu/docs/superpowers/plans/2026-07-29-family-home.md
T
fizzleaf 309598bfa6 feat(api): 添加帮助中心反馈系统和VIP服务功能
- 在ApiClient中新增submitFeedback、myFeedback、helpArticles、helpArticleDetail、
  siteArticles、promotions、vipPackages、createVipOrder、vipOrders等方法
- 添加帮助文章和站点资讯的参数验证逻辑
- 更新测试文件添加新的API方法测试用例
- 在HTML页面中添加反馈、帮助和VIP服务相关页面的脚本引用
- 更新加入家谱页面为完整的申请流程界面
- 修改资讯详情页面为站点资讯展示页面
- 更新AxiosRequestUtil中认证处理逻辑
- 添加世系树渲染的HTML生成函数用于页面复用
- 更新文档中的API契约说明和页面规划
2026-07-30 16:04:48 +08:00

377 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 家谱主页 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, /&lt;始祖&gt;/);
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
? '<ul class="lineage-tree">' + nodes.join('') + '</ul>'
: '<div class="api-empty">暂无世系树</div>';
}
function renderTree(data) {
var container = query('[data-lineage-tree]');
if (container) container.innerHTML = renderLineageTreeHtml(data);
}
```
在 UMD 导出对象加入:
```js
renderLineageTreeHtml: renderLineageTreeHtml
```
- [ ] **Step 4: 运行 GREEN**
Run:
```powershell
node --test tests/lineage-pages.test.js tests/family-home-pages.test.js
node --check public/js/lineage-pages.js
```
Expected: PASS,现有世系管理页输出不变。
---
### Task 3: 开放真实家谱主页
**Files:**
- Modify: `profile-family-home.html`
- Modify: `public/js/family-home-pages.js`
- Modify: `tests/family-home-pages.test.js`
- Modify: `tests/pending-pages.test.js`
- Modify: `tests/stage6-navigation.test.js`
**Interfaces:**
- Consumes:
- `api.genealogyOverview(genealogyId)`
- `api.lineageTree(genealogyId)`
- `LineagePages.renderLineageTreeHtml(data)`
- Produces: `initFamilyHomePage()``init()`
- [ ] **Step 1: 写失败页面测试**
断言:
- `profile-family-home.html` 不再包含 `data-feature-status="pending"``pending-pages.js`
- 加载顺序为 `profile-common.js``lineage-pages.js``family-home-pages.js`
- 标题、摘要、计数、管理入口、世系预览均有稳定 `data-*` hook
- 硬编码“四川武胜汤氏族”被删除;
- “邀请家人”不再是可点击业务入口,并明确提示“PC 暂未开放邀请”;
- 谱文、相册、视频、功德、祭祀、世系、动态入口继续携带 `data-genealogy-context-link`
- 初始化只并行调用:
```js
Promise.all([
api.genealogyOverview(genealogyId),
api.lineageTree(genealogyId)
])
```
- [ ] **Step 2: 运行 RED**
Run:
```powershell
node --test tests/family-home-pages.test.js tests/pending-pages.test.js tests/stage6-navigation.test.js
```
Expected: FAIL,主页仍为 pending 且包含硬编码家谱。
- [ ] **Step 3: 实现只读初始化流程**
```js
async function initFamilyHomePage() {
var api = root.GenealogyApi && root.GenealogyApi.defaultClient;
var genealogyId = root.ProfileUI && root.ProfileUI.getGenealogyId();
var results;
var overview;
if (!genealogyId) {
root.location.replace('profile-families.html?next=profile-family-home.html');
return;
}
try {
results = await Promise.all([
api.genealogyOverview(genealogyId),
api.lineageTree(genealogyId)
]);
overview = normalizeFamilyOverview(results[0], genealogyId);
if (!overview) throw new Error('家谱概览响应无效');
renderFamilyOverview(overview);
renderManagementAccess(overview.canManage);
query('[data-lineage-home-tree]').innerHTML =
root.LineagePages.renderLineageTreeHtml(results[1]);
} catch (error) {
if (shouldRedirectToLogin(api, error)) return redirectToLogin(api);
renderFamilyHomeError(error);
}
}
```
页面只展示:
- 家谱名称、编号、姓氏、堂号、祖籍/地区;
- `memberCount``personCount`
- 真实世系树及进入完整世系页的链接;
- 后端已经接入的内容模块入口。
- [ ] **Step 4: 运行 GREEN**
Run:
```powershell
node --test tests/family-home-pages.test.js tests/pending-pages.test.js tests/stage6-navigation.test.js
node --check public/js/family-home-pages.js
```
Expected: PASS。
---
### Task 4: 规划记录与完整验收
**Files:**
- Modify: `docs/PC接口对接规划.md`
- Modify: `docs/superpowers/plans/2026-07-29-family-home.md`
**Interfaces:**
- Consumes: Task 13 的只读家谱主页闭环。
- [ ] **Step 1: 更新规划字段表**
新增家谱主页小节,逐字段记录:
| 字段 | 分类 | 页面用途 |
| --- | --- | --- |
| `genealogyId` | I/A | 当前上下文、两条请求 path、响应一致性校验 |
| `genealogyNo``genealogyName``surname` | R | 标题和基础信息 |
| `ancestralHall``originPlace``regionFullName` | R | 非空时展示 |
| `memberCount``personCount` | R | 非负只读计数 |
| `canManage``canEditContent` | I | 权限显隐,不作为用户输入 |
| `ownerUserId``coverOssId` | I | 当前主页不展示、不手填 |
| `LineagePersonTreeView.spouses/children` | R | 递归世系预览 |
同时记录:
- `/overview` 实际是详情别名,不包含内容聚合统计;
- `/genealogy/dashboard/overview` 是后台权限接口,不进入 PC 前端;
- 成员邀请没有 PC 接口,继续阻断。
- [ ] **Step 2: 聚焦验证**
Run:
```powershell
node --test tests/family-home-pages.test.js tests/lineage-pages.test.js tests/pending-pages.test.js tests/stage6-navigation.test.js tests/api-client-contract.test.js
node --check public/js/family-home-pages.js
node --check public/js/lineage-pages.js
```
Expected: 全部 PASS。
- [ ] **Step 3: 全量和差异验证**
Run:
```powershell
npm test
git -c safe.directory=D:/WorkSpace/Web/jiapu diff --check
```
Expected: 0 failed,差异检查无错误。
- [ ] **Step 4: 浏览器真实只读验证**
使用真实登录态验证:
1. 无家谱上下文时只跳转选择页,不发家谱业务请求;
2. 有真实家谱时标题、概览计数和世系树来自 PC 响应;
3. 普通成员看不到管理按钮,管理者可见;
4. 所有入口透传同一 `genealogyId`
5. 空世系、403、404、网络错误都有明确页面状态;
6. 控制台无错误;
7. 不创建或修改任何真实数据。