标签完成检查以后,如果只留下最终画面,后续很难回答它采用了哪份原文、哪种字符规则以及哪套字体方案。另一种常见问题是导出直接引用页面对象:用户切换模板后,已经准备归档的数据跟着改变。沿着上一章的批量模板,本篇建立独立的版面快照,导出前重新检查,并把规则身份和设计参数一起保留下来。

版面快照与规则归档

保存能够解释版面的输入

快照应同时表达内容、设计和判断规则。内容包括模板身份、字段与组合原文;设计包括角色字号、行框、间距和内容框;判断规则包括码点策略、实际字体方案及字体环境版本。它们共同决定当前标签如何布局,缺少任何一组,后续恢复都可能采用新的默认值。

标准标签仍是“春日花草茶”“净含量 50g”“冲泡建议:85℃温水”。普通标题字号三十二、行框四十四,说明字号二十二、行框三十,行间距六,内容框二百八十宽、一百二十高。三行的行框占用一百一十六,是设计参数的求和结果;自然字宽仍由当前字体测量,快照不把字符数量换算成字宽。

快照字段组保存内容恢复时的用途
模板与原文身份、名称、三个字段、组合文本对应原始业务输入
版面逐行字号与行框、间距、内容框重建设计关系
字体实际字体族、环境版本明确测量上下文
规则码点策略、结构版本选择正确的读取与检查逻辑
修订当前输入修订号阻止过期结果提交

结构版本与规则版本各有职责。结构版本回答有哪些字段、字段如何解释;规则版本回答字符数量按什么策略计算。不能把两者合成一个含义模糊的版本号,也不能因为结构没变,就在恢复时静默切换字符规则。当前仍采用第二篇的码点规则和普通标题方案。

新增 model/LabelArchive.ets,使用明确类型复制模板字段并重新生成角色行。所有嵌套对象都由当前调用重新建立,不把页面数组原样交给归档结果。原文、角色行和模板字段虽然存在信息重叠,但有助于读取端核对派生关系,避免模板字段被改动后仍携带旧原文。

// model/LabelArchive.ets
import { LabelTemplate, renderLabel, validateTemplates } from './BatchLabel';
import { RoleRow, makeRows } from './Typography';

export interface LayoutArchive {
  schema: string; rule: string; revision: number;
  fontFamily: string; fontEpoch: number;
  template: LabelTemplate; raw: string; rows: RoleRow[];
  contentWidthVp: number; contentHeightVp: number; gapVp: number;
}

export function captureLayout(input: LabelTemplate, revision: number,
  fontFamily: string, fontEpoch: number): LayoutArchive {
  validateTemplates([input]);
  if (!Number.isSafeInteger(revision) || revision < 0 ||
    !Number.isSafeInteger(fontEpoch) || fontEpoch < 0 || fontFamily.trim().length === 0) {
    throw new Error('快照环境无效');
  }
  const template: LabelTemplate = {
    id: input.id, name: input.name, title: input.title, net: input.net, brew: input.brew
  };
  const raw = renderLabel(template);
  return {
    schema: 'label-layout-v1', rule: 'codepoint-v2', revision: revision,
    fontFamily: fontFamily, fontEpoch: fontEpoch,
    template: template, raw: raw, rows: makeRows(raw, false),
    contentWidthVp: 280, contentHeightVp: 120, gapVp: 6
  };
}

这里采用逐字段复制,既保留空字段,也明确快照的白名单。页面状态、缓存对象、界面上下文和事件回调都不进入结果,它们与当前进程或组件生命周期相关,不适合作为可交换的数据。快照保留字体方案身份,而字体资源内容的管理仍应遵循资源层的版本约定。

以候选快照重新检查

生成过程从独立候选开始,然后对候选中的模板运行上一章检查,使用候选保存的字体方案和内容宽度。字符问题或版面问题都阻止生成可归档文本。礼盒样本的冲泡字段为空,因此应先补齐第三行,再重新检查;不能因为标准标签通过,就把整个模板集合都标记为可导出。

修订号在开始和结束各核对一次。当前检查是同步过程,仍保留这个提交约束,后续替换成异步测量时也能沿用。字体资源版本、模板内容或版面方案改变,都应推进同一导出输入修订;只改变当前选中项而不清除旧输出,会让用户误把另一份模板的快照带走。

// service/LabelExport.ets
import { JSON } from '@kit.ArkTS';
import { UIContext } from '@kit.ArkUI';
import { LabelTemplate } from '../model/BatchLabel';
import { captureLayout } from '../model/LabelArchive';
import { FontCache } from '../model/FontCache';
import { checkTemplates } from './BatchCheck';

export function exportLayout(context: UIContext, cache: FontCache,
  input: LabelTemplate, revision: number, family: string, epoch: number,
  readRevision: () => number): string {
  const candidate = captureLayout(input, revision, family, epoch);
  if (candidate.revision !== readRevision()) { throw new Error('输入已变化'); }
  const issues = checkTemplates(context, cache, [candidate.template],
    candidate.fontFamily, candidate.fontEpoch, candidate.contentWidthVp);
  if (issues.length > 0) { throw new Error('请先处理当前模板问题'); }
  const text = JSON.stringify(candidate, null, 2);
  if (candidate.revision !== readRevision()) { throw new Error('输入已变化'); }
  return text;
}

华为对象属性序列化说明给出了结构化对象转为文本的接口形态。本文使用明确的快照对象,缩进只影响阅读格式,不改变业务含义。需要比较数据时应比较字段,而不是依赖空格、换行或属性展示顺序。

工程继续采用二十版本及以上开发套件。测量流程沿用华为文字测量示例中的界面上下文及单位换算方式;文本序列化和本地界面操作无需增加权限。导出函数是应用层编排,缓存复用只影响测量调用,不改变候选数据。

快照结构与导出开发预览

输出独立文本并保持失效状态

页面提供标准标签与礼盒标签切换。切换会推进修订、清空旧快照并回到待生成;生成失败也清空当前输出,防止旧文本继续被复制。原文区域完整展示字段,不采用标签内容框的裁切规则。快照文本支持长按选择复制,可送入既有业务归档入口。

// pages/ArchivePanel.ets
import { TEMPLATES, renderLabel } from '../model/BatchLabel';
import { FontCache } from '../model/FontCache';
import { exportLayout } from '../service/LabelExport';

@Component
export struct ArchivePanel {
  @State selected: number = 0;
  @State output: string = '';
  @State status: string = '待生成';
  private revision: number = 0;
  private cache: FontCache = new FontCache();

  choose(index: number): void {
    this.selected = index;
    this.revision++;
    this.output = '';
    this.status = '待生成';
  }
  generate(): void {
    this.output = '';
    try {
      const text = exportLayout(this.getUIContext(), this.cache,
        TEMPLATES[this.selected], this.revision, 'sans-serif', 0, () => this.revision);
      this.output = text;
      this.status = '快照已生成';
    } catch { this.status = '生成失败,请检查当前模板'; }
  }
  build() {
    Column({ space: 16 }) {
      Text('标签版面快照').fontSize('26fp')
      Row({ space: 12 }) {
        Button('标准标签').onClick(() => { this.choose(0); })
        Button('礼盒标签').onClick(() => { this.choose(1); })
      }
      Text('当前模板 ' + TEMPLATES[this.selected].name).fontSize('18fp')
      Text(renderLabel(TEMPLATES[this.selected])).fontSize('20fp').width('100%')
      Text('规则 codepoint-v2').fontSize('16fp')
      Text('字体 sans-serif').fontSize('16fp')
      Text('内容框 280 × 120vp').fontSize('16fp')
      Text(this.status).fontSize('18fp')
      Button('生成快照').onClick(() => { this.generate(); })
      Text('快照包含原文、版面与规则').fontSize('16fp')
      if (this.output.length > 0) {
        Text(this.output).fontFamily('monospace').fontSize('14fp')
          .width('100%').copyOption(CopyOptions.LocalDevice)
      }
    }.width('100%').alignItems(HorizontalAlign.Start)
  }
}

外层继续用纵向滚动区域承载面板。字体环境从第六篇的资源回退方案进入,当前就绪集合为空,所以实际字体为通用字体。接入动态字体时,生成入口应读取已确认的当前字体与环境版本,并让资源更新推进导出修订,不能只修改预览里的字体文字。

生成前的标签快照界面

归档内容需要自洽,也需要可恢复

下列对象展示标准模板的完整结构,保留的是设计输入,不包含自然字宽的数值。读取端可由模板字段重建原文,再与保存的原文比较;根据普通角色规则重建各行设计,再比较字号、行框和间距。结构不匹配时应拒绝自动恢复,交由明确的迁移流程处理。

{
  "schema": "label-layout-v1", "rule": "codepoint-v2", "revision": 0,
  "fontFamily": "sans-serif", "fontEpoch": 0,
  "template": { "id": "tea-basic", "name": "标准标签", "title": "春日花草茶", "net": "净含量 50g", "brew": "冲泡建议:85℃温水" },
  "raw": "春日花草茶\n净含量 50g\n冲泡建议:85℃温水",
  "rows": [
    { "key": 0, "text": "春日花草茶", "sizeVp": 32, "boxVp": 44 },
    { "key": 1, "text": "净含量 50g", "sizeVp": 22, "boxVp": 30 },
    { "key": 2, "text": "冲泡建议:85℃温水", "sizeVp": 22, "boxVp": 30 }
  ],
  "contentWidthVp": 280, "contentHeightVp": 120, "gapVp": 6
}

恢复时还需要判断字体资源身份是否仍可对应当前环境。同名字体可能已经替换内容,单靠名称不足以恢复旧字形关系;环境版本应连接到应用资源管理记录。缓存条目不随快照持久保存,恢复后在当前界面上下文重新测量,再给出当前容纳结论。

归档结构还需要避免把显示名称误当作模板身份。名称可能因产品文案调整而改变,身份应保持稳定;如果业务确实创建了新模板,则应生成新的身份并保留旧快照。模板在列表中的先后顺序不影响单份归档内容,导出选择始终落在明确模板上,不依赖当前列表下标作为长期标识。

字体环境版本也不能单独承担全部资源管理。它表达当前页面何时需要重新测量,真正长期归档时,还应由资源层把该版本关联到已管理的字体资源身份。恢复旧材料时,如果资源已更新,应按版本迁移策略决定采用旧资源还是重新检查,而不是直接沿用旧的成功提示。快照中的规则身份同样需要有可追溯的实现约定。

数据复制与数据校验是两道不同步骤。复制可以让候选脱离页面对象,却不能让缺失字段变成有效内容;重新检查可以发现字符和版面问题,却不能代替结构版本的读取检查。当前流程先建独立候选、再检查候选、最后序列化,避免检查页面原文后又导出另一份被修改的对象。

快照文本的阅读格式可以变化,字段语义应保持稳定。归档系统若压缩空白或调整属性排列,读取结果仍应一致;如果需要防止内容被改动,应由业务归档协议对明确的字节或规范化数据计算摘要,并管理相应记录,不能把修订号当成内容摘要。修订号服务于当前编辑会话,两者用途不同。

检查快照复制时,可以在捕获后修改原模板,确认候选字段和角色行不跟随变化。检查提交时,分别覆盖开始修订不一致与结束修订不一致;空字段、过长文字和测量异常都应阻止输出。结构版本、规则身份和设计参数应同时进入回归用例,避免只检查标题文本。

八篇工程链路至此从原文归一化、字符规则、自然尺寸、区域布局、标题层级、字体缓存和批量定位,收束为一份可以解释和重建的版面快照。归档保留明确输入,当前检查保留当前上下文,两者的职责也随之清晰。

Logo

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

更多推荐