Files
jiapu/docs/superpowers/plans/2026-07-29-genealogy-join-review.md
T
2026-08-03 16:49:38 +08:00

8.2 KiB
Raw Blame History

家谱加入申请与审核 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 页面闭环。

Architecture: utils/ApiClient.js 已拥有本批全部 PC path/body。新增 join-pages.js 负责可申请家谱选择、申请表单、我的申请和撤销;新增 join-review-pages.js 负责当前家谱待审核列表和通过/拒绝。两个 UMD 模块只使用响应中的字符串 ID,写入后重读对应列表。

Tech Stack: 静态 HTML、原生 JavaScript UMD、Axios、Node node:test

Global Constraints

  • 后端 D:\WorkSpace\Java\Genealogy 全程只读。
  • 只调用 /genealogy/pc/**
  • 家谱 ID 和申请 ID 必须来自 PC 响应或当前家谱上下文,不能手填。
  • inviterUserId 没有安全 PC 来源,不展示、不发送。
  • 所有写操作防重复,401 清登录态,403 保留登录态。
  • 每项生产行为先运行失败测试,再最小实现。

Task 1: 申请数据边界

Files:

  • Create: tests/join-pages.test.js
  • Create: public/js/join-pages.js

Interfaces:

  • Consumes: genealogyOptions(query)applyToGenealogy(genealogyId, body)myGenealogyJoinApplies()cancelGenealogyJoinApply(applyId)

  • Produces:

    • normalizeJoinGenealogy(item)
    • buildJoinApplyBody(values)
    • validateJoinApplyBody(body)
    • normalizeJoinApply(item)
    • normalizeJoinApplies(data)
    • renderJoinGenealogyOptions(data)
    • renderMyJoinApplies(data)
  • Step 1: 写失败的纯行为测试

用完整 AppGenealogyVoGenealogyJoinApplyVo 字面量验证:

assert.deepEqual(JoinPages.buildJoinApplyBody({
  applicantName: ' 叶子 ',
  phone: ' <测试联系电话> ',
  relationDesc: ' 族亲 ',
  applyReason: ' 申请加入 ',
  inviterUserId: 'must-drop'
}), {
  applicantName: '叶子',
  phone: '<测试联系电话>',
  relationDesc: '族亲',
  applyReason: '申请加入'
});

断言:

  • applicantName <= 50phone <= 30relationDesc <= 100applyReason <= 500

  • 可申请家谱必须有稳定 genealogyId/genealogyName/surname,停用家谱拒绝。

  • 申请必须有稳定 applyId/genealogyIdstatus;状态只允许 0/1/2/3

  • 安全数字长 ID 只接受字符串;不安全 number 拒绝。

  • 普通用户列表不展示内部用户 ID、邀请人 ID、审核人 ID或原始 JSON。

  • 只有 status=0 渲染撤销按钮。

  • Step 2: 运行 RED

Run:

node --test tests/join-pages.test.js

Expected: FAIL,模块不存在。

  • Step 3: 最小实现纯函数

所有可见文本使用 escapeHtml;响应任一必需字段无效时拒绝该条,数组包含非法元素时整批返回空数组。

  • Step 4: 运行 GREEN

Run:

node --test tests/join-pages.test.js
node --check public/js/join-pages.js

Expected: PASS。


Task 2: 申请页面与我的申请

Files:

  • Modify: join-genealogy.html
  • Modify: profile-join-family.html
  • Modify: public/js/join-pages.js
  • Modify: tests/join-pages.test.js
  • Modify: tests/pending-pages.test.js
  • Modify: tests/pc-scope.test.js

Interfaces:

  • Produces: initJoinApplyPage()initMyJoinAppliesPage()

  • Step 1: 写失败的页面行为测试

断言:

  • 两页均不再 pending,并加载 join-pages.js

  • join-genealogy.html 有关键词搜索、后端家谱选项、申请资料表单;不存在 genealogyId/inviterUserId/applyId 文本输入。

  • profile-join-family.html 有我的申请列表和刷新入口;申请 ID 只存在于响应渲染按钮属性。

  • 页面没有邀请码输入或“分享码”伪语义。

  • Step 2: 运行 RED

Run:

node --test tests/join-pages.test.js tests/pending-pages.test.js tests/pc-scope.test.js

Expected: FAIL,页面仍 pending/缺少真实行为。

  • Step 3: 实现申请和撤销

join-genealogy.html

  1. 读取 genealogyOptions({keyword})
  2. 用户只能点击响应卡片选择家谱,内部保存字符串 genealogyId
  3. 构造并校验申请 body。
  4. 提交后重读 myGenealogyJoinApplies(),必须找到同一响应 applyId
  5. 跳转 profile-join-family.html

profile-join-family.html

  1. 读取并渲染我的申请。
  2. 只有待审核申请显示撤销。
  3. 二次确认后调用撤销接口并重读列表,确保该申请不再为待审核。
  • Step 4: 运行 GREEN

Run:

node --test tests/join-pages.test.js tests/pending-pages.test.js tests/pc-scope.test.js
node --check public/js/join-pages.js

Expected: PASS。


Task 3: 管理员审核

Files:

  • Create: tests/join-review-pages.test.js
  • Create: public/js/join-review-pages.js
  • Modify: profile-join-review.html
  • Modify: tests/pending-pages.test.js
  • Modify: tests/pc-scope.test.js

Interfaces:

  • Consumes: genealogyDetail(genealogyId)pendingGenealogyJoinApplies(genealogyId)auditGenealogyJoinApply(genealogyId, applyId, body)

  • Produces:

    • getCurrentGenealogyId(search)
    • buildJoinAuditBody(values)
    • validateJoinAuditBody(body)
    • renderPendingJoinApplies(data, canManage)
    • initJoinReviewPage()
  • Step 1: 写失败的审核行为测试

assert.deepEqual(JoinReviewPages.buildJoinAuditBody({
  status: '2',
  auditRemark: ' 资料不一致 ',
  applyId: 'must-drop'
}), {
  status: '2',
  auditRemark: '资料不一致'
});

断言:

  • status 只允许 1/2auditRemark <= 500

  • 页面只从 URL/ProfileUI 读取家谱 ID。

  • 只有 genealogyDetail.canManage=true 显示审核动作。

  • 待审核列表显示申请人名称、申请手机号、关系和原因,但隐藏内部用户 ID、邀请人/审核人 ID。

  • 通过和拒绝按钮使用响应中的稳定 applyId

  • Step 2: 运行 RED

Run:

node --test tests/join-review-pages.test.js

Expected: FAIL,模块不存在且页面仍 pending。

  • Step 3: 实现审核页面

加载当前家谱详情和待审核列表;没有上下文时阻止请求并返回家谱选择入口。点击通过使用 {status:'1'};拒绝要求输入可选审核说明并使用 {status:'2', auditRemark}。审核成功后重读待审核列表,确保同一 applyId 不再存在。

  • Step 4: 运行 GREEN

Run:

node --test tests/join-review-pages.test.js tests/pending-pages.test.js tests/pc-scope.test.js
node --check public/js/join-review-pages.js

Expected: PASS。


Task 4: 导航、规划与验证

Files:

  • Modify: profile-families.html

  • Modify: profile-family-admin.html

  • Modify: profile.html

  • Modify: tests/stage6-navigation.test.js

  • Modify: docs/PC接口对接规划.md

  • Modify: docs/superpowers/plans/2026-07-29-genealogy-join-review.md

  • Step 1: 写失败的导航测试

断言我的家谱入口可达申请页,家谱管理入口携带上下文进入审核页;不存在指向手填家谱 ID 或邀请码页面的入口。

  • Step 2: 运行 RED

Run:

node --test tests/stage6-navigation.test.js tests/pending-pages.test.js

Expected: FAIL,审核导航尚未稳定携带家谱上下文。

  • Step 3: 开放导航并更新规划

记录申请/审核 DTO、VO、权限、状态、字段来源、写后重读和 inviterUserId 阻断。

  • Step 4: 聚焦和全量验证

Run:

node --test tests/join-pages.test.js tests/join-review-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js tests/pc-scope.test.js tests/stage6-navigation.test.js
npm test
node --check public/js/join-pages.js
node --check public/js/join-review-pages.js
git -c safe.directory=D:/WorkSpace/Web/jiapu diff --check

Expected: 全部 PASS。

  • Step 5: 浏览器验证并报告

真实账号验证可申请家谱列表、我的申请空状态、无家谱上下文审核分支、隐私和控制台错误。未经用户明确授权,不提交或审核真实申请。