From fb1743aa2a6e76b3c0356e633054b5c8fe5b4cca Mon Sep 17 00:00:00 2001 From: fizzleaf Date: Wed, 29 Jul 2026 16:57:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(api):=20=E5=AE=8C=E5=96=84=E5=AE=B6?= =?UTF-8?q?=E8=B0=B1=E7=B3=BB=E7=BB=9FAPI=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现家谱管理相关方法,包括创建、详情、概览、我的家谱和选项查询 - 添加家谱加入申请功能,支持申请、审核、取消和待审核列表操作 - 集成通知详情获取方法和通知ID安全验证机制 - 完善功德记录、谱文、相册、视频、祭祀活动的完整CRUD操作契约 - 实现家谱成员管理功能,包含成员列表、更新、移除和转让所有者操作 - 优化路径ID验证逻辑,拒绝不安全的数值ID并提供明确错误提示 - 更新测试用例以验证所有新增API方法的路径和请求体白名单机制 --- docs/PC接口对接规划.md | 549 ++++++++++- ...2026-07-29-genealogy-create-first-batch.md | 30 +- .../plans/2026-07-29-stage6-pc-pages.md | 509 ++++++++++ .../2026-07-29-stage6-pc-pages-design.md | 119 +++ join-genealogy.html | 16 +- profile-album-detail.html | 120 +++ profile-album-edit.html | 100 ++ profile-album.html | 101 +- profile-article-edit.html | 29 +- profile-article.html | 30 +- profile-ceremony-detail.html | 67 ++ profile-ceremony.html | 50 + profile-content.html | 22 +- profile-create-family.html | 28 +- profile-families.html | 8 +- profile-family-admin.html | 124 +-- profile-family-home.html | 16 +- profile-feed-detail.html | 72 ++ profile-feed-edit.html | 23 +- profile-feed.html | 29 +- profile-generation.html | 5 +- profile-gift-edit.html | 48 +- profile-gift.html | 50 +- profile-growth-edit.html | 32 +- profile-growth.html | 13 +- profile-memo-edit.html | 15 +- profile-memo.html | 11 +- profile-merit-edit.html | 85 +- profile-merit.html | 64 +- profile-messages.html | 76 +- profile-relative-edit.html | 137 ++- profile-relative.html | 76 +- profile-tree.html | 144 ++- profile-video-edit.html | 128 +++ profile-video.html | 73 +- profile.html | 10 +- public/css/profile-module.css | 80 ++ public/js/album-pages.js | 736 ++++++++++++++ public/js/article-pages.js | 567 +++++++++++ public/js/ceremony-admin-pages.js | 920 ++++++++++++++++++ public/js/ceremony-pages.js | 316 ++++++ public/js/feed-pages.js | 559 +++++++++-- public/js/genealogy-entry-pages.js | 287 +++++- public/js/generation-pages.js | 130 ++- public/js/growth-pages.js | 577 ++++++++--- public/js/lineage-pages.js | 343 ++++++- public/js/member-admin-pages.js | 581 +++++++++++ public/js/memo-pages.js | 565 ++++++++--- public/js/merit-pages.js | 644 ++++++++++++ public/js/notification-pages.js | 332 ++++++- public/js/profile-common.js | 10 +- public/js/relative-pages.js | 654 +++++++++++-- public/js/video-pages.js | 616 ++++++++++++ tests/album-pages.test.js | 195 ++++ tests/api-client-contract.test.js | 446 ++++++++- tests/article-pages.test.js | 188 ++++ tests/ceremony-admin-pages.test.js | 251 +++++ tests/ceremony-pages.test.js | 138 +++ tests/feed-pages.test.js | 196 ++++ tests/genealogy-entry-pages.test.js | 202 +++- tests/generation-pages.test.js | 115 ++- tests/growth-pages.test.js | 203 +++- tests/lineage-pages.test.js | 249 ++++- tests/member-admin-pages.test.js | 176 ++++ tests/memo-pages.test.js | 207 +++- tests/merit-pages.test.js | 216 ++++ tests/notification-pages.test.js | 109 ++- tests/pc-scope.test.js | 7 +- tests/pending-pages.test.js | 33 +- tests/relative-pages.test.js | 198 +++- tests/security-upload-scope.test.js | 5 +- tests/stage6-navigation.test.js | 35 + tests/video-pages.test.js | 154 +++ utils/ApiClient.js | 237 ++++- 74 files changed, 13166 insertions(+), 1320 deletions(-) create mode 100644 docs/superpowers/plans/2026-07-29-stage6-pc-pages.md create mode 100644 docs/superpowers/specs/2026-07-29-stage6-pc-pages-design.md create mode 100644 profile-album-detail.html create mode 100644 profile-album-edit.html create mode 100644 profile-ceremony-detail.html create mode 100644 profile-ceremony.html create mode 100644 profile-feed-detail.html create mode 100644 profile-video-edit.html create mode 100644 public/js/album-pages.js create mode 100644 public/js/article-pages.js create mode 100644 public/js/ceremony-admin-pages.js create mode 100644 public/js/ceremony-pages.js create mode 100644 public/js/member-admin-pages.js create mode 100644 public/js/merit-pages.js create mode 100644 public/js/video-pages.js create mode 100644 tests/album-pages.test.js create mode 100644 tests/article-pages.test.js create mode 100644 tests/ceremony-admin-pages.test.js create mode 100644 tests/ceremony-pages.test.js create mode 100644 tests/member-admin-pages.test.js create mode 100644 tests/merit-pages.test.js create mode 100644 tests/stage6-navigation.test.js create mode 100644 tests/video-pages.test.js diff --git a/docs/PC接口对接规划.md b/docs/PC接口对接规划.md index ec25e2d..089713f 100644 --- a/docs/PC接口对接规划.md +++ b/docs/PC接口对接规划.md @@ -156,13 +156,39 @@ | C11 | `completed` 只有 string 类型,无枚举 | 不能决定复选框提交 `0/1`、true/false 或其他值 | Apifox 增加枚举和中文含义 | | C12 | `feedType`、`recordType`、`registerSource` 等无枚举 | 无法判断文本输入还是选择项 | 明确为自由文本,或补全枚举 | | C13 | 多个日期字段没有 format/时区规则 | 页面值和后端解析可能不一致 | 明确 date 或 date-time,并统一时区 | -| C14 | 只有配额,没有我的家谱/成员入口 | 无法取得 `genealogyId`、成员或受邀用户选项 | 增加 PC 家谱列表/选项和成员选项接口,或明确外部上下文契约 | +| C14 | 已由后端 PC 实链接决 | `GET /genealogy/pc/genealogies/mine` 和 `/options` 返回完整 `AppGenealogyVo`,`genealogyId` 从响应取得 | 前端只从真实列表选择,不允许手填 ID | | C15 | `bindingMode=SPECIFIED` 需要 `appUserId`,但无业务用户选项接口 | 不能提供合法选择器 | 增加家谱成员/业务用户选项接口 | | C16 | 邀约的 `inviteeUserIds` 没有选项接口 | 不能让管理员选择受邀人 | 增加同一家谱成员选项接口 | | C17 | 视频、成长、亲友、备忘、功德、通知响应未展开 | 无法安全展示、编辑或单条操作 | 分别增加资源 View 和列表/详情响应 | | C18 | 更新接口未说明缺省、null、空串语义 | 可选字段可能无法清空 | Apifox 对每个可清空字段写明更新规则 | | C19 | 多数 View 没有 `canEdit` / `canDelete` 等权限字段 | 页面无法可靠决定按钮可见性 | 增加能力字段,或提供明确且可实现的角色规则 | | C20 | 文章、相册、祭祀只有删除操作 | 没有真实 ID 来源和完整资源流程 | 补全 PC 列表/详情/新增/修改后才开放页面 | +| C21 | 家族圈动态只返回 `mediaOssIds`,PC 没有 OSS ID 批量解析/访问 URL 接口 | 已上传图片只能统计数量,不能在列表和详情安全回显缩略图 | PC 增加按 OSS ID 返回授权 URL、文件名和媒体类型的接口,或让动态 View 直接返回媒体对象数组 | +| C22 | 停用动态不会出现在普通列表,普通详情接口也拒绝读取 | 管理员可以停用,但停用后无法从 PC 页面重新读取、恢复或验证最终状态 | 增加包含停用记录的管理列表/详情,或增加独立的状态恢复接口 | +| C23 | `VideoVo` 只返回 `coverOssId` / `videoOssId`,没有 PC 文件访问 URL 或解析接口 | 可以维护视频记录,但不能安全播放视频或回显封面 | 视频 View 直接返回授权 URL,或增加按 OSS ID 获取文件访问地址的 PC 接口 | +| C24 | 视频列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法从页面重新读取和恢复 | 增加视频 management 列表/详情,或增加独立状态恢复接口 | +| C25 | `GrowthRecordVo` 只返回附件 `mediaOssIds`,没有 PC 文件访问 URL 或解析接口 | 可以维护附件引用,但不能安全预览、下载或回显文件名 | 成长记录 View 返回附件对象,或增加按 OSS ID 获取授权地址、文件名和类型的 PC 接口 | +| C26 | 成长记录列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读、恢复或再次编辑 | 增加成长记录 management 列表/详情,或增加独立状态恢复接口 | +| C27 | `giftAmount` 只有 `number` / `BigDecimal` 类型,没有精度、范围和正负规则 | 前端不能自行限定两位小数、最大金额或禁止负数 | Apifox 明确 scale、precision、minimum/maximum 和负数业务含义 | +| C28 | `RelativeRecordVo` 只返回附件 `mediaOssIds`,没有 PC 文件访问 URL 或解析接口 | 可以维护附件引用,但不能安全预览、下载或回显文件名 | 亲友记录 View 返回附件对象,或增加按 OSS ID 获取授权地址、文件名和类型的 PC 接口 | +| C29 | 亲友记录列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读、恢复或再次编辑 | 增加亲友记录 management 列表/详情,或增加独立状态恢复接口 | +| C30 | `MemoVo` 只返回附件 `mediaOssIds`,没有 PC 文件访问 URL 或解析接口 | 可以维护附件引用,但不能安全预览、下载或回显文件名 | 备忘录 View 返回附件对象,或增加按 OSS ID 获取授权地址、文件名和类型的 PC 接口 | +| C31 | 备忘录列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读、恢复或再次编辑 | 增加备忘录 management 列表/详情,或增加独立状态恢复接口 | +| C32 | 功德金额只有 `number` / `BigDecimal` 类型,没有精度、范围和正负规则 | 前端不能自行限定两位小数、最大金额或禁止负数 | Apifox 明确 scale、precision、minimum/maximum 和负数业务含义 | +| C33 | 功德记录列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读、恢复或再次编辑 | 增加功德记录 management 列表/详情,或增加独立状态恢复接口 | +| C34 | YAML `NotificationView` 声明了家谱编号/名称、发送人昵称/手机号和业务摘要,但 PC `toVo` 未填充这些字段 | 页面可能长期显示空白,导出契约与真实 PC 响应不一致 | 后端补齐字段,或从 PC Schema 删除不会返回的字段并明确隐私策略 | +| C35 | PC 谱文后端列表支持可选 `categoryId`,YAML 未声明该 query,且没有文章分类选项接口 | 前端无法可靠提供分类筛选或编辑分类 | YAML 补齐 query,并提供当前家谱文章分类选项接口 | +| C36 | `ArticleVo` 只返回 `coverOssId`,没有 PC 文件访问 URL | 谱文可维护封面引用,但不能安全回显封面图片 | Article View 返回封面 URL,或增加 OSS ID 授权解析接口 | +| C37 | 谱文列表只返回正常记录,详情拒绝读取停用记录 | `status=1` 后无法写后重读或恢复 | 增加谱文 management 列表/详情,或独立状态恢复接口 | +| C38 | `AlbumVo` / `AlbumPhotoVo` 只返回 `coverOssId` / `ossId`,没有 PC 文件访问 URL | 相册和照片记录可维护,但不能安全回显封面或照片内容 | View 返回授权 URL,或增加 OSS ID 授权解析接口 | +| C39 | 相册和照片列表只返回正常记录,且没有停用记录的 management 入口 | `status=1` 后无法从页面重读、恢复或再次编辑 | 增加相册/照片 management 列表,或独立状态恢复接口 | +| C40 | `CeremonyVo` 只返回 `coverOssId`,没有 PC 文件访问 URL | 活动记录可维护,但不能安全回显封面 | View 返回封面授权 URL,或增加 OSS ID 授权解析接口 | +| C41 | 活动列表和详情只允许读取正常记录 | `status=1` 后无法从页面重读、恢复或再次编辑 | 增加活动 management 列表/详情,或独立状态恢复接口 | +| C42 | `CeremonyGiftBody.giftAmount` 的 YAML 没有精度和最大值,后端实际拒绝负数 | 页面可按后端约束拒绝负数,但不能自行限定小数位或最大金额 | YAML 补充 minimum、scale、precision 和 maximum | +| C43 | YAML 成员更新角色枚举含 `owner/visitor`,PC 后端 DTO 只接受 `admin/editor/member` | 按 YAML 展示会产生必然失败的写入 | YAML 收紧枚举;本轮前端按 PC 后端校验冻结为三种可分配角色 | +| C44 | `GenealogyMemberVo` 声明昵称、手机号、家谱和世系人物名称等字段,PC `toVo` 实际只填充 ID、成员名、角色、关系、来源和时间 | 列表部分说明可能长期为空,手机号还有隐私风险 | 后端补齐非敏感展示字段并删除手机号,或从 PC Schema 删除不会返回的字段 | +| C45 | PC 没有世系关系解除接口 | 页面无法合法解除父母、配偶、子女或兄弟姐妹关系 | 后端增加带权限和关系完整性校验的解除接口 | +| C46 | 成员更新只在 `lineagePersonId != null` 时写入,没有解除成员与世系人物绑定的语义 | 下拉留空只能表示“不修改”,不能完成解绑 | 增加明确解绑接口或约定可验证的 null/空值语义 | 当前复核状态: @@ -175,8 +201,28 @@ | C01/C08 | 前端按后端 PC 实链解决 | 只调用 `/genealogy/pc/region/*`;列表按 `RegionSelectVo` 的 `regionCode/regionName/regionLevel` 读取,不保留公共路径 fallback | | C09 | 前端按后端 PC 实链解决 | `SysOssResumableInitVo` 明确提供 `uploadId/instant/ossId/url/fileName/uploadedChunks`,完成响应使用 `SysOssUploadVo` | | C10 | 前端边界解决 | 后端初始化 `ossId` 为 Long、完成响应为 string;浏览器统一转十进制字符串,隐藏回填且不转 `Number` | +| C11(备忘录) | 本轮按后端 PC 实链解决 | YAML 仍只有 string;PC `AppMemoServiceImpl` 明确校验 `completed` 只能为 `0/1`,含义为否/是,前端冻结为未完成/已完成 | | C13 | 后续日期模块仍阻断 | 本阶段上传与区划响应不消费日期;其他模块仍需逐字段核验 Java 日期类型 | +| C17(成长记录) | 本轮按后端 PC 实链解决 | YAML 响应仍为泛型;PC `PcGrowthRecordController` 明确返回 `GrowthRecordVo`,前端只冻结该 PC 响应,不读取 APP 路径 | +| C17(亲友记录) | 本轮按后端 PC 实链解决 | YAML 响应仍为泛型;PC `PcRelativeRecordController` 明确返回 `RelativeRecordVo`,前端只冻结该 PC 响应,不读取 APP 路径 | +| C17(备忘录) | 本轮按后端 PC 实链解决 | YAML 响应仍为泛型;PC `PcMemoController` 复用的 PC 支持层明确返回 `MemoVo`,前端只冻结该 PC 响应,不读取 APP 路径 | +| C17(功德记录) | 本轮按后端 PC 实链解决 | YAML 响应仍为泛型;PC `PcMeritRecordController` 复用的 PC 支持层明确返回 `MeritRecordVo`,前端只冻结该 PC 响应,不读取 APP 路径 | +| C17(消息通知) | 本轮按 YAML 详情 DTO 和后端 PC 实链解决 | 详情为完整 `NotificationView`;列表导出仍是泛型数组,但 PC 控制器实际返回同一 `NotificationVo`。前端只接受直接数组,不读取 APP 路径 | +| C20(谱文) | 本轮按后端 PC 实链解决 | PC 控制器实际具备列表、详情、新增、修改、删除并返回 `ArticleVo`;页面不再只开放删除 | | C18 | 阻断 | 未说明可选更新字段的省略、`null`、空串语义 | +| C34 | 阻断非核心展示 | PC 实际未填充 `genealogyNo/genealogyName/senderNickName/senderPhone/bizSummary`;页面兼容空值并始终隐藏手机号,核心通知读取和已读闭环不依赖这些字段 | +| C35 | 阻断分类能力 | 本轮不发送 YAML 未声明的 `categoryId` query,也不提供手填分类 ID | +| C36/C37 | 阻断非核心展示/停用管理 | 本轮不猜封面 URL,并固定提交 `status=0`,避免产生不可重读谱文 | +| C20(相册/照片) | 本轮按后端 PC 实链接决 | PC 控制器具备相册列表、新增、修改、删除和照片列表、新增、删除;页面只从响应取得稳定 ID | +| C38/C39 | 阻断非核心展示/停用管理 | 本轮不猜文件 URL,并固定提交 `status=0`,避免产生不可重读的相册或照片 | +| C20(祭祀活动/祭品) | 本轮按后端 PC 实链接决 | PC 控制器具备活动完整 CRUD 和祭品列表、新增、删除;页面只使用响应中的稳定 ID | +| C16 | 本轮按后端 PC 实链接决 | `members/options` 返回同一家谱成员及真实 `appUserId`;管理员邀约名单只允许选择正常且已绑定账号的成员 | +| C15 | 本轮按后端 PC 实链接决 | `members/options` 是当前家谱真实业务账号的唯一选择源;`SPECIFIED` 只提交正常且已绑定账号成员的 `appUserId` | +| C40/C41 | 阻断非核心展示/停用管理 | 本轮不猜封面 URL,并固定提交 `status=0`,避免产生不可重读活动 | +| C42 | 阻断金额精度/上限 | 前端只执行后端已确认的非负校验,不自行限制小数位和最大金额 | +| C43 | 前端按后端 PC 实链收紧 | 成员角色选择器只允许 `admin/editor/member`;不发送 YAML 中后端拒绝的 `owner/visitor` | +| C44 | 阻断非核心 enrichment | 页面兼容未填充名称,始终隐藏成员、邀请人手机号和内部用户 ID;核心成员管理不依赖这些字段 | +| C45/C46 | 阻断 | 页面不提供世系关系解除,也不把空 `lineagePersonId` 解释为解绑 | 阶段 0 验收标准: @@ -376,12 +422,13 @@ `-1` 显示为“不限”,不能参与普通减法或显示负数。 -配额响应不包含 `genealogyId`,不能替代“我的家谱”。在 C14 完成前: +配额响应不包含 `genealogyId`,不能替代“我的家谱”。当前由 `GET /genealogy/pc/genealogies/mine` 返回完整 `AppGenealogyVo`: -- 所有家谱内页面缺少真实 `genealogyId` 时必须阻止请求; -- 不显示静态家谱卡片作为真实数据; -- 不从 APP 接口获取家谱; -- 不允许用户手工输入家谱 ID。 +- `profile-families.html` 从真实列表生成家谱入口; +- `genealogyId` 在浏览器边界保持字符串,并由 `profile-common.js` 统一读取和传播; +- 所有家谱内页面缺少真实 `genealogyId` 时必须阻止请求并回到家谱选择入口; +- 不显示静态家谱卡片,不从 APP 接口获取家谱,不允许用户手工输入家谱 ID; +- 切换家谱通过进入新的带 `genealogyId` URL 完成整页导航,旧页面模块状态不会复用。 ### 7.5 行政区划 @@ -455,6 +502,17 @@ 4. 点赞、取消、评论、删除成功后以接口返回和重新读取结果校正计数。 5. 删除有回复的评论后保留占位,不从 DOM 直接移除整棵回复。 +2026-07-28 家族圈实施状态: + +- `ApiClient` 的 14 个家族圈 PC 操作已由契约测试锁定;页面首屏只调用 `/feeds/page`,评论和回复分别调用各自的 `/page`。 +- 已新增 `profile-feed-detail.html`,以 `genealogyId + feedId` 形成稳定详情深链;列表、详情和编辑链接都保留字符串 ID。 +- 列表和详情消费 `AppFamilyFeedVo` 的发布人、点赞状态与计数、评论计数、置顶、状态和时间字段;评论消费 `FamilyFeedCommentVo` 的归属、父评论、层级、删除占位、回复计数和时间字段。 +- `GET /genealogy/pc/genealogies/{genealogyId}` 的 `canEditContent/canManage` 控制编辑和高级设置,`GET /genealogy/pc/auth/profile` 的 `userId` 结合响应中的发布人/评论人 ID 控制本人删除按钮;后端仍执行最终权限校验。 +- 发布图片只允许文件选择;`mediaOssIds` 为隐藏的上传派生字段,多文件结果使用英文逗号连接,不允许手填 OSS ID。 +- 新增、修改、点赞、取消点赞、评论和删除均有同资源动作锁;成功后重新读取详情、分页列表或评论分页以校正页面状态。 +- `status=1` 使用后端定义的“停用”语义,不再显示为“草稿”。C21、C22 未解决前,图片缩略图回显和停用记录恢复仍属于后端阻断,不宣称这两项闭环完成。 +- 已在真实 Chrome 登录态验证:当前账号的 `/genealogies/mine` 返回空列表;无家谱上下文时家族圈不发业务请求,所有发布入口统一改写到 `profile-families.html?next=profile-feed-edit.html`。发布表单实测为多文件选择、隐藏 OSS ID、普通用户隐藏且禁用管理字段。由于账号没有家谱,本轮不能进行真实动态列表/详情读取或写操作联调。 + ### 7.7 字辈谱 `profile-generation.html` 同时提供“单条维护”和“批量维护”。 @@ -486,6 +544,14 @@ 普通展示列表使用正常字辈接口;管理页面使用 management 接口以包含停用项。 +当前实现状态: + +- 页面先读取家谱详情,以 `canEditContent` 决定调用正常列表还是 management 列表;无维护权限时不渲染新增、编辑、停用和批量维护控件,后端继续执行最终权限校验。 +- `genealogyId` 只来自家谱上下文,`poemId` 只来自列表响应;所有业务 ID 在浏览器边界保持字符串,不提供手工 ID 输入。 +- 单条新增、修改和状态切换成功后重新读取列表;批量维护严格执行 preview → 当前请求体签名确认 → save → 重新读取列表。 +- 批量预览逐项校验 `action`、状态、必需的新旧文本和内部 ID,并核对四类汇总计数;勾选 `disableMissing` 时保存确认明确说明只停用后续世代、不删除历史。 +- 已在真实 Chrome 登录态验证无家谱上下文分支:不发送字辈业务请求,新增按钮和批量面板隐藏,家谱上下文入口统一返回家谱选择页。当前账号家谱列表为空,因此正常/management 列表、单条写入和批量写入仍缺少真实家谱数据联调证据。 + ### 7.8 世系人物 全部操作集中在 `profile-tree.html`,详情和编辑可使用抽屉/弹窗,但所有字段都要有明确控件。 @@ -493,7 +559,7 @@ | 字段 | 必填性 | 类型 | 页面处理 | | --- | --- | --- | --- | | `bindingMode` | 必填 | S | NONE 不绑定、SELF 当前账号、SPECIFIED 指定账号 | -| `appUserId` | SPECIFIED 时必填 | S/I | 从真实业务用户选项选择;没有接口时禁用 SPECIFIED,禁止文本 ID | +| `appUserId` | SPECIFIED 时必填 | S/I | 从当前家谱 `members/options` 的正常且已绑定账号成员中选择;禁止文本 ID | | `personNo` | 可选 | U/A | 高级设置可填;留空由服务端生成 | | `name` | 必填 | U | 姓名 | | `aliasName` | 可选 | U | 别名/曾用名 | @@ -543,6 +609,19 @@ `LineagePersonTreeView` 补全前,不得继续依靠多个字段 fallback 猜树结构。 +当前实现状态: + +- 已接入 PC 世系树、普通列表、分页列表、人物选项、详情、新增、修改、逻辑停用,以及父母、子女、兄弟姐妹、配偶四类关系新增;成功写入后统一重新读取树、列表、分页和选项,返回稳定 `personId` 时再读取详情。 +- 页面先读取家谱详情,`canEditContent` 只控制新增、编辑、停用和关系维护;树、列表、详情、筛选和分页保持只读可用,后端继续执行最终查看和编辑权限校验。 +- `LineagePersonBody` 的全部字段均有明确来源:世代从正常字辈列表选择并自动回填字辈,父母从世系人物选项选择,头像通过统一上传组件产生隐藏 `avatarOssId`,所有业务 ID 在浏览器边界保持字符串且不可手填。 +- 新增和编辑人物开放 `NONE`、`SELF`、`SPECIFIED`;`SPECIFIED` 选择器只消费当前家谱 `members/options`,过滤未绑定账号、停用成员和当前账号,不显示手机号,不允许手填 `appUserId`。 +- 编辑已绑定其他账号的人物时,用详情响应的 `appUserId` 精确匹配同一成员选项;真实选项不存在时不伪造回填,保存会被前端校验阻止。 +- 分页查询已接入 `keyword`、`generation`、`personStatus`、`pageNum/pageSize`;详情展示家谱、绑定账号、父母、配偶、出生、逝世、安葬、人物状态、简介和备注等响应字段。 +- DELETE 确认明确使用“停用”语义,并提示存在正常子女时后端会拒绝;页面没有伪造解除关系或物理删除。 +- 已在真实 Chrome 登录态验证无家谱上下文分支:不发送世系业务请求,读区域显示选择家谱提示,新增、关系维护和完整表单隐藏,分页与配偶专用字段不误显示,所有家谱链接返回家谱选择页。当前账号家谱列表为空,因此真实树、列表、详情和写操作仍缺少有数据账号联调证据。 +- C18 仍适用于本模块:可选字段清空语义未明确,本轮仅提交非空值,不宣称可选字段清空闭环。 +- `SPECIFIED` 账号绑定相关聚焦测试 42/42 通过;真实账号页面验证统一留到阶段 6 收尾执行。 + ### 7.9 视频 计划新增 `profile-video-edit.html`,`profile-video.html` 负责列表。 @@ -560,7 +639,25 @@ 流程:选视频 → 分片上传 → 得到 `videoOssId` → 可选封面上传 → 填标题说明 → 保存 → 重新读取详情。 -当前视频列表、详情、新增和修改均返回泛型对象/列表。C17 完成前可以完成静态表单布局和 `ApiClient` 契约测试,但不得把真实 CRUD 页面标记为已完成。 +YAML 的视频响应仍使用泛型 `ObjectResult` / `ListResult`,但后端已存在独立 `PcVideoController`,其 PC method/path 与 YAML 一致,并明确返回 `VideoVo`。根据本轮“后端源码可作为可靠补充契约”的约定,页面响应字段由该 PC 控制器及 `VideoVo` 冻结: + +- I:`videoId`、`genealogyId`、`coverOssId`、`videoOssId`、`publisherUserId`; +- R:`genealogyNo`、`genealogyName`、`surname`、`publisherNickName`、`publisherPhone`、`publishTime`、`viewCount`、`remark`; +- U/R:`videoTitle`、`videoDesc`; +- F/A/R:`durationSeconds`; +- U/S/R:`sortOrder`; +- A/R:`status`,当前固定提交 `0`。 + +当前接入状态: + +- `profile-video.html` 已接入正常视频列表、详情读取、权限化编辑/删除入口、空错状态和无家谱上下文分支; +- 新增 `profile-video-edit.html`,新增和修改只提交 `VideoBody` 七个字段;视频与封面都通过统一分片上传产生隐藏 OSS ID,不允许手填; +- 选择视频文件后由浏览器媒体元数据自动读取时长,`durationSeconds` 只读; +- 保存成功后使用稳定 `videoId` 重新读取详情,再返回列表;删除确认明确会释放视频和封面文件引用,删除后重读列表; +- 后端 `BigNumberSerializer` 对超出 JavaScript 安全整数范围的 `Long` 输出字符串,对安全整数输出 number;前端接受两种输入并统一保留为字符串; +- C23 未解决前只显示文件“已上传”,不猜测 OSS URL,不宣称播放或封面预览完成; +- C24 未解决前不开放 `status=1`,避免产生无法重新读取和恢复的停用视频; +- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:视频列表不发业务请求,发布入口及编辑表单保持隐藏,所有上下文链接返回家谱选择页,控制台无错误。当前账号没有家谱,真实列表、详情、上传和 CRUD 写入仍待有数据账号验证。 ### 7.10 贺礼邀约与祭祀 @@ -583,6 +680,8 @@ “我的邀请”接口有完整响应,可独立实现接受/拒绝流程。管理端替换受邀人依赖 C16,且必须先有真实活动 ID。 +当前接入状态:`profile-gift.html` 已使用 `GET /genealogy/pc/genealogies/ceremony-invitations/mine` 展示当前账号的邀请列表和响应内详情;仅 `PENDING` 邀请显示接受/拒绝,并向 `PUT /genealogy/pc/genealogies/{genealogyId}/ceremonies/{ceremonyId}/invitations/me` 提交唯一字段 `inviteStatus`,成功后重读列表。内部 ID 和坐标不直接展示,坐标仅用于生成地图链接。2026-07-29 使用真实账号联调,PC “我的邀请”接口成功返回空数组,页面空状态和鉴权正常;由于当前账号没有待响应邀请,真实接受/拒绝写入仍待有邀请数据时复核。后端仓库中的同组 `AppCeremonyController` 仍以 `/genealogy/app/genealogies` 为基础路径,与冻结 YAML 的 `/genealogy/pc` 不一致,但线上 PC mine 路径已实际可用,前端继续以 YAML 为唯一契约,不回退 APP 路径。 + 当前没有祭祀活动列表、详情、新增、修改和献礼列表/新增接口。现有 `profile-gift-edit.html` 中的 `ceremonyType`、`ceremonyTitle`、`ceremonyTime`、`location`、`ceremonyDesc`、`coverOssId` 不属于任何现有 PC 写入 DTO,必须继续保持禁用,不得向猜测路径提交。 两条删除接口只能在后端补齐列表和详情、产生稳定 ID 后开放: @@ -605,9 +704,27 @@ | `remindTime` | 可选 | S | 日期时间控件 | | `mediaOssIds` | 可选 | F/I | 多附件上传 | | `sortOrder` | 可选 | U/S | 管理高级设置 | -| `status` | 可选 | S | 0 正常、1 停用 | +| `status` | 可选 | A | 当前固定提交 0 正常;C26 解决前不开放 1 停用 | -列表、详情、新增和修改响应仍是泛型对象。补齐 `GrowthRecordView` 前不得展示原始 JSON,也不得猜 `recordId` 开启编辑和删除。 +YAML 的列表、详情、新增和修改响应仍是泛型对象,但后端已存在独立 `PcGrowthRecordController`,其 PC method/path 与 YAML 一致,并明确返回 `GrowthRecordVo`。根据本轮“后端源码可作为可靠补充契约”的约定,页面响应字段由该 PC 控制器及 `GrowthRecordVo` 冻结: + +- I:`recordId`、`genealogyId`、`appUserId`、`lineagePersonId`、`mediaOssIds`; +- R:`genealogyNo`、`genealogyName`、`surname`、`appUserNickName`、`appUserPhone`、`lineagePersonNo`、`lineagePersonName`、`remark`; +- U/R:`recordType`、`recordTitle`、`recordContent`; +- S/R:`recordDate`、`remindTime`; +- U/S/R:`sortOrder`; +- A/R:`status`,当前固定提交 `0`。 + +当前接入状态: + +- `profile-growth.html` 已接入当前账号成长记录列表、详情、编辑和删除入口;列表接口不传后端额外的 `all` 参数,只消费当前 YAML 声明的请求; +- `profile-growth-edit.html` 已接入新增和修改;`lineagePersonId` 只从真实世系人物选项接口选择,`recordId` 只来自列表响应或 URL,均按字符串处理且不能手填; +- `recordType` 后端 DTO 和服务均为不带枚举的自由字符串,因此保留可选文本输入;`recordDate` 提交 `yyyy-MM-dd`,`remindTime` 由 `datetime-local` 转为后端 `Date` 接受的 `yyyy-MM-dd HH:mm:ss`; +- 多附件通过统一分片上传产生隐藏 `mediaOssIds`,支持保留、追加和清除附件选择,不显示 OSS ID; +- 新增和修改成功后必须使用稳定 `recordId` 重读同一条详情并校验身份,再返回列表;删除成功后重读列表,确认文案明确逻辑删除且会释放附件引用; +- C18 未解决前,标题以外可选文本、人物和日期字段清空不宣称闭环;前端对空可选字段保持省略,附件清除使用后端已明确的空引用处理; +- C25 未解决前只显示附件数量,不猜测 OSS URL、文件名或下载地址;C26 未解决前固定 `status=0`,不产生无法重新读取和恢复的停用记录; +- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:列表和详情不发成长记录业务请求,编辑表单及页头保存按钮隐藏,入口统一返回家谱选择页。当前账号没有家谱,真实列表、详情、世系选项、上传和 CRUD 写入仍待有数据账号验证。 ### 7.12 亲友记录 @@ -619,13 +736,33 @@ | `relationName` | 可选 | U | 关系名称 | | `eventName` | 可选 | U | 事件名称 | | `eventTime` | 可选 | S | 日期时间选择器 | -| `giftAmount` | 可选 | U | 金额输入,明确精度和非负规则后校验 | +| `giftAmount` | 可选 | U | 金额输入;只提交 JSON 序列化后数值不变的有限数字,C27 解决前不猜精度、范围或非负规则 | | `recordContent` | 可选 | U | 内容 | | `mediaOssIds` | 可选 | F/I | 多附件上传 | | `sortOrder` | 可选 | U/S | 管理高级设置 | -| `status` | 可选 | S | 0 正常、1 停用 | +| `status` | 可选 | A | 当前固定提交 0 正常;C29 解决前不开放 1 停用 | -补齐 `RelativeRecordView` 前只完成页面字段布局和请求契约,不开放真实列表、编辑和删除。 +YAML 的列表、详情、新增和修改响应仍是泛型对象,但后端已存在独立 `PcRelativeRecordController`,其 PC method/path 与 YAML 一致,并明确返回 `RelativeRecordVo`。根据本轮“后端源码可作为可靠补充契约”的约定,页面响应字段由该 PC 控制器及 `RelativeRecordVo` 冻结: + +- I:`relativeId`、`genealogyId`、`appUserId`、`mediaOssIds`; +- R:`genealogyNo`、`genealogyName`、`surname`、`appUserNickName`、`appUserPhone`、`remark`; +- U/R:`relativeName`、`relationName`、`eventName`、`recordContent`; +- S/R:`eventTime`; +- U/R:`giftAmount`,后端 `BigDecimal` 响应按字符串保留; +- U/S/R:`sortOrder`; +- A/R:`status`,当前固定提交 `0`。 + +当前接入状态: + +- `profile-relative.html` 已接入当前账号亲友记录列表、详情、编辑和删除入口;列表接口不传后端额外的 `all` 参数,只消费当前 YAML 声明的请求; +- `profile-relative-edit.html` 已接入新增和修改;`relativeId` 只来自列表响应或 URL,按字符串处理且不能手填; +- `eventTime` 使用 `datetime-local`,提交前转为后端 `Date` 接受的 `yyyy-MM-dd HH:mm:ss`,并校验真实月日和时分秒; +- `giftAmount` 请求按 YAML 的 `number` 提交;提交前比较用户十进制输入与 `JSON.stringify(Number(value))` 的精确数值,序列化会改值的超大或高精度金额直接拒绝。后端 `BigDecimal` 响应按原字符串展示,不转回 `Number`; +- 多附件通过统一分片上传产生隐藏 `mediaOssIds`,支持保留、追加和清除附件选择,不显示 OSS ID; +- 新增和修改成功后必须使用稳定 `relativeId` 重读同一条详情并校验身份,再返回列表;删除成功后重读列表,确认文案明确逻辑删除且会释放附件引用; +- C18 未解决前,可选文本、时间和金额字段的清空不宣称闭环;附件清除使用后端已明确的空引用处理; +- C27 未解决前不增加两位小数、最大金额或非负限制;C28 未解决前只显示附件数量;C29 未解决前固定 `status=0`,并拒绝编辑停用响应,避免意外恢复; +- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:列表和详情不发亲友记录业务请求,编辑表单及页头保存按钮隐藏,入口统一返回家谱选择页。当前账号没有家谱,真实列表、详情、上传和 CRUD 写入仍待有数据账号验证。 ### 7.13 备忘录 @@ -635,13 +772,31 @@ | --- | --- | --- | --- | | `memoTitle` | 必填 | U | 标题 | | `memoContent` | 可选 | U | 内容 | -| `remindTime` | 可选 | S | 日期时间 | -| `completed` | 可选 | S | 应为开关;C11 明确提交值前保持禁用 | -| `mediaOssIds` | 可选 | F/I | 多附件上传 | -| `sortOrder` | 可选 | U/S | 管理高级设置 | -| `status` | 可选 | S | 0 正常、1 停用 | +| `remindTime` | 可选 | S | `datetime-local` 选择,提交为 `yyyy-MM-dd HH:mm:ss` 并校验真实日期 | +| `completed` | 可选 | S | PC 后端已核验:`0` 未完成、`1` 已完成;创建默认 `0` | +| `mediaOssIds` | 可选 | F/I | 多附件上传自动产生;隐藏保存,允许追加和清除,不允许手填 | +| `sortOrder` | 可选 | U/S | 安全整数;创建默认 `0` | +| `status` | 可选 | I/A | 页面固定隐藏提交 `0`;不开放不可重读的停用状态 | -`completed` 表示业务完成状态,`status` 表示记录正常/停用,两者不能合并。补齐 `MemoView` 前不得猜 `memoId` 或响应字段。 +PC `MemoVo` 响应字段及页面用途: + +| 字段 | 类型 | 标记 | 页面处理 | +| --- | --- | --- | --- | +| `memoId`、`genealogyId`、`appUserId` | Long | I | 仅从 PC 响应取得并按十进制字符串保存;用于详情、编辑、删除和写后重读,不显示、不手填 | +| `genealogyNo`、`genealogyName`、`surname` | string | R | 家谱上下文只读信息;当前备忘录页不重复展示 | +| `appUserNickName` | string | R | 详情显示记录人 | +| `appUserPhone` | string | I | 隐私字段,不渲染 | +| `memoTitle`、`memoContent`、`remindTime`、`completed` | string | R | 列表摘要、详情展示和编辑回填 | +| `mediaOssIds` | string | I | 只统计附件数量并回填隐藏上传字段,不暴露原始 OSS ID | +| `sortOrder` | Long | I | 编辑回填;列表顺序由后端负责 | +| `status` | string | I | 仅接受正常记录 `0`;停用响应拒绝进入编辑器 | +| `remark` | string | R | 详情只读展示,MemoBody 不含此字段,因此不提交 | + +`completed` 表示业务完成状态,`status` 表示记录正常/停用,两者不能合并。列表、详情、新增、修改和删除只调用 `/genealogy/pc/genealogies/{genealogyId}/memos` 及其 `/{memoId}` 子路径;列表不发送后端管理专用的 `all` 查询。新增/修改成功后使用稳定 `memoId` 重读同一详情,删除后重读列表。 + +- C18 未解决前,可选内容和提醒时间的清空不宣称闭环;附件清除使用后端已明确的空引用处理; +- C30 未解决前只显示附件数量;C31 未解决前固定 `status=0`,拒绝编辑停用响应,避免意外恢复; +- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:列表和详情显示明确提示,编辑表单及页头保存按钮隐藏,控制台无错误。当前账号没有可用家谱,真实列表、详情、上传和 CRUD 写入仍待有数据账号验证。 ### 7.14 功德记录 @@ -653,12 +808,33 @@ | `meritType` | 可选 | S | donation 捐赠、repair 修祠、public 公益、other 其他 | | `meritTitle` | 必填 | U | 标题 | | `meritContent` | 可选 | U | 内容 | -| `amount` | 可选 | U | 金额 | -| `meritTime` | 可选 | S | date-time | -| `sortOrder` | 可选 | U/S | 管理高级设置 | -| `status` | 可选 | S | 0 正常、1 停用 | +| `amount` | 可选 | U | YAML `number`;提交前拒绝 JSON 序列化会改变精确数值的输入,不自行限制小数位、范围或正负 | +| `meritTime` | 可选 | S | `datetime-local` 选择,提交为后端示例和 `Date` 接受的 `yyyy-MM-dd HH:mm:ss` | +| `sortOrder` | 可选 | U/S | 安全整数;创建默认 `0` | +| `status` | 可选 | I/A | 页面固定隐藏提交 `0`;不开放不可重读的停用状态 | -现有页面错误地使用了 `service`,但契约枚举中不存在该值;必须删除 `service`,增加 `repair` 和 `public`。现有页面还缺少 `sortOrder`、`status`。 +PC `MeritRecordVo` 响应字段及页面用途: + +| 字段 | 类型 | 标记 | 页面处理 | +| --- | --- | --- | --- | +| `meritId`、`genealogyId`、`appUserId` | Long | I | 仅从 PC 响应取得并按十进制字符串保存;用于详情、编辑、删除和写后重读,不显示、不手填 | +| `genealogyNo`、`genealogyName`、`surname` | string | R | 家谱上下文只读信息;当前功德页不重复展示 | +| `appUserNickName` | string | R | 详情显示记录人 | +| `appUserPhone` | string | I | 隐私字段,不渲染 | +| `donorName`、`meritType`、`meritTitle`、`meritContent` | string | R | 列表摘要、详情展示和编辑回填;HTML 内容转义后展示 | +| `amount` | BigDecimal | R | 按响应原始字符串展示,不转回 `Number` | +| `meritTime` | Date | R | 列表、详情展示和 `datetime-local` 编辑回填 | +| `sortOrder` | Long | I | 编辑回填;列表顺序由后端负责 | +| `status` | string | I | 仅接受正常记录 `0`;停用响应拒绝进入编辑器 | +| `remark` | string | R | 详情只读展示,`MeritRecordBody` 不含此字段,因此不提交 | + +现有页面中的契约外 `service` 已删除,补齐 `repair` 和 `public`;由于 `MeritRecordBody` 没有附件字段,功德页不借用其他模块的 `mediaOssIds` 或上传控件。ApiClient 的新增和修改方法再次按 8 个 YAML 字段白名单过滤,调用者额外传入 `appUserId`、`mediaOssIds` 等字段也不会发送。 + +- 列表、详情、新增、修改和删除只调用 `/genealogy/pc/genealogies/{genealogyId}/merit-records` 及其 `/{meritId}` 子路径; +- 新增/修改成功后使用稳定 `meritId` 重读同一详情,删除成功后重读列表; +- C18 未解决前,可选内容、金额和时间的清空不宣称闭环;C32 未解决前不增加两位小数、最大金额或非负限制;C33 未解决前固定 `status=0` 并拒绝编辑停用响应; +- PC 写操作最终由 `assertCanEditContent` 鉴权;当前 View 没有 `canEdit/canDelete`,页面保留操作入口并明确处理 403,不猜角色字段; +- 2026-07-29 已在真实登录 Chrome 验证无家谱上下文:列表和详情显示明确提示,编辑表单及页头保存按钮隐藏,契约外类型和附件控件不存在,控制台无错误。当前账号没有可用家谱,真实列表、详情和 CRUD 写入仍待有数据账号验证。 列表、详情、新增和修改仍是泛型响应。补齐 `MeritRecordView` 后再开放完整 CRUD。 @@ -666,28 +842,276 @@ 页面:`profile-messages.html`。 -| 字段 | 必填性 | 类型 | 页面处理 | +接口冻结: + +- `GET /genealogy/pc/notifications`:可选 query `readStatus=0|1`,返回非分页直接数组;全部通知时省略 query。 +- `GET /genealogy/pc/notifications/unread-count`:返回非负 int64 未读数量。 +- `GET /genealogy/pc/notifications/{notificationId}`:读取当前账号可见的正常通知详情。 +- `POST /genealogy/pc/notifications/{notificationId}/read`:将当前账号的一条正常未读通知标记已读。 +- `POST /genealogy/pc/notifications/read-all`:将当前账号全部未读通知标记已读。 +- 五个接口均由 `ApiClient` 自动携带 Bearer 和 `clientid`;页面不采集 Header。YAML 未声明业务错误码;前端统一处理未登录/401、403、404、业务失败和网络失败。 + +| 字段 | 必填性 / 类型 | 标记 | 页面用途、来源与提交时机 | | --- | --- | --- | --- | -| `readStatus` | 可选 query | S | 全部=省略、未读=0、已读=1 | -| `notificationId` | 单条已读必填 path | I/A | 从被点击通知记录带入 | -| 未读数量 `data` | 响应 | R | 顶部徽标,int64 按安全数值/字符串处理 | +| query `readStatus` | 可选 string;枚举 `0/1` | S | 消息页筛选选择;全部=省略、未读=0、已读=1;切换筛选时提交 | +| path `notificationId` | 详情/单条已读必填 int64 | I/A | 从列表响应的稳定十进制字符串取得;点击详情或“标记已读”时自动进入 path,不允许手填或转 `Number` | +| `notificationId` | 响应 int64 | I | 列表行内部关联详情和已读动作;不在可见页面展示 | +| `genealogyId` | 响应 int64,可空 | I | 仅保留为响应内部上下文;不展示、不提交、不允许手填 | +| `genealogyNo` | 响应 string | R | 详情只读候选字段;当前 PC 未填充,不作为功能依赖 | +| `genealogyName` | 响应 string | R | 详情只读展示;当前 PC 可能为空 | +| `senderUserId` | 响应 int64,可空 | I | 内部关联字段;不展示、不提交 | +| `senderNickName` | 响应 string | R | 详情只读展示;当前 PC 可能为空 | +| `senderPhone` | 响应 string | I | 隐私字段,页面始终隐藏 | +| `noticeType` | 响应 string,无枚举/默认值 | R | 列表和详情只读展示;不据此猜测跳转 | +| `noticeTitle` | 响应 string | R | 列表和详情标题,转义后展示 | +| `noticeContent` | 响应 string | R | 列表摘要和详情正文,转义后展示 | +| `bizType` | 响应 string,无枚举/默认值 | R | 详情只读展示;不据此拼业务 URL | +| `bizId` | 响应 int64,可空 | I | 内部业务关联字段;PC 未提供目标路由契约前不展示、不跳转 | +| `bizSummary` | 响应 string | R | 详情只读展示;当前 PC 可能为空 | +| `publishTime` | 响应 string `date-time` | R | 列表和详情只读展示,不由页面提交 | +| `readStatus` | 响应 string;枚举 `0/1` | R | 0=未读、1=已读;仅正常未读记录显示单条已读按钮 | +| `readTime` | 响应 string `date-time`,可空 | R | 详情只读展示,未读时为空 | +| `status` | 响应 string;枚举 `0/1` | I | 仅用于确认正常状态 `0` 才能执行单条已读,不提供页面编辑 | +| `remark` | 响应 string | R | 详情只读展示 | +| 未读数量 `data` | 响应 int64 | R | 顶部计数;只接受非负安全整数 | -通知列表当前是 `ListResult`,没有通知 ID、标题、正文、类型、时间、已读状态和目标深链。补齐 `NotificationView` 前: +响应和权限边界: -- 可以调用未读数量和全部已读; -- 不得展示原始对象; -- 不得猜 `notificationId` 开放单条已读; -- 不得根据未声明字段拼详情跳转。 +- YAML 的详情对象没有声明字段级 `required`、长度、范围或默认值;页面不补造限制,并兼容可选展示字段为空。 +- PC 后端按当前 `APP_USER` 登录账号隔离通知,只返回 `status=0` 的正常通知;列表按接收记录倒序,非分页。 +- 详情和单条已读必须命中当前账号的接收记录;重复单条已读保持已读状态,页面仍做写操作防重复。 +- C34 未解决前,`genealogyNo/genealogyName/senderNickName/senderPhone/bizSummary` 不能被视为稳定可用;手机号即使后端补齐也不展示。 +- 页面不展示原始 JSON,不使用 APP 接口,不开放删除,不根据 `bizType/bizId` 猜业务深链。 +- 2026-07-29 已在真实登录 Chrome 验证当前账号:未读数为 0,全部和仅未读筛选均成功同步,空列表/空详情状态正确。账号暂无通知,因此有数据详情和单条/全部已读真实写入仍待有数据时复核。 -### 7.16 当前只有删除能力的模块 +### 7.16 谱文 -| 模块 | 当前操作 | 页面策略 | +页面:`profile-article.html`、`profile-article-edit.html`。 + +| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 | +| --- | --- | --- | --- | +| `categoryId` | 可选 int64 | I/S | C35 未解决前省略;没有真实分类选项时不提供输入框 | +| `articleTitle` | 必填 string | U | 编辑页标题;保存时提交 | +| `articleSummary` | 可选 string | U | 编辑页摘要;非空时提交 | +| `coverOssId` | 可选 string/int64 | F/I | 选择封面并完成统一上传后自动产生;隐藏保存,禁止手填 | +| `articleContent` | 必填 string | U | wangEditor 正文;同步到 textarea 后提交 | +| `authorName` | 可选 string | U | 编辑页落款;非空时提交 | +| `sortOrder` | 可选 int64 | U/S | 编辑页安全整数;非空时提交 | +| `status` | 可选 string `0/1` | A/I | C37 未解决前固定隐藏提交 `0` | +| `genealogyId`、`articleId` | path int64 | I/A | 家谱上下文和列表/详情响应;按十进制字符串处理,不显示、不手填 | + +PC `ArticleVo` 响应字段及页面用途: + +| 字段 | 类型 | 标记 | 页面处理 | +| --- | --- | --- | --- | +| `articleId`、`genealogyId` | Long | I | 列表、详情、编辑、删除和写后重读的稳定 ID | +| `genealogyNo`、`genealogyName`、`surname` | string | R | 家谱只读上下文 | +| `categoryId` | Long,可空 | I | 仅内部保留;无分类选项源时不回填为可编辑 ID | +| `categoryName`、`categoryCode` | string | R | 详情分类只读展示 | +| `articleTitle`、`articleSummary`、`articleContent`、`authorName` | string | R/U | 列表、详情展示和编辑回填;可见内容均转义 | +| `coverOssId` | Long,可空 | I | 编辑隐藏回填;C36 未解决前不拼封面 URL | +| `publishTime`、`viewCount` | Date/Long | R | 详情发布时间和浏览量 | +| `sortOrder` | Long | I/U | 编辑回填;列表顺序由后端负责 | +| `status` | string `0/1` | I | 只允许正常记录进入编辑器 | +| `remark` | string | R | 详情只读展示,不进入 ArticleBody | + +接入边界: + +- 列表、详情、新增、修改和删除只调用 `/genealogy/pc/genealogies/{genealogyId}/articles` 及其 `/{articleId}` 子路径; +- YAML 响应仍为泛型包装,但 PC 控制器明确返回 `ArticleVo`,前端严格校验该 VO,不读取 APP 路径; +- 新增/修改成功后使用稳定 `articleId` 重读同一详情;删除后重读列表;所有写操作防重复; +- 页面先读取家谱详情,只有 `canEditContent/canManage` 显示新增、编辑和删除;后端继续执行最终权限校验; +- C35 未解决前不开放分类筛选/编辑;C36 未解决前不猜封面地址;C37 未解决前不开放停用; +- 相关聚焦测试 49/49 通过;真实账号页面验证统一留到阶段 6 收尾执行。 + +### 7.17 相册与照片 + +页面:`profile-album.html`、`profile-album-edit.html`、`profile-album-detail.html`。 + +相册请求字段: + +| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 | +| --- | --- | --- | --- | +| `albumName` | 必填 string | U | 编辑页相册名称;保存时提交 | +| `albumDesc` | 可选 string | U | 编辑页描述;非空时提交 | +| `coverOssId` | 可选 string/int64 | F/I | 选择封面并完成统一上传后自动产生;隐藏保存,禁止手填 | +| `sortOrder` | 可选 int64 | U/S | 编辑页安全整数;非空时提交 | +| `status` | 可选 string `0/1` | A/I | C39 未解决前固定隐藏提交 `0` | +| `genealogyId`、`albumId` | path int64 | I/A | 家谱上下文及相册响应;按十进制字符串处理,不显示、不手填 | + +照片请求字段: + +| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 | +| --- | --- | --- | --- | +| `ossId` | 必填 string/int64 | F/I | 选择照片并完成统一上传后自动产生;隐藏保存,禁止手填 | +| `photoTitle`、`photoDesc`、`photographer` | 可选 string | U | 照片表单;非空时提交 | +| `shootTime` | 可选 date | S | 日期选择器;非空时提交 `YYYY-MM-DD` | +| `sortOrder` | 可选 int64 | U/S | 照片表单安全整数;非空时提交 | +| `status` | 可选 string `0/1` | A/I | C39 未解决前固定隐藏提交 `0` | +| `albumId`、`photoId` | path int64 | I/A | 相册和照片响应;按十进制字符串处理,不显示、不手填 | + +PC 响应字段及页面用途: + +| 对象 | 字段 | 标记 | 页面处理 | +| --- | --- | --- | --- | +| `AlbumVo` | `albumId`、`genealogyId` | I | 列表、编辑、详情、删除和写后重读的稳定 ID | +| `AlbumVo` | `genealogyNo`、`genealogyName`、`surname` | R | 家谱只读上下文 | +| `AlbumVo` | `albumName`、`albumDesc` | R/U | 列表/详情展示和编辑回填;可见内容均转义 | +| `AlbumVo` | `coverOssId` | F/I | 编辑隐藏回填;C38 未解决前不拼封面 URL | +| `AlbumVo` | `photoCount` | R | 列表和详情照片数量 | +| `AlbumVo` | `sortOrder`、`status` | I/U | 编辑回填;只允许正常记录进入编辑器 | +| `AlbumVo` | `remark` | R | 详情只读展示,不进入请求 Body | +| `AlbumPhotoVo` | `photoId`、`genealogyId`、`albumId` | I | 照片删除和写后重读的稳定 ID | +| `AlbumPhotoVo` | `genealogyNo`、`genealogyName`、`surname`、`albumName` | R | 家谱与相册只读上下文 | +| `AlbumPhotoVo` | `ossId` | F/I | 文件引用;C38 未解决前不拼照片 URL | +| `AlbumPhotoVo` | `photoTitle`、`photoDesc`、`photographer`、`shootTime` | R/U | 照片列表展示;可见内容均转义 | +| `AlbumPhotoVo` | `sortOrder`、`status` | I | 列表顺序和正常状态校验 | +| `AlbumPhotoVo` | `remark` | R | 只读展示,不进入请求 Body | + +接入边界: + +- 相册只调用 `/genealogy/pc/genealogies/{genealogyId}/albums` 及其 `/{albumId}` 子路径;照片只调用该相册下的 `/photos` 及 `/{photoId}`; +- 后端没有单独的相册详情接口,详情和编辑页必须重读相册列表并用响应中的稳定 `albumId` 精确匹配; +- YAML 响应仍为泛型包装,前端只接受后端 PC 控制器实际返回的 `AlbumVo` / `AlbumPhotoVo` 直接数组,不读取 APP 路径; +- 新增/修改相册后重读相册列表;新增/删除照片后重读照片列表;所有写操作防重复; +- 页面先读取家谱详情,只有 `canEditContent/canManage` 显示新增、编辑、删除和照片维护; +- C38 未解决前不猜文件地址;C39 未解决前不开放停用; +- 相关聚焦测试 49/49 通过;真实账号页面验证统一留到阶段 6 收尾执行。 + +### 7.18 祭祀活动、祭品与管理员受邀名单 + +页面:`profile-ceremony.html`、`profile-gift-edit.html`、`profile-ceremony-detail.html`;当前账号收到的邀请继续由 `profile-gift.html` 负责。 + +活动请求字段: + +| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 | +| --- | --- | --- | --- | +| `ceremonyType` | 必填 string,无枚举 | U | 编辑页活动类型;保存时提交,不借用其他端枚举 | +| `ceremonyTitle` | 必填 string | U | 编辑页标题;保存时提交 | +| `ceremonyDesc` | 可选 string | U | 编辑页说明;非空时提交 | +| `ceremonyTime` | 可选 date-time | S | 日期时间选择器;转换为后端 Date 可解析格式后提交 | +| `location` | 可选 string | U | 编辑页地点名称;非空时提交 | +| `locationAddress` | 可选 string,最长 300 | U | 编辑页详细地址;非空时提交 | +| `longitude` | 可选 number,`[-180, 180]` | U/S | 与纬度同时填写并保存 | +| `latitude` | 可选 number,`[-90, 90]` | U/S | 与经度同时填写并保存 | +| `coverOssId` | 可选 string/int64 | F/I | 选择封面并完成统一上传后自动产生;隐藏保存,禁止手填 | +| `sortOrder` | 可选 int64 | U/S | 编辑页安全整数;非空时提交 | +| `status` | 可选 string `0/1` | A/I | C41 未解决前固定隐藏提交 `0` | +| `genealogyId`、`ceremonyId` | path int64 | I/A | 家谱上下文和活动响应;按十进制字符串处理,不显示、不手填 | + +祭品与受邀名单请求字段: + +| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 | +| --- | --- | --- | --- | +| `giverName` | 可选 string | U | 祭品表单赠送人姓名;非空时提交 | +| `giftAmount` | 必填 number,后端要求非负 | U | 祭品表单金额;添加祭品时提交;C42 未解决前不限制精度和最大值 | +| `giftMessage` | 可选 string | U | 祭品留言;非空时提交 | +| `inviteeUserIds` | 必填、唯一 int64 数组,可为空 | S/I | 从 `members/options` 的正常且已绑定账号成员多选产生;更新名单时提交完整数组,禁止手填 | +| `giftId`、`invitationId`、`inviteeUserId` | path/response int64 | I/A | 只从 PC 响应取得;用于删除、名单匹配和写后重读,不显示原值 | + +PC 响应字段及页面用途: + +| 对象 | 字段 | 标记 | 页面处理 | +| --- | --- | --- | --- | +| `CeremonyVo` | `ceremonyId`、`genealogyId`、`sponsorUserId` | I | 活动 CRUD、详情和写后重读的稳定 ID | +| `CeremonyVo` | `genealogyNo`、`genealogyName`、`surname` | R | 家谱只读上下文 | +| `CeremonyVo` | `sponsorNickName` | R | 发起人只读展示 | +| `CeremonyVo` | `sponsorPhone` | I | 隐私字段,始终不展示 | +| `CeremonyVo` | `ceremonyType`、`ceremonyTitle`、`ceremonyDesc`、`ceremonyTime`、`location`、`locationAddress` | R/U | 列表/详情展示和编辑回填;可见内容均转义 | +| `CeremonyVo` | `longitude`、`latitude` | I/S | 仅用于生成安全地图链接,不直接展示数值 | +| `CeremonyVo` | `coverOssId` | F/I | 编辑隐藏回填;C40 未解决前不拼封面 URL | +| `CeremonyVo` | `giftCount`、`giftAmount` | R | 详情祭品数量与礼金合计 | +| `CeremonyVo` | `sortOrder`、`status` | I/U | 编辑回填;只允许正常记录进入编辑器 | +| `CeremonyVo` | `remark` | R | 详情只读展示,不进入请求 Body | +| `CeremonyGiftVo` | `giftId`、`genealogyId`、`ceremonyId`、`giverUserId` | I | 祭品删除、关联和写后重读的稳定 ID | +| `CeremonyGiftVo` | `genealogyNo`、`genealogyName`、`surname`、`ceremonyTitle` | R | 家谱与活动只读上下文 | +| `CeremonyGiftVo` | `giverNickName`、`giverName`、`giftAmount`、`giftMessage`、`giftTime` | R | 祭品列表展示;可见内容均转义 | +| `CeremonyGiftVo` | `giverPhone` | I | 隐私字段,始终不展示 | +| `CeremonyGiftVo` | `status`、`remark` | R/I | 正常状态校验和只读备注 | +| `CeremonyInvitationVo` | 全部 ID、版本 | I | 邀请状态匹配和更新后的重读校验,不显示原值 | +| `CeremonyInvitationVo` | `inviteStatus`、投递/阅读/响应时间 | R | 管理员邀请列表只读展示 | +| `GenealogyMemberVo` 选项 | `appUserId` | I/S | 受邀人的唯一真实业务用户 ID 来源 | +| `GenealogyMemberVo` 选项 | `memberName`、`appUserNickName` | R/S | 受邀人选择器标签;手机号不展示 | + +接入边界: + +- 活动只调用 `/genealogy/pc/genealogies/{genealogyId}/ceremonies` 及 `/{ceremonyId}`;祭品只调用其 `/gifts` 子路径; +- 管理员邀请列表和替换只调用同一活动下的 `/invitations` 与 `/invitees`,受邀人来源只调用当前家谱 `/members/options`; +- 新增/修改活动后重读同一详情,新增/删除祭品后重读祭品列表,更新受邀人后重读邀请列表;所有写操作防重复; +- 页面先读取家谱详情,只有 `canEditContent/canManage` 显示活动、祭品和受邀名单维护; +- 空数组允许取消全部尚未响应邀请;响应过的历史邀请由后端保留状态,前端不伪造删除; +- C40 未解决前不猜封面地址;C41 未解决前不开放停用;C42 未解决前不猜金额精度和上限; +- 相关聚焦测试 56/56 通过;真实账号页面验证统一留到阶段 6 收尾执行。 + +### 7.19 家谱成员管理、退出与谱主转移 + +页面:`profile-family-admin.html`。 + +成员修改请求字段: + +| 请求字段 | 必填性 / 类型 | 标记 | 页面来源与提交时机 | +| --- | --- | --- | --- | +| `memberName` | 可选 string,最长 50 | U | 管理员编辑表单;非空时提交 | +| `relationName` | 可选 string,最长 100 | U | 管理员编辑表单;后端明确接收空串时可清空 | +| `roleType` | 可选 string,`admin/editor/member` | S | 按当前管理者和目标角色过滤;保存时提交,不发送 `owner/visitor` | +| `lineagePersonId` | 可选 int64 | S/I | 从当前家谱世系人物选项选择;非空时提交,留空表示不修改而不是解绑 | +| `genealogyId`、`memberId` | path int64 | I/A | 家谱上下文和成员列表响应;按字符串处理,不显示、不手填 | + +退出与谱主转移: + +| 字段/操作 | 必填性 / 类型 | 标记 | 页面来源与提交时机 | +| --- | --- | --- | --- | +| `DELETE /members/me` | 无 Body | S/A | 当前正常成员二次确认后提交;谱主不显示退出按钮 | +| `targetMemberId` | 必填 int64 | S/I | 谱主从当前正常成员列表选择新谱主;禁止选择自己和手填 ID | +| `DELETE /members/{memberId}` | path int64 | S/I | 管理员从真实成员行执行“移出家谱”;谱主不可被移出 | + +PC `GenealogyMemberVo` 响应字段及页面用途: + +| 字段 | 标记 | 页面处理 | | --- | --- | --- | -| 内容文章 | 删除文章 | `profile-article*` 保持设计;没有列表/详情时不开放删除 | -| 相册 | 删除相册、删除照片 | `profile-album.html` 保持设计;不使用静态 ID 调删除 | -| 祭祀 | 删除活动、删除祭品 | 活动 CRUD 和礼物列表补齐前保持禁用 | +| `memberId`、`genealogyId`、`appUserId`、`lineagePersonId`、`inviterUserId` | I | 当前成员匹配、修改、移出、转让和世系绑定的稳定 ID;不显示原值 | +| `genealogyNo`、`genealogyName`、`surname` | R | C44 未解决前允许为空的家谱只读上下文 | +| `appUserNickName`、`lineagePersonNo`、`lineagePersonName`、`memberName` | R/U | 成员列表标签和编辑回填;可见内容均转义 | +| `appUserPhone`、`inviterPhone` | I | 隐私字段,始终不展示 | +| `roleType` | R/S | 响应可含 `owner/admin/editor/member/visitor`;修改只提交三种后端可分配角色 | +| `relationName` | R/U | 列表展示和编辑回填 | +| `joinSource`、`inviterNickName`、`joinTime` | R | 加入来源只读说明;C44 未填充时允许为空 | +| `status` | I | 列表只接受后端返回的正常状态 `0` | -删除按钮必须由真实响应中的 ID 和权限字段产生。禁止用 DOM 序号、示例 ID 或 URL 猜测资源 ID。 +权限与状态边界: + +- 所有账号可按家谱查看权限读取正常成员;只有家谱详情 `canManage=true` 时显示修改和移出; +- 谱主不能直接修改或移出;只有谱主可管理管理员、分配 `admin` 或转让谱主;管理员只能管理非管理员成员并分配 `editor/member`; +- 普通成员、编辑和管理员可退出家谱;谱主必须先转让谱主。退出和移出只停用成员关系,不删除世系人物; +- 更新后重读成员列表并核对同一 `memberId`;移出后核对该成员不再出现在正常列表;谱主转移后重读家谱详情和成员列表; +- 页面没有直接新增成员接口;新增成员必须通过后端已有的加入/邀请业务闭环; +- C44 未解决前不依赖 enrichment 字段并隐藏手机号;C45/C46 未解决前不伪造关系解除或成员-世系人物解绑; +- 相关聚焦测试 49/49 通过;真实账号页面验证统一留到阶段 6 收尾执行。 + +### 7.20 家谱创建与加入申请契约 + +页面:`profile-create-family.html`;加入申请和审核页面留到下一批开放。 + +`AppGenealogyCreateBody`: + +| 字段 | 类型 | 必填 | 来源 | 页面与提交时机 | +| --- | --- | --- | --- | --- | +| `genealogyName` | string | 是 | U | 创建页谱名;提交时发送 | +| `surname` | string | 是 | U | 创建页姓氏;提交时发送 | +| `regionCode` | string | 是 | S | 省市区选择器自动取最后一级;提交时发送 | +| `ancestralHall`、`originPlace`、`addressDetail`、`intro` | string | 否 | U | 创建页可选输入;非空时发送 | +| `coverOssId` | int64/string | 否 | F | 封面上传成功后自动产生;不允许手填 | +| `visibility` | string enum `0/1/2` | 否 | S | 私密/公开/成员可见 | +| `joinMode` | string enum `0/1/2` | 否 | S | 关闭/审核/邀请模式 | + +实现边界: + +- 创建只调用 `POST /genealogy/pc/genealogies`,提交前读取 `GET /genealogy/pc/genealogies/quota`; +- 地区选项复用 `/genealogy/pc/region/children?parentCode=...`,不手写地区编码; +- 创建成功后使用响应中的稳定 `genealogyId` 重读同一家谱,匹配后进入家谱主页; +- ApiClient 已同时冻结加入申请、我的申请、待审核、审核和撤销的 PC path/body 白名单,但本批未提前开放加入 UI; +- `inviterUserId` 没有安全 PC 邀请来源,本轮加入申请契约不发送该字段; +- 创建页面无家谱 ID、申请 ID、用户 ID 或 OSS ID 文本输入,所有写操作防重复。 ## 8. 分阶段执行顺序 @@ -769,6 +1193,8 @@ 3. 世系人物; 4. 我的贺礼邀请。 +当前进度:阶段 4 已按顺序完成。家族圈的列表、正常状态详情、新增、修改、删除、点赞、评论、回复、权限显隐、空/错状态、防重复和写后重读已接入,C21 图片 URL 回显和 C22 停用记录管理继续阻断;字辈谱的正常/management 权限分流、单条新增修改和状态切换、批量预览保存、响应校验、防重复、写后重读及无家谱上下文分支已接入;世系人物的树、列表、分页、详情、完整字段表单、权限显隐、四类关系新增、逻辑停用、防重复和写后重读已接入,C15 继续阻断新增 `SPECIFIED` 账号绑定,C18 继续阻断可选字段清空闭环;我的贺礼邀请已接入当前账号列表、响应内详情、PENDING 接受/拒绝、长 ID 校验、权限/空错状态、防重复和写后重读,C16 继续阻断管理员替换受邀人。真实账号已验证邀请读取和空状态;该账号没有待响应邀请,接受/拒绝真实写入仍待有数据时复核。下一阶段为阶段 5,按顺序先核对视频 DTO 是否已完整。 + 每个模块采用同一闭环: 1. `ApiClient` 方法和契约测试; @@ -792,19 +1218,36 @@ 任何模块的 View/List DTO 未补齐时,只完成表单布局和契约测试,不宣称真实功能完成。 -### 阶段 6:后端补齐后再开放的模块 +当前进度:阶段 5 已按顺序完成。视频、成长记录、亲友记录、备忘录、功德记录和消息通知均已按对应 PC 契约开放;各模块的文件 URL、停用记录、可选字段清空和金额业务边界仍按 C18、C23–C34 保持阻断,不通过 APP 接口或猜测字段补齐。 -范围: +### 阶段 6:当前 PC 契约可闭环模块 -- 文章; -- 相册/照片; -- 祭祀活动/祭品; -- 管理员邀约名单; -- 指定账号绑定世系人物; -- 家谱和成员管理; -- 世系关系解除。 +已完成: -这些模块不允许通过 APP 接口或猜测路径提前实现。 +- 谱文列表、详情、新增、修改和删除; +- 相册列表、新增、修改、删除,以及照片列表、新增和删除; +- 祭祀活动列表、详情、新增、修改和删除,祭品列表、新增和删除; +- 管理员受邀名单读取和完整替换,当前账号邀请读取与响应; +- 从同家谱正常成员选项中选择账号完成 `SPECIFIED` 世系人物绑定; +- 家谱成员列表、修改、移出、当前成员退出和谱主转移; +- 个人中心、家谱首页和家谱内容页的稳定入口。 + +契约收口: + +- `replaceCeremonyInvitees(genealogyId, ceremonyId, inviteeUserIds)` 只接收真实成员选项产生的 ID 数组,`utils/ApiClient.js` 唯一封装 `{ inviteeUserIds }`; +- `/members/options` 严格按 YAML 不发送 Query;后端存在但 YAML 未定义的 `keyword` 不进入前端契约; +- 成员角色构造边界只接受 `admin/editor/member`,`owner/visitor` 在请求构造前失败; +- 所有业务 ID 来自 URL、上下文或响应选项,页面不提供手填 ID/OSS ID。 + +仍阻断: + +- C35–C46 中记录的分类选项、文件 URL、停用记录、金额业务精度/上限、响应 enrichment 与解绑契约; +- 世系关系解除没有 PC 接口;成员移出/退出不会伪造成世系关系解除或成员与世系人物解绑; +- 真实账号 `19181970173` 已验证登录和无家谱上下文分支:阶段 6 页面均阻止业务请求、隐藏管理操作并返回家谱选择页,浏览器控制台无错误。该账号当前无家谱数据,因此列表有数据态、详情以及真实写操作仍待有数据账号复核,未制造家谱或业务记录。 + +### 阶段 7:剩余 PC 页面分批开放 + +首批已完成家谱生命周期 ApiClient 契约和创建家谱页面:额度、三级地区、封面上传派生、严格 DTO 校验、防重复与创建后重读均已接入。加入申请/审核、反馈/工单、VIP 和家谱主页按独立后续批次实施;成员邀请、分享和资料完善提醒没有 PC Controller,继续阻断。 ## 9. 文件级施工建议 @@ -822,9 +1265,13 @@ | `public/js/auth-pages.js` / `security-pages.js` | 认证业务流程 | | `public/js/feed-pages.js` | 家族圈 | | `public/js/generation-pages.js` | 字辈 | -| `public/js/lineage-pages.js` | 世系 | +| `public/js/lineage-pages.js` | 世系与指定账号绑定 | +| `public/js/video-pages.js` | 视频列表、详情、编辑、上传元数据和 CRUD | | `public/js/growth-pages.js`、`relative-pages.js`、`memo-pages.js` | 对应族务记录 | -| 新增专用视频/功德/邀约脚本 | 仅在 DTO 完整后新增 | +| `public/js/ceremony-pages.js` | 当前用户贺礼邀请列表、响应内详情和接受/拒绝 | +| `public/js/article-pages.js`、`album-pages.js` | 谱文、相册和照片 | +| `public/js/ceremony-admin-pages.js` | 祭祀活动、祭品与管理员邀约 | +| `public/js/member-admin-pages.js` | 成员修改、移出、退出和谱主转移 | 不得创建一个包揽所有业务的巨型页面脚本,也不得为了单一页面引入通用框架。 diff --git a/docs/superpowers/plans/2026-07-29-genealogy-create-first-batch.md b/docs/superpowers/plans/2026-07-29-genealogy-create-first-batch.md index 0fe6da7..9bfb388 100644 --- a/docs/superpowers/plans/2026-07-29-genealogy-create-first-batch.md +++ b/docs/superpowers/plans/2026-07-29-genealogy-create-first-batch.md @@ -33,7 +33,7 @@ - `auditGenealogyJoinApply(genealogyId, applyId, body)` - `cancelGenealogyJoinApply(applyId)` -- [ ] **Step 1: 写失败的路径和 body 白名单测试** +- [x] **Step 1: 写失败的路径和 body 白名单测试** 测试固定以下请求: @@ -69,7 +69,7 @@ await client.auditGenealogyJoinApply(genealogyId, applyId, { - 审核 body 只保留 `status/auditRemark`。 - 所有 path ID 拒绝不安全数字。 -- [ ] **Step 2: 运行 RED** +- [x] **Step 2: 运行 RED** Run: @@ -79,11 +79,11 @@ node --test --test-name-pattern "genealogy lifecycle" tests/api-client-contract. Expected: FAIL,新方法尚不存在。 -- [ ] **Step 3: 最小实现方法和白名单** +- [x] **Step 3: 最小实现方法和白名单** 使用现有 `request()`、`pickDefined()` 和 `toRequiredPathId()`;不增加兼容别名或 APP fallback。 -- [ ] **Step 4: 运行 GREEN** +- [x] **Step 4: 运行 GREEN** Run: @@ -110,7 +110,7 @@ Expected: PASS。 - `normalizeCreatedGenealogy(item)` - `buildCreatedGenealogyUrl(item)` -- [ ] **Step 1: 写失败的创建行为测试** +- [x] **Step 1: 写失败的创建行为测试** ```js assert.deepEqual(GenealogyEntryPages.buildGenealogyCreateBody({ @@ -141,7 +141,7 @@ assert.deepEqual(GenealogyEntryPages.buildGenealogyCreateBody({ - 创建响应必须有稳定 `genealogyId`、谱名和姓氏。 - 跳转地址只能由响应 ID 产生:`profile-family-home.html?genealogyId=...`。 -- [ ] **Step 2: 运行 RED** +- [x] **Step 2: 运行 RED** Run: @@ -151,11 +151,11 @@ node --test tests/genealogy-entry-pages.test.js Expected: FAIL,纯函数不存在。 -- [ ] **Step 3: 最小实现纯函数** +- [x] **Step 3: 最小实现纯函数** 严格构造 `AppGenealogyCreateBody`,可选空值省略,不读取页面外字段。 -- [ ] **Step 4: 运行 GREEN** +- [x] **Step 4: 运行 GREEN** Run: @@ -182,7 +182,7 @@ Expected: PASS。 - Consumes: Task 1 和 Task 2、`RegionPages`、`UploadPages` - Produces: `initGenealogyCreatePage()` -- [ ] **Step 1: 写失败的页面测试** +- [x] **Step 1: 写失败的页面测试** 断言页面: @@ -193,7 +193,7 @@ Expected: PASS。 - 表单只包含后端创建 DTO 字段和地区选择辅助字段。 - 创建按钮、状态区域和额度区域可由脚本控制。 -- [ ] **Step 2: 运行 RED** +- [x] **Step 2: 运行 RED** Run: @@ -203,7 +203,7 @@ node --test tests/genealogy-entry-pages.test.js tests/pending-pages.test.js test Expected: FAIL,页面仍 pending 且缺上传/初始化行为。 -- [ ] **Step 3: 实现页面初始化和提交** +- [x] **Step 3: 实现页面初始化和提交** 初始化顺序: @@ -218,7 +218,7 @@ Expected: FAIL,页面仍 pending 且缺上传/初始化行为。 401 清登录态;403 保留登录态;业务错误显示在表单状态区域。 -- [ ] **Step 4: 运行 GREEN** +- [x] **Step 4: 运行 GREEN** Run: @@ -237,11 +237,11 @@ Expected: PASS。 - Modify: `docs/PC接口对接规划.md` - Modify: `docs/superpowers/plans/2026-07-29-genealogy-create-first-batch.md` -- [ ] **Step 1: 更新契约和阶段状态** +- [x] **Step 1: 更新契约和阶段状态** 记录 `AppGenealogyCreateBody`、`AppGenealogyVo`、额度、权限、上传派生字段、加入申请 ApiClient 契约及仍未开放的加入 UI。 -- [ ] **Step 2: 聚焦和全量验证** +- [x] **Step 2: 聚焦和全量验证** Run: @@ -253,6 +253,6 @@ git -c safe.directory=D:/WorkSpace/Web/jiapu diff --check Expected: 全部 PASS。 -- [ ] **Step 3: 浏览器验证并报告** +- [x] **Step 3: 浏览器验证并报告** 使用真实账号验证创建页登录态、额度、地区加载、无手填 ID、控制台错误和提交前校验。不得为测试消耗真实创建额度;除非用户明确授权,不执行最终创建写操作。 diff --git a/docs/superpowers/plans/2026-07-29-stage6-pc-pages.md b/docs/superpowers/plans/2026-07-29-stage6-pc-pages.md new file mode 100644 index 0000000..6073f18 --- /dev/null +++ b/docs/superpowers/plans/2026-07-29-stage6-pc-pages.md @@ -0,0 +1,509 @@ +# 阶段 6 PC 功能页面 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. Project rules reserve code edits for the primary agent; subagents may only perform read-only exploration and review. + +**Goal:** 实现所有当前 PC 接口已具备闭环但前端缺失或未开放的谱文、相册/照片、祭祀/祭品、管理员邀约、指定账号绑定和家谱成员管理页面。 + +**Architecture:** `utils/ApiClient.js` 唯一拥有 PC method/path/query/body;每个业务使用独立 UMD 页面脚本,页面只消费脚本导出的规范化、校验、渲染和初始化接口。`profile-common.js` 提供家谱上下文,`upload-pages.js` 产生 OSS ID,所有写操作使用稳定字符串 ID、防重复提交并在成功后重读。 + +**Tech Stack:** 静态 HTML、原生 JavaScript UMD、Axios 请求封装、Node `node:test`、现有 profile CSS、wangEditor v5、统一分片上传。 + +## Global Constraints + +- 后端目录 `D:/WorkSpace/Java/Genealogy` 严格只读。 +- 只使用 `/genealogy/pc/**`,不得使用 APP 接口或旧路径 fallback。 +- 不允许手填业务 ID、用户 ID 或 OSS ID;int64 在浏览器边界保存为十进制字符串。 +- 普通列表无法重读停用记录的模块固定提交 `status=0`。 +- 每个模块严格执行 RED → GREEN → 聚焦测试 → 真实账号非破坏性验证 → 规划更新。 +- 当前工作树已有前几阶段改动;不得重置、覆盖或提交无关文件。除非用户另行要求,本计划不创建 Git commit。 + +--- + +### Task 1: 扩展 PC ApiClient 契约 + +**Files:** +- Modify: `utils/ApiClient.js` +- Modify: `tests/api-client-contract.test.js` + +**Interfaces:** +- Produces: + - `articles(genealogyId)`, `articleDetail(genealogyId, articleId)`, `createArticle(genealogyId, body)`, `updateArticle(genealogyId, articleId, body)` + - `albums(genealogyId)`, `createAlbum(genealogyId, body)`, `updateAlbum(genealogyId, albumId, body)`, `albumPhotos(genealogyId, albumId)`, `createAlbumPhoto(genealogyId, albumId, body)` + - `ceremonies(genealogyId)`, `ceremonyDetail(genealogyId, ceremonyId)`, `createCeremony`, `updateCeremony`, `ceremonyGifts`, `createCeremonyGift` + - `replaceCeremonyInvitees(genealogyId, ceremonyId, inviteeUserIds)` + - `genealogyMembers(genealogyId)`, `genealogyMemberOptions(genealogyId)`, `updateGenealogyMember`, `removeGenealogyMember`, `leaveGenealogy`, `transferGenealogyOwner` + +- [x] **Step 1: 写失败的路径和 body 白名单测试** + +```js +await client.createArticle('9007199254740993001', { + articleTitle: '族史', + articleContent: '

正文

', + coverOssId: '9007199254740993002', + appUserId: 'must-drop', + status: '0' +}); +assert.deepEqual(calls[0].data, { + articleTitle: '族史', + articleContent: '

正文

', + coverOssId: '9007199254740993002', + status: '0' +}); + +await client.replaceCeremonyInvitees( + '9007199254740993001', + '9007199254740993003', + ['9007199254740993004'] +); +assert.deepEqual(calls.at(-1).data, { + inviteeUserIds: ['9007199254740993004'] +}); +``` + +- [x] **Step 2: 运行 RED** + +Run: `node --test --test-name-pattern "article|album|ceremony admin|genealogy member" tests/api-client-contract.test.js` + +Expected: FAIL,原因是新方法不存在或仍只有删除方法。 + +- [x] **Step 3: 最小实现所有方法和白名单** + +```js +var ARTICLE_FIELDS = [ + 'categoryId', 'articleTitle', 'articleSummary', 'coverOssId', + 'articleContent', 'authorName', 'sortOrder', 'status' +]; +var ALBUM_FIELDS = ['albumName', 'albumDesc', 'coverOssId', 'sortOrder', 'status']; +var ALBUM_PHOTO_FIELDS = [ + 'ossId', 'photoTitle', 'photoDesc', 'photographer', + 'shootTime', 'sortOrder', 'status' +]; +var CEREMONY_FIELDS = [ + 'ceremonyType', 'ceremonyTitle', 'ceremonyDesc', 'ceremonyTime', + 'location', 'locationAddress', 'longitude', 'latitude', + 'coverOssId', 'sortOrder', 'status' +]; +var CEREMONY_GIFT_FIELDS = ['giverName', 'giftAmount', 'giftMessage']; +var MEMBER_UPDATE_FIELDS = ['memberName', 'relationName', 'roleType', 'lineagePersonId']; +``` + +每个方法只通过既有 `request()`、`pickDefined()`、`toRequiredPathId()` 和路径 builder 发请求;成员 options 按 YAML 不发送 Query。 + +- [x] **Step 4: 运行 GREEN 和语法检查** + +Run: `node --test tests/api-client-contract.test.js` + +Run: `node --check utils/ApiClient.js` + +Expected: PASS。 + +--- + +### Task 2: 谱文列表、详情、创建、修改和删除 + +**Files:** +- Create: `public/js/article-pages.js` +- Create: `tests/article-pages.test.js` +- Modify: `profile-article.html` +- Modify: `profile-article-edit.html` +- Modify: `tests/pending-pages.test.js` +- Modify: `tests/pc-scope.test.js` + +**Interfaces:** +- Consumes: Task 1 article ApiClient methods、`ProfileUI.requireGenealogyContext()`、`ProfileUpload.bindUploadField()`、`RichEditorPages` +- Produces: `buildArticleBody`, `normalizeArticle`, `normalizeArticles`, `matchesArticle`, `renderArticleList`, `renderArticleDetail`, `initArticleListPage`, `initArticleEditPage` + +- [x] **Step 1: 写失败的谱文行为测试** + +```js +assert.deepEqual(ArticlePages.buildArticleBody({ + articleTitle: '族史', + articleContent: '

正文

', + coverOssId: '9007199254740993002', + categoryId: '', + status: '1' +}), { + articleTitle: '族史', + articleContent: '

正文

', + coverOssId: '9007199254740993002', + status: '0' +}); +assert.equal( + ArticlePages.normalizeArticle({ articleId: Number.MAX_SAFE_INTEGER + 1 }), + null +); +assert.doesNotMatch( + ArticlePages.renderArticleDetail({ + articleId: '9007199254740993003', + articleTitle: '', + articleContent: '', + status: '0' + }), + /