本章导读:这是“寻迹校园 HarmonyOS NEXT 实战”系列第 38 篇。本文以 HomePage.splitScreenHome()ItemDetailPageMatchResultsPage 和精确 1280vp 模拟器证据为主线,分析超宽屏为何采用 34% 查询列表、44% 中间详情、22% 匹配辅助的主从工作台,并说明 selectedReportIdpreviewReportIdsourceReportId 如何避免候选预览破坏原始查询上下文。

寻迹校园 XL 三栏工作台原创封面图

上图为原创生成的三栏信息架构图,不是项目截图。左栏负责“我正在查什么”,中栏负责“我正在看什么”,右栏负责“系统召回了什么”。

一、大屏价值不是把手机卡片排成更多列

校园失物匹配需要在多个事实之间来回核对:查询记录、候选记录、相似点、冲突、公开描述和后续动作。单纯把手机列表改成四列卡片,虽然一屏显示更多,却让用户不断打开详情、返回列表、再打开下一条。

三栏工作台的目标是保留上下文:

  • 左栏筛选和查询不动;
  • 右栏候选始终可见;
  • 点击候选只改变中间证据详情;
  • 用户随时知道候选是由哪条记录发起的;
  • 认领时继续携带源查询 ID。

二、三栏分别对应三个业务问题

区域 宽度 回答的问题 核心状态
左栏 34% 当前选择哪条记录作为查询 selectedReportId
中栏 44% 当前查看查询本身还是某个候选 previewReportId
右栏 22% 这条查询召回了哪些脱敏候选 queryReportId + candidates

三个区域不是平级信息流,而是明确的主从关系。

三、本章的代码入口

XL 工作台并没有单独创建 WorkspacePage.ets,而是在 HomePage 的分栏实现中组合现有页面:

HomePage
├─ 左:homeHeaderAndFilters + reportList
├─ 中:ItemDetailPage(embedded=true)
└─ 右:MatchResultsPage(embedded=true)

这让独立页面和嵌入页面共享加载、错误、状态和业务动作。

四、为什么选择 34%/44%/22%

左栏需要容纳搜索框、四维筛选、状态筛选和记录卡片;中栏包含标题、照片、公开特征、状态和主要动作;右栏最多显示三条候选、相似点、冲突和预览按钮。

因此中栏最大,左栏次之,右栏最窄。比例不是平均分配,而是按任务密度分配。

项目还为左栏加 300–460vp 约束,防止百分比在极端窗口中失控。

五、workspaceView 是能力参数

Index.etsscreenWidth >= 1280 时下传:

HomePage({
  splitView: true,
  mediumView: false,
  workspaceView: true
})

HomePage 不需要知道设备是 PC、2-in-1 还是最大化模拟器,只判断当前布局是否具备第三栏能力。

六、左栏是查询上下文,不只是导航列表

左栏同时保存:

  • 搜索关键词;
  • 全部/丢失/拾得状态筛选;
  • 类别、地点、时间、颜色;
  • 筛选面板展开态;
  • 记录列表滚动位置;
  • 当前查询记录的选中边框。

用户点击右栏候选时,左栏不应改变,否则匹配候选会失去来源。

七、selectedReportId 表达“查询是谁”

页面用 selectedReportId 保存左栏当前记录:

@Local selectedReportId: string = '';

当用户点击另一条左栏记录,页面同时更新查询和初始预览:

this.selectedReportId = report.id;
this.previewReportId = report.id;

这意味着中栏先展示查询本身,右栏再为它加载相反类型候选。

八、previewReportId 表达“中间正在看谁”

previewReportId 可能等于查询 ID,也可能等于某个候选 ID。右栏点击候选时只执行:

private previewCandidate(candidate: MatchCandidate): void {
  this.previewReportId = candidate.report.id;
}

没有修改 selectedReportId,所以候选不会反客为主。

查询、预览与 sourceReportId 状态流原创图

上图展示双 ID 模型:左栏选中确定匹配查询,右栏候选只驱动中栏预览;进入认领流程时,候选 ID 与源查询 ID 同时保留。

九、双 ID 比一个 selectedId 更可靠

如果只使用一个 selectedId,点击候选后会发生歧义:

  • 右栏应该继续显示原查询候选,还是改为候选自己的反向匹配?
  • 左栏哪个卡片应该保持选中?
  • 认领请求中的“我用哪条记录进行比较”从哪里获得?
  • 窗口缩回双栏时应该保留查询还是候选?

selectedReportId + previewReportId 把“查询”与“视图焦点”拆成两个事实。

十、sourceReportId 为什么必须继续传递

中栏嵌入详情时,参数为:

sourceReportId: this.previewReportId === this.selectedReportId ?
  '' : this.selectedReportId

当中栏显示候选,sourceReportId 指向原查询。用户点击认领时,ClaimRouteParam 同时带入候选 reportId 和 sourceReportId。

这让后续页面知道认领来自哪次查询,而不是把候选详情当成孤立入口。

十一、ItemDetailPage 的 embedded 模式

中栏复用 ItemDetailPage,但设置 embedded: true。嵌入模式的职责是:

  • 不重复显示全屏 Header;
  • 保留详情加载、状态标签和业务按钮;
  • 填充中栏高度并自行滚动;
  • 继续通过 pathStack 打开认领、举报或独立流程;
  • 使用相同 Service 读取权威 Report。

大屏复用的不是静态组件快照,而是完整页面主体。

十二、MatchResultsPage 的 embedded 模式

右栏复用 MatchResultsPage,接收:

MatchResultsPage({
  queryReportId: this.selectedReportId,
  embedded: true,
  selectedCandidateId: this.previewReportId === this.selectedReportId ? '' : this.previewReportId,
  onSelectCandidate: (candidate: MatchCandidate) => this.previewCandidate(candidate)
})

独立模式点击候选会 push 详情路由;嵌入模式则调用回调更新中栏。业务候选卡、分数、相似点与冲突仍是同一份实现。

十三、候选选中态如何反馈

右栏按钮文案会根据当前候选变化:

  • 未选中:在中间查看
  • 已选中:正在中间预览

卡片边框也从普通 BORDER 切换为 2vp BRAND_PRIMARY。同时提供可访问描述“在中间详情栏预览此候选”。

这避免用户只看到中栏变化,却不知道右栏哪个候选触发了变化。

十四、右栏为什么必须独立 Scroll

候选卡包含标题、分数、相似点、冲突和按钮,不能假设 22% 宽度下一屏总能显示三条。MatchResultsPage 使用自己的 Scroll,候选区可独立滚动。

2026-08-12 精确断点验收中,对右栏执行滚动输入后,候选 2、候选 3 与按钮进入视口;这证明当时模拟器的滚动容器响应,不能替代实体鼠标滚轮证据。

十五、左栏滚动为什么不应拖动整页

左栏查询可能有多条记录和展开筛选;中栏详情也可能很长;右栏候选同样需要滚动。三栏如果共享一个根 Scroll,滚动任何区域都会带走其他上下文。

当前三栏分别拥有自己的滚动范围,让用户比较候选时仍能看到查询标题和右侧分数。

十六、筛选刷新怎样保护选中记录

refreshReports() 读取新列表后检查当前查询是否仍存在:

const selectionAvailable = this.reports.some(
  (report: ItemReport) => report.id === this.selectedReportId
);
if (!selectionAvailable) {
  this.selectedReportId = this.reports.length > 0 ? this.reports[0].id : '';
  this.previewReportId = this.selectedReportId;
}

如果筛选仍包含当前记录,就保持查询;如果记录被排除,才切到第一条结果。这样不会每输入一个字符都重置到顶部。

十七、候选预览后再筛选会发生什么

候选 previewReportId 可能不在左栏查询列表中,因为左栏显示查询集合,右栏显示相反类型候选。只要 selectedReportId 仍有效,页面可以继续保留候选预览。

如果查询本身被新筛选条件排除,页面将选择新的查询并把预览复位为新查询。这是由查询有效性决定,而不是由布局决定。

十八、空数据时三栏不能留下两个白洞

当左栏无记录时,中栏显示“选择一条本机记录查看详情”或对应空态,右栏显示“选择左侧记录后显示脱敏候选”。

空态需要告诉用户下一步:登记记录、调整筛选或等待数据加载。只渲染空 Column 会让用户误以为页面损坏。

十九、错误状态必须局部化

左栏列表加载失败,不代表导航和整个应用失效;右栏匹配失败,也不应清空中栏详情。

每个嵌入页面保留自己的 loading/error/retry:

  • 左栏重新加载本机记录;
  • 中栏重新读取当前 Report;
  • 右栏重新执行本地匹配;
  • 主 Shell 与路由栈保持可用。

局部错误才能支持工作台继续完成其他任务。

二十、三栏不是三个独立业务源

左栏、中栏和右栏都通过 reportService 与匹配逻辑消费同一份 Repository 数据。页面只保存筛选和预览等短生命周期状态。

如果每栏维护自己的 Report 副本,隐藏、撤回、认领或结案后会产生互相矛盾的状态。项目通过 dataRevision 触发刷新,仍以 Service/Repository 为权威来源。

二十一、为什么右栏最多展示 Top 3

当前匹配合约只返回相反类型 Top 3,并显示相似理由与冲突。限制候选数量有三项意义:

  • 控制用户比较负担;
  • 为每条候选保留足够解释空间;
  • 小艺辅助文本保持脱敏、短小和可核验;
  • 右栏不会退化成另一个无限列表。

Top 3 是召回界面选择,不代表只有三条记录存在。

二十二、分数颜色为什么统一使用品牌蓝

候选分数表示公开信息相似度,不代表归属概率。所有 82/61/48 等分数统一使用品牌蓝,冲突使用警告色,并显示免责声明“仅比较脱敏公开信息,不代表物品归属”。

如果高分用绿色、低分用红色,用户容易把分数理解成“正确/错误”或“可以/不可以认领”。

二十三、精确 1280vp 的运行证据

2026-08-12 MateBook Pro API 22 模拟器记录显示:

  • 1278.95vp 为 lg 双栏;
  • 1280vp 立即出现 xl 三栏;
  • 点击候选 1 后,中栏显示候选详情;
  • 缩回 1279 档时右栏隐藏,中栏详情保持;
  • 恢复 1280vp 后候选仍标记“正在中间预览”;
  • 右栏滚动和 Tab/Shift+Tab 输入链路通过。

这些是有日期的模拟器证据,不是当前实体 2-in-1 兼容结论。

二十四、当前首发仍是 Phone

尽管 XL 代码、设计稿和模拟器证据存在,当前 entry/src/main/module.json5 仍只声明 deviceTypes: ["phone"]

保留大屏实现便于后续恢复设备声明,但在完成实体设备、外设、窗口吸附与商店材料前,不能把这套工作台描述为已经面向 2-in-1 用户正式发布。

二十五、实体设备还要验证什么

大屏实机验收至少应覆盖:

  • 系统缩放下 34/44/22 是否仍可用;
  • 窗口贴边、最大化与恢复;
  • 鼠标点击、滚轮和触控板;
  • 蓝牙键盘正反向焦点;
  • 字体放大和长标题;
  • 图片加载和三个独立 Scroll;
  • 页面切换后的查询状态;
  • 性能与重绘抖动。

模拟器布局树不能替代这些硬件证据。

二十六、工程复盘:把“选择”拆成查询与预览

三栏工作台最关键的设计不是比例,而是双 ID。selectedReportId 稳定表达查询来源,previewReportId 允许用户临时查看候选,sourceReportId 再把两者关系带入认领流程。

这种状态模型让布局可以从三栏退回双栏而不改变业务事实,也让右栏候选只是辅助视图,而不是新的全局选择。

二十七、本文小结

“寻迹校园”的 XL 工作台用 34% 左栏保留搜索与查询,用 44% 中栏展示当前证据详情,用 22% 右栏展示 Top 3 脱敏候选。ItemDetailPageMatchResultsPage 通过 embedded 模式复用,双 ID 保持查询与预览边界,sourceReportId 保护后续认领语义。

当前代码和 2026-08-12 模拟器证据能证明三栏结构与交互链路;实体 PC/2-in-1 外设兼容、当前商店设备支持和多用户远程能力仍未由这些证据证明。

系列导航:第 38 篇 / 共 50 篇。上一篇:《sm、md、lg、xl 四档响应式 Shell》;下一篇:《1280vp 临界点往返状态恢复》。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐