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

上图为原创生成的三栏信息架构图,不是项目截图。左栏负责“我正在查什么”,中栏负责“我正在看什么”,右栏负责“系统召回了什么”。
一、大屏价值不是把手机卡片排成更多列
校园失物匹配需要在多个事实之间来回核对:查询记录、候选记录、相似点、冲突、公开描述和后续动作。单纯把手机列表改成四列卡片,虽然一屏显示更多,却让用户不断打开详情、返回列表、再打开下一条。
三栏工作台的目标是保留上下文:
- 左栏筛选和查询不动;
- 右栏候选始终可见;
- 点击候选只改变中间证据详情;
- 用户随时知道候选是由哪条记录发起的;
- 认领时继续携带源查询 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.ets 在 screenWidth >= 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,所以候选不会反客为主。

上图展示双 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 脱敏候选。ItemDetailPage 与 MatchResultsPage 通过 embedded 模式复用,双 ID 保持查询与预览边界,sourceReportId 保护后续认领语义。
当前代码和 2026-08-12 模拟器证据能证明三栏结构与交互链路;实体 PC/2-in-1 外设兼容、当前商店设备支持和多用户远程能力仍未由这些证据证明。
系列导航:第 38 篇 / 共 50 篇。上一篇:《sm、md、lg、xl 四档响应式 Shell》;下一篇:《1280vp 临界点往返状态恢复》。
更多推荐


所有评论(0)