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

60 KiB
Raw Blame History

家谱项目全量治理设计

日期: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.jsonAPP.openapi.yaml 不是两个独立接口所有者,而是同一次 Apifox 导出的两个产物。两份文件不得分别手工维护,必须通过自动合同验证语义一致。

五、三人联合治理机制

5.1 人员组成

总共三人:主代理和两位评审者。三个人都必须完整审查同一个业务域,不能把页面、接口和交互割裂分工,也不能出现“该问题不属于我”的责任边界。

三个人均需覆盖:

  • 页面美观与样式完整性;
  • 功能和业务严谨性;
  • App、PC 接口分类与合同完整性;
  • 交互、导航、页面状态和跨页数据;
  • 真实数据进入后的布局、反馈与业务结果。

5.2 四轮互补流程

第一轮为独立首审。三个人在不查看另外两人初步结论的前提下,分别阅读页面源码、测试、OpenAPI 和业务说明,并审查同一组 MuMu 证据,独立形成完整判断,避免首个意见造成锚定。

第二轮为交叉补漏。三份结果公开后,每个人逐条检查另外两份结论,判断是否漏掉页面状态、接口边界、视觉问题、业务风险或连锁影响。

第三轮为反向质询。合并后的结论重新交给三个人,每个人主动尝试证明结论可能错误,包括检查接口是否真的缺失、页面问题是否由测试数据造成、视觉方案在边界状态下是否仍成立,以及导航和数据刷新是否在重复进入后仍然正确。

第四轮为共同收敛。每个问题必须带有页面、状态、接口、代码位置、MuMu 证据、影响等级和处理建议。不能只记录“感觉不好看”“接口可能有问题”等不可验证意见。

三人意见不能简单少数服从多数。任意一人提出有证据的疑点时,必须继续调查;只有证据充分后才能形成共同结论。

5.3 共享工作区规则

  • 审查阶段三个人只读,不边看边改。
  • MuMu 由主代理统一导航、切换状态和采集截图,避免多人同时操作造成状态干扰。
  • 两位评审者审查同一份已经打开确认的 MuMu 截图。
  • 评审者可以要求补拍状态,主代理补充证据后三人重新判断。
  • 实施阶段由主代理统一修改工作区,两位评审者负责复核方案、变更、测试与 MuMu 证据。

六、联合评审模型

每个页面使用统一中文评审结构,至少记录:

  • 页面编号、页面路由和业务目标;
  • 用户身份、角色、权限和前置条件;
  • 进入来源、返回目标和流程终点;
  • 页面全部适用状态;
  • 页面全部用户操作;
  • 每个操作成功、失败、取消后的页面和数据结果;
  • 对应 App 接口和关联 PC 接口;
  • 缺失、冲突或不完整的接口;
  • 样式、美观、功能、交互和导航问题;
  • 真实数据和边界数据风险;
  • MuMu、源码和 OpenAPI 证据;
  • 三人初审、补漏、质询和最终结论。

每个用户功能都按以下顺序推演:

进入
→ 输入或选择
→ 本地校验
→ 提交锁定
→ 发起请求
→ 成功或失败
→ 页面反馈
→ 数据刷新
→ 返回或流程结束
→ 再次进入

同时检查取消、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 后端更新闭环

三人形成接口问题单
→ 用户确认少数业务决策
→ 后端修改 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 清理顺序与验证

确认当前权威资料
→ 删除失效文档
→ 清理重复或失效测试
→ 建立完整资产引用关系
→ 清理无用资产和生成物
→ 删除残留引用
→ 运行聚焦与全量验证
→ MuMu 检查代表流程

阶段 0 只清理已经能证明无效的内容,不顺便重构业务代码,也不开始接口接入。

十一、文档体系与生命周期

11.1 核心当前文档

  • docs/项目当前总览.md:项目唯一入口和当前进度。
  • docs/家谱项目全量治理设计.md:长期有效的治理规则。
  • docs/家谱项目全量治理实施计划.md:当前执行批次与验证要求;整体完成后将长期内容合并到总览并删除。
  • docs/接口与页面映射总表.md:页面、功能、状态与接口关系的唯一总表。

11.2 临时文档

  • docs/项目基线清理清单.md:阶段 0 使用,清理完成并合并有效信息后删除。
  • docs/评审/<业务域>联合评审.md:业务域审查期间使用,完成并合并长期结论后删除。
  • docs/待后端处理接口问题.md:只保留尚未解决的问题;后端合同通过验证后删除对应条目。

11.3 生命周期

草稿
→ 三人评审
→ 当前有效
→ 内容进入唯一所有者
→ 过程文档删除

不保留“旧版”“最终版2”“备份”或历史归档。任何规则只能有一个当前所有者。

十二、测试策略

测试体系按以下层级组织:

  1. 项目基础合同:页面清单、路由、共享组件、唯一所有者、响应式、编译和 OpenAPI 双格式一致性。
  2. 接口合同测试:路径、方法、参数、鉴权、响应字段、错误码、分页和状态枚举。
  3. 业务状态测试:校验、权限、状态转换、防重复提交、失败重试、过期和数据刷新。
  4. 导航流程测试:进入、返回、取消、完成和重复进入后的栈与数据状态。
  5. 纯函数运行测试:密码、验证码、字段归一化和状态机等纯逻辑。
  6. MuMu 验收:视觉、美观、键盘、安全区、手势和 Android 返回键。

每个确认问题必须测试先行:

第一步:写出能够复现问题的失败测试
第二步:运行并确认测试因目标问题失败
第三步:实施最小修改
第四步:运行聚焦测试
第五步:运行全局响应式合同和编译审计
第六步:运行全量合同
第七步:在 MuMu 验证完整流程和视觉状态

全量测试数量可以因清理和新增真实合同发生变化,不以保持原有数量为目标。

十三、中文文档与代码注释规范

  • 新建规格、计划、报告和问题单使用中文文件名和中文内容。
  • 接口路径、字段名、函数名等技术标识保留原文,并提供中文解释。
  • Vue、JavaScript、SCSS 和测试脚本继续使用英文技术文件名,避免工具兼容问题。
  • 测试代码同样使用中文注释说明准备条件、执行步骤、预期结果和合同目的。
  • 复杂业务函数必须使用详细中文注释说明每一步的业务目的、执行顺序、状态变化、失败处理和设计原因。
  • 简单赋值不堆叠无意义注释,但不能让复杂流程只能依赖阅读实现细节才能理解。

复杂流程注释采用明确步骤形式,例如:

// 第一步:校验页面必填字段,阻止无效请求进入后端。
// 第二步:锁定提交状态,防止连续点击产生重复记录。
// 第三步:调用业务接口,并根据明确的后端状态更新页面。
// 第四步:成功后刷新来源数据,再结束当前流程。
// 第五步:失败时保留用户输入,并提供可理解的重试入口。

十四、精准代码与复杂度控制

所有新增或修改代码都必须以最小、精准、可验证为标准。代码行数少不是唯一目标,但每一行都必须能够追溯到当前确认的问题、业务合同或验证要求。

  • 一个模块、函数或状态对象只承担一个清晰职责。
  • 业务规则必须有唯一所有者,页面、组件、工具和测试不得复制同一判断。
  • 改变合同后同步删除旧字段、旧入口、旧分支和旧注释,不保留“以防万一”的兼容读取。
  • 不为单次使用创建抽象层,也不为了未来可能需求增加配置、参数或扩展点。
  • 不把多个互不相关的业务流程塞进同一个大函数;复杂流程按明确业务步骤拆分,并保持输入、输出和副作用可见。
  • 优先使用清晰命名、早返回和直接数据流,避免深层嵌套、隐式全局状态和难以追踪的副作用。
  • 只有两个以上真实消费者需要同一规则时才提取共享能力,不能为了“看起来高级”制造工具层。
  • 不新增没有必要的依赖,不用大范围重构解决局部问题。
  • 中文注释解释“为什么、当前步骤和业务边界”,不能逐字翻译语法,也不能用大量注释掩盖混乱实现。
  • 测试验证对外业务合同和关键状态,不把内部实现细节固化成脆弱断言。
  • 每次实施只处理一个可独立验证的批次;相邻但无关的问题只记录,不顺手修改。

每个代码批次完成前,三个人必须共同检查:

  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:跨模块最终闭环

使用真实接口完整验证认证、创建或加入家谱、管理成员、发布内容、新增记录、接收消息、返回个人中心、退出和重新进入。

十六、每个业务域的固定执行流程

三人独立首审
→ 交叉补漏
→ 反向质询
→ 共同结论
→ 后端接口问题单
→ Apifox 修改与双格式导出
→ 五方案比较与批次设计
→ 失败测试
→ 最小实施
→ 聚焦、全局和编译验证
→ MuMu 完整流程与视觉验收
→ 删除旧合同和无用内容
→ 业务域完成

每个确认问题提出五个可行方案,从业务正确性、全局一致性、回归风险、维护成本和 MuMu 可验证性选择最优方案。

十七、进度与用户沟通

每个业务域只使用以下中文状态:

未开始
基线清理中
独立首审中
交叉复核中
待业务确认
待后端修改
待实施
实施中
MuMu 验证中
回归验证中
已完成

开始批次时报告当前阶段、业务域、目标、页面、接口、状态、后端依赖和明确不处理范围。

执行过程中只报告有实际价值的进展,包括已完成内容、三人发现、当前检查、阻断问题和下一步,不向用户倾倒无关工具日志。

只有三个人无法根据证据安全决定的真实业务问题才打断用户,例如角色权限、永久删除、重新申请、费用权益、隐私范围或会改变产品含义的流程分歧。

提问时一次只问一个业务问题,并说明发生位置、当前冲突、已排除方案、推荐方案、影响和阻塞范围。

十八、业务域完成定义

一个业务域只有同时满足以下条件才能标记完成:

  • 三人完成独立首审、交叉补漏和反向质询;
  • 每个页面的全部适用状态都有本轮 MuMu 证据;
  • 页面视觉达到完整、统一、美观的正式成品标准;
  • 所有页面操作都有明确业务结果;
  • 进入、返回、取消、完成、失败和重复进入均通过验证;
  • 每个页面数据需求都映射到明确 App 接口;
  • 若当前批次存在接口缺口,后端已在 Apifox 完成相应修复;
  • JSON 与 YAML 来自同次导出且语义一致;
  • 聚焦测试、全局响应式合同、编译审计和全量合同通过;
  • 真实接口数据下再次完成 MuMu 验收;
  • 没有遗留旧路径、旧字段、废弃测试或无用资产;
  • 阻断级和高优先级问题为零;
  • 其他未解决事项有明确负责人和状态。
  • 本批代码通过三人复杂度审查,不存在新增重复规则、无意义抽象、旧兼容路径或职责混乱的大函数。

十九、当前实施前置条件

阶段 0 已经完成。后续实施必须满足:

  1. 当前业务域已经由三个人完成独立首审、交叉补漏、反向质询和最终收敛。
  2. 设计结论已经进入本文件,实施步骤已经进入唯一实施计划。
  3. 需要后端变更时,先形成可直接修改 Apifox 的问题单;新导出通过双格式和接口差异合同后才能接入。
  4. 每个批次先写失败测试,再实施最小代码;不得同时混合导航、短信、持久化和全局无障碍等多个阶段。
  5. 业务代码变更后必须运行聚焦合同、全局响应式合同、编译审计和全量合同,并在当前 MuMu 复核完整流程。

用户已经授权三位审查者自行裁决有充分项目证据的专业方案。只有角色权限、隐私范围、永久删除、收费权益、法律责任或确实无法从现有业务判断的产品含义才再次询问用户。

二十、导航栈语义统一设计

20.1 当前证据

只读扫描得到以下统一口径:

  • 52 个活动页面中共有 navigateTo 59navigateBack 11redirectTo 14reLaunch 7,合计 91 次直接调用。
  • 活动共享组件另有 navigateBack 2reLaunch 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.navigateTouni.redirectTouni.reLaunchuni.switchTabuni.navigateBack 的业务模块。
  • 页面和组件只调用语义方法,不拼接页面路径,不保存 fallback URL,不接收后端原始跳转 URL。
  • 注册表中的路径集合必须与 pages.json 的 52 个活动路由精确相等;缺失、重复和陈旧条目均使合同失败。

路由参数只能使用注册表声明的标量值。ID 统一按字符串处理并逐项编码,不允许把对象、完整来源 URL、页面快照或领域数据塞进查询参数。

20.4 语义方法

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(可选)、refreshoperation 必须属于目标路由注册的 resultOperationsentityId 若存在必须为非空字符串,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 返回、取消与完成

返回优先级固定为:

关闭最上层预览、弹窗、搜索或抽屉
→ 消费 T03 等页面内部轨迹
→ 页面正在提交时阻止离开并给出状态
→ 有未保存输入时显示放弃确认
→ 执行普通 goBack

页头返回、页面取消按钮和 Android 返回键必须进入同一状态机,不能出现三个不同结果。

关键流程终点:

  • A04 取消或“已有账号”返回 A01;注册并建立会话后 goRoot(G01)
  • A05 取消返回 A01;只有未来真实重设接口成功才写入 password-reset 结果并回 A01。本地 state=success、计时器或视觉占位成功态只能普通返回,不得生成业务成功结果。
  • A01 登录成功 goRoot(G01);会话失效和退出成功也只允许 goRoot(A01)
  • F02、F03 完成或返回精确回 F01F06 新建回 F04,编辑回 F05;F09 上传完成回 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 页面继续负责低频业务编排:

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 必须被一个规范窗口原子替换,客户端不保留兼容读取。

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 跳转目标。
  • LineageGraphWindowstate 为 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 入边不会取消窗口入口身份。每个入口节点的 entryReasonGENEALOGY_ROOT/WINDOW_CUT/DISCONNECTED_COMPONENT,非入口节点为 null;每个入口在当前窗口恰有零条 primary 入边,每个非入口恰有一条 primary 入边,因此从任意焦点沿 primary 必然回溯到某个入口。GENEALOGY_ROOT 表示全谱主森林根,WINDOW_CUT 表示其 canonical primary 父边在窗口外,DISCONNECTED_COMPONENT 表示没有可达全谱根的 canonical primary 链;因此第 50 代焦点的上二代窗口不会被伪装成全谱根。全谱根只由 overview 的可见根/隐私根计数和 locator 的根可见性分支表达。
  • 人物节点以 visibility 为 discriminator 使用 oneOfVisibleLineagePerson 固定包含 id、generation、displayName、sex、avatarOssId、branchId、branchPath、order、visibility=VISIBLE、entryReason;其 id 是可用于 FOCUS、locator、搜索和写接口的稳定人物 ID,sex 只允许 MALE/FEMALE/UNKNOWNRedactedLineagePerson 只允许 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、orderpartners 只允许一个单亲成员,或同一伴侣关系中的两人;每项包含 personId、partnerRole、orderpartnerRole 只允许 ANCHOR/PARTNER,且恰有一个 ANCHOR 与 anchorPersonId 相等。双人家庭的 partnerRelationship 必含 relationshipId、relationshipKind=PARTNER、relationType、status,其中 relationTypeMARRIAGE/PARTNERSHIP/UNKNOWNstatusACTIVE/ENDED/UNKNOWN;单亲家庭该字段为 null。同一人物的不同伴侣关系必须拆成不同家庭单元。
  • ParentChildEdge 从家庭单元指向一个子女,包含 id、familyUnitId、childId、lineageParentId、parentRelations、primary、order。每个 parentRelations 项都包含稳定 relationshipId、relationshipKind=PARENT_CHILD、personId、parentRole、relationTypeparentRole 使用 FATHER/MOTHER/PARENT/GUARDIAN/UNKNOWNrelationType 使用 BIOLOGICAL/ADOPTIVE/STEP/GUARDIAN/UNKNOWNlineageParentId 必须同时出现在该边的父母关系和家庭成员中,子女不得同时是该家庭成员;primary 入边与 entryPersonIds 必须满足上一条的零条/恰一条森林不变量,布局不得根据数组顺序猜测主世系父母。
  • 所有父子关系都参加缺引用、自环和有向循环校验,不能只检查 primary 主森林;primary 只决定布局主干,不降低次要收养、继亲或监护关系的数据正确性要求。
  • boundary 锚点可以是 PERSON、FAMILY_UNIT 或 WINDOWPERSON/FAMILY_UNIT 的 anchorId 必须引用对应实体,WINDOW 的 anchorId 固定为 null。方向为 ANCESTORS、DESCENDANTS、LATERAL,原因只允许 MISSING、REDACTED、UNLOADEDhiddenCount 只能是非负整数或 nullcursor 只在 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=FOCUSfocusPersonId 可选且只能是调用者可见的稳定人物 IDREDACTED opaque ID 不得作为焦点;缺省时服务端按“当前账号绑定成员→主始祖”确定并在响应中返回实际焦点。不可见、过期或不存在的焦点统一返回 404 LINEAGE_FOCUS_NOT_AVAILABLE,不泄漏成员是否存在;ancestorDepth/descendantDepth 是 0—20 的整数、可选且默认 2;boundaryId/cursor/treeVersion 必须缺席。
  • mode=BOUNDARYboundaryId/cursor/treeVersion 三项必填;focusPersonId/ancestorDepth/descendantDepth 必须缺席。
  • 两种模式的 limit 都是整数,最小 1、默认 200、最大 500;任何混合、缺项或多余组合返回 HTTP 422LINEAGE_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 }。根可见性与路径完整性是两个独立 discriminatorrootVisibility=VISIBLErootPersonId 是可见稳定 IDrootVisibility=REDACTEDrootPersonId=nullpathCompleteness=COMPLETE 表示路径没有隐私缺口,pathCompleteness=REDACTED_GAPS 表示含一个或多个隐私段,允许“可见根→隐私中间祖先→可见目标”。

ancestorPathSegmentskind 使用 oneOfVISIBLE 段精确为 { 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 客户端所有者与处理链

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=truetargetPersonId 指向最靠近当前可见窗口的稳定人物 ID,仅含隐私成员时 focusable=false、targetPersonId=null。点击可聚焦聚合节点后以该成员重新聚焦,不存在 expandedAggregateIds 或不断增长的页内展开状态。完整规范图继续供搜索和线性列表使用;布局只消费投影,Scene 不得再次决定聚合规则。
  • 布局按 order、id 稳定排序,自底向上计算子树轮廓;家庭联合点必须位于可见子女跨度中心,单子女时严格同轴。
  • Scene 根对象精确为 { sceneVersion, treeVersion, focusPersonId, bounds, items }utils/lineage/scene.jssceneVersion 唯一所有者,按 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 路径:

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 的 oneOfPARTNER 分支只更新 relationType/statusPARENT_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、单一 parentIdfatherId/motherIdparentId 双读、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 一起完成;不借此提前改造全项目文字体系。任何阶段完成前都不开始下一阶段的业务代码。