HarmonyOS公交路线异常标注与候选恢复【共创稿事节】
一条已经选中的公交换乘路线,并不会因为候选列表仍然能显示就持续有效。前六篇把市民中心、科技馆和图书馆组织为线路图,加入了计划班次、换乘约束、屏幕布局与读屏顺序,还用离线版本包更新站点资料。此时需要处理一个容易被忽视的变化:用户保存的路线 L1-0835>L2-0848 仍指向原来的车次,但科技馆 ST-B 的无台阶通行条件在新资料中出现分歧。如果仅显示原来的三十分钟行程,视觉上没有任何异常,实际筛选条件却已经不同。
这里将异常处理拆成三个相互独立的环节:识别数据变化、确定路线受到的影响、等待有依据的修订后重新筛选。站点异常来自应用保存的离线资料与变更记录,不与城市车辆实时状态混为一谈。这样既能保留使用者之前选择的路线,也能避免把尚待核对的通行条件包装成确定的推荐结果。

从站点版本差异识别异常,而不是直接覆盖结果
第05篇引入本地 v4 与导入 v5 的站点表。它们都使用 线路ID:站点ID 作为唯一定位键,因此 L1:ST-B 与 L2:ST-B 是两个线路状态节点,共享“科技馆”这个物理站点名称。不能因为站名相同,就把一条线路上的标志变动直接应用到另一条线路。合并时首先按稳定键查找,再比较修订号与可达性字段,最后根据变化性质选择应用或复核。
这次资料变化中,L1:ST-B 的无台阶标志从“有”变成“无”;另一方面,L2:ST-E 的图书馆站点标志由“无”变为“有”。对需要无台阶换乘的候选路线,前一个变化直接影响可行性,不能因导入包版本较新就忽略分歧。应用保留已生效快照及待复核变更,并在界面明确标出它们的不同状态。第二个变化可以成为下一份候选快照的一部分,但仍需通过数据源和路线级约束检查。
异常对象要包含来源版本、站点稳定键、字段路径、旧值、新值、复核状态和关联候选键。只保存“异常一条”这样的计数会让后续无法解释为什么某条路线从推荐列表消失。也不宜把异常直接写在原始站点对象上,否则重复导入时很容易把前一次的复核结果覆盖。下面的Node.js代码以纯数据判断变化,输出可用于页面展示和持久化的差异条目。
const assert = require('node:assert/strict');
const local = [
{ key: 'L1:ST-B', revision: 4, stepFree: true },
{ key: 'L2:ST-E', revision: 4, stepFree: false }
];
const incoming = [
{ key: 'L1:ST-B', revision: 5, stepFree: false },
{ key: 'L2:ST-E', revision: 5, stepFree: true }
];
function compareStationVersions(base, update) {
const index = new Map(base.map(row => [row.key, row]));
const changes = [];
for (const row of update) {
const prior = index.get(row.key);
if (!prior || row.revision <= prior.revision) continue;
if (prior.stepFree !== row.stepFree) {
changes.push({ key: row.key, field: 'stepFree',
before: prior.stepFree, after: row.stepFree,
revision: row.revision,
status: prior.stepFree && !row.stepFree ? 'needs_review' : 'changed' });
}
}
return changes;
}
const changes = compareStationVersions(local, incoming);
assert.equal(changes.length, 2);
assert.equal(changes[0].key, 'L1:ST-B');
assert.equal(changes[0].status, 'needs_review');
assert.equal(changes[1].key, 'L2:ST-E');
console.log(changes);
这里的 changed 只是资料层的变化标记,不等同于可达性已经确认。对于人工维护的无台阶字段,数据管理页面应该允许查看来源和修订历史,再决定是否采用更新。重复导入同一修订号时不新建相同差异;修订号更低的文件也不能倒灌覆盖当前数据。即使两个记录使用相同站点名称,只要稳定键不同,就必须分别处理。
让路线约束重新计算,不能沿用旧标签
原候选在本地计划表里从08:35市民中心乘坐L1,08:42到达科技馆,08:48换乘L2,09:05到达图书馆。六分钟是两段计划时刻之间的间隔,并不是已经确认的站内步行时长。上一章在读屏卡片中说明了换乘关系;本章需要把异常变成可被筛选器处理的条件,而不只是给原卡片贴一个红色图标。
应用可以把路线是否可选归结为两类检查:一类是拓扑和计划时刻,例如乘车节点是否仍存在、前段到达是否早于后段发车,以及是否达到预留的最小换乘窗口;另一类是使用者指定的约束,例如要求无台阶通行时,任何关键换乘节点都不能处于待复核状态。数据更新使某个约束缺少可靠依据时,候选应暂停推荐,同时保留原有选择键,用于查看受影响片段和重新选择。
特别需要区分“没有找到符合约束的路径”和“线路图根本没有路径”。前者可能是无台阶资料待确认,后者可能是站点或边被移除。如果统一显示“路线不存在”,使用者无法判断究竟要更新资料还是选择其他路线。下面的检查函数在不改变原候选对象的前提下,给出明确的结果类型。
const assert = require('node:assert/strict');
const route = { key: 'L1-0835>L2-0848',
transfer: { from: 'L1:ST-B', to: 'L2:ST-B', arrive: 522, depart: 528 },
requireStepFree: true };
const stations = new Map([
['L1:ST-B', { stepFree: true, pendingReview: true }],
['L2:ST-B', { stepFree: true, pendingReview: false }]
]);
function assessCandidate(candidate, snapshot, minMinutes = 4) {
const t = candidate.transfer;
const from = snapshot.get(t.from);
const to = snapshot.get(t.to);
if (!from || !to) return { status: 'missing_node' };
if (t.depart - t.arrive < minMinutes) return { status: 'tight_transfer' };
if (from.pendingReview || to.pendingReview) {
return { status: 'needs_review', reason: 'station_revision' };
}
if (candidate.requireStepFree && (!from.stepFree || !to.stepFree)) {
return { status: 'blocked', reason: 'step_free_requirement' };
}
return { status: 'available', key: candidate.key };
}
assert.equal(assessCandidate(route, stations).status, 'needs_review');
assert.equal(assessCandidate(route, new Map()).status, 'missing_node');
console.log(assessCandidate(route, stations));
函数中的时刻使用当天零点起的分钟数,08:42对应522,08:48对应528。它只适合本例的同日计划表;跨午夜的班次应把服务日与日期一起纳入字段,避免分钟数回绕造成负数。对于异步加载的路线详情,还应记录数据版本:如果第一个查询基于v4,第二个查询基于v5,而v4结果晚一步返回,页面不能把旧结果重新覆盖到当前列表。显示层应检查请求携带的版本戳,与最新快照不一致的结果直接丢弃。

恢复时保留原候选身份,但重新确认有效性
处理异常不等于把红色状态按钮改成绿色。路线恢复至少涉及资料来源确认、版本递增、约束重新求值和界面选中状态恢复。假设核对后形成新的 v6 站点记录,确认科技馆线路节点仍支持无台阶通行,那么应用可以形成新的快照,再执行与正常查询一致的筛选流程。若核对结果确认无台阶条件已经取消,则原路线仍然不满足该使用者的约束,应该留在历史选择区,不进入当前可推荐列表。
恢复动作必须携带明确的修订版本,并拒绝低版本覆盖。写入之前将新快照与旧快照分开计算,先检查所有记录的键是否重复、关联边是否完整,之后再执行持久化提交。界面上的选择与焦点也不应只靠行号:原候选仍符合条件时可用稳定键恢复;若已失效,则焦点返回候选列表标题,同时提供“查看异常原因”的入口。下列函数仅负责应用层状态转换,数据持久化由调用方在检查通过后完成。
const assert = require('node:assert/strict');
function resolveRecovery(current, update, selectedKey) {
if (!update.reviewedBy || update.revision <= current.revision) {
throw new Error('复核记录或修订版本无效');
}
if (current.key !== update.key) throw new Error('站点身份不一致');
const station = { ...current, stepFree: update.stepFree,
pendingReview: false, revision: update.revision };
return { station, selectedKey,
canRecommend: station.stepFree === true,
focusTarget: station.stepFree ? selectedKey : 'candidate-heading' };
}
const original = { key: 'L1:ST-B', stepFree: true,
pendingReview: true, revision: 4 };
const restored = resolveRecovery(original,
{ key: 'L1:ST-B', stepFree: true, revision: 6, reviewedBy: '资料核对员' },
'L1-0835>L2-0848');
assert.equal(restored.canRecommend, true);
assert.equal(restored.station.pendingReview, false);
assert.throws(() => resolveRecovery(original,
{ key: 'L1:ST-B', stepFree: false, revision: 3, reviewedBy: '资料核对员' },
'L1-0835>L2-0848'));
console.log(restored);
这段逻辑没有直接修改传入对象,调用方可以在写入失败时继续保留旧快照。恢复后的可推荐判断还必须对另一段L2乘车边、换乘时间以及其他要求执行完整检查,不能把单个站点修订通过当作整条路线都符合要求。对于用户取消无台阶筛选的情况,也应在记录中明确保存筛选条件变化,避免后来误认为站点已经完成复核。
页面如何传达异常而不干扰阅读顺序
在手机窄屏下,可以让异常消息排在路线概要之后、具体乘车片段之前,让读屏依次了解“原候选受到影响”“影响位置”“处理方式”。之前已经定义过路线概要、出发站、换乘提示、到达站和操作按钮五个语义单元;现在只需在受影响的换乘单元前加入异常解释,不必为了展示警示而改变所有卡片的基础顺序。大字号时允许异常说明自然换行,避免把“待复核”压缩成单个没有含义的图标。
无障碍条件变化既影响可达性,也影响责任归属,因此界面不应在刷新后悄悄移除旧选择。保留候选身份可以让使用者查看何时、因哪份资料更新而暂停推荐,但不能让历史选择继续触发“开始导航”操作。操作按钮宜根据 available、needs_review、blocked 三种业务状态分别呈现,受影响时优先提供“查看差异”和“重新筛选”。如果当前线路图没有可替代的路线,就应清楚显示空结果,而不是生成没有来源的站点或额外线路。

存储、并发与后续维护
应用层状态适合拆成已生效快照、待复核变更、用户选择和页面焦点四份数据。页面出现时可以通过稳定键把它们关联起来;退出时只保存必要的业务状态,不要保存整个组件树。对小规模偏好值,HarmonyOS的Preferences提供键值持久化与flush接口,但它面向应用配置类数据,不宜代替多记录的事务型数据库。若使用Preferences保存选择键或最近一次有效版本,应在成功写入后明确调用持久化,并注意并发访问和多进程约束。
华为开发者的List文档说明,长列表可以通过懒加载降低一次性创建的开销;列表项在数据变更后还要维护稳定键,避免筛选时把某个站点的展开状态错配到另一项。焦点控制文档提供了可聚焦和主动请求焦点的属性,但调用时机仍要由应用决定:新列表完成数据更新后,再对确定存在的目标请求焦点,比在异步回调刚开始时强行跳转更可靠。异常提示也应记录具体来源版本,便于下一篇处理应用退出、重新进入及资源回收时还原一致的显示状态。
相关技术资料:华为开发者创建列表(List)、焦点控制、Preferences键值持久化。
路线异常处理的重点不是增加多少警示样式,而是让资料版本、候选键和可行性判断始终指向同一份数据。只要能够保留变更来源、正确阻止受影响候选,并在新快照形成后重新执行完整筛选,路线恢复就可以成为可追踪、可维护的工程流程,而不会依赖页面刷新时碰巧出现的结果。
更多推荐

所有评论(0)