同一份现场巡检记录在手机和大屏设备上使用时,最容易出现两类相互独立的问题。其一是窗口宽度变化后,输入项被重复创建或次序错乱,使页面把已有值写到错误的字段中。其二是草稿长期积累后,开发者试图按时间直接删除旧记录,结果把还没有确认保存的内容一起清掉。FormContinuity的收尾工作,需要同时解决布局变化与数据生命周期,但必须让两者保持清晰边界。

继续沿用前一章已经合并并保存的site-check:schema=1、revision=4、savedRevision=4。三个字段分别为site=北区巡检点B、issue=设备标签磨损,需补贴并拍照、priority=高。字段键及内容在窗口调整前后保持一致;布局只决定它们在屏幕上的位置,不改变编辑记录的身份和版本。清理策略则以草稿状态、保留期限及冲突处理结果为依据,不使用屏幕宽度作为删除条件。

HarmonyOS 表单自适应布局与草稿清理流程

一、业务字段顺序与布局列数分离

表单模型应维护一份固定的字段顺序表。手机宽度较小时,可以采用单列,把地点、问题摘要、优先级依次垂直排列;在空间充足的窗口里,可以让地点和优先级占较窄的列,将问题摘要保留更大的编辑区域。即使字段分布到两列,最终保存仍然按稳定主键重建对象,而不是把“第一列第二项”当成业务键。

这里把430vp作为紧凑窗口输入、840vp作为宽窗口输入,两者只用于说明同一布局策略的两种结果,不是设备型号推导。应用层可以先把宽度划成COMPACT、MEDIUM、WIDE三档,再返回列数与字段位置。不同设备发生分屏、悬停态变化或窗口缩放时,宽度都可能改变;与其给每个设备名称写一套布局,不如用实际可用宽度进行决策。

宽度阈值应集中维护,避免页面里散落多处条件判断。阈值是当前表单所需的设计约束,需要结合标签长度、输入区域最小宽度和文字缩放情况调整。下面的布局计算返回纯数据映射,用于驱动ArkUI渲染;其中issue无论在哪种布局中都保持同一个键。

const FIELD_ORDER = Object.freeze(['site', 'issue', 'priority']);
const SAVED_DRAFT = Object.freeze({
  draftId: 'site-check', schema: 1, revision: 4, savedRevision: 4,
  fields: {
    site: '北区巡检点B',
    issue: '设备标签磨损,需补贴并拍照',
    priority: '高'
  }
});
function layoutFor(widthVp) {
  if (!Number.isFinite(widthVp) || widthVp <= 0) {
    throw new Error('WINDOW_WIDTH_INVALID');
  }
  const mode = widthVp < 600 ? 'COMPACT' :
    widthVp < 840 ? 'MEDIUM' : 'WIDE';
  const columns = mode === 'COMPACT' ? 1 : 2;
  const placements = FIELD_ORDER.map((key, index) => ({
    key, column: columns === 1 ? 0 : index % columns,
    row: columns === 1 ? index : Math.floor(index / columns)
  }));
  return { mode, columns, placements };
}
console.assert(layoutFor(430).columns === 1);
console.assert(layoutFor(840).columns === 2);

二、让组件适配窗口,而非修改存储结构

HarmonyOS ArkUI的GridRow与GridCol提供栅格容器和子项布局能力。实际组件可以设置响应式列数、跨度与偏移,并根据窗口变化重新选择排布;GridCol的span是栅格占用列数,而不是业务表单字段数量。华为开发文档的GridCol组件参考及跨设备栅格规则说明了这些配置方式。

无论使用栅格还是自定义响应式容器,页面组件应通过key读取和更新草稿字段。切换列数时仅重建布局视图,不应重新分配draftId,也不能把保存修订号重置为0。文本框的输入过程还有焦点、光标及输入法状态,需要根据实际交互决定是否暂存;这些临时属性不应写入业务快照。用户在宽窗口编辑摘要,缩窄窗口后仍应该看到相同文本,待保存状态也不应凭空发生变化。

HarmonyOS 多尺寸表单开发环境与布局规则

三、草稿状态不能只用更新时间判断

草稿清理要区分EDITING、DIRTY、SAVED、CONFLICT_REVIEW和ARCHIVED。仍在编辑、存在未落盘修改或冲突未解决的记录,都不应被自动清理。已保存的普通草稿也不应刚过一天就删除;它可能是人员下周继续填写的巡检记录。对于业务侧已经提交并确认归档的记录,可以依据约定保留期,在清理候选中登记其到期时间。

本章把保留窗口设置为30天,使用显式的retentionUntil数值做判断。清理资格仍必须满足状态与修订号条件:已经归档、没有未处理冲突、当前修订号等于已保存修订号、归档记录已获得确认,而且超过保留期。只要有任一项不满足,就保留原数据并返回具体原因。这种清理策略可以放在存储层附近实现,避免由页面销毁事件直接执行删除。

const DAY_MS = 24 * 60 * 60 * 1000;
function cleanupEligibility(draft, nowMs) {
  if (!Number.isFinite(nowMs)) throw new Error('TIME_INVALID');
  if (draft.status !== 'ARCHIVED') return 'KEEP_NOT_ARCHIVED';
  if (draft.revision !== draft.savedRevision) return 'KEEP_DIRTY';
  if (draft.pendingConflicts > 0) return 'KEEP_CONFLICT';
  if (draft.archiveConfirmed !== true) return 'KEEP_UNCONFIRMED';
  if (!Number.isFinite(draft.archivedAt)) return 'KEEP_NO_ARCHIVE_TIME';
  if (nowMs < draft.archivedAt + 30 * DAY_MS) return 'KEEP_RETENTION';
  return 'CLEANUP_ELIGIBLE';
}
const draftAtArchive = {
  ...SAVED_DRAFT, status: 'ARCHIVED', pendingConflicts: 0,
  archiveConfirmed: true, archivedAt: Date.UTC(2026, 9, 30)
};
console.assert(cleanupEligibility(draftAtArchive,
  Date.UTC(2026, 9, 31)) === 'KEEP_RETENTION');

如果记录处于CONFLICT_REVIEW,哪怕更新时间很早也应保留两个候选分支和用户选择信息。已经归档但仍保留本地未保存修改的异常情况,也要优先保留内容并提示状态不一致。清理工作可以记录记录标识、原因代码和执行时间,但不要把巡检问题摘要写入审计日志。对重复清理的处理应是幂等的:不存在的键不构成新的错误,真正的持久化失败则需保留重试线索。

四、归档确认与实际清理分成两个阶段

“提交成功”“归档成功”“已经删除本地草稿”是三种不同状态。提交可能只代表业务系统接收了数据;归档需要保存可追溯的版本标识;删除本地草稿还需要满足保留期。把这三步放进单个按钮事件,容易在网络状态变化时得到半完成的记录。更稳妥的处理方式是先把revision=4的内容转换为不可继续编辑的归档记录,确认其写入完成,再单独执行到期清理。

在纯数据层,可以把“选择待删除键”和“执行删除”拆开。清理函数先筛选符合条件的记录,生成候选清单;随后存储适配层按清单逐项删除并记录确认。删除失败不应该在页面上直接把草稿标为已清理。对Preferences类型的本地存储,华为官方User Preferences参考提供deleteSync以及flush等操作,实际删除确认应遵循所用存储模式的持久化机制。

function planCleanup(records, nowMs) {
  const remove = [];
  const keep = [];
  for (const d of records) {
    const reason = cleanupEligibility(d, nowMs);
    const item = { draftId: d.draftId, revision: d.revision, reason };
    (reason === 'CLEANUP_ELIGIBLE' ? remove : keep).push(item);
  }
  return { remove, keep };
}
const scheduled = planCleanup([
  draftAtArchive,
  { ...SAVED_DRAFT, status: 'DIRTY', pendingConflicts: 0,
    archiveConfirmed: false, archivedAt: 0 }
], Date.UTC(2026, 11, 2));
console.assert(scheduled.remove.length === 1);
console.assert(scheduled.keep[0].reason === 'KEEP_NOT_ARCHIVED');

HarmonyOS 表单归档和清理状态界面

五、把多尺寸布局与清理过程纳入回归核对

布局检查应从数据不变式出发,而非只核对按钮是否排列整齐。针对430vp和840vp分别计算列数,然后确保返回的字段键集合都严格等于site、issue、priority,没有重复也没有缺项。将同一份修订号4快照传给两套布局,输出的草稿值和保存状态必须一致。进一步改变窗口宽度,检查未提交的输入仍保留在模型层,而非被组件重建覆盖。

清理检查则至少包括:刚归档未过期、归档到期、有待处理冲突、保存未确认、存在脏数据,以及同一草稿重复清理。针对cleanupEligibility返回值执行表驱动断言,可在改动保留周期后快速发现误删条件。若业务需要按部门设置不同期限,也应把保留规则绑定到明确的策略版本,避免客户端单方面改变历史记录的清理结果。

对于现场巡检,清理更要区分草稿与已提交的正式业务记录。本章处理的是轻量草稿的本地生命周期,不改变正式巡检台账的保存制度。归档摘要保留必要的标识、版本与确认信息;个人信息和现场数据的保留期限应按实际业务制度制定。状态收敛后,界面可以将当前草稿标为ARCHIVED,但只有存储删除完成后才显示该本地草稿已清理。

六、四篇连载形成一个完整的数据闭环

FormContinuity从稳定字段身份出发,逐步引入修订号、页面切换保存、三方冲突合并和布局适配。到本章为止,site-check在两种窗口尺寸里保持同一份schema=1数据,经过第03篇的字段选择形成revision=4已保存快照,随后通过归档确认与到期规则判断清理资格。组件布局、字段编辑、保存队列、跨端冲突和清理流程各有独立职责,下一次增加新字段时只需要在模型和迁移逻辑中扩展,而不必重新推翻页面与存储层。

Logo

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

更多推荐