844 lines
60 KiB
Markdown
844 lines
60 KiB
Markdown
# 家谱项目全量治理设计
|
||
|
||
> 日期: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 完成或返回精确回 F01;F06 新建回 F04,编辑回 F05;F09 上传完成回 F08。
|
||
- R02 回 R01;R04 回 R03;R06、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/spouses+fatherId/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/H5,Vue 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 或 WINDOW;PERSON/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` 可选且只能是调用者可见的稳定人物 ID,REDACTED 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 API;App-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 一起完成;不借此提前改造全项目文字体系。任何阶段完成前都不开始下一阶段的业务代码。
|