Files
jiapuapp/docs/家谱项目全量治理设计.md
T
2026-07-22 17:31:38 +08:00

844 lines
60 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.
# 家谱项目全量治理设计
> 日期:2026-07-22
> 状态:阶段 0 已完成;导航栈语义与 T01 大规模世系树设计已经三人终审,尚未实施业务代码
> 适用范围:当前 UniApp 家谱项目、根目录 OpenAPI 文档、全部活动页面、共享组件、测试、正式资产与后续真实接口接入
## 一、背景
项目第一轮国风视觉与响应式适配已经完成,并形成了可继续工作的页面基线。但此前验收包含用户抽查和代理逐页审核,不能据此认定所有页面、所有真实数据和所有跨页流程都已经最终正确。
当前根目录已经取得由 Apifox 导出的两份接口文档:
- `APP.openapi.json`
- `APP.openapi.yaml`
两份文件来自同一个 Apifox 项目,只是表示格式不同。接口由后端根据此前思维导图设计,并非根据当前页面逐项编写,因此可能混入 PC 接口,也可能存在 App 接口缺失、字段不足、状态不完整或页面与接口目标不一致等问题。
后续工作不能简单地让页面迁就现有接口,也不能默认现有页面天然正确。必须从真实业务目标出发,对页面、接口、功能、交互、导航、视觉和数据状态进行联合治理。
## 二、治理目标
本轮治理需要同时达到以下目标:
1. 清理失效、重复、冲突或确实无用的文档、测试、资产和生成物,使工作区只保留当前有效内容。
2. 区分 App 接口、PC 接口、可共用接口和缺失接口,严格分析 OpenAPI 合同。
3. 建立页面、用户操作、业务状态、接口和数据结果之间的精确映射。
4. 重新评估全部活动页面的视觉完整性与美观性,不把“没有溢出”当成视觉完成。
5. 检查功能、权限、交互、导航、失败处理、重复操作和跨页数据一致性。
6. 按业务域向后端持续提交可直接修改 Apifox 的完整接口问题单。
7. 采用测试先行和小批次实施,每批均在 MuMu Android 中完成真实流程和视觉验收。
8. 建立中文文档、中文进度和详细中文代码注释规范,使后续接手者能够明确知道当前做到哪一步、每一步在做什么。
## 三、明确不做的事情
- 不重新推翻已经建立的国风设计方向。
- 不因为旧页面曾经通过就忽略新发现的可复现问题。
- 不因为现有接口已经写好就强迫页面接受错误业务。
- 不因为现有页面已经完成就无条件要求后端照搬。
- 不一次混合实施多个业务域。
- 不用浏览器截图代替 MuMu 视觉验收。
- 不制造临时假接口、双读取兼容路径或伪持久化成功。
- 不通过删除失败测试来制造全量通过。
- 不执行 Git add、commit、push、restore、checkout 或 reset 等 Git 变更操作。
## 四、权威顺序与唯一所有者
发生冲突时,采用以下权威顺序:
1. 用户确认的真实业务目标。
2. 三人共同推演并验证的完整用户流程。
3. 后端在 Apifox 中确认的接口合同。
4. 页面、共享组件和运行时代码。
5. 自动化测试与 MuMu 当前轮验收证据。
6. 当前 OpenAPI 导出、现有 mock 和历史资料。
关键合同的唯一所有者如下:
- 接口源头:后端维护的 Apifox 项目。
- 自动分析格式:`APP.openapi.json`
- 人工阅读与跨工具格式:`APP.openapi.yaml`
- 九宫格切片与可变高度装饰面:`styles/adaptive-frame-profiles.scss`
- 密码策略:`utils/validation.js`
- 响应式覆盖范围:`tests/responsive-layout-coverage.json`
- 固定尺寸例外:`tests/responsive-layout-allowlist.json`
- 项目当前状态入口:`docs/项目当前总览.md`
- 页面与接口关系:`docs/接口与页面映射总表.md`
`APP.openapi.json``APP.openapi.yaml` 不是两个独立接口所有者,而是同一次 Apifox 导出的两个产物。两份文件不得分别手工维护,必须通过自动合同验证语义一致。
## 五、三人联合治理机制
### 5.1 人员组成
总共三人:主代理和两位评审者。三个人都必须完整审查同一个业务域,不能把页面、接口和交互割裂分工,也不能出现“该问题不属于我”的责任边界。
三个人均需覆盖:
- 页面美观与样式完整性;
- 功能和业务严谨性;
- App、PC 接口分类与合同完整性;
- 交互、导航、页面状态和跨页数据;
- 真实数据进入后的布局、反馈与业务结果。
### 5.2 四轮互补流程
第一轮为独立首审。三个人在不查看另外两人初步结论的前提下,分别阅读页面源码、测试、OpenAPI 和业务说明,并审查同一组 MuMu 证据,独立形成完整判断,避免首个意见造成锚定。
第二轮为交叉补漏。三份结果公开后,每个人逐条检查另外两份结论,判断是否漏掉页面状态、接口边界、视觉问题、业务风险或连锁影响。
第三轮为反向质询。合并后的结论重新交给三个人,每个人主动尝试证明结论可能错误,包括检查接口是否真的缺失、页面问题是否由测试数据造成、视觉方案在边界状态下是否仍成立,以及导航和数据刷新是否在重复进入后仍然正确。
第四轮为共同收敛。每个问题必须带有页面、状态、接口、代码位置、MuMu 证据、影响等级和处理建议。不能只记录“感觉不好看”“接口可能有问题”等不可验证意见。
三人意见不能简单少数服从多数。任意一人提出有证据的疑点时,必须继续调查;只有证据充分后才能形成共同结论。
### 5.3 共享工作区规则
- 审查阶段三个人只读,不边看边改。
- MuMu 由主代理统一导航、切换状态和采集截图,避免多人同时操作造成状态干扰。
- 两位评审者审查同一份已经打开确认的 MuMu 截图。
- 评审者可以要求补拍状态,主代理补充证据后三人重新判断。
- 实施阶段由主代理统一修改工作区,两位评审者负责复核方案、变更、测试与 MuMu 证据。
## 六、联合评审模型
每个页面使用统一中文评审结构,至少记录:
- 页面编号、页面路由和业务目标;
- 用户身份、角色、权限和前置条件;
- 进入来源、返回目标和流程终点;
- 页面全部适用状态;
- 页面全部用户操作;
- 每个操作成功、失败、取消后的页面和数据结果;
- 对应 App 接口和关联 PC 接口;
- 缺失、冲突或不完整的接口;
- 样式、美观、功能、交互和导航问题;
- 真实数据和边界数据风险;
- MuMu、源码和 OpenAPI 证据;
- 三人初审、补漏、质询和最终结论。
每个用户功能都按以下顺序推演:
```text
进入
→ 输入或选择
→ 本地校验
→ 提交锁定
→ 发起请求
→ 成功或失败
→ 页面反馈
→ 数据刷新
→ 返回或流程结束
→ 再次进入
```
同时检查取消、Android 返回键、重复点击、网络失败、权限变化、目标数据过期和跨页状态一致性。
## 七、视觉与美观验收标准
第一轮样式审核是继续工作的基线,不是永远不能重开的封印。包括 G01—G10 在内,只要三人通过页面、接口、流程或 MuMu 证据发现明确且可复现的问题,就允许重新打开处理。
视觉验收不能只检查溢出、遮挡和响应式,还必须检查:
- 页面整体是否有协调的视觉中心和信息层级;
- 标题、正文、辅助信息、错误提示和操作区是否层级清楚;
- 字号、字重、颜色、间距、对齐、留白和视觉重心是否协调;
- 卡片、输入框、按钮、弹窗、列表项、图标、背景、边框和装饰是否完整;
- 默认、按下、选中、禁用、加载、空、错误和成功状态是否都有正式设计;
- 素材是否缺失、模糊、变形、错误裁切或风格不一致;
- 页面是否过空、过密、装饰不足或装饰堆叠;
- 同一模块与跨模块是否保持统一国风语言;
- 真实长短数据、键盘、弹窗和滚动状态下是否仍然美观。
缺少皮肤、页面明显失衡、视觉层级混乱、素材错误或半成品表现不能统一降级为低优先级体验问题。
现有国风设计体系是新增或修复样式的基础:
1. 优先复用现有共享组件和正式资产。
2. 能通过共享规则解决时,不在单页复制样式或素材。
3. 需要扩展时,优先扩展现有视觉体系。
4. 确实没有合适素材时,按真实槽位尺寸、色彩、纹理和透明要求制作正式资产。
5. 如果现有页面结构经真实流程与 MuMu 证据证明明显不合理,可以重新设计该页面,但必须保持产品信息与交互合同、复用现有设计语言,并先更新设计和测试,不能写单设备补丁。
6. 不用 emoji、文字符号、临时 SVG、CSS 拼图或占位框冒充正式资产。
7. 新资产必须在 MuMu 中验证清晰度、比例、裁切和状态完整性。
## 八、接口治理与后端协作
### 8.1 接口分类
每个接口标记为以下一种类型:
- App 可直接使用;
- PC 专用,不进入 App
- PC 与 App 可能共用,需要后端确认;
- App 接口存在但合同不完整;
- 页面需要但 App 接口缺失;
- 接口存在但当前页面没有合理用途;
- 接口与页面都需要重新定义。
接口审查至少覆盖鉴权、请求参数、响应字段、字段可空性、枚举、分页、错误码、权限、幂等、上传、删除、审核和状态转换。
### 8.2 接口问题单
每条接口问题必须包含:
- 问题编号、业务域、优先级和状态;
- 页面、路由、触发操作、用户身份和前置条件;
- 业务目标和完整使用场景;
- 现有接口路径、方法、请求、响应与当前问题;
- 建议新增或修改的接口能力;
- 鉴权、参数、字段类型、必填性、约束和成功响应;
- 分页、错误码、权限、幂等和数据副作用;
- 页面成功、失败、取消、返回和数据刷新表现;
- 验收步骤和三人共同结论。
不能只向后端提交“缺接口”或“字段不够”等模糊描述。
### 8.3 后端更新闭环
```text
三人形成接口问题单
→ 用户确认少数业务决策
→ 后端修改 Apifox
→ 用户重新导出 JSON 和 YAML
→ 验证双格式语义一致
→ 检查本次接口差异
→ 运行接口合同测试
→ 页面接入真实接口
→ MuMu 验证完整状态
→ 三人共同复核
→ 问题关闭
```
后端提出替代方案时,三个人重新评估它是否满足业务和页面,不因接口已经完成就自动接受。
## 九、问题等级与暂停条件
### 9.1 问题等级
- 阻断级:权限越权、隐私泄露、数据丢失、重复写入、关键流程无法完成或接口无法支撑业务。立即暂停当前批次。
- 高优先级:业务流程错误、返回目标错误、状态结果不一致、关键错误无反馈、主要页面缺少样式或呈现半成品。当前业务域不得带问题完成。
- 中优先级:局部状态样式、交互反馈、文案层级或边界数据存在问题。必须在该业务域闭环前解决。
- 体验优化:不影响理解和任务完成的细节打磨,可在本批后段处理。
### 9.2 必须暂停的情况
- 真实业务目的无法确定;
- 页面和接口表达不同状态模型;
- 缺少关键 App 接口;
- 后端字段、权限或错误码不足以安全实现;
- 三人对业务正确性仍有实质分歧;
- 共享合同的唯一所有者尚未确定;
- 没有有效 MuMu 证据;
- 测试无法先复现问题;
- 同一问题连续三个实现假设失败。
等待后端时,将当前批次标记为“待后端处理”,转向下一个没有依赖关系的业务域,不制作临时兼容路径。
## 十、阶段 0:项目基线清理
### 10.1 清理原则
旧内容不归档。确认已失效、已替代或确实无用后,直接从工作目录删除。Git 历史承担追溯作用,但本轮不执行 Git 变更命令。
清理候选无需用户逐项确认,但必须由三个人共同复核:
- 三人一致确认无用:由主代理统一删除。
- 任意一人提出有效疑点:继续调查,暂不删除。
- 审查人员不同时修改或删除文件。
### 10.2 文档清理
文档只有在内容被当前权威文档完整覆盖、没有独有有效决策、且不会继续作为执行入口时,才能删除。删除旧文档时必须同时清理所有指向旧入口的引用。
### 10.3 测试清理
每个测试需要记录保护的页面或功能、具体合同、当前有效性、重复关系和失败价值,并归类为保留、合并、重写或删除。
- 测试失败不是删除理由。
- 对应功能删除或合同被新唯一测试完整覆盖时,才能删除旧测试。
- 只断言旧文档文字、旧素材路径或无业务价值内部实现的测试应重写或删除。
- 截图采集工具不计入合同测试总数。
- 浏览器测试可以保留独立逻辑价值,但不能作为视觉验收。
- 生成物依赖先判断生成流程是否仍为正式能力。
### 10.4 资产清理
资产引用扫描必须覆盖 Vue、JavaScript、SCSS、JSON、配置、设计清单、构建脚本、CSS 变量和动态路径。
资产分为正式运行时资产、共享视觉资产、设计母版、构建输入、可再生成产物、临时 MuMu 证据、重复资产、被替代资产和已删除功能专用资产。
重复素材需要比较文件哈希、像素尺寸、透明区域、压缩质量和交互状态用途。确认合并后只保留一个公共所有者,更新全部引用后删除副本。
### 10.5 清理顺序与验证
```text
确认当前权威资料
→ 删除失效文档
→ 清理重复或失效测试
→ 建立完整资产引用关系
→ 清理无用资产和生成物
→ 删除残留引用
→ 运行聚焦与全量验证
→ MuMu 检查代表流程
```
阶段 0 只清理已经能证明无效的内容,不顺便重构业务代码,也不开始接口接入。
## 十一、文档体系与生命周期
### 11.1 核心当前文档
- `docs/项目当前总览.md`:项目唯一入口和当前进度。
- `docs/家谱项目全量治理设计.md`:长期有效的治理规则。
- `docs/家谱项目全量治理实施计划.md`:当前执行批次与验证要求;整体完成后将长期内容合并到总览并删除。
- `docs/接口与页面映射总表.md`:页面、功能、状态与接口关系的唯一总表。
### 11.2 临时文档
- `docs/项目基线清理清单.md`:阶段 0 使用,清理完成并合并有效信息后删除。
- `docs/评审/<业务域>联合评审.md`:业务域审查期间使用,完成并合并长期结论后删除。
- `docs/待后端处理接口问题.md`:只保留尚未解决的问题;后端合同通过验证后删除对应条目。
### 11.3 生命周期
```text
草稿
→ 三人评审
→ 当前有效
→ 内容进入唯一所有者
→ 过程文档删除
```
不保留“旧版”“最终版2”“备份”或历史归档。任何规则只能有一个当前所有者。
## 十二、测试策略
测试体系按以下层级组织:
1. 项目基础合同:页面清单、路由、共享组件、唯一所有者、响应式、编译和 OpenAPI 双格式一致性。
2. 接口合同测试:路径、方法、参数、鉴权、响应字段、错误码、分页和状态枚举。
3. 业务状态测试:校验、权限、状态转换、防重复提交、失败重试、过期和数据刷新。
4. 导航流程测试:进入、返回、取消、完成和重复进入后的栈与数据状态。
5. 纯函数运行测试:密码、验证码、字段归一化和状态机等纯逻辑。
6. MuMu 验收:视觉、美观、键盘、安全区、手势和 Android 返回键。
每个确认问题必须测试先行:
```text
第一步:写出能够复现问题的失败测试
第二步:运行并确认测试因目标问题失败
第三步:实施最小修改
第四步:运行聚焦测试
第五步:运行全局响应式合同和编译审计
第六步:运行全量合同
第七步:在 MuMu 验证完整流程和视觉状态
```
全量测试数量可以因清理和新增真实合同发生变化,不以保持原有数量为目标。
## 十三、中文文档与代码注释规范
- 新建规格、计划、报告和问题单使用中文文件名和中文内容。
- 接口路径、字段名、函数名等技术标识保留原文,并提供中文解释。
- Vue、JavaScript、SCSS 和测试脚本继续使用英文技术文件名,避免工具兼容问题。
- 测试代码同样使用中文注释说明准备条件、执行步骤、预期结果和合同目的。
- 复杂业务函数必须使用详细中文注释说明每一步的业务目的、执行顺序、状态变化、失败处理和设计原因。
- 简单赋值不堆叠无意义注释,但不能让复杂流程只能依赖阅读实现细节才能理解。
复杂流程注释采用明确步骤形式,例如:
```js
// 第一步:校验页面必填字段,阻止无效请求进入后端。
// 第二步:锁定提交状态,防止连续点击产生重复记录。
// 第三步:调用业务接口,并根据明确的后端状态更新页面。
// 第四步:成功后刷新来源数据,再结束当前流程。
// 第五步:失败时保留用户输入,并提供可理解的重试入口。
```
## 十四、精准代码与复杂度控制
所有新增或修改代码都必须以最小、精准、可验证为标准。代码行数少不是唯一目标,但每一行都必须能够追溯到当前确认的问题、业务合同或验证要求。
- 一个模块、函数或状态对象只承担一个清晰职责。
- 业务规则必须有唯一所有者,页面、组件、工具和测试不得复制同一判断。
- 改变合同后同步删除旧字段、旧入口、旧分支和旧注释,不保留“以防万一”的兼容读取。
- 不为单次使用创建抽象层,也不为了未来可能需求增加配置、参数或扩展点。
- 不把多个互不相关的业务流程塞进同一个大函数;复杂流程按明确业务步骤拆分,并保持输入、输出和副作用可见。
- 优先使用清晰命名、早返回和直接数据流,避免深层嵌套、隐式全局状态和难以追踪的副作用。
- 只有两个以上真实消费者需要同一规则时才提取共享能力,不能为了“看起来高级”制造工具层。
- 不新增没有必要的依赖,不用大范围重构解决局部问题。
- 中文注释解释“为什么、当前步骤和业务边界”,不能逐字翻译语法,也不能用大量注释掩盖混乱实现。
- 测试验证对外业务合同和关键状态,不把内部实现细节固化成脆弱断言。
- 每次实施只处理一个可独立验证的批次;相邻但无关的问题只记录,不顺手修改。
每个代码批次完成前,三个人必须共同检查:
1. 是否存在重复规则、重复请求或重复状态。
2. 是否引入不必要的抽象、依赖、参数或兼容分支。
3. 函数和文件是否具有清晰单一职责。
4. 旧路径、孤立变量、失效注释和无用导入是否由本批产生。
5. 是否可以用更直接、更少副作用的方式表达同一业务。
6. 每个修改是否都有对应失败测试、业务证据和 MuMu 验证。
任意评审者能够用证据指出代码复杂、重复或所有权不清时,该批次不能进入完成状态。
## 十五、项目阶段顺序
### 阶段 0:项目基线清理
清理失效文档、测试、资产和生成物,建立可信验证基线。
### 阶段 1:全项目共享合同
建立 OpenAPI 一致性、App/PC 分类、登录态、权限、统一错误、分页、上传、防重复提交和导航栈语义。
导航栈先形成统一设计和测试合同,具体修改随业务域逐页验证,不机械批量替换。
### 阶段 2:认证与账户
覆盖 A 系列,并同时审查 M03—M05 依赖的账户安全合同,包括注册、登录、短信验证码、找回密码、登录态失效、修改密码、换绑手机号和注销。
### 阶段 3:家谱主体与申请
覆盖 G 系列,包括列表、创建、概览、搜索、加入申请、审核、角色、权限、设置、字辈谱和家谱上下文。
### 阶段 4:世系与成员
覆盖 T 系列,包括树图、成员选择、详情、添加、编辑、关系、目录、隐私、权限和保存后的跨页一致性。
### 阶段 5:家族圈与内容媒体
覆盖 F 系列,包括动态、文章、评论、相册、照片、视频、上传、分页、草稿、发布、编辑和删除。
### 阶段 6:人物与族务记录
覆盖 R 系列,包括人物档案、礼物、祭祀、成长、人生事件、备忘、功德、时间线和汇总。
### 阶段 7:消息通知
覆盖 N 系列,包括未读、已读、全部已读、消息目标、过期、权限失效和业务结果一致性。
### 阶段 8:个人中心与设置
覆盖剩余 M 系列,包括资料、帮助、反馈、推广、会员订单、协议、退出登录和账户状态。
### 阶段 9:跨模块最终闭环
使用真实接口完整验证认证、创建或加入家谱、管理成员、发布内容、新增记录、接收消息、返回个人中心、退出和重新进入。
## 十六、每个业务域的固定执行流程
```text
三人独立首审
→ 交叉补漏
→ 反向质询
→ 共同结论
→ 后端接口问题单
→ Apifox 修改与双格式导出
→ 五方案比较与批次设计
→ 失败测试
→ 最小实施
→ 聚焦、全局和编译验证
→ MuMu 完整流程与视觉验收
→ 删除旧合同和无用内容
→ 业务域完成
```
每个确认问题提出五个可行方案,从业务正确性、全局一致性、回归风险、维护成本和 MuMu 可验证性选择最优方案。
## 十七、进度与用户沟通
每个业务域只使用以下中文状态:
```text
未开始
基线清理中
独立首审中
交叉复核中
待业务确认
待后端修改
待实施
实施中
MuMu 验证中
回归验证中
已完成
```
开始批次时报告当前阶段、业务域、目标、页面、接口、状态、后端依赖和明确不处理范围。
执行过程中只报告有实际价值的进展,包括已完成内容、三人发现、当前检查、阻断问题和下一步,不向用户倾倒无关工具日志。
只有三个人无法根据证据安全决定的真实业务问题才打断用户,例如角色权限、永久删除、重新申请、费用权益、隐私范围或会改变产品含义的流程分歧。
提问时一次只问一个业务问题,并说明发生位置、当前冲突、已排除方案、推荐方案、影响和阻塞范围。
## 十八、业务域完成定义
一个业务域只有同时满足以下条件才能标记完成:
- 三人完成独立首审、交叉补漏和反向质询;
- 每个页面的全部适用状态都有本轮 MuMu 证据;
- 页面视觉达到完整、统一、美观的正式成品标准;
- 所有页面操作都有明确业务结果;
- 进入、返回、取消、完成、失败和重复进入均通过验证;
- 每个页面数据需求都映射到明确 App 接口;
- 若当前批次存在接口缺口,后端已在 Apifox 完成相应修复;
- JSON 与 YAML 来自同次导出且语义一致;
- 聚焦测试、全局响应式合同、编译审计和全量合同通过;
- 真实接口数据下再次完成 MuMu 验收;
- 没有遗留旧路径、旧字段、废弃测试或无用资产;
- 阻断级和高优先级问题为零;
- 其他未解决事项有明确负责人和状态。
- 本批代码通过三人复杂度审查,不存在新增重复规则、无意义抽象、旧兼容路径或职责混乱的大函数。
## 十九、当前实施前置条件
阶段 0 已经完成。后续实施必须满足:
1. 当前业务域已经由三个人完成独立首审、交叉补漏、反向质询和最终收敛。
2. 设计结论已经进入本文件,实施步骤已经进入唯一实施计划。
3. 需要后端变更时,先形成可直接修改 Apifox 的问题单;新导出通过双格式和接口差异合同后才能接入。
4. 每个批次先写失败测试,再实施最小代码;不得同时混合导航、短信、持久化和全局无障碍等多个阶段。
5. 业务代码变更后必须运行聚焦合同、全局响应式合同、编译审计和全量合同,并在当前 MuMu 复核完整流程。
用户已经授权三位审查者自行裁决有充分项目证据的专业方案。只有角色权限、隐私范围、永久删除、收费权益、法律责任或确实无法从现有业务判断的产品含义才再次询问用户。
## 二十、导航栈语义统一设计
### 20.1 当前证据
只读扫描得到以下统一口径:
- 52 个活动页面中共有 `navigateTo 59``navigateBack 11``redirectTo 14``reLaunch 7`,合计 91 次直接调用。
- 活动共享组件另有 `navigateBack 2``reLaunch 2`,活动页面与组件共 95 次。
- 封存的 A06 另有 2 次,完整页面与组件源码共 97 次;项目没有 `switchTab` 调用。
- MuMu 已复现 F01→F03→“返回家族圈”后留下两个 F01,A01→A04→“登录”后留下两个 A01,T03 可连续叠出三个同路由页面。
- 栈深为 1 时直接进入 F03,当前 `PageHeader` 会回到固定 G01,而不是业务父页 F01。
因此问题不是某几个按钮写错,而是项目没有统一表达“打开页面、替换步骤、返回来源、完成流程、切换根页和直接进入回退”的语义合同。
### 20.2 八类问题的五方案终选
以下评分依次为“业务正确性/全局一致性/回归安全/维护成本/MuMu 可验证性”,5 分最好。每类只保留最终选中方案,其他方案不得作为兼容路径残留。
| 问题类型 | 方案一 | 方案二 | 方案三 | 方案四 | 方案五与终选 |
| --- | --- | --- | --- | --- | --- |
| 导航所有权 | 各页继续直接调用 `2/1/5/1/2` | 只封装页头 `2/2/4/3/3` | 无注册表的字符串工具 `3/3/4/3/4` | 只有注册表、页面仍直调 `4/4/3/3/4` | **路由注册表+唯一导航网关 `5/5/3/4/5`** |
| 三个根页 | 改原生 TabBar `4/2/2/2/4` | `navigateTo` `1/1/4/3/3` | `redirectTo` `2/2/3/3/4` | 页面各自混用 `2/1/4/2/2` | **保留自定义 Tabbar,统一 `goRoot` `5/5/4/5/5`** |
| 返回与直接进入 | 固定 `navigateBack` `1/1/4/4/2` | 页头保存 fallback URL `2/2/4/2/3` | 一律回根页 `2/3/3/4/4` | 查询参数传原始 return URL `3/2/2/2/3` | **栈感知返回+规范父页+类型化来源 `5/5/3/4/5`** |
| 列表/详情/编辑完成 | `redirectTo` 列表 `2/2/3/3/4` | `reLaunch` 列表 `1/2/2/4/4` | 固定 `navigateBack(delta)` `3/2/2/2/3` | 页面全局存整对象 `3/2/2/1/2` | **`returnTo/finishPage`+一次性结果 `5/5/3/4/5`** |
| T03 连续看亲属 | 重复 `navigateTo` `3/2/4/3/3` | 每次 `redirectTo` `2/2/3/3/4` | 每次 `reLaunch` `1/2/2/4/4` | 原生栈设上限 `3/2/3/2/3` | **单个 T03+页内成员轨迹 `5/5/3/4/5`** |
| 认证流程终点 | 保持混用 `2/1/4/2/3` | 全部 `navigateBack` `2/2/3/4/3` | 全部 `redirectTo` `3/2/3/3/4` | 全部 `reLaunch` `2/3/2/4/4` | **按进入、取消、完成和会话终止分别处理 `5/5/4/4/5`** |
| 通知与外部目标 | 后端下发原始 URL `1/1/2/3/2` | N02 自己匹配字符串 `2/2/3/2/3` | 任意 URL 白名单正则 `3/3/2/2/3` | 每个消息类型写独立跳转 `4/3/3/2/4` | **`targetType+业务 ID` 本地注册表映射 `5/5/4/4/5`** |
| Android 返回与未保存 | 只处理页头 `2/2/4/3/2` | 只处理 Android 键 `2/2/4/3/2` | 每页自由实现 `3/2/3/2/3` | 所有页面总是确认 `2/3/2/3/4` | **统一优先级状态机 `5/5/3/4/5`** |
### 20.3 唯一所有者
- `utils/navigation-routes.js` 是 52 个活动路由的唯一语义注册表,拥有路由键、路径、页面类型、规范父页、父页参数映射、根页、必填参数、可选参数、允许来源和目标页允许消费的结果操作枚举。
- `utils/navigation.js` 是项目唯一允许调用 `uni.navigateTo``uni.redirectTo``uni.reLaunch``uni.switchTab``uni.navigateBack` 的业务模块。
- 页面和组件只调用语义方法,不拼接页面路径,不保存 fallback URL,不接收后端原始跳转 URL。
- 注册表中的路径集合必须与 `pages.json` 的 52 个活动路由精确相等;缺失、重复和陈旧条目均使合同失败。
路由参数只能使用注册表声明的标量值。ID 统一按字符串处理并逐项编码,不允许把对象、完整来源 URL、页面快照或领域数据塞进查询参数。
### 20.4 语义方法
```text
openPage(routeKey, params, sourceKey)
打开普通子页;sourceKey 必须等于当前真实页面。当前已经是同一路由且关键参数相同则不重复入栈。
replaceStep(routeKey, params, sourceKey)
只替换同一流程中的临时步骤;sourceKey 同样必须来自当前真实页面,禁止用于“返回列表”。
goBack()
栈内有上一页时 navigateBack;没有时按当前路由的规范父页逐级回退。
returnTo(routeKey, targetParams = {}, result = null)
按路由键寻找最近实例并精确返回;targetParams 只用于目标不在栈内时构造合法回退 URL。
finishPage(routeKey, targetParams, result)
校验目标参数和类型化结果后调用 returnTo;结果只能包含 operation、entityId 和 refresh。
goRoot(routeKey, params)
只接受 A01、G01、F01、M01 四个根语义;使用 reLaunch 清理旧流程。
```
六个公开语义方法中,除纯判断外的导航动作都返回 `Promise`,并共享一个在途转场锁:同一目标的重复调用复用同一个 Promise,其他并发转场明确返回忙碌结果。导航失败或被锁拒绝时必须回滚刚写入的一次性结果。
一次性结果只存在于当前 JavaScript 进程,目标页消费一次后立即删除。字段键精确为 `operation、entityId(可选)、refresh``operation` 必须属于目标路由注册的 `resultOperations``entityId` 若存在必须为非空字符串,`refresh` 必须是布尔值。它不是领域数据持久化,不允许加入 `treeVersion、genealogyId、focusId、payload`,也不允许保存完整对象、表单内容、列表快照或接口响应。
`sourceKey` 只证明当前 JavaScript 进程内的一次真实导航:`openPage/replaceStep` 必须核对它与 `getCurrentPages()` 的当前真实路由一致。栈深为 1 或外部直接进入时,查询串里的 `sourceKey` 一律视为不可信并忽略,只按注册表的 `parent/parentParamMap` 建立回退目标;外部链接不能伪造来源合同。
### 20.5 根页与页头
- G01、F01、M01 继续使用自定义 `AppTabbar`;切换根页统一 `goRoot`,点击当前项不做任何跳转。
- A01 是认证根页;登录、注册完成取得有效会话后统一 `goRoot(G01)`
- G01、F01、M01 不显示普通返回箭头。左侧品牌只有绑定明确动作时才可点击,否则为非交互品牌标识。
- 根页右侧分别保留 G01 消息、F01 发布、M01 资料操作。
- 普通 `PageHeader` 删除 `fallbackUrl` 和直接路由判断;普通返回统一进入导航网关,未保存页面使用同一个返回守卫。
- 根页没有弹层时,Android 返回交给系统;有弹层时先关闭弹层。
### 20.6 返回、取消与完成
返回优先级固定为:
```text
关闭最上层预览、弹窗、搜索或抽屉
→ 消费 T03 等页面内部轨迹
→ 页面正在提交时阻止离开并给出状态
→ 有未保存输入时显示放弃确认
→ 执行普通 goBack
```
页头返回、页面取消按钮和 Android 返回键必须进入同一状态机,不能出现三个不同结果。
关键流程终点:
- A04 取消或“已有账号”返回 A01;注册并建立会话后 `goRoot(G01)`
- A05 取消返回 A01;只有未来真实重设接口成功才写入 `password-reset` 结果并回 A01。本地 `state=success`、计时器或视觉占位成功态只能普通返回,不得生成业务成功结果。
- A01 登录成功 `goRoot(G01)`;会话失效和退出成功也只允许 `goRoot(A01)`
- F02、F03 完成或返回精确回 F01F06 新建回 F04,编辑回 F05F09 上传完成回 F08。
- R02 回 R01R04 回 R03R06、R07 回 R05。
- T04 保存后回 T01 并定位新成员;T05、T08 回当前 T03;T06 保存后回 T01 并刷新原焦点;T07 选择成员后回 T01 定位或进入 T03。
- N02 默认回 N01;通知业务目标失效、无权限或字段不足时留在 N02 的明确状态,不猜测页面。
当前 OpenAPI 已确认 `POST /genealogy/app/auth/register` 的成功响应复用 `LoginResult`,其 `LoginVo` 可返回 `token/accessToken/tokenValue`。因此 A04 的正式终点不是“注册后再登录”,而是保存有效会话后直接 `goRoot(G01)`;在真实注册与行为验证尚未接入前,不得把当前占位反馈冒充注册完成。
### 20.7 T03 单实例成员轨迹
- T03 原生页面实例只保留一个。初始成员成功读取后先把轨迹初始化为恰好 `[initialPersonId]`;初始读取失败不建立轨迹。点击亲属只更新页面内 `personId` 和成员轨迹,不再 `navigateTo` 同一路由。
- 新成员资料成功读取后才写入轨迹;读取失败保留原成员和原轨迹。
- 返回先逐项弹出成员轨迹,轨迹结束后才返回 T01、T07、R02 或实际来源。
- T05、T08 返回时刷新当前轨迹项,不新增 T03。
- 如果 T03 已位于原生栈下方,再次打开同一 `genealogyId` 的 T03 时必须回到该实例,并用目标页允许的类型化一次性操作请求加载目标成员;不得创建第二个 T03。若栈中 T03 属于不同 `genealogyId`,网关返回 `T03_CONTEXT_CONFLICT` 并拒绝压栈,调用方必须先通过根语义切换家谱上下文。
- 从直接链接进入 T03 且没有来源页时,规范父页为带当前成员定位参数的 T01;缺少 `genealogyId` 时继续回退 G01。
### 20.8 通知和外部进入
- 后端只返回 `targetType` 与该业务所需 ID,不返回客户端路由字符串。
- `targetType` 必须由本地固定映射转换为路由键,并先验证登录态、权限、目标存在性和必填参数。
- 外部进入先建立正确根页,再打开目标;目标失效时落入可理解的消息状态,不打开空白页面。
- 未知 `targetType`、多余参数和原始 URL 必须明确拒绝。
### 20.9 导航完成定义
- 页面和组件中直接调用五种 Uni 导航 API 的扫描结果为零。
- 路由注册表与 `pages.json` 精确闭合。
- 所有根页、普通页、直接进入、完成回流和异常回退满足上述不变量。
- MuMu 对每个受影响流程验证进入、返回、取消、完成和重复进入;T03 验证 A→B→C→B→A→来源且原生栈不增长。
- 每个迁移批次独立运行聚焦合同、全局响应式合同、编译审计和全量合同,不进行机械一次性替换。
## 二十一、T01 大规模世系树专项设计
### 21.1 已证实的 P0 与当前上限
当前 T01 不是只在“几百代”时才有风险,默认 6 人数据已经确定性断线:
- 布局得到 103 的横坐标为 550,唯一子女 106 的横坐标为 675。
- 父干线只画在 550,水平支线却只覆盖子女的 `minX..maxX`,唯一子女时只位于约 675—680,两段完全不相交。
- `NODE_HALF_HEIGHT=47`,普通节点实际半高为 43,只有选中节点半高为 47;普通节点端点天然留下约 4rpx 间隙。
- 现有 10 代×12 人压力数据中,约 45/54 个父分组会出现相同父干线断开;测试只断言线段数量大于 30,因此稳定漏报。
- 当前布局每一代都重新过滤全体成员,最坏为 `O(G×N)`,一人一代时接近 `O(N²)`;300 代单链约生成 13233 行 CSS Grid track。
- 页面、公共 mock 与 OpenAPI 分别使用 `parentId`、无关系字段、递归 `children/spousesfatherId/motherId`,直接接接口可能只显示根节点或完全没有连线。
### 21.2 五方案终选
评分依次为“业务正确性/全局一致性/回归安全/维护成本/MuMu 可验证性”。
| 方案 | 评分 | 结论 |
| --- | --- | --- |
| 修补现有 CSS Grid 与全量 DOM | `2/4/4/4/5` | 可修眼前断线,无法满足百代与宽支系,拒绝。 |
| 只改为可折叠线性目录 | `4/5/4/4/5` | 作为无障碍和降级模式保留,不能替代图谱。 |
| 全量 SVG 整洁树 | `3/3/3/3/4` | 几何易测,但数千图元和超大世界尺寸风险高,拒绝。 |
| Canvas 画线、DOM 叠节点 | `4/4/2/2/3` | App-vue 跟手手势中存在跨层不同帧错位风险,拒绝。 |
| **渐进图窗口+视口单 Canvas 同画节点和边** | **`5/5/3/3/5`** | **最终方案。** |
DCloud 官方文档明确说明 App-vue 的流畅 Canvas 动画应把绘制放在视图层并避免频繁跨层通信;renderjs 只支持 App-vue/H5Vue 3 项目不能直接在当前 `script setup` 页面内使用该写法。因此使用普通 Options API 的专用 Canvas 组件承载 renderjs,T01 页面继续负责低频业务编排:
- <https://uniapp.dcloud.net.cn/tutorial/renderjs>
- <https://uniapp.dcloud.net.cn/component/canvas.html>
- <https://uniapp.dcloud.net.cn/tutorial/performance.html>
### 21.3 产品与页面结构
T01 定位为“以焦点成员为中心的可探索世系窗口”,不是一次性展开整本族谱的手机长图。
- 焦点优先级固定为:路由 `selectedId` → 当前账号绑定成员 → 后端主始祖。合法 `state=EMPTY` 显示空谱并允许录入首位成员;只有 `state=POPULATED` 缺少有效焦点或窗口入口时才显示合同异常,客户端不猜测成员。
- 初始窗口固定为焦点上两代、下两代;这只是首次窗口,不是最大世代。
- 主世系是骨架;配偶并排,每段家庭关系具有独立联合点,子女从对应联合点向下。
- 同一家庭 Scene 在任何状态下最多直接显示 5 名真实子女。投影先沿 primary 入边从焦点反向推导受保护主路径;每个祖先家庭都必须保留通向焦点的那名子女,再用相邻 order 补足最多 5 人,不能把焦点祖先链聚合掉。其余成员形成“前 N 人”“后 N 人”聚合节点;存在隐藏可见成员时点击后以最近的稳定人物 ID 重建窗口,只有隐私成员时聚合节点不可聚焦。原焦点进入会话历史,聚合不在原图无限展开,也不截断后端数据。
- 顶部继续使用 `PageHeader`,下方为薄上下文工具条;一级操作是搜索和全谱,图谱/列表切换、关系图例、关系维护和阅读帮助进入更多。
- 左侧大面积代际栏改为 Canvas 内轻量标尺;缺代显示“中间缺 N 代”,不能把第 14 世和第 50 世误画成相邻世代。
- 右下控制组提供放大、缩小、适合当前支系和回到焦点,点击区域至少 44dp。
- 资料抽屉只有收起与半屏两档;完整资料进入既有 T03,禁止在 T01 再复制一套全屏人物详情。
- 抽屉吸附完成后才更新视口底部 inset,并把选中节点移入剩余安全区域;不得覆盖节点。
### 21.4 缩放、定位和概览
缩放范围为 0.30—2.00,默认按当前窗口适配且不自动放大超过 1.0:
| LOD | 缩放 | 内容 |
| --- | --- | --- |
| L0 | 0.30—0.44 | 当前已加载窗口的家庭、支系聚合与数量,不显示姓名。 |
| L1 | 0.45—0.74 | 姓名和隐私、缺失等状态。 |
| L2 | 0.75—1.34 | 标准人物节点、世代、关系和支系。 |
| L3 | 1.35—2.00 | 在标准节点上增加生卒摘要。 |
- 阈值使用 0.05 迟滞,避免临界缩放闪烁;Canvas 文字低于 12sp 时隐藏次要信息,不继续压小字号。
- 双指缩放以手势中心为基点;按钮每次按固定比例缩放;搜索定位后至少进入 L2。
- 单击只选中并打开抽屉;双击、点击居中或搜索结果才改变焦点并重建上二下二窗口。`REDACTED` 节点只允许显示通用隐私说明,不允许双击、居中、进入详情或成为焦点。
- 当前会话内保留最多 20 条定位记录;只保存 `focusPersonId、treeVersion、matrix`,不做最近浏览和离线持久化。
- 全谱是独立的“世代×支系”聚合视图,不是把全部人物缩小。它只展示权限过滤后的密度、根、支系覆盖和待归支统计,点击后再请求局部窗口。
- 图谱和线性列表消费同一规范图、焦点、树版本、已加载边界和搜索结果;不存在独立的页内聚合展开状态。Canvas 不可用或无障碍需要时,列表仍能搜索、选择可见成员并进入 T03/T04/T06,隐私成员只显示不可跳转的通用说明。
### 21.5 四类端点与异常数据
| 状态 | 后端或客户端事实 | 页面行为 |
| --- | --- | --- |
| 未录入 | 后端 boundary `MISSING` | 连到完整占位端点;有权限才显示补录。 |
| 隐私隐藏 | 后端 boundary `REDACTED` | 连到匿名端点,不返回可推断身份的信息。 |
| 尚未加载 | 后端 boundary `UNLOADED` | 显示数量和展开动作,必须携带版本绑定 cursor。 |
| 加载失败 | 客户端 boundary runtime `FAILED` | 保留原图与 cursor,只局部重试。 |
重复 ID、缺引用、自环、主图循环、多条主入边、反向代际和版本错配不能进入正常布局。部分合法数据仍可展示时显示诊断横幅;不允许通过不画线来隐藏错误。
### 21.6 传输合同
Apifox 是接口唯一源头,本地 JSON/YAML 只是同版本只读导出。旧递归 `LineagePersonTreeView` 必须被一个规范窗口原子替换,客户端不保留兼容读取。
```text
LineageGraphWindow
├─ version { schemaVersion, treeVersion, generatedAt }
├─ state EMPTY | POPULATED
├─ genealogyId
├─ nodes[]
├─ familyUnits[]
├─ edges[]
└─ window { focusPersonId, entryPersonIds, scope, generationRange, returnedNodeCount, boundaries[] }
```
关键字段合同:
- 所有人员、家庭、关系和引用 ID 都是非空字符串;非空 `avatarOssId` 也必须是字符串,未设置头像时只允许 `null`。后端 `int64` 不得作为 JSON Number 传给 JavaScript。稳定人物 ID 不得以 `redacted:` 开头;匿名人物 ID 精确使用 `redacted:{treeVersion}:{token}`,客户端可据此阻止它进入 FOCUS、locator、搜索、写接口和 overview 跳转目标。
- `LineageGraphWindow``state` 为 discriminator 使用 `oneOf`。EMPTY 只允许 `nodes/familyUnits/edges=[]`,且 `focusPersonId=null、entryPersonIds=[]、generationRange=null、returnedNodeCount=0、boundaries=[]`;这是合法空谱,不是合同错误。POPULATED 必须有非空 nodes、引用其中 VISIBLE 节点的非空 `focusPersonId`、至少一个引用当前 nodes 的 `entryPersonIds`、非空 generationRange,且 `returnedNodeCount === nodes.length`
- `entryPersonIds` 精确等于“当前返回窗口中没有 primary 入边的节点集合”,不是全谱始祖列表;secondary 入边不会取消窗口入口身份。每个入口节点的 `entryReason``GENEALOGY_ROOT/WINDOW_CUT/DISCONNECTED_COMPONENT`,非入口节点为 `null`;每个入口在当前窗口恰有零条 primary 入边,每个非入口恰有一条 primary 入边,因此从任意焦点沿 primary 必然回溯到某个入口。`GENEALOGY_ROOT` 表示全谱主森林根,`WINDOW_CUT` 表示其 canonical primary 父边在窗口外,`DISCONNECTED_COMPONENT` 表示没有可达全谱根的 canonical primary 链;因此第 50 代焦点的上二代窗口不会被伪装成全谱根。全谱根只由 overview 的可见根/隐私根计数和 locator 的根可见性分支表达。
- 人物节点以 `visibility` 为 discriminator 使用 `oneOf``VisibleLineagePerson` 固定包含 `id、generation、displayName、sex、avatarOssId、branchId、branchPath、order、visibility=VISIBLE、entryReason`;其 `id` 是可用于 FOCUS、locator、搜索和写接口的稳定人物 ID,`sex` 只允许 `MALE/FEMALE/UNKNOWN``RedactedLineagePerson` 只允许 `id、generation、displayName、order、visibility=REDACTED、entryReason`,其中 displayName 固定为通用“隐私成员”,id 是仅在当前 `treeVersion` 内可作当前图内部引用的不可解析 opaque ID,不得用于 FOCUS、locator、搜索或写接口,也不得进入任何 `focusPersonId/targetPersonId`;不得返回 sex、avatar、branchId、branchPath 或其他身份字段。两类节点的 `generation` 都是大于等于 1 的整数,`order` 是非负整数。
- `FamilyUnit` 包含稳定 `id、anchorPersonId、partnerRelationship、partners、order``partners` 只允许一个单亲成员,或同一伴侣关系中的两人;每项包含 `personId、partnerRole、order``partnerRole` 只允许 `ANCHOR/PARTNER`,且恰有一个 ANCHOR 与 `anchorPersonId` 相等。双人家庭的 `partnerRelationship` 必含 `relationshipId、relationshipKind=PARTNER、relationType、status`,其中 `relationType``MARRIAGE/PARTNERSHIP/UNKNOWN``status``ACTIVE/ENDED/UNKNOWN`;单亲家庭该字段为 `null`。同一人物的不同伴侣关系必须拆成不同家庭单元。
- `ParentChildEdge` 从家庭单元指向一个子女,包含 `id、familyUnitId、childId、lineageParentId、parentRelations、primary、order`。每个 `parentRelations` 项都包含稳定 `relationshipId、relationshipKind=PARENT_CHILD、personId、parentRole、relationType``parentRole` 使用 `FATHER/MOTHER/PARENT/GUARDIAN/UNKNOWN``relationType` 使用 `BIOLOGICAL/ADOPTIVE/STEP/GUARDIAN/UNKNOWN``lineageParentId` 必须同时出现在该边的父母关系和家庭成员中,子女不得同时是该家庭成员;primary 入边与 `entryPersonIds` 必须满足上一条的零条/恰一条森林不变量,布局不得根据数组顺序猜测主世系父母。
- 所有父子关系都参加缺引用、自环和有向循环校验,不能只检查 `primary` 主森林;`primary` 只决定布局主干,不降低次要收养、继亲或监护关系的数据正确性要求。
- boundary 锚点可以是 PERSON、FAMILY_UNIT 或 WINDOWPERSON/FAMILY_UNIT 的 `anchorId` 必须引用对应实体,WINDOW 的 `anchorId` 固定为 `null`。方向为 ANCESTORS、DESCENDANTS、LATERAL,原因只允许 MISSING、REDACTED、UNLOADED`hiddenCount` 只能是非负整数或 `null`cursor 只在 UNLOADED 时为非空字符串,其他原因固定为 `null`。网络 FAILED 是客户端状态。
- 当前窗口内返回但匿名化的人物使用 `Node.visibility=REDACTED`;窗口之外因权限隐藏的拓扑使用 `boundary.reason=REDACTED`。同一个隐藏对象在同一响应中不得既作为匿名节点又作为隐藏边界,避免重复计数和关系推断。
- 每个窗口对其返回的边保持引用闭包;服务端 `limit` 绝对上限 500。
- cursor 与 `genealogyId、treeVersion、boundaryId` 绑定;版本变化返回 `409 TREE_VERSION_CHANGED`
- 关系写操作携带 `If-Match`,成功返回新版本和受影响的人员、家庭及关系 ID。
`/lineage/tree` 使用两个互斥查询模式,不能由客户端拼出第三种组合:
- `mode=FOCUS``focusPersonId` 可选且只能是调用者可见的稳定人物 IDREDACTED opaque ID 不得作为焦点;缺省时服务端按“当前账号绑定成员→主始祖”确定并在响应中返回实际焦点。不可见、过期或不存在的焦点统一返回 `404 LINEAGE_FOCUS_NOT_AVAILABLE`,不泄漏成员是否存在;`ancestorDepth/descendantDepth` 是 0—20 的整数、可选且默认 2;`boundaryId/cursor/treeVersion` 必须缺席。
- `mode=BOUNDARY``boundaryId/cursor/treeVersion` 三项必填;`focusPersonId/ancestorDepth/descendantDepth` 必须缺席。
- 两种模式的 `limit` 都是整数,最小 1、默认 200、最大 500;任何混合、缺项或多余组合返回 HTTP `422``LINEAGE_QUERY_INVALID`。接口同时明确 `400/401/403/404/422/429/5xx` 响应。
全谱概览不复用窗口的 `returnedNodeCount` 冒充全谱总数。`/lineage/tree/overview` 只接受必填查询参数 `treeVersion`;服务端只返回该快照,版本已变化时返回 `409 TREE_VERSION_CHANGED`,客户端不得把不同版本的概览与窗口并用。
`LineageOverview` 包含同一 `{ schemaVersion, treeVersion, generatedAt }` 以及 `state、genealogyId、genealogyPersonCount、genealogyRootPersonIds、redactedGenealogyRootCount、generationRange、buckets`,并以 `state=EMPTY/POPULATED` 使用 `oneOf`。EMPTY 精确要求 `genealogyPersonCount=0、genealogyRootPersonIds=[]、redactedGenealogyRootCount=0、generationRange=null、buckets=[]`。POPULATED 要求 `genealogyPersonCount>0`、非空 generationRange 和 buckets,且 `genealogyRootPersonIds.length + redactedGenealogyRootCount >= 1`;各 bucket 的 `visibleCount/redactedCount/unloadedCount` 均为非负整数,三类计数在全部 buckets 的总和等于 `genealogyPersonCount`
`genealogyRootPersonIds` 只包含调用者可见、可用于 FOCUS 的稳定人物 ID;隐私根只计入 `redactedGenealogyRootCount`,绝不返回 opaque ID。bucket 的 `focusPersonId` 只能是该 bucket 内可见稳定人物 ID;没有可见目标时固定为 `null`
定位接口返回 `{ treeVersion, genealogyId, personId, rootVisibility, rootPersonId, pathCompleteness, ancestorPathSegments, generation, branchId }`。根可见性与路径完整性是两个独立 discriminator`rootVisibility=VISIBLE``rootPersonId` 是可见稳定 ID`rootVisibility=REDACTED``rootPersonId=null``pathCompleteness=COMPLETE` 表示路径没有隐私缺口,`pathCompleteness=REDACTED_GAPS` 表示含一个或多个隐私段,允许“可见根→隐私中间祖先→可见目标”。
`ancestorPathSegments``kind` 使用 `oneOf`VISIBLE 段精确为 `{ kind: "VISIBLE", personIds: [...] }`,数组非空且只含可见稳定 ID;REDACTED 段精确为 `{ kind: "REDACTED", hiddenCount }``hiddenCount` 为正整数或因权限不披露时为 `null`,不得包含任何人物 ID。相邻段 kind 必须交替,整条路径最后一个 VISIBLE 段必须以目标 `personId` 结束;可见根时第一个 VISIBLE 段必须从 `rootPersonId` 开始,隐私根时第一段必须是 REDACTED。COMPLETE 恰好没有 REDACTED 段,REDACTED_GAPS 至少有一段。任何分支都不得返回客户端路由或隐私 opaque ID。
### 21.7 客户端所有者与处理链
```text
OpenAPI 响应
→ normalize
→ validate
→ project
→ 确定性 family-unit tidy-tree layout
→ Scene 与空间索引
→ 平台 Canvas 渲染器
```
- `utils/lineage/` 是规范化、校验、投影、布局和空间索引的唯一客户端所有者。
- `normalize` 负责按 `order、id` 作一次确定性排序,最坏复杂度为 `O(N log N + E)``validate` 通过索引和一次图遍历保持 `O(N+E)``project` 只消费已规范化顺序并保持 `O(N+E)`,不得在投影阶段重复全量排序。
- `project` 只从已校验的完整窗口图生成当前可见投影:先由唯一 primary 入边推导焦点到窗口入口的受保护人员与家庭,再为每个家庭选出包含受保护子女的最多 5 名真实子女;无受保护子女的家庭稳定选择 order 最小的 5 人。其余连续区间生成带 `direction、hiddenCount、focusable、targetPersonId` 的“前 N 人/后 N 人”虚拟聚合节点;存在隐藏可见成员时 `focusable=true``targetPersonId` 指向最靠近当前可见窗口的稳定人物 ID,仅含隐私成员时 `focusable=false、targetPersonId=null`。点击可聚焦聚合节点后以该成员重新聚焦,不存在 `expandedAggregateIds` 或不断增长的页内展开状态。完整规范图继续供搜索和线性列表使用;布局只消费投影,Scene 不得再次决定聚合规则。
- 布局按 `order、id` 稳定排序,自底向上计算子树轮廓;家庭联合点必须位于可见子女跨度中心,单子女时严格同轴。
- Scene 根对象精确为 `{ sceneVersion, treeVersion, focusPersonId, bounds, items }``utils/lineage/scene.js``sceneVersion` 唯一所有者,按 `treeVersion + projectionSignature + LINEAGE_LAYOUT_VERSION + fontMetricsVersion` 生成确定性摘要;Scene 使用世界坐标并只由可 JSON 序列化的数组、对象、字符串、数字和布尔值组成,不得跨 renderjs 边界传递 `Map`、函数、Vue、DOM、rpx、Canvas context、空间索引或平台对象。瞬时 `selectedId` 不属于 Scene payload,也不参与 `sceneVersion`
- `components/LineageViewport.vue` 使用普通 Options APIApp-vue/H5 的 renderjs 持有 Scene、相机、手势、LOD、裁剪、命中和逐帧绘制。
- Canvas 物理尺寸始终等于视口,不等于几百代的世界尺寸;只绘制视口加 overscan 内对象。
- 节点、文字、联合点、关系线、聚合节点、端点、代际标尺和选中光晕全部在同一 Canvas、同一矩阵、同一帧绘制;选中光晕由 renderjs 使用独立 `selectedId` 和 Scene 内稳定人物 Rect 动态绘制,不修改 Scene、布局或版本。
- DOM 只保留 PageHeader、工具条、两档抽屉、搜索、全谱概览和线性列表。
- mp-weixin 复用同一 Scene 的 Canvas 适配器;窗口超过能力或持续不达性能门槛时进入线性列表,不另造关系规则。
- renderjs 读取 `scene.sceneVersion` 后先验证版本与 payload、在视图层重建空间索引,再原子交换 Scene、索引和版本;缺少版本或同一版本对应不同 payload 必须拒绝并保留旧 Scene。平移、缩放与惯性期间零跨层通信;只在手势结束、焦点变化或业务确需边界请求时回传相机状态。
- 视口、底部 inset 或 DPR 变化时先重设 transform,再把 backing store 设为视口乘 DPR、CSS 尺寸设为逻辑视口;字体就绪或回退字体变化后重新测量文字、生成新的 `fontMetricsVersion` 并生成新 Scene,不能沿用旧测量值。
### 21.8 “连线永不断”不变量
1. 每个家庭成员都恰有一个可追踪的 `family:{familyUnitId}:partner:{personId}` 连接器指向联合点;单亲联合点若与人物锚点重合,可以省略孤立的零长度笔画,但关系对象仍可反查。
2. 折线相邻段端点在世界坐标中完全相等,DPR 变换后误差不超过 0.5 个物理像素。
3. 单子女使用连续路径,不创建孤立零长度横梁。
4. 锚点从实际 Scene Rect 计算,不允许全局半高常量。
5. 选中态只画外部光晕,不改变节点宽高、布局或锚点。
6. 每条 `ParentChildEdge` 恰好对应一个 `edge:{edgeId}` 连接器,其起点必须是 `familyPoint(familyUnitId)`,终点必须是实际子女人物锚点;Scene connector 也能反查唯一传输 edge。
7. 主关系线不得穿过无关人物节点,纯主树的主关系交叉数必须为零。
8. 视口裁剪依据路径包围盒;即使两个端点都在视口外,只要路径穿过视口就必须绘制。
9. 未录入、隐私、未加载和聚合关系必须连接到明确端点,不允许悬空或静默消失。
10. 新窗口通过校验后原子替换旧 Scene;不得用新节点配旧边或新边配旧节点。
### 21.9 后端最小改动
为避免静默破坏既有 v1 消费者,后端新增并固定以下四条 v2 路径:
```http
GET /genealogy/app/v2/genealogies/{genealogyId}/lineage/tree
GET /genealogy/app/v2/genealogies/{genealogyId}/lineage/tree/overview
GET /genealogy/app/v2/genealogies/{genealogyId}/lineage/persons/{personId}/locator
PATCH /genealogy/app/v2/genealogies/{genealogyId}/lineage/relationships/{relationshipId}
```
- `/lineage/tree` 查询参数固定为 `mode、focusPersonId、ancestorDepth、descendantDepth、boundaryId、cursor、limit、treeVersion`,按 FOCUS/BOUNDARY 互斥规则校验;不再接受 `branchId、generation`
- `/lineage/tree/overview` 必须携带当前 `treeVersion`,只返回同版本的代际与支系聚合桶,并只为存在可见稳定人物 ID 的桶提供 `focusPersonId`;版本变化返回 `409 TREE_VERSION_CHANGED`
- `/locator` 返回目标人物、根可见性分支、`ancestorPathSegments` 隐私安全分段祖先路径、代数、支系和树版本;REDACTED 段不得返回人物 ID,任何分支都不得返回 opaque ID。
- 现有人物搜索只需稳定返回字符串 `personId`;新增人物、编辑人物和编辑关系接口必须加入版本并发合同。当前 OpenAPI 没有 T06 所需的关系修改入口,因此新增上述关系 PATCH。不可变 `relationshipKind` 只允许 `PARTNER/PARENT_CHILD`,必须与服务端既有关系一致;请求使用以该字段为 discriminator 的 `oneOf`PARTNER 分支只更新 `relationType/status`PARENT_CHILD 分支只更新 `relationType/parentRole`。每个分支除 `relationshipKind` 外至少包含一个可修改字段,省略字段保持原值;只有 discriminator 的空更新返回 `422 RELATIONSHIP_PATCH_EMPTY`。参与人和 `relationshipKind` 不可在 PATCH 中偷换,也不以客户端删除再新增模拟修改。
- 旧 v1 `/genealogy/app/genealogies/{genealogyId}/lineage/tree` 保持原合同供既有消费者使用;当前 App 只接入上述四条明确的 `/genealogy/app/v2/...` 路径,不双读、不做运行时版本探测。
### 21.10 明确删除的旧路径
同一迁移中删除:页面内置运行时 members、递归 `children/spouses`、单一 `parentId``fatherId/motherId``parentId` 双读、`person.id || person.personId`、API 或 fixture 的 `x/y`、数字 int64 ID、默认“族人/主支”语义兜底、顶层 `.map(toTreeNode)`、全量 CSS Grid track、多 `<view>` 拼线、Canvas 线叠 DOM 节点、选中态改尺寸和全局 `NODE_HALF_HEIGHT`
### 21.11 测试与 MuMu 门槛
测试先锁定当前 103→106、普通节点 4rpx 和 10×12 大量父干线断开,再依次覆盖:
- 合法单根、单亲、多配偶、多根、收养、隐私、字符串大 ID 和窗口边界。
- 旧递归结构、`parentId`、数字 ID、重复、缺引用、自环、循环、多主入边和版本错配必须明确失败。
- 6 人、10×12、20×20、5×100、100×3、300×1、单家庭 200 子女、缺第 2—49 代、乱序输入和极不平衡树;500 代单链只作纯逻辑校验,不作为 MuMu 手工搜索目标。
- 每条线的锚点、连续段、反查、穿节点、交叉、DPR、视口裁剪和选中前后几何。
- 搜索定位、聚合跳转与返回焦点、版本冲突、焦点保持、Canvas context 恢复和图谱/列表一致性。
当前 MuMu 不启动、不关闭,不调整模拟器窗口缩放比例、设备分辨率、方向或系统字体;App 内双指缩放属于 T01 功能验证。只在现有 `emulator-5554 / 720×1280 / 320dpi` 配置验证:
- 默认首屏和 103→106;单指平移、双指缩放、按钮缩放及四级 LOD。
- 10、50、100、300 代搜索或概览定位。
- 宽支系聚合、前后人数、聚合跳转、回到上一焦点,且受保护祖先链始终连续。
- 多配偶联合点及子女归组;未录入、隐私、未加载和局部失败端点。
- 两档抽屉、T03/T04/T06/T07 返回定位、长姓名、同名搜索、空谱、无权限和异常关系。
- 60 秒连续操作、20 次聚合跳转与搜索,以及自动几何探针和同场景逐边视觉探针;两者都为零违规才通过。
性能门槛:单窗口不超过 500 人;`dataReady` 定义为规范图完成校验的时间点,`interactive` 定义为首帧绘制结束且命中索引可用,二者间隔不超过 800ms;输入到反馈 p95 不超过 100ms;持续手势帧耗时 p95 不超过 32ms;不连续出现两帧超过 100ms;20 次聚合跳转与搜索后驻留内存相对稳定态增长不超过 15%。每项 MuMu 时延指标至少采样 30 次并按 nearest-rank 计算 p95;手势帧由 renderjs 的 `requestAnimationFrame` 记录,输入延迟从视图层触摸时间戳量到下一完成帧;内存先预热 3 轮,再在相同空闲点比较 20 轮。不把全 App 冷启动混进 T01 门槛。未达到门槛时缩小窗口或进入列表,不能提高上限掩盖问题。
## 二十二、当前精确执行顺序
后续不再按页面样式迁移重做,而按以下独立阶段执行:
1. 导航栈语义统一:先测试和共享所有者,再按认证、G、T、F、R、N/M 小批迁移,每批 MuMu 闭环。
2. T01 大规模世系树:先向后端提交新图合同,等待 Apifox 更新并验证,再按图合同、布局、Scene、Canvas、页面交互分批实施。
3. 短信验证码完整状态机。
4. 跨页面领域数据持久化。
5. 全局文字层级与无障碍第二轮。
T01 必需的线性目录和 44dp 控件随 T01 一起完成;不借此提前改造全项目文字体系。任何阶段完成前都不开始下一阶段的业务代码。