Files
jiapu/docs/PC接口对接规划.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

1667 lines
122 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.
# PC 接口字段与页面落地规划(供 AI 执行)
> 文档状态:规划,不代表当前代码已经完成
> 基线日期:2026-07-27
> 当前本地快照:根目录 `PC.openapi.json`OpenAPI 3.1.0
> 使用对象:另一台电脑上的执行 AI
> 本轮边界:只写规划;执行 AI 才能修改页面、脚本和测试
## 0. 强制要求:必须打开 Apifox 查看
执行 AI 在修改任何代码前,必须实际登录并打开 Apifox 的“家谱”项目,进入每一个本轮接口的详情页逐项查看。不得只读取 `PC.openapi.json`、Apifox 本地缓存、旧截图或历史文档。
每个接口都必须在 Apifox 详情页记录:
1. method、完整 path、接口状态和所属 PC 目录;
2. Path、Query、Header、Cookie 和 Body 的全部字段;
3. 每个字段的类型、必填/可选/条件必填、默认值、枚举、格式、长度和取值范围;
4. 字段说明、示例值、关联字典和条件显示规则;
5. 请求体中折叠对象、数组元素和嵌套字段的完整结构;
6. 成功响应、失败响应、列表元素、详情对象和分页结构的完整字段;
7. 登录要求、角色权限、业务前置条件和错误码;
8. Apifox 页面有但 OpenAPI 导出遗漏的字段或说明。
核对结果必须写入本规划对应模块的字段表,并为每个字段标明 U/S/F/A/R/I/V 分类、使用页面、数据来源和提交时机。Apifox 详情没有确认的字段不得进入代码;Apifox 与导出文件不一致时,以现场核对结果形成问题清单,先让接口 owner 修正并重新导出,再实施。
## 1. 最终目标
执行 AI 要把 Apifox 中的 PC 接口准确落到现有 PC 页面,最终同时满足:
1. 请求体中的必填字段、可选字段全部有明确页面归属。
2. 字段被明确分成:
- 用户输入;
- 用户选择;
- 文件选择后自动生成;
- 页面上下文自动带入;
- 接口响应后只读展示;
- 仅内部保存、不得展示或手工填写;
- 纯前端校验字段、不得发给后端。
3. ID、Token、`clientid``tenantId`、OSS ID、分页参数等系统字段不得让用户手工输入。
4. 下拉框和选择器必须有真实数据源;没有选项接口时不得改成“请输入 ID”。
5. Apifox 未展开的响应 DTO、未声明的枚举和互相冲突的路径不得靠前端猜测。
6. APP 接口不得用于填补 PC 接口缺口。
7. 所有家谱内业务必须使用真实 `genealogyId`,不得写死示例值。
8. 每一阶段必须有契约测试、页面行为测试和真实联调结果。
## 2. 执行 AI 的工作协议
执行 AI 开始前必须按顺序完成:
1. 阅读项目根目录 `AGENTS.md`
2. 检查工作树,只修改本计划点名的文件;保留用户已有改动。
3. 登录 Apifox,打开“家谱”项目,逐条核对实时 PC 接口详情。
4. 从 Apifox 重新导出最新 OpenAPI,和仓库 `PC.openapi.json` 做结构化差异比较。
5. 完成第 5 节“契约阻断项”;阻断项未解决的模块只保留页面设计,不接真实写操作。
6. 按第 8 节阶段顺序施工,每完成一阶段立即验证,不能把所有模块一次性混改。
遇到以下情况时,停止当前模块并整理成一份待后端确认清单,同时继续其他不受影响的模块:
- Apifox 页面和导出文件的 method、path、必填性、枚举或类型不一致;
- 响应仍是无属性的 `ObjectResult` / `ListResult`
- 选择字段没有合法选项来源;
- 更新接口没有说明“字段不传”和“传 null/空串”的区别;
- 页面需要的业务能力在 PC 目录中不存在。
不得通过兼容多个旧字段、读取 APP DTO、保留旧路径 fallback 或展示原始 JSON 绕过阻断。
## 3. 契约依据与优先级
发生冲突时按以下顺序处理:
1. Apifox “家谱”项目中当前 PC 接口详情;
2. 从该详情当场重新导出的 OpenAPI 文件;
3. 本规划中的字段和流程分类;
4. 当前仓库代码;
5. 旧规划和历史交接记录。
通常以 Apifox 的 PC 目录作为 PC 前端接口的正式契约源。2026-07-28 用户明确指定本轮直接以 `D:/WorkSpace/Java/Genealogy/doc/apifox/genealogy-pc-openapi.yaml` 对接,因此该 YAML 是本轮冻结契约;在线 Apifox 的旧重复记录不再覆盖它。`utils/ApiClient.js` 是前端路径和请求方法的唯一 owner。页面脚本只能调用 `ApiClient` 业务方法,不能直接拼 URL 或调用 Axios。
旧 OpenAPI 文件不再作为新增功能依据,只用于结构化差异比较和追踪导出遗漏。
如果 Apifox 在线内容发生变化,先更新本地 OpenAPI、本规划的受影响章节和契约测试,再修改运行时代码。不得让文档、测试和运行时同时保留两套契约。
## 4. 当前基线与复核结论
### 4.1 当前导出范围
当前 `PC.openapi.json` 共 96 个操作、15 个标签、68 个 Schema
| 模块 | 操作数 | 当前主要页面 |
| --- | ---: | --- |
| 验证中心 | 3 | 登录、注册、找回密码、安全设置 |
| 认证登录 | 11 | `login.html``register.html``forgot-password.html`、个人资料和安全页 |
| 文件上传 | 3 | 所有头像、图片、附件和视频选择控件 |
| 家谱 | 1 | `profile-families.html` |
| 家族圈 | 14 | `profile-feed.html``profile-feed-edit.html`、待新增动态详情页 |
| 行政区划 | 8 | 个人资料及以后需要地区的表单 |
| 字辈谱 | 6 | `profile-generation.html` |
| 世系人物 | 12 | `profile-tree.html` |
| 内容文章 | 1 | `profile-article.html``profile-article-edit.html` |
| 相册 | 2 | `profile-album.html` |
| 视频 | 5 | `profile-video.html`、待新增视频编辑页 |
| 贺礼邀约 | 4 | `profile-gift.html`、邀约管理面板 |
| 祭祀 | 2 | `profile-gift.html` |
| 族务记录 | 20 | 成长、亲友、备忘录、功德录页面 |
| 消息通知 | 4 | `profile-messages.html` |
| **合计** | **96** | |
### 4.2 已对照到的 Apifox 现状
本机 Apifox 缓存中的“家谱”项目包含 APP、PC、共享区划等 246 条接口记录。当前导出的 96 条路径均能在该项目缓存的接口树中找到,但存在以下差异:
1. Apifox 缓存同时保留了两条逻辑相同的短信接口:
- 错误旧路径:`genealogy/pc/auth/sms/{operationCode}/code`
- 正确路径:`/genealogy/pc/auth/sms/{operationCode}/code`
当前导出只保留正确路径。执行前必须在 Apifox 删除或正式废弃缺少 `/` 的旧定义,前端只实现正确路径。
2. 导出文件同时含 `/genealogy/region/*``/genealogy/pc/region/*` 两套共 8 个区划操作;两套的参数、响应和描述完全相同。执行前必须由后端选定一套唯一正式路径并删除另一套。选定后实际唯一操作数应从 96 收口为 92。
3. 旧版“85 接口规划”已经失效。新增范围主要包括 PC 区划 4 条、视频列表/新增/详情/修改 4 条、功德列表/新增/详情/修改 4 条。
4. 当前 96 个操作全部被声明为需要 `Authorization`,连注册、登录、短信发送和验证中心也不例外。这与未登录流程冲突,必须在 Apifox 逐项修正 security。
5. 当前有 21 个成功响应使用无业务属性的 `ObjectResult`,12 个使用无元素 Schema 的 `ListResult`;另有 `LineagePersonTreeView` 名义上是专用 DTO,但没有任何属性。
### 4.3 2026-07-28 补齐版 YAML 与在线 Apifox 复核
补齐文件 `genealogy-pc-openapi.yaml` 含 98 个 path、137 个 HTTP operation、17 个 tag、319 个 schema;旧 `PC.openapi.json` 含 71 个 path、96 个 operation、68 个 schema。补齐文件新增了家谱、成员、相册、文章、祭祀、通知、反馈、VIP 和官网内容等路径及 DTO,但它还不能替代在线 Apifox:
1. 已实际登录并打开 Apifox“家谱”项目的 PC 模块。在线概览显示 **142 个接口、87 个数据模型**,与补齐 YAML 的 137 个 HTTP operation 不一致。
2. 在线“认证登录”目录仍显示 **12 个接口**,同时存在:
- 错误旧路径:`genealogy/pc/auth/sms/{operationCode}/code`
- 正确路径:`/genealogy/pc/auth/sms/{operationCode}/code`
补齐 YAML 只包含正确路径,因此 C02 在线尚未完成。
3. 补齐 YAML 给注册、密码登录、短信登录和短信发送标了 `security: []`;在线密码登录详情仍展示必需的 `Authorization` API Key,并显示 `Auth 1`。因此 C03 的导出定义和在线详情冲突。
4. 在线 `GET /genealogy/pc/auth/profile` 成功响应仍为泛型 `ObjectResult`;展开后的 `data` 只有任意附加属性,没有 `ProfileView` 字段。补齐 YAML 同样引用泛型 `ObjectResult`C04/C06 未完成。
5. `ProfileUpdateBody.avatar` 仍为 `integer<int64>`,而 `FileUploadVo.ossId``LineagePersonView.avatarOssId` 等 OSS ID 为 `string`;部分头像字段也仍为 `integer<int64>`。C10 未完成。
6. 已用真实登录响应和后端 `AppLoginVo` 复核:token 的唯一 JSON 字段是 `access_token`;原 YAML 的 `token``accessToken``tokenValue``userId``tenantId``clientId` 均不是该响应的直接字段,现已删除。
7. `ProfileUpdateBody` 仍只有 `nickName``realName``avatar``sex``birthday``email`,没有地区字段,也没有说明缺省、`null`、空串的清空语义。C07/C18 未完成。
用户已明确要求本轮直接按补齐 YAML 施工,所以在线旧短信路径和公开接口 security 残留不再阻断本轮实现。YAML 自身仍存在的 profile 泛型响应、头像 ID 类型冲突和更新清空语义缺失继续保留为阻断项,前端不得自行补造。
## 5. 实施前必须解决的契约阻断项
以下事项不是前端自由选择,必须先在 Apifox 明确:
| 编号 | 问题 | 影响 | 完成条件 |
| --- | --- | --- | --- |
| C01 | 两套区划路径完全重复 | 页面和客户端会形成双路径 | Apifox 只保留一套;导出和客户端只出现一次 |
| C02 | 短信接口有缺少开头 `/` 的旧记录 | 可能生成错误 URL | 删除/废弃旧记录,只保留正确路径 |
| C03 | 所有操作均要求 Authorization | 未登录流程无法成立 | 注册、登录、找回、验证、短信等逐项标明公开;登录后接口明确鉴权 |
| C04 | 33 个操作使用泛型对象/列表响应 | 列表、详情、编辑回填和 ID 来源不确定 | 为每个业务资源增加明确 View/DTO Schema |
| C05 | `LineagePersonTreeView` 没有属性 | 世系树只能猜字段 | 补齐节点 ID、人物资料、配偶、子女和递归结构 |
| C06 | 用户资料响应未展开 | 个人资料无法可靠回填 | 增加 `ProfileView`,明确所有可读字段 |
| C07 | `ProfileUpdateBody` 没有地区字段 | 现居地区无法保存 | 明确增加地区编码字段或删除“保存地区”功能 |
| C08 | 区划响应没有元素 DTO | 下拉选项的 code/name/level 不受契约约束 | 增加 `RegionView` 及列表/路径响应 |
| C09 | 上传初始化响应未展开,却描述了 `instant=true` | 秒传、续传和 `uploadId` 状态不明确 | 增加初始化响应 DTO,明确 `instant`、已传分片和 OSS 信息 |
| C10 | OSS ID 类型冲突 | 可能发生 JS 精度丢失或校验失败 | 所有 `ossId``avatar``avatarOssId`、视频和附件 ID 统一为 string |
| C11 | `completed` 只有 string 类型,无枚举 | 不能决定复选框提交 `0/1`、true/false 或其他值 | Apifox 增加枚举和中文含义 |
| C12 | `feedType``recordType``registerSource` 等无枚举 | 无法判断文本输入还是选择项 | 明确为自由文本,或补全枚举 |
| C13 | 多个日期字段没有 format/时区规则 | 页面值和后端解析可能不一致 | 明确 date 或 date-time,并统一时区 |
| 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/空值语义 |
当前复核状态:
| 阻断项 | 状态 | 在线/补齐文件证据 |
| --- | --- | --- |
| C02 | 本轮按 YAML 解决 | 只实现带 `/` 的正确路径,不保留旧路径 fallback |
| C03 | 本轮按 YAML 解决 | 验证、注册、登录、短信、找回使用 `security: []`;其他认证接口携带 Bearer |
| C04/C06 | 阻断 | profile GET 在线及 YAML 都只有泛型 `ObjectResult` |
| C07 | 阻断 | `ProfileUpdateBody` 没有地区字段 |
| 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 仍只有 stringPC `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 验收标准:
- Apifox 中不存在旧短信路径和双区划路径;
- 重新导出的 OpenAPI 只包含一套正式契约;
- 公开与鉴权接口标记正确;
- 本阶段要实现的每个列表/详情都有明确响应 DTO;
- 所有枚举、日期和 ID 类型已确定;
- 将旧输入发送给后端时会明确失败,前端不保留兼容读取。
## 6. 页面字段分类标准
执行 AI 对每个请求字段都要采用下面一种处理类型:
| 类型 | 含义 | 页面处理 |
| --- | --- | --- |
| U:用户输入 | 姓名、标题、正文、手机号等 | 可见输入框/文本域,展示必填或选填 |
| S:用户选择 | 枚举、人员、字辈、地区、状态等 | 单选、下拉、多选、日期或开关;选项必须有真实来源 |
| F:文件派生 | OSS ID、文件名、MD5、大小等 | 用户只选文件;值由上传组件生成并隐藏 |
| A:自动填写 | header、租户、上下文 ID、分页、默认值等 | 页面不可编辑,由统一 owner 注入 |
| R:响应只读 | 创建时间、计数、发布人、状态文本等 | 列表、详情、徽标或只读信息 |
| I:内部状态 | token、challengeId、validToken、资源 ID 等 | 仅内存/安全存储/URL 中保存,不显示原值 |
| V:前端校验 | 确认密码、注销确认文字等 | 可见但不得发给后端 |
通用规则:
1. 必填字段使用明确的必填标识,并在提交前校验。
2. 可选字段也必须有页面归属;不填写时直接省略,除非 Apifox 明确要求传 null。
3. 枚举必须使用选择控件,禁止自由文本。
4. 外键 ID 必须使用搜索/选择控件,禁止“请输入人物 ID”“请输入 OSS ID”。
5. `sortOrder``status` 这类管理字段:
- 有管理权限时放在“高级设置”;
- 普通用户使用隐藏默认值;
- 后端未返回权限时不得自行开放高级设置。
6. 所有 int64 ID 在浏览器中按字符串保存和比较。
7. 响应中的 `code``msg` 由请求层统一处理;业务页只消费 `data``rows/total`
## 7. 字段、页面和流程规划
### 7.1 全局自动字段
| 字段 | 类型 | 唯一来源 | 规则 |
| --- | --- | --- | --- |
| `clientid` | A | `ApiClient` 配置 | 每个需要它的请求自动加 Header,不进入表单或 body |
| `Authorization` | I/A | 登录响应 token | 仅鉴权接口携带;注册登录等公开接口不携带 |
| `tenantId` | A | 环境配置 | 登录、注册、验证等自动带入,不让用户输入 |
| `genealogyId` | I/A | PC 家谱上下文 | `profile-common.js` 读取和传播;缺失时阻止家谱内请求 |
| `feedId``commentId``personId``poemId``articleId``albumId``photoId``videoId``ceremonyId``giftId``recordId``relativeId``memoId``meritId``notificationId` | I/A | 列表/详情响应或 URL | 由被点击记录带入,禁止文本框输入 |
| `pageNum``pageSize` | A/S | 分页组件 | 页码由组件维护;用户只操作翻页/每页条数 |
| `ossId` / `mediaOssIds` | F/I | 文件上传响应 | 页面展示预览和文件名,隐藏保存 ID |
| `sortOrder` | S/A | 高级设置或默认值 | 不得把空字符串转成 0;未填写时省略 |
| `status` | S/A | 权限化状态选择或默认值 | 枚举 0=正常、1=停用;不得把 1 标成“草稿” |
### 7.2 验证中心与认证登录
#### 页面字段矩阵
| 字段 | 必填性 | 类型 | 页面/流程 | 处理 |
| --- | --- | --- | --- | --- |
| `operationCode` | 必填 path | A | 所有认证动作 | 页面动作固定为 `password-login``sms-login``register``forgot-password``phone-change``account-deactivate` |
| `clientid` | 必填 header | A | 所有认证请求 | `ApiClient` 自动注入 |
| `tenantId` | 必填 | A | 所有登录/验证表单 | 环境配置自动注入 |
| `subject` | require 可选,challenge/verify 必填 | A | 验证流程 | 从当前手机号/账号派生,用户不重复填写 |
| `grantType` | 必填 | A | 注册、登录、短信、找回 | 密码动作固定 `password`,短信动作固定 `sms` |
| `phone` | 必填 | U | 登录、注册、找回、换绑 | 手机号输入和格式校验 |
| `password` | 必填 | U/A | 密码登录、注册 | 用户输入明文,提交前生成 32 位 MD5;日志不得记录 |
| `oldPassword` | 必填 | U/A | 修改密码 | 同上 |
| `newPassword` | 必填 | U/A | 修改/重置密码 | 同上 |
| `confirmPassword` | 前端字段 | V | 注册、修改、找回 | 只比较一致性,不发送 |
| `smsCode` | 必填 | U | 短信登录、注册、找回、换绑、注销 | 4 位验证码输入 |
| `nickName` | 可选 | U | 注册 | 昵称输入 |
| `registerSource` | 可选 | A | 注册 | 当前端固定 `PC`Apifox 需确认枚举 |
| `validToken` | 条件必填 | I/A | 密码登录、发送短信 | 验证成功后仅存内存,使用一次即清除 |
| `challengeId` | 必填 | I/A | 验证校验 | 挑战响应自动回填 |
| `providerCode``captchaType` | 可选 | A | 验证校验 | 挑战响应决定,用户不可修改 |
| `payload.track` | 条件必填 | A | 天爱验证码 | 控件原样生成轨迹、尺寸和时间 |
| `payload.uuid` | 条件必填 | I/A | 系统图形验证码 | 挑战响应回填 |
| `payload.code` | 条件必填 | U | 系统图形验证码 | 用户输入图片答案 |
天爱控件产生的嵌套字段全部属于 A,不得另做表单让用户填写:
- `bgImageWidth``bgImageHeight`:控件实际显示尺寸,必填;
- `templateImageWidth``templateImageHeight`:模板实际显示尺寸,可选;
- `startTime``stopTime`:操作开始和结束毫秒时间戳,必填;
- `left``top`:控件最终偏移,可选;
- `trackList`:必填轨迹数组;
- 每个轨迹点的 `x``y``t``type` 均由控件生成;
- `data`:不同验证码类型的扩展数据,原样提交。
验证和登录响应字段:
| 响应字段 | 类型 | 页面处理 |
| --- | --- | --- |
| `required` | R/A | 决定是否进入挑战流程 |
| `providerCode``captchaType` | I/A | 选择正确验证码控件 |
| `sceneCode` | I/R | 服务端解析结果,可用于只读诊断;不得回传 |
| `ttlSeconds``expireSeconds` | R/A | 倒计时和过期重试 |
| `challengeId``uuid` | I | 只在当前挑战内保存 |
| `img` | R | 系统图形验证码图片 |
| `payload` | I/A | 天爱控件初始化数据 |
| `passed` | R/A | 决定校验成功或失败 |
| `validToken` | I | 一次性票据,不显示、不落长期存储 |
| `message` | R | 安全地显示验证结果 |
| `access_token` | string / I | 登录接口 `data`;密码或短信登录成功时立即保存,后续仅用于 Bearer 鉴权 |
| `expire_in` | integer<int64> / I/R | 登录接口 `data`;登录成功时取得,可用于会话到期提示,不由用户填写 |
| `client_id``clientKey``deviceType``userType` | string / I/R | 登录接口 `data`;登录成功时取得,仅作会话上下文或诊断,不回传为用户输入 |
| `profile` | object / R | 登录接口 `data.profile`;登录成功后取得,可用于页面只读展示或资料页初始回填 |
| `profile.userId``profile.tenantId``profile.userNo` | integer/string、string、string / I/R | 后端自动产生;不得让用户手工填写,业务 ID 超出 JS 安全整数时保持字符串 |
| `profile.phone``profile.nickName``profile.realName``profile.email` | string/null / R | 后端用户资料;登录成功后只读展示或作为资料页回填,编辑时仍只提交 `ProfileUpdateBody` 允许字段 |
| `profile.avatar` | integer/string/null / R | 后端头像 OSS ID;只读回填,用户换头像必须经文件选择和上传组件产生,禁止手填 OSS ID |
| `profile.sex` | enum `0/1/2` / R | 后端用户资料;页面映射男/女/未知 |
| `profile.birthday` | date/null / R | 后端用户资料;页面按日期展示 |
| `profile.registerSource``profile.loginIp``profile.loginDate` | string/null / R/I | 后端自动产生;只读或内部诊断,不作为表单输入 |
| `profile.status` | string / R/I | 后端账号状态;只读或内部诊断,不作为表单输入 |
| `profile.clientKey``profile.deviceType` | string / I/R | 后端登录上下文;只读或内部诊断 |
#### 认证业务流程
密码登录:
1. 用户输入手机号和密码。
2.`password-login + tenantId + phone` 调用 `require`
3.`required=false`,直接提交密码登录。
4. 若需要验证,调用 challenge,展示服务端指定控件,再调用 verify。
5. 得到 `validToken` 后与 MD5 密码一起登录。
6. 成功后只读取并保存 `data.access_token``token``accessToken``tokenValue` 均视为非法旧响应,不做兼容读取。
短信类动作:
1. 用户输入手机号。
2. 按当前动作完成 require → challenge → verify。
3. 把一次性 `validToken` 提交给发送短信接口。
4. 用户输入短信码后执行短信登录、注册、找回、换绑或注销。
5. `validToken` 不进入最终注册、短信登录、换绑或注销 body,除非 Apifox 明确修改契约。
#### 个人资料字段
`ProfileUpdateBody` 的所有字段必须进入 `profile-data.html`
| 字段 | 必填性 | 类型 | 控件 |
| --- | --- | --- | --- |
| `nickName` | 可选 | U | 文本,最长 30 |
| `realName` | 可选 | U | 文本,最长 30 |
| `avatar` | 可选 | F/I | 图片上传、预览、删除;隐藏保存 OSS ID |
| `sex` | 可选 | S | 男 0、女 1、未知 2,三项都要有 |
| `birthday` | 可选 | S | 日期选择器,`yyyy-MM-dd` |
| `email` | 可选 | U | email 输入,最长 100 |
现有页面整改点:
- 当前 `avatarOssId` 字段名与请求契约 `avatar` 不一致;
- 补齐 YAML 中 `avatar` 仍是 `integer<int64>`,但示例值已超过 JavaScript 安全整数范围;浏览器边界必须按十进制字符串保留精度,且只能由上传响应自动回填,不能转 `Number` 或让用户手填;
- 缺少 `realName``email` 和性别“未知”;
- 页面现居地区、父亲、微信/QQ、学历/职业不在 `ProfileUpdateBody`,不得混入保存请求;
- 在后端增加地区字段前,“保存地区”按钮必须关闭或改成纯查询演示;
- `GET /auth/profile` 必须补齐 `ProfileView` 后才能严格回填。
### 7.3 文件上传
用户在任何业务页只操作“选择文件、取消、重试”;下面字段全部自动产生:
| 阶段 | 字段 | 必填性 | 类型 | 来源 |
| --- | --- | --- | --- | --- |
| 初始化 | `uploadId` | 必填 | I/A | 客户端生成一次并贯穿全过程 |
| 初始化 | `fileName` | 必填 | F | `File.name` |
| 初始化 | `fileMd5` | 必填 | F | 文件内容计算 |
| 初始化 | `totalSize` | 必填 | F | `File.size` |
| 初始化 | `totalChunks` | 必填 | F | 根据大小和分片大小计算 |
| 初始化 | `chunkSize` | 必填 | A | 上传组件统一配置,默认示例 4 MiB,最后一片除外 |
| 初始化 | `contentType` | 可选 | F | `File.type` |
| 分片 | `chunkIndex` | 必填 | A | 上传循环生成 |
| 分片 | `chunkMd5` | 必填 | F | 当前分片计算 |
| 分片 | `file` | 必填 | F | 当前 Blob |
| 完成 | `uploadId/fileName/fileMd5/totalSize/totalChunks` | 必填 | I/A/F | 必须与初始化一致 |
响应处理:
- `FileUploadVo.ossId`I,隐藏写入业务表单;
- `url``thumbnailUrl`R,用于预览;
- `fileName``originalName`R,用于文件列表;
- 初始化 `instant=true` 时直接读取 `ossId/url/fileName` 完成秒传;`instant=false` 时使用服务端 `uploadId``uploadedChunks` 跳过已上传分片;
- 初始化 Long `ossId` 和完成响应 string `ossId` 均在浏览器边界转为十进制字符串;
- 上传成功但业务保存失败时,当前契约没有释放引用接口,必须让后端补充生命周期规则。
统一由 `public/js/upload-pages.js` 管理分片、进度、重试、取消、MD5 和隐藏 ID。各业务页面不得复制上传算法,也不得出现“请输入 OSS ID”的可见输入框。
### 7.4 家谱上下文与配额
`GET /genealogy/pc/genealogies/quota` 只负责在 `profile-families.html` 展示:
| 响应字段 | 类型 | 页面呈现 |
| --- | --- | --- |
| `createUsed``createLimit``createRemaining` | R | 创建额度已用/上限/剩余 |
| `canCreate` | R | 控制“创建家谱”是否可用 |
| `joinUsed``joinLimit``joinRemaining` | R | 加入额度已用/上限/剩余 |
| `canJoin` | R | 控制“加入家谱”是否可用 |
`-1` 显示为“不限”,不能参与普通减法或显示负数。
配额响应不包含 `genealogyId`,不能替代“我的家谱”。当前由 `GET /genealogy/pc/genealogies/mine` 返回完整 `AppGenealogyVo`
- `profile-families.html` 从真实列表生成家谱入口;
- `genealogyId` 在浏览器边界保持字符串,并由 `profile-common.js` 统一读取和传播;
- 所有家谱内页面缺少真实 `genealogyId` 时必须阻止请求并回到家谱选择入口;
- 不显示静态家谱卡片,不从 APP 接口获取家谱,不允许用户手工输入家谱 ID;
- 切换家谱通过进入新的带 `genealogyId` URL 完成整页导航,旧页面模块状态不会复用。
### 7.5 行政区划
本 PC 前端只实现 `/genealogy/pc/region/*`,不调用重复的 `/genealogy/region/*`,也不保留 fallback。
| 字段 | 必填性 | 类型 | 页面处理 |
| --- | --- | --- | --- |
| `parentCode` | 可选 query | A/S | 省级不传;选择上一级后自动查询下级 |
| `regionCode` | 必填 path | I/A | 从被选择的区划记录带入 |
| `keyword` | 必填 query | U | 搜索框 |
| `level` | 可选 query | S | 1 省、2 市、3 区县、4 乡镇街道、5 村社区 |
| `limit` | 可选 query | A | 搜索组件固定合理上限,不让用户自由输入 |
| `clientid` | 可选 header | A | 统一请求层注入 |
真实 Java `RegionSelectVo` 已明确 `regionCode``regionName``regionLevel` 等字段。个人资料当前仍没有可保存的地区字段,因此区划组件只提供级联、搜索和路径回显,不提交到资料更新接口。
### 7.6 家族圈
页面归属:
- `profile-feed.html`:分页列表、点赞、轻量评论;
- `profile-feed-edit.html`:新增和编辑;
- 新增 `profile-feed-detail.html`:详情、完整评论树、回复、通知跳转和分享深链。
#### 写入字段
| 字段 | 必填性 | 类型 | 页面处理 |
| --- | --- | --- | --- |
| `genealogyId` | 必填 path | I/A | 家谱上下文 |
| `feedId` | 详情/修改/删除必填 | I/A | 列表记录或 URL |
| `feedType` | 可选 | A 或 S | 当前默认隐藏 `text`;Apifox 补枚举后才能改成选择 |
| `feedContent` | 必填 | U | 正文编辑器 |
| `mediaOssIds` | 可选 | F/I | 多文件上传结果拼成英文逗号串,用户只看缩略图 |
| `sortOrder` | 可选 | S/A | 管理员高级设置;普通发布默认省略/0 |
| `status` | 可选 | S/A | 0 正常、1 停用;不得显示为“发布/草稿” |
| `parentCommentId` | 可选 | I/A | 点击“回复”后自动带入;一级评论省略或 null |
| `commentContent` | 必填 | U | 评论输入,最多 1000 字 |
| `pageNum/pageSize` | 可选 | A/S | 分页组件 |
#### 动态响应字段
| 字段 | 页面处理 |
| --- | --- |
| `feedId``genealogyId` | I,用于路由和后续操作 |
| `genealogyNo``genealogyName` | R,详情来源信息 |
| `publisherUserId` | I,用于权限判断,但不能单独代替后端鉴权 |
| `publisherNickName``publisherStatus` | R,发布人和状态 |
| `feedType``feedContent` | R,类型和正文 |
| `mediaOssIds` | I/F,解析后通过文件 URL 规则展示,不直接显示 ID |
| `likedByMe` | R,决定点赞按钮状态 |
| `likeCount``commentCount` | R,计数 |
| `pinned``pinnedTime` | R,置顶徽标和时间 |
| `sortOrder``status` | R/管理信息 |
| `remark` | R,仅在产品确认需要时展示,不能当正文 |
| `createTime``updateTime` | R,发布时间和编辑时间 |
评论响应字段:
- `commentId``genealogyId``feedId``parentCommentId``appUserId``parentAppUserId`I
- `appUserNickName``appUserAvatar``parentAppUserNickName`R
- `commentContent`R;为 null 且 `userDeleted=1` 时显示“该评论已删除”占位;
- `replyCount`R,控制“展开回复”;
- `commentLevel`R`root``reply`
- `status``createTime`R。
流程:
1. 列表首屏调用分页接口,不同时调用非分页接口重复取数。
2. 点击记录进入详情,以 `genealogyId + feedId` 取详情。
3. 一级评论调用 comments 分页;回复只调用对应 comment 的 replies 分页。
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` 同时提供“单条维护”和“批量维护”。
单条字段:
| 字段 | 必填性 | 类型 | 控件 |
| --- | --- | --- | --- |
| `generationNo` | 必填 | U/S | 正整数世代序号 |
| `generationText` | 必填 | U | 最长 50 |
| `description` | 可选 | U | 文本域,最长 500 |
| `sortOrder` | 可选 | U/S | 管理员高级数字字段 |
| `status` | 可选 | S | 0 正常、1 停用 |
| `poemId` | 修改必填 path | I/A | 列表记录 |
批量字段:
| 字段 | 必填性 | 类型 | 控件 |
| --- | --- | --- | --- |
| `poemText` | 必填 | U | 文本域,最长 26000;支持合同中列出的分隔符 |
| `disableMissing` | 可选 | S | 复选框,必须有“将停用后续世代、不删除历史”的确认说明 |
批量流程必须是 preview → 用户检查 → save
- 预览展示 `createCount``updateCount``keepCount``disableCount`
- 逐项结果来自 `items`;每项展示 `generationNo``oldGenerationText``newGenerationText``oldStatus``newStatus``action``warning`
- `poemId``genealogyId` 是内部字段;
- 保存前再次确认,因为服务端会基于最新数据重新计算差异。
普通展示列表使用正常字辈接口;管理页面使用 management 接口以包含停用项。
当前实现状态:
- 页面先读取家谱详情,以 `canEditContent` 决定调用正常列表还是 management 列表;无维护权限时不渲染新增、编辑、停用和批量维护控件,后端继续执行最终权限校验。
- `genealogyId` 只来自家谱上下文,`poemId` 只来自列表响应;所有业务 ID 在浏览器边界保持字符串,不提供手工 ID 输入。
- 单条新增、修改和状态切换成功后重新读取列表;批量维护严格执行 preview → 当前请求体签名确认 → save → 重新读取列表。
- 批量预览逐项校验 `action`、状态、必需的新旧文本和内部 ID,并核对四类汇总计数;勾选 `disableMissing` 时保存确认明确说明只停用后续世代、不删除历史。
- 已在真实 Chrome 登录态验证无家谱上下文分支:不发送字辈业务请求,新增按钮和批量面板隐藏,家谱上下文入口统一返回家谱选择页。当前账号家谱列表为空,因此正常/management 列表、单条写入和批量写入仍缺少真实家谱数据联调证据。
### 7.8 世系人物
全部操作集中在 `profile-tree.html`,详情和编辑可使用抽屉/弹窗,但所有字段都要有明确控件。
| 字段 | 必填性 | 类型 | 页面处理 |
| --- | --- | --- | --- |
| `bindingMode` | 必填 | S | NONE 不绑定、SELF 当前账号、SPECIFIED 指定账号 |
| `appUserId` | SPECIFIED 时必填 | S/I | 从当前家谱 `members/options` 的正常且已绑定账号成员中选择;禁止文本 ID |
| `personNo` | 可选 | U/A | 高级设置可填;留空由服务端生成 |
| `name` | 必填 | U | 姓名 |
| `aliasName` | 可选 | U | 别名/曾用名 |
| `sex` | 可选 | S | 0 男、1 女、2 未知 |
| `generation` | 可选 | S/I | 从正常字辈列表选择世代 |
| `generationName` | 可选 | A/R | 随字辈选项自动回填并只读显示 |
| `fatherId` | 可选 | S/I | 世系人物搜索选择器 |
| `motherId` | 可选 | S/I | 世系人物搜索选择器 |
| `avatarOssId` | 可选 | F/I | 上传头像自动回填 |
| `birthDate` | 可选 | S | date-time 选择器 |
| `birthLunar` | 可选 | S | 0 公历、1 农历开关 |
| `birthPlace` | 可选 | U | 出生地文本 |
| `deathDate` | 可选 | S | date-time 选择器 |
| `deathLunar` | 可选 | S | 0 公历、1 农历开关 |
| `deathPlace` | 可选 | U | 逝世地 |
| `burialPlace` | 可选 | U | 安葬地 |
| `personStatus` | 可选 | S | 0 健在、1 已故、2 未知 |
| `biography` | 可选 | U | 人物简介 |
| `sortOrder` | 可选 | U/S | 管理高级设置 |
| `remark` | 可选 | U | 备注 |
| `relationName` | 可选 | U | 仅“添加配偶”快捷流程显示 |
人物响应字段按以下方式使用:
- `personId``genealogyId``appUserId``fatherId``motherId``avatarOssId`I
- `genealogyName``genealogyNo`R
- `appUserNickName`R,显示绑定账号;
- `personNo``name``aliasName``sex``generation``generationName`R/编辑回填;
- `fatherName``motherName``spouseNames`R
- 出生、逝世、安葬、状态、简介、备注、排序字段:R/编辑回填。
关系流程:
1. 先选中一个现有 `personId` 作为关系锚点。
2. 点击父母、子女、兄弟姐妹或配偶。
3. 打开完整 `LineagePersonBody` 表单,创建的是一个新人物并建立关系,不是绑定两个现有人物 ID。
4. 配偶流程才显示 `relationName`
5. 当前没有解除关系接口,页面不得伪造“解除关系”。
6. DELETE 是逻辑停用;存在正常子女时可能失败,确认文案不能写成物理删除。
列表筛选:
- `keyword`U,搜索姓名、别名或人物编号;
- `generation`S,从字辈接口选择;
- `personStatus`S0/1/2
- `pageNum/pageSize`:分页组件自动维护。
`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` 负责列表。
| 字段 | 必填性 | 类型 | 页面处理 |
| --- | --- | --- | --- |
| `videoTitle` | 必填 | U | 标题 |
| `videoDesc` | 可选 | U | 说明文本域 |
| `coverOssId` | 可选 | F/I | 封面上传、预览、隐藏 ID |
| `videoOssId` | 必填 | F/I | 视频文件上传;不得输入 ID |
| `durationSeconds` | 可选 | F/A/R | 从视频元数据读取,只读显示 |
| `sortOrder` | 可选 | U/S | 管理高级设置 |
| `status` | 可选 | S | 0 正常、1 停用 |
| `genealogyId``videoId` | path | I/A | 上下文和记录 |
流程:选视频 → 分片上传 → 得到 `videoOssId` → 可选封面上传 → 填标题说明 → 保存 → 重新读取详情。
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 贺礼邀约与祭祀
邀约字段:
| 字段 | 必填性 | 类型 | 页面处理 |
| --- | --- | --- | --- |
| `inviteeUserIds` | 必填 | S/I | 家谱成员多选;提交完整名单,空数组表示取消全部未响应邀请 |
| `inviteStatus` | 必填 | S | 当前用户只能选择 ACCEPTED 接受或 DECLINED 拒绝 |
| `genealogyId``ceremonyId` | path | I/A | 活动上下文 |
邀约响应在 `profile-gift.html` 展示:
- `invitationId``genealogyId``ceremonyId``inviteeUserId`I
- `inviteStatus`RPENDING/ACCEPTED/DECLINED/CANCELED 徽标;
- `inviteVersion`I/R,只在管理审计需要时显示;
- `deliveredTime``readTime``responseTime`R
- `ceremonyTitle``ceremonyTime``location``locationAddress`R
- `longitude``latitude`:地图定位内部值,页面显示地图/地址而非原始数字。
“我的邀请”接口有完整响应,可独立实现接受/拒绝流程。管理端替换受邀人依赖 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 后开放:
- 删除活动是逻辑删除活动及祭品并释放封面引用;
- 删除祭品只操作被选中的真实 `giftId`
- 删除确认必须说明影响范围。
### 7.11 成长记录
页面:`profile-growth.html``profile-growth-edit.html`
| 字段 | 必填性 | 类型 | 页面处理 |
| --- | --- | --- | --- |
| `lineagePersonId` | 可选 | S/I | 世系人物搜索选择器,禁止 ID 输入框 |
| `recordType` | 可选 | U 或 S | C12 未解决前按 Apifox 明确结果决定,不能猜枚举 |
| `recordTitle` | 必填 | U | 标题 |
| `recordContent` | 可选 | U | 正文编辑器 |
| `recordDate` | 可选 | S | 日期控件,等待 C13 明确格式 |
| `remindTime` | 可选 | S | 日期时间控件 |
| `mediaOssIds` | 可选 | F/I | 多附件上传 |
| `sortOrder` | 可选 | U/S | 管理高级设置 |
| `status` | 可选 | A | 当前固定提交 0 正常;C26 解决前不开放 1 停用 |
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 亲友记录
页面:`profile-relative.html``profile-relative-edit.html`
| 字段 | 必填性 | 类型 | 页面处理 |
| --- | --- | --- | --- |
| `relativeName` | 必填 | U | 亲友姓名 |
| `relationName` | 可选 | U | 关系名称 |
| `eventName` | 可选 | U | 事件名称 |
| `eventTime` | 可选 | S | 日期时间选择器 |
| `giftAmount` | 可选 | U | 金额输入;只提交 JSON 序列化后数值不变的有限数字,C27 解决前不猜精度、范围或非负规则 |
| `recordContent` | 可选 | U | 内容 |
| `mediaOssIds` | 可选 | F/I | 多附件上传 |
| `sortOrder` | 可选 | U/S | 管理高级设置 |
| `status` | 可选 | A | 当前固定提交 0 正常;C29 解决前不开放 1 停用 |
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 备忘录
页面:`profile-memo.html``profile-memo-edit.html`
| 字段 | 必填性 | 类型 | 页面处理 |
| --- | --- | --- | --- |
| `memoTitle` | 必填 | U | 标题 |
| `memoContent` | 可选 | U | 内容 |
| `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`;不开放不可重读的停用状态 |
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 功德记录
页面:`profile-merit.html``profile-merit-edit.html`
| 字段 | 必填性 | 类型 | 页面处理 |
| --- | --- | --- | --- |
| `donorName` | 必填 | U | 功德人姓名 |
| `meritType` | 可选 | S | donation 捐赠、repair 修祠、public 公益、other 其他 |
| `meritTitle` | 必填 | U | 标题 |
| `meritContent` | 可选 | U | 内容 |
| `amount` | 可选 | U | YAML `number`;提交前拒绝 JSON 序列化会改变精确数值的输入,不自行限制小数位、范围或正负 |
| `meritTime` | 可选 | S | `datetime-local` 选择,提交为后端示例和 `Date` 接受的 `yyyy-MM-dd HH:mm:ss` |
| `sortOrder` | 可选 | U/S | 安全整数;创建默认 `0` |
| `status` | 可选 | I/A | 页面固定隐藏提交 `0`;不开放不可重读的停用状态 |
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。
### 7.15 消息通知
页面:`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、业务失败和网络失败。
| 字段 | 必填性 / 类型 | 标记 | 页面用途、来源与提交时机 |
| --- | --- | --- | --- |
| 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 | 顶部计数;只接受非负安全整数 |
响应和权限边界:
- YAML 的详情对象没有声明字段级 `required`、长度、范围或默认值;页面不补造限制,并兼容可选展示字段为空。
- PC 后端按当前 `APP_USER` 登录账号隔离通知,只返回 `status=0` 的正常通知;列表按接收记录倒序,非分页。
- 详情和单条已读必须命中当前账号的接收记录;重复单条已读保持已读状态,页面仍做写操作防重复。
- C34 未解决前,`genealogyNo/genealogyName/senderNickName/senderPhone/bizSummary` 不能被视为稳定可用;手机号即使后端补齐也不展示。
- 页面不展示原始 JSON,不使用 APP 接口,不开放删除,不根据 `bizType/bizId` 猜业务深链。
- 2026-07-29 已在真实登录 Chrome 验证当前账号:未读数为 0,全部和仅未读筛选均成功同步,空列表/空详情状态正确。账号暂无通知,因此有数据详情和单条/全部已读真实写入仍待有数据时复核。
### 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` 响应字段及页面用途:
| 字段 | 标记 | 页面处理 |
| --- | --- | --- |
| `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` |
权限与状态边界:
- 所有账号可按家谱查看权限读取正常成员;只有家谱详情 `canManage=true` 时显示修改和移出;
- 谱主不能直接修改或移出;只有谱主可管理管理员、分配 `admin` 或转让谱主;管理员只能管理非管理员成员并分配 `editor/member`
- 普通成员、编辑和管理员可退出家谱;谱主必须先转让谱主。退出和移出只停用成员关系,不删除世系人物;
- 更新后重读成员列表并核对同一 `memberId`;移出后核对该成员不再出现在正常列表;谱主转移后重读家谱详情和成员列表;
- 页面没有直接新增成员接口;新增成员必须通过后端已有的加入/邀请业务闭环;
- C44 未解决前不依赖 enrichment 字段并隐藏手机号;C45/C46 未解决前不伪造关系解除或成员-世系人物解绑;
- 相关聚焦测试 49/49 通过;真实账号页面验证统一留到阶段 6 收尾执行。
### 7.20 家谱创建与加入申请契约
页面:`profile-create-family.html``join-genealogy.html``profile-join-family.html``profile-join-review.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` 重读同一家谱,匹配后进入家谱主页;
- 申请页先从 `GET /genealogy/pc/genealogies/options` 选择响应中的家谱,再调用 `POST /genealogy/pc/genealogies/{genealogyId}/join-applies`
- 我的申请页调用 `GET /genealogy/pc/genealogies/join-applies/mine`;只有 `status=0` 的响应项可以调用 `DELETE /genealogy/pc/genealogies/join-applies/{applyId}`
- 审核页只从当前家谱上下文取得 `genealogyId`,先读取家谱详情并要求 `canManage=true`,再读取 `GET /genealogy/pc/genealogies/{genealogyId}/join-applies/pending`
- 审核只调用 `PUT /genealogy/pc/genealogies/{genealogyId}/join-applies/{applyId}/audit``status=1` 通过、`status=2` 拒绝;
- `inviterUserId` 没有安全 PC 邀请来源,本轮加入申请契约不发送该字段;
- 创建/申请/审核页面均无家谱 ID、申请 ID、用户 ID 或 OSS ID 文本输入,所有写操作防重复并在成功后重读对应资源。
`AppGenealogyJoinApplyBody`
| 字段 | 类型/约束 | 必填 | 来源 | 页面与提交时机 |
| --- | --- | --- | --- | --- |
| `applicantName` | string,最长 50 | 否 | U | 申请页姓名;非空时提交 |
| `phone` | string,最长 30 | 否 | U | 申请页联系电话;非空时提交 |
| `relationDesc` | string,最长 100 | 否 | U | 申请页关系说明;非空时提交 |
| `applyReason` | string,最长 500 | 否 | U | 申请页申请理由;非空时提交 |
| `inviterUserId` | int64/string | 邀请模式条件必填 | I | PC 无安全来源,不展示、不发送;邀请模式继续阻断 |
`GenealogyJoinApplyVo` 与审核:
| 字段 | 类型/枚举 | 来源 | 页面用途 |
| --- | --- | --- | --- |
| `applyId``genealogyId` | int64/string | R/I | 响应按钮属性和写入 path;不显示、不手填 |
| `genealogyName``surname` | string | R | 我的申请列表只读展示 |
| `applicantName``phone``relationDesc``applyReason` | string | R | 管理员待审核列表;只展示申请人主动提交的 `phone` |
| `auditTime``auditRemark` | datetime/string | R | 我的申请审核结果展示 |
| `status` | string `0/1/2/3` | R | 待审核/通过/拒绝/撤销;仅 `0` 可撤销 |
| `appUserId``inviterUserId``auditUserId` | int64/string | I | 普通列表和审核列表均不展示 |
| `appUserPhone``inviterPhone``auditPhone` | string | I | 账号/邀请/审核人员隐私字段均不展示 |
| 审核 `status` | string `1/2` | S | 管理员点击通过或拒绝时提交 |
| 审核 `auditRemark` | string,最长 500 | U | 拒绝时可选填写,非空时提交 |
### 7.21 意见反馈与工单契约
页面:`profile-feedback.html``submit-ticket.html``my-tickets.html``ticket-detail.html`
后端只提供统一反馈集合:
- `POST /genealogy/pc/feedback`:当前业务用户提交反馈;
- `GET /genealogy/pc/feedback`:读取当前业务用户自己的反馈列表;
- 没有独立 ticket path;“工单”只是帮助中心对同一反馈记录的页面名称;
- 没有用户侧回复、追问、关闭或删除接口,页面不制造这些操作。
`AppFeedbackBody`
| 字段 | 类型/约束 | 必填 | 来源 | 页面与提交时机 |
| --- | --- | --- | --- | --- |
| `feedbackType` | string enum `advice/bug/complaint/other` | 否 | S | 两个提交页选择;空值省略并由后端默认 `advice` |
| `feedbackContent` | string,非空 | 是 | U | 问题或建议正文;提交时发送 |
| `contactInfo` | string | 否 | U | 用户主动提供的联系方式;非空时发送 |
| `feedbackTitle` | — | — | V | 旧页面字段不属于 DTO,已从表单移除且不发送 |
`FeedbackVo`
| 字段 | 类型/枚举 | 来源 | 页面用途 |
| --- | --- | --- | --- |
| `feedbackId` | int64/string | R/I | 提交后重读匹配、列表详情链接和 URL;不展示、不手填 |
| `feedbackType``feedbackContent``contactInfo` | string | R | 列表或详情只读展示 |
| `handleStatus` | string `0/1/2/3` | R | 待处理/处理中/已处理/已关闭 |
| `handleResult``handleTime``remark` | string/datetime | R | 后端存在时在详情展示 |
| `status` | string `0/1` | I | 响应有效性校验,不提供用户修改入口 |
| `appUserId``handlerId` | int64/string | I | 内部用户/处理人编号,不展示 |
| `appUserNickName``appUserPhone` | string | I | 后台 enrichment,不在用户页面展示 |
实现边界:
- ApiClient 是 GET/POST path 和三字段 body 白名单唯一 owner
- 提交使用防重复锁;成功响应必须包含稳定字符串 `feedbackId`
- 提交后重读我的反馈列表并精确找到同一 `feedbackId`,否则不宣称成功;
- 工单详情从 URL 读取列表响应产生的 `feedbackId`,只在我的列表中精确匹配,不回退第一条;
- 401 清理登录态;403 保留登录态并显示后端权限错误;页面不展示原始 JSON。
### 7.22 VIP 套餐与会员订单契约
页面:`profile-services.html`
PC 端只开放三条业务用户接口:
- `GET /genealogy/pc/vip/packages`:读取可购买套餐,成功响应 `List<VipPackageVo>`,无分页结构;
- `POST /genealogy/pc/vip/orders`:创建当前业务用户的会员订单,body 为 `AppVipOrderBody`,成功响应 `VipOrderVo`
- `GET /genealogy/pc/vip/orders`:读取当前业务用户的订单,成功响应 `List<VipOrderVo>`,无分页结构;
- 三条接口均要求 APP_USER 登录态;创建接口有后端防重复提交;
- PC Controller 没有支付发起、支付回调、取消、关闭或退款接口,页面不得制造这些动作。
`AppVipOrderBody`
| 字段 | 类型/约束 | 必填 | 来源 | 页面与提交时机 |
| --- | --- | --- | --- | --- |
| `packageId` | int64,稳定字符串 ID | 是 | S/I | 用户点击套餐响应生成的卡片时选择;创建订单时发送,禁止手填 |
| `genealogyId` | int64,稳定字符串 ID | 否 | S/I | 用户从“我的家谱”响应生成的下拉项中选择;非空时创建订单发送,禁止手填 |
| `payType` | string;后端空值默认 `wechat` | 否 | A | PC 页面不提供支付方式选择且默认省略;等待后端开放真实支付能力后再确认枚举 |
`VipPackageVo`
| 字段 | 类型/枚举 | 来源 | 页面用途 |
| --- | --- | --- | --- |
| `packageId` | int64/string | R/I | 套餐卡片选择和订单 body;不展示、不手填 |
| `packageName``packageDesc` | string | R | 套餐名称和说明 |
| `packageType` | string `vip/storage` | R | 显示为会员套餐/存储扩容;未知值整条过滤 |
| `price``originalPrice` | decimal,非负 | R | 价格展示;不参与前端金额计算 |
| `durationValue` | integer,非负,可空 | R | 与期限单位组合展示 |
| `durationUnit` | string `permanent/day/month/year` | R | 永久/天/月/年 |
| `genealogyLimit``memberLimit``storageLimitMb` | integer,非负,可空 | R | 套餐额度只读展示 |
| `featureJson` | string/JSON | I | 契约未说明用户侧展示结构,当前不解析、不展示 |
| `sortOrder` | integer | I | 后端列表顺序 owner,前端不重新排序 |
| `status` | string `0/1` | I | 只接受正常 `0` 套餐;不提供修改入口 |
| `remark` | string | R | 非空时作为套餐补充说明展示 |
`VipOrderVo`
| 字段 | 类型/枚举 | 来源 | 页面用途 |
| --- | --- | --- | --- |
| `orderId` | int64/string | R/I | 创建后重读精确匹配;不展示、不手填 |
| `orderNo` | string | R | 订单列表展示 |
| `packageId` | int64/string | R/I | 响应有效性校验,不展示 |
| `packageName` | string | R | 订单套餐名称 |
| `genealogyId` | int64/string,可空 | R/I | 响应有效性校验,不展示 |
| `genealogyNo``genealogyName` | string,可空 | R | 关联家谱信息展示 |
| `orderAmount``payAmount` | decimal,非负 | R | 订单金额和应付金额展示;以后端值为准 |
| `payType` | string,可空 | R | 后端返回时只读展示,不作为支付入口 |
| `payStatus` | string `0/1/2/3` | R | 待支付/已支付/已关闭/已退款 |
| `payTime``expireTime` | datetime,可空 | R | 支付和有效期信息展示 |
| `status` | string `0/1` | I | 响应有效性校验,不提供修改入口 |
| `remark` | string | R | 非空时展示 |
| `appUserId``appUserNickName``appUserPhone` | int64/string | I | 当前账号及 enrichment 字段,不在页面展示 |
实现边界:
- `ApiClient` 是三条 path 与三字段请求白名单唯一 owner;
- 页面启动时并行读取套餐、我的家谱和订单;所有业务 ID 仅来自响应;
- 创建使用前端防重复锁;创建响应必须是完整有效 `VipOrderVo`,随后重读订单列表并精确找到同一 `orderId` 才宣称成功;
- 页面省略 `payType`,由后端使用已确认的默认值;不展示后端尚未提供的支付、取消或退款按钮;
- 401 清理登录态;403 保留登录态并展示后端错误;非法套餐、订单、金额、枚举或不安全数字 ID 整条过滤。
### 7.23 家谱主页只读概览契约
页面:`profile-family-home.html`
只使用两条现有 PC 读取接口:
- `GET /genealogy/pc/genealogies/{genealogyId}/overview`:返回当前家谱 `AppGenealogyVo`
- `GET /genealogy/pc/genealogies/{genealogyId}/lineage/tree`:返回 `List<LineagePersonTreeView>`
- `genealogyId` 只从 `profile-common.js` 的当前家谱上下文取得,两条请求使用同一个稳定字符串 ID;
- 后端 `overview` 当前实际复用家谱详情服务,不包含文章、相册、视频、祭祀或动态聚合统计。
`AppGenealogyVo` 主页字段:
| 字段 | 类型/约束 | 来源 | 页面用途 |
| --- | --- | --- | --- |
| `genealogyId` | int64/string | I/A/R | 当前上下文、两条请求 path、响应一致性校验;不展示、不手填 |
| `genealogyNo` | string | R | 家谱编号只读展示 |
| `genealogyName` | string,非空 | R | 页面标题和概览名称 |
| `surname` | string | R | 姓氏只读展示 |
| `ancestralHall` | string,可空 | R | 堂号非空时展示 |
| `originPlace` | string,可空 | R | 祖籍非空时展示 |
| `regionFullName` | string,可空 | R | 完整地区非空时展示 |
| `memberCount``personCount` | integer,非负 | R | 家谱成员数和世系人物数;不在前端计算 |
| `status` | string `0/1` | I | 只接受正常 `0` 响应 |
| `canManage` | boolean | I | 严格为 `true` 时显示家谱管理入口 |
| `canEditContent` | boolean | I | 保留为内容权限上下文,主页不据此制造写操作 |
| `ownerUserId``coverOssId` | int64/string | I | 当前主页不展示、不手填 |
| 其余地址、角色、成员状态字段 | 对应 DTO 类型 | I | 当前主页不消费,不输出原始 JSON |
`LineagePersonTreeView`
| 字段 | 类型/结构 | 来源 | 页面用途 |
| --- | --- | --- | --- |
| `personId``genealogyId` | int64/string | R/I | 树节点稳定标识和响应校验;不直接展示 |
| `name``generationName`、人物摘要字段 | string | R | 复用世系页的安全节点渲染 |
| `relationType` | string `father/mother/spouse/child/adoptive` | R | 关系数据;主页不提供关系写操作 |
| `relationName` | string | R | 非空时作为关系说明 |
| `spouses``children` | `LineagePersonTreeView[]` | R | 递归世系预览 |
实现边界:
- `lineage-pages.js` 是世系节点规范化和树 HTML 的唯一 owner;主页不得复制一套树字段解释;
- 页面并行读取概览和世系树,只展示基础信息、后端已有计数和真实树;
- `/genealogy/dashboard/overview` 是后台权限接口,不属于 PC 家谱主页契约,前端不调用;
- PC 没有成员邀请创建或分享接口,“邀请家人”只显示阻断说明,不提供可点击操作;
- 主页没有写请求;401 清理登录态,403 保留登录态并显示读取错误。
### 7.24 帮助中心文章契约
页面:`help.html`。契约 owner`utils/ApiClient.js``helpArticles` / `helpArticleDetail``public/js/help-pages.js`
接口:
| method | 完整 path | 目录 | 鉴权 | 页面时机 |
| --- | --- | --- | --- | --- |
| GET | `/genealogy/pc/help-articles` | 内容文章 / PC 帮助文章列表 | APP_USER 登录态 | 帮助中心初始化或用户点击刷新 |
| GET | `/genealogy/pc/help-articles/{helpId}` | 内容文章 / PC 帮助文章详情 | APP_USER 登录态 | 用户从列表点击“查看完整解答” |
请求字段:
| 位置 | 字段 | 必填 | 类型/约束 | 分类 | 页面来源与提交时机 |
| --- | --- | --- | --- | --- | --- |
| Query | `helpCategory` | 否 | string;后端按完全相等过滤 | S | 当前页面不提供猜测分类选项,因此初始化时省略;后端提供正式分类字典后才能开放选择 |
| Path | `helpId` | 是 | int64;前端始终按非零十进制字符串处理 | I | 只能来自列表响应;用户点击该文章详情时进入 path |
| Header | `clientid` | 基础请求自动 | string | A | `AxiosRequestUtil` 统一填写;页面不输入 |
| Header | `Authorization` | 是 | `Bearer <access_token>` | A | `AxiosRequestUtil` 从登录态自动填写;页面不读取、不展示 token |
| Body | — | — | 两个接口均无 Body | — | — |
`HelpArticleVo` 响应字段:
| 字段 | 类型 | 分类 | 页面使用 |
| --- | --- | --- | --- |
| `helpId` | int64/string | I | 校验稳定 ID、绑定列表项并精确重读详情;不向用户显示 |
| `helpCategory` | string | R | 列表和详情分类文字,可空 |
| `helpTitle` | string | R | 列表标题,空值使整条响应失败 |
| `helpContent` | string | R | 详情正文;转义并保留换行,不执行响应 HTML |
| `coverOssId` | int64/string | I | 当前响应没有文件 URL,页面不显示、不拼接、不允许手填 |
| `sortOrder` | int64 | I | 仅后端排序,不展示 |
| `viewCount` | int64/string | R | 非负十进制计数;详情 GET 会由后端累计浏览量 |
| `status` | string | I | 只接受正常值 `0`;停用文章整条拒绝 |
| `remark` | string | I | 后台备注,不展示 |
响应与错误边界:
- 列表响应必须是直接数组,不接受分页 `rows` 猜测;任一元素缺少稳定 ID、标题、正文或正常状态时整批失败;
- 详情必须返回与 path 中相同的 `helpId`,不回退到列表第一条;
- YAML 把两条接口标为 `security: []`,但 `PcHelpArticleController` 没有 `@SaIgnore`,部署环境无 token 实测返回“认证失败”;按用户指定的后端项目为准,当前客户端必须携带登录 token,此项作为 Apifox/后端冲突保留;
- 后端只声明统一成功包装,未提供帮助文章专属失败码;详情不存在或停用时按业务错误展示,不制造 404 枚举;
- 无登录态或 401 清理登录状态并进入登录页;403 保留登录态并展示读取错误;
- 页面只有读取动作,不提供编辑、发布、分类管理、封面 OSS ID 或成员邀请操作。
### 7.25 应用推广契约
页面:`app.html` 的“应用推广”区域。契约 owner:`utils/ApiClient.js``promotions``public/js/app-promotion-pages.js`
接口:
| method | 完整 path | 目录 | 鉴权 | 页面时机 |
| --- | --- | --- | --- | --- |
| GET | `/genealogy/pc/promotions` | 应用推广 / PC 应用推广列表 | APP_USER 登录态 | 应用下载页初始化;仅在本地已有登录 token 时请求一次 |
请求字段:
| 位置 | 字段 | 必填 | 类型/约束 | 分类 | 页面来源与提交时机 |
| --- | --- | --- | --- | --- | --- |
| Query | `platform` | 否 | stringSQL 字典 `gen_promotion_platform``all` 全部(默认)、`app` APP、`pc` PC、`wechat` 小程序 | A | 应用下载页初始化时固定发送 `pc`;服务端返回 `platform=pc``platform=all` 的正常记录,不让用户选择或手填 |
| Header | `clientid` | 基础请求自动 | string | A | `AxiosRequestUtil` 统一填写;页面不输入 |
| Header | `Authorization` | 是 | `Bearer <access_token>` | A | `AxiosRequestUtil` 从登录态自动填写;页面不读取、不展示 token |
| Path | — | — | 无 Path 参数 | — | — |
| Body | — | — | 无 Body | — | — |
`AppPromotionVo` 列表元素字段:
| 字段 | 类型 | 必填/约束 | 分类 | 页面使用 |
| --- | --- | --- | --- | --- |
| `promotionId` | int64/string | SQL `BIGINT NOT NULL` 主键;前端按非零十进制字符串处理 | I | 稳定绑定卡片,不展示、不允许手填 |
| `promotionKey` | string | SQL `varchar(80) NOT NULL DEFAULT 'banner'` | I | 后台识别字段,页面不展示、不提交 |
| `promotionTitle` | string | SQL `varchar(200) NOT NULL`;页面要求非空 | R | 推广卡片标题,HTML 转义后展示 |
| `promotionDesc` | string | SQL `varchar(500) DEFAULT ''`,可空 | R | 推广卡片说明,HTML 转义后展示 |
| `coverOssId` | int64/string | 可空;响应没有可访问文件 URL | I | 不显示、不拼接 URL、不允许用户手填 OSS ID |
| `targetUrl` | string | SQL `varchar(500) DEFAULT ''`,可空;页面只接受绝对 `http`/`https` URL | R | 合法时作为新窗口外链;危险、相对或无效地址降级为不可点击卡片 |
| `platform` | string | SQL `varchar(30) NOT NULL DEFAULT 'all'`;字典枚举 `all/app/pc/wechat` | I | 仅后端筛选,当前页面不展示 |
| `sortOrder` | int64 | SQL `BIGINT NOT NULL DEFAULT 0`;后端按升序排序,同序按 `promotionId` 降序 | I | 仅后端排序,页面不二次解释或展示 |
| `status` | string | SQL `char(1) NOT NULL DEFAULT '0'``0` 正常、`1` 停用,页面只接受 `0` | I | 停用或缺失状态的记录使整批响应失败 |
| `remark` | string | SQL `varchar(500) DEFAULT NULL`,可空 | I | 后台备注,不展示 |
响应、权限与错误边界:
- 成功响应经统一请求层解包后必须是直接 `AppPromotionVo[]`,不是分页结构;任一元素缺少稳定 ID、标题或正常状态时整批失败;
- 空数组显示“当前暂无应用推广”;未登录时页面本身仍可浏览,但推广区域只显示登录入口且不发请求;
- YAML 把该接口标为 `security: []`,并且未导出真实 Query `platform`;后端 `PcPromotionApiController` 没有 `@SaIgnore`,全局拦截器要求 APP_USER 登录态。SQL 字典已确认 `all/app/pc/wechat`,服务层按“请求平台或 `all`”过滤,因此当前实现携带登录 token 并固定发送 `platform=pc`
- SQL `gen_app_promotion` 另有 `(tenant_id, platform, status, del_flag)``(tenant_id, sort_order)` 索引;`del_flag`、租户及审计字段不属于 `AppPromotionVo`,页面不接收、不展示;
- 401 清理失效 token 并把推广区域切回登录提示;403、网络失败或畸形响应只在推广区域显示读取失败,不跳转、不清理有效登录态;
- 后端只声明统一成功包装,未提供推广专属失败响应和错误码;字段长度、默认值及平台枚举已由后端 SQL 冻结,不再作为阻断项;
- 页面没有推广新增、编辑、上下架、排序、封面上传能力,不调用 APP 或后台管理接口,也不向用户暴露推广 ID、推广键或 OSS ID。
### 7.26 官网资讯契约
页面:`news.html``article-detail.html`。契约 owner`utils/ApiClient.js``siteArticles``utils/AxiosRequestUtil.js` 的公开请求鉴权语义和 `public/js/site-news-pages.js`
接口:
| method | 完整 path | 目录 | 鉴权 | 页面时机 |
| --- | --- | --- | --- | --- |
| GET | `/genealogy/pc/site/articles` | 站点内容 / PC 站点文章列表 | 公开,`auth: false` | 资讯列表初始化、分类切换后的页面初始化,以及详情页按列表响应 ID 精确重读 |
请求字段:
| 位置 | 字段 | 必填 | 类型/约束 | 分类 | 页面来源与提交时机 |
| --- | --- | --- | --- | --- | --- |
| Query | `articleType` | 否 | stringSQL 字典 `gen_site_article_type``news` 新闻(默认)、`notice` 公告、`download` 下载 | S | 用户点击固定分类链接后从 URL Query 取得;“全部”省略该字段,非法值不发请求 |
| Query | `limit` | 否 | integer,客户端只允许 1–100;服务端正数最大取 100 | A | 列表和详情重读均由页面固定提交 `100` |
| Header | `clientid` | 基础请求自动 | string | A | `AxiosRequestUtil` 统一填写;页面不输入 |
| Header | `Authorization` | 不发送 | — | — | 方法设置 `auth: false`;即使本地已有 token 也不发送,公开请求 401/403 不清理既有登录态 |
| Path | — | — | 无 Path 参数 | — | — |
| Body | — | — | 无 Body | — | — |
`SiteArticleVo` 列表元素字段:
| 字段 | 类型 | 必填/约束 | 分类 | 页面使用 |
| --- | --- | --- | --- | --- |
| `articleId` | int64/string | SQL `BIGINT NOT NULL` 主键;前端按非零十进制字符串处理 | I | 只用于生成详情链接和精确匹配,不展示、不允许手填 |
| `articleType` | string | SQL `varchar(50) NOT NULL DEFAULT 'news'`;枚举 `news/notice/download` | R | 转换为新闻、公告、下载标签;枚举外记录使整批失败 |
| `articleTitle` | string | SQL `varchar(200) NOT NULL`;页面要求非空 | R | 列表和详情标题,HTML 转义 |
| `articleSummary` | string | SQL `varchar(500) DEFAULT ''`,可空 | R | 列表摘要和详情导语,HTML 转义 |
| `articleContent` | string | SQL `text`,可空 | R | 详情正文;HTML 转义并只保留换行,不执行响应 HTML |
| `coverOssId` | int64/string | SQL `BIGINT`,可空;没有文件 URL 契约 | I | 不显示、不拼接 URL、不允许手填 |
| `externalUrl` | string | SQL `varchar(500) DEFAULT ''`,可空 | R | 只接受绝对 HTTP/HTTPS;合法时显示安全新窗口外链 |
| `publishTime` | string/date | SQL `datetime`,可空 | R | 列表和详情发布时间文本,HTML 转义 |
| `sortOrder` | int64 | SQL `BIGINT NOT NULL DEFAULT 0` | I | 仅后端排序,不展示 |
| `status` | string | SQL `char(1) NOT NULL DEFAULT '0'``0` 正常、`1` 停用 | I | 页面只接受 `0`,其他值使整批失败 |
| `remark` | string | SQL `varchar(500) DEFAULT NULL`,可空 | I | 后台备注,不展示 |
响应、详情与错误边界:
- 成功响应经统一请求层解包后必须是直接 `SiteArticleVo[]`;不接受分页 `rows`、旧字段别名或任一非法元素;
- 服务端只查正常状态,按 `sortOrder ASC``publishTime DESC``articleId DESC` 排序;页面保持服务端顺序;
- 后端没有站点文章单条详情接口;详情 ID 只能来自列表链接,详情页重读 `{ limit: 100 }` 后精确匹配同一字符串 ID,不存在时不得回退第一条;
- 标题、摘要、正文、类型和时间全部转义;正文只保留换行,危险或相对外链不渲染;
- 空数组显示“当前暂无资讯”;非法分类/详情 ID 不发请求;网络、业务或畸形响应只更新内容区域,不跳转登录;
- YAML 已导出 method/path、`articleType``limit``security: []`,但响应只引用通用列表结果,未导出 `SiteArticleVo` 11 个字段;完整字段、长度、默认值和枚举由后端 VO、SQL 与字典补齐;
- 2026-07-30 使用本地页面对当前部署环境做匿名实测时,接口返回“认证失败,无法访问系统资源”;页面保持在资讯页且不清理已有 token。该行为与 YAML 的 `security: []` 冲突,后端需确认部署环境是否遗漏公开放行;前端不通过附带登录 token 绕过公开契约;
- `coverOssId` 的文件 URL 和站点文章单条详情接口继续作为后端契约阻断;页面不猜 URL、不制造详情路径;
- 本批不修改 `about/culture/privacy/terms/surname`,也不复用登录后的家谱谱文 CRUD 脚本。
## 8. 分阶段执行顺序
### 阶段 0:冻结契约
工作:
1. 完成第 5 节 C01-C20 的本阶段相关项。
2. 从 Apifox 重导 OpenAPI。
3. 写契约快照测试,统计操作、请求字段、required、枚举、response `$ref`
4. 确认旧路径和旧字段会失败,不保留兼容代码。
验证:
- JSON 可被解析;
- 实际接口数与收口后的 Apifox 一致;
- 0 个重复逻辑路径;
- 本轮实现模块不存在泛型业务响应;
- 登录注册等公开接口不带 Authorization。
### 阶段 1:请求基础设施、验证和认证
工作:
1. 收口 `config.js``AxiosRequestUtil.js``ApiClient.js` 的 header、token、tenant 和错误处理。
2. 完成验证中心、登录、注册、短信、找回、换绑、注销、退出。
3. 完成个人资料全部 6 个写入字段。
4. 为每条认证流程测试 body 中不存在 `clientId``sceneCode` 和旧验证码字段。
验证:
- 密码、短信两种登录成功;
- 验证策略开/关两条路径都成功;
- 一次性 `validToken` 不能复用;
- 401 清除登录态,403 保留登录态;
- 确认密码等 V 类字段未进入请求。
### 阶段 2:文件上传和区划
前提:C01、C08、C09、C10、C13 完成。
工作:
1. 实现单分片小文件和多分片大文件同一流程。
2. 实现秒传、进度、重试、重复提交锁。
3. 只保留一套区划 API,并实现级联、搜索、路径回显。
4. 所有业务页移除可见 OSS ID 输入框。
验证:
- 普通图片、最后一片不足 chunkSize、大视频、秒传分别通过;
- MD5、大小、分片数量与初始化一致;
- 页面刷新后的业务对象能重新显示文件;
- 区划不同时请求两套路径。
### 阶段 3:家谱上下文
前提:C14 完成,或产品正式给出可验证的外部上下文契约。
工作:
1. `profile-common.js` 成为 `genealogyId` 的唯一 owner。
2. 我的家谱页提供真实选择入口。
3. 所有家谱内链接传播同一个 ID,切换家谱时清空旧页面状态。
验证:
- 无 ID 不发请求;
- 切换两个家谱不串数据;
- ID 始终按字符串处理;
- 不存在示例 ID 和 APP 请求。
### 阶段 4:已具备明确 DTO 的家谱模块
顺序:
1. 家族圈;
2. 字辈谱;
3. 世系人物;
4. 我的贺礼邀请。
当前进度:阶段 4 已按顺序完成。家族圈的列表、正常状态详情、新增、修改、删除、点赞、评论、回复、权限显隐、空/错状态、防重复和写后重读已接入,C21 图片 URL 回显和 C22 停用记录管理继续阻断;字辈谱的正常/management 权限分流、单条新增修改和状态切换、批量预览保存、响应校验、防重复、写后重读及无家谱上下文分支已接入;世系人物的树、列表、分页、详情、完整字段表单、权限显隐、四类关系新增、逻辑停用、防重复和写后重读已接入,C15 继续阻断新增 `SPECIFIED` 账号绑定,C18 继续阻断可选字段清空闭环;我的贺礼邀请已接入当前账号列表、响应内详情、PENDING 接受/拒绝、长 ID 校验、权限/空错状态、防重复和写后重读,C16 继续阻断管理员替换受邀人。真实账号已验证邀请读取和空状态;该账号没有待响应邀请,接受/拒绝真实写入仍待有数据时复核。下一阶段为阶段 5,按顺序先核对视频 DTO 是否已完整。
每个模块采用同一闭环:
1. `ApiClient` 方法和契约测试;
2. 列表/详情读取;
3. 新增;
4. 修改;
5. 删除/停用;
6. 权限、空状态、错误状态、重复提交;
7. 写入后重新读取验证。
### 阶段 5:补齐 DTO 后的资源模块
顺序:
1. 视频;
2. 成长记录;
3. 亲友记录;
4. 备忘录;
5. 功德记录;
6. 消息通知。
任何模块的 View/List DTO 未补齐时,只完成表单布局和契约测试,不宣称真实功能完成。
当前进度:阶段 5 已按顺序完成。视频、成长记录、亲友记录、备忘录、功德记录和消息通知均已按对应 PC 契约开放;各模块的文件 URL、停用记录、可选字段清空和金额业务边界仍按 C18、C23–C34 保持阻断,不通过 APP 接口或猜测字段补齐。
### 阶段 6:当前 PC 契约可闭环模块
已完成:
- 谱文列表、详情、新增、修改和删除;
- 相册列表、新增、修改、删除,以及照片列表、新增和删除;
- 祭祀活动列表、详情、新增、修改和删除,祭品列表、新增和删除;
- 管理员受邀名单读取和完整替换,当前账号邀请读取与响应;
- 从同家谱正常成员选项中选择账号完成 `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 校验、防重复与创建后重读均已接入。第二批已完成申请加入、我的申请、撤销和管理员审核页面;家谱/申请 ID 均来自响应或当前上下文,写后重读确认。第三批已完成意见反馈和工单展示:四页共用 PC 反馈集合、提交后三字段白名单重读、详情精确匹配和隐私过滤。第四批已完成 VIP 套餐、我的家谱选项和会员订单:页面不手填业务 ID,不伪造支付能力,创建后重读同一订单确认。第五批已完成家谱主页只读概览:复用 PC overview 与世系树,权限控制管理入口,不制造聚合统计或邀请能力。第六批已完成帮助中心:登录后读取 PC 帮助文章列表,列表响应产生详情 ID,详情精确重读并安全展示正文;Apifox 的匿名标记与后端实际鉴权冲突已记录。第七批已完成应用下载页的 PC 推广列表:未登录不发请求,登录后固定发送 SQL 字典确认的 `platform=pc`,只读取服务端返回的 `pc + all` 正常记录,外链仅允许绝对 HTTP/HTTPS;推广 ID、后台键和 OSS ID 均不向用户开放。第八批已完成官网资讯列表与详情:公开请求不发送或清理 token,分类使用 SQL 字典 `news/notice/download`,详情 ID 只来自列表响应并通过重读列表精确匹配;YAML 通用响应缺失完整 `SiteArticleVo`,且当前部署环境匿名访问仍返回认证失败,两项差异均已记录。成员邀请、分享和资料完善提醒没有 PC Controller,继续阻断。
## 9. 文件级施工建议
执行 AI 应优先修改现有 owner,避免页面各自复制逻辑:
| 文件 | 职责 |
| --- | --- |
| `PC.openapi.json` | 当次 Apifox 导出快照,不手工补字段 |
| `config.js` | 环境、API 基础地址、非敏感客户端配置 |
| `utils/ApiClient.js` | 所有正式 PC method/path/query/body |
| `utils/AxiosRequestUtil.js` | header、鉴权、响应解包、401/403 |
| `utils/FormUtil.js` | 表单取值、空可选字段省略、前端校验 |
| `public/js/profile-common.js` | 登录页外的家谱上下文 |
| `public/js/upload-pages.js` | 唯一上传流程 |
| `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/video-pages.js` | 视频列表、详情、编辑、上传元数据和 CRUD |
| `public/js/growth-pages.js``relative-pages.js``memo-pages.js` | 对应族务记录 |
| `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` | 成员修改、移出、退出和谱主转移 |
不得创建一个包揽所有业务的巨型页面脚本,也不得为了单一页面引入通用框架。
## 10. 测试与验收
### 10.1 必须新增或更新的测试
1. OpenAPI 契约快照:
- operation 数;
- method/path
- body Schema
- required
- enum
- response `$ref`
- 公开/鉴权标记。
2. `ApiClient` 契约测试:
- URL、query、body、header
- int64 ID 不转 Number
- 可选空字段被省略;
- 旧路径/旧字段不存在。
3. 页面字段覆盖测试:
- 每个请求 Schema 字段都能在本规划的 U/S/F/A/I/V 分类中找到;
- 用户可编辑字段有对应 `name`
- 自动字段没有可见文本输入;
- 枚举选项和值与 Apifox 一致。
4. 流程测试:
- 验证与短信;
- 上传;
- 家谱上下文;
- 列表 → 详情 → 编辑 → 重读;
- 删除/停用;
- 401/403/404/409/422/429。
5. 安全测试:
- 富文本经过净化;
- 密码、token、validToken 不写日志;
- 外链和上传文件类型受限;
- 错误消息不直接插入 HTML。
### 10.2 建议验证命令
先跑最小相关测试,再跑全量:
```powershell
node --test tests/api-client-contract.test.js
node --test tests/auth-pages.test.js tests/captcha-pages-contract.test.js
node --test tests/feed-pages.test.js tests/generation-pages.test.js tests/lineage-pages.test.js
node --test tests/growth-pages.test.js tests/relative-pages.test.js tests/memo-pages.test.js tests/notification-pages.test.js
node --test tests/*.test.js
```
执行 AI 若新增测试文件,应在本节补上精确命令。
### 10.3 模块完成定义
一个模块只有同时满足以下条件才可标记完成:
1. Apifox 契约无未决冲突。
2. 所有请求字段已分类并落到页面。
3. 列表和详情响应有明确 DTO。
4. `ApiClient` 不存在旧路径或 fallback。
5. 页面有加载、空、成功、校验失败、无权限和网络失败状态。
6. 写操作防重复提交。
7. 成功写入后通过重新读取验证。
8. 相关测试和全量测试通过。
9. 没有原始 JSON、手填 ID、示例业务数据或 APP 接口冒充真实能力。
## 11. 执行完成后的报告格式
执行 AI 每个阶段只报告:
```text
Changed: 修改了哪些 owner、页面和契约。
Verified: 运行了哪些测试,真实联调覆盖了哪些流程。
Blocked: 哪些字段或流程仍缺 Apifox/后端契约,具体缺什么。
Next: 下一阶段可以开始的最小范围。
```
不得用“应该可以”“大概完成”代替测试证据。