【项目实训博客4】历史记录功能的设计与实现
一、开发背景在前期开发中项目已经完成了辩论主流程、裁判结果页以及复盘页的基本功能能够支持一场辩论从开始到结束的完整展示。但如果仅停留在实时流程层面整个系统仍然存在一个明显缺口已结束的辩论无法稳定回看裁判结果与复盘数据缺少统一归档入口replay / 回放模式缺乏可靠的数据来源用户无法从历史视角对多场训练结果进行比较和追溯因此本阶段的目标是补齐历史记录功能将单场辩论的过程数据、阶段数据、结果数据和复盘数据统一沉淀为可复用的本地记录并在此基础上提供历史列表、历史详情和回放入口。二、功能目标本阶段历史功能主要包括以下三个部分历史记录的本地存档历史列表的摘要展示历史详情的完整回看与结果复用其核心目标不是单纯新增一个“历史页面”而是建立一套可复用的记录体系使项目具备以下能力对整场辩论进行完整归档从摘要信息快速定位某一场记录在详情页中查看对局过程与结果为 replay 模式提供稳定输入三、整体结构设计本次实现采用了“记录管理器 摘要列表 详情展示”的结构。3.1 记录层由DebateRecordManager统一负责整场记录的采集、保存、加载与索引维护。3.2 列表层由DebateHistoryListPanel负责读取历史摘要并生成记录列表。3.3 详情层由DebateHistoryDetailPanel负责展示完整历史记录并复用已有的结果页和复盘页组件。这种拆分方式的优点在于数据采集与 UI 展示解耦列表层只处理摘要不直接依赖完整记录历史详情页可以直接复用赛后模块后续扩展 replay 或筛选功能时改动范围相对可控四、历史记录的数据采集与归档4.1 记录管理器的职责历史功能的数据核心集中在DebateRecordManager中。该管理器承担的职责包括创建当前辩论记录记录论题选择记录用户辩位选择记录对话历史记录阶段切换记录争议点状态保存裁判评分保存复盘报告生成历史摘要本地写入 JSON 文件维护索引文件从职责定位上看它并不是单纯的“保存工具”而更像一条完整的赛后归档链路。4.2 记录写入时机当前实现中记录并不是只在辩论结束时一次性写入而是按照流程逐步沉淀选题后开始建立记录选辩位后补充玩家快照辩论过程中持续记录发言和阶段变化比赛结束后标记主体完成裁判页提交评分复盘页提交报告最终归档为完整记录这种设计比“最后一次性保存所有数据”更稳健原因主要有两点中间过程已有阶段性落盘降低丢失风险replay、历史详情、结果复用都可以共享同一份记录结构4.3 索引文件的作用历史列表并不直接扫描所有完整记录文件而是通过index.json管理摘要信息。这样做的原因是列表页只需要摘要不需要完整对局内容读取索引比逐个解析完整 JSON 更轻量后续记录数量增加时性能和结构都会更稳定因此历史功能实际采用了两层数据完整记录文件保存单场辩论全部信息索引文件保存列表页所需摘要信息五、历史摘要模型设计历史列表使用的是DebateRecordSummaryModel其作用是为列表展示提供最小必要信息。当前摘要主要包含记录 ID创建时间论题标题最终阶段名称发言数量阶段流转数量争议点数量是否包含评分结果是否包含复盘报告这种设计有两个明显优点5.1 列表展示足够简洁用户在列表页可以快速判断一条记录是否值得继续点开。5.2 完整记录与摘要职责分离列表页不需要知道太多细节详情页再按记录 ID 加载完整内容结构更清晰。六、历史列表页的实现6.1 列表页的主要职责DebateHistoryListPanel负责刷新记录摘要显示总记录数处理空状态动态生成列表项点击条目后联动详情页在实现上这一页延续了项目已有的 UI 结构习惯支持Show()/Hide()保留统一的入场 / 退场动画列表项采用动态实例化方式生成6.2 列表项展示内容每条历史记录在列表中主要展示三类信息1标题信息辩题名称2元信息创建时间最终阶段当前记录状态3统计信息发言数阶段数争议点数量这样做的目的是让用户在不进入详情页的前提下先对记录形成初步判断。6.3 空状态处理如果没有任何历史记录列表页会显示“暂无历史记录”。虽然这只是很小的一部分但在历史页这种入口型页面里空状态其实很重要。否则页面会给人一种“功能失效”而不是“数据为空”的感觉。七、历史详情页的实现7.1 详情页的功能定位历史详情页不是单纯的“文本展开”而是一个完整的历史回看界面。它的核心任务是回答以下问题这场辩论是什么时候发生的玩家当时扮演了什么辩位辩论过程中发生了哪些发言各阶段如何推进争议点如何归属是否存在评分和复盘是否可以进入回放模式7.2 详情页模块划分DebateHistoryDetailPanel当前主要分为以下几个区域1基本信息区展示辩题开始 / 结束时间玩家辩位最终阶段统计数据2发言历史区展示每条发言的序号所属阶段发言者发言内容3阶段流转区展示每次阶段切换的顺序切换时间起始阶段与目标阶段4争议点区展示争议点标题归属阵营胜出原因5结果复用区复用JudgeResultPanelReviewPanel6回放入口在有 timeline 数据时允许进入 replay 模式。这种设计方式本质上是在做“总览 明细 后续操作”三层结构而不是简单地把 JSON 内容平铺出来。八、结果页与复盘页的复用历史功能的一个关键设计点是没有为“历史结果”重新单独写一套新的展示逻辑而是直接复用了原有模块JudgeResultPanelReviewPanel8.1 这样做的好处1避免重复实现Judgement 和 Review 本身已经有成熟的 UI 结构与数据入口再写一套历史版本会明显增加维护成本。2保证展示一致性正常流程中看到的结果和历史回看中看到的结果应该尽可能一致。3方便 replay 复用如果历史详情、正式流程、回放流程都共享同样的数据展示入口那么后续功能扩展会更顺畅。8.2 统一入口的意义这一点和之前做 Judgement / Review 时的思路是统一的页面组件应尽量保留清晰的统一数据入口例如SetData(...)。这样控制器只需要负责“把什么数据送进来”而不需要关心具体渲染细节。九、回放入口的衔接历史详情页中保留了 replay 按钮其基本逻辑是读取当前记录 ID通过ReplayContextManager进入回放上下文重新加载 Debate 场景在 Debate 场景中按记录内容执行回放初始化也就是说历史功能不是终点而是 replay 模式的上游入口。从结构角度看这样的设计比较合理因为历史页天然承担“回看过去”的职责replay 本质上就是在历史记录基础上的动态重放不必额外建立一条与归档系统平行的数据链路十、实现过程中遇到的问题10.1 测试数据与正式数据混用的问题历史功能最容易出现的问题之一是页面看起来“有数据”但这些数据并不一定来自正式归档。如果测试脚本直接给 UI 注入临时数据会出现一种假象页面显示正常但实际本地记录并没有写完整历史列表和详情页读取不到正式结果因此本阶段实现中比较强调的一点是历史页的内容必须来自记录文件本身而不是临时测试数据。10.2 阵营颜色判断问题历史发言记录在展示时阵营颜色必须基于speakerId或阵营映射判断不能简单依赖显示名称。否则在以下情况下容易出错名称重复名称文本变化replay / 历史回放时角色顺序不同这一点在项目中已经有较明确约定因此历史页也必须遵循同样的判定逻辑。10.3 动态列表布局稳定性问题历史列表和详情页都依赖运行时动态生成条目因此布局系统需要特别注意子项高度VerticalLayoutGroupContentSizeFitterScrollView 内容撑开逻辑如果这些基础布局没有处理稳随着记录条目增多很容易出现滚动范围错误列表压缩文本显示不完整因此这次实现中对条目容器和运行时生成 UI 的约束相对明确没有做过度自由化的布局。十一、当前功能完成度11.1 已完成内容历史存档支持整场辩论本地保存支持索引摘要生成支持按记录 ID 加载完整记录历史列表支持摘要展示支持记录数量统计支持空状态显示支持刷新列表历史详情支持显示完整概览信息支持显示发言历史支持显示阶段流转支持显示争议点归属支持复用裁判结果页和复盘页支持进入 replay11.2 当前阶段的意义到这一阶段为止项目已经不仅仅是“可以打一场辩论”而是开始具备以下能力保存一场辩论回看一场辩论复用一场辩论的结果与复盘将一场辩论作为后续回放的输入源这意味着赛后链路已经从一次性展示逐步转向可复用、可追溯的结构。十二、后续优化方向历史功能当前已经完成基础闭环但在表现层和交互层还有继续优化的空间。后续可以继续推进的方向主要包括12.1 历史详情页的可视化增强例如更清晰的模块分区时间线式阶段展示争议点归属的图形化表达12.2 列表筛选与检索例如按辩题筛选按时间排序按是否有复盘 / 评分过滤12.3 replay 的时间线细化目前历史记录已经具备 timeline 数据基础后续可继续细化回放过程中的节奏还原和阶段还原。12.4 多场训练对比如果后续需要进一步强化“训练平台”属性历史记录之间的横向比较会是一个值得继续扩展的方向。十三、总结本阶段历史功能的核心成果不是新增了一个“查看记录”的页面而是建立了一套完整的赛后归档与复用体系。当前形成的链路可以概括为Debate → Judgement → Review → History → Replay其中历史功能承担的角色不是附属页面而是整个系统的“长期记忆层”它保存辩论过程它承接赛后结果它支持复盘复用它为回放提供来源对于一个辩论训练项目而言这一层是很必要的。因为训练价值并不只来自“完成一场”更来自“能够回到过去的一场并从中继续提取经验”。