feat(api): 添加帮助中心反馈系统和VIP服务功能

- 在ApiClient中新增submitFeedback、myFeedback、helpArticles、helpArticleDetail、
  siteArticles、promotions、vipPackages、createVipOrder、vipOrders等方法
- 添加帮助文章和站点资讯的参数验证逻辑
- 更新测试文件添加新的API方法测试用例
- 在HTML页面中添加反馈、帮助和VIP服务相关页面的脚本引用
- 更新加入家谱页面为完整的申请流程界面
- 修改资讯详情页面为站点资讯展示页面
- 更新AxiosRequestUtil中认证处理逻辑
- 添加世系树渲染的HTML生成函数用于页面复用
- 更新文档中的API契约说明和页面规划
This commit is contained in:
fizzleaf
2026-07-30 16:04:48 +08:00
parent fb1743aa2a
commit 309598bfa6
53 changed files with 6740 additions and 282 deletions
@@ -0,0 +1,376 @@
# 家谱主页 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. 不创建或修改任何真实数据。