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

保存能够解释版面的输入
快照应同时表达内容、设计和判断规则。内容包括模板身份、字段与组合原文;设计包括角色字号、行框、间距和内容框;判断规则包括码点策略、实际字体方案及字体环境版本。它们共同决定当前标签如何布局,缺少任何一组,后续恢复都可能采用新的默认值。
标准标签仍是“春日花草茶”“净含量 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
}
恢复时还需要判断字体资源身份是否仍可对应当前环境。同名字体可能已经替换内容,单靠名称不足以恢复旧字形关系;环境版本应连接到应用资源管理记录。缓存条目不随快照持久保存,恢复后在当前界面上下文重新测量,再给出当前容纳结论。
归档结构还需要避免把显示名称误当作模板身份。名称可能因产品文案调整而改变,身份应保持稳定;如果业务确实创建了新模板,则应生成新的身份并保留旧快照。模板在列表中的先后顺序不影响单份归档内容,导出选择始终落在明确模板上,不依赖当前列表下标作为长期标识。
字体环境版本也不能单独承担全部资源管理。它表达当前页面何时需要重新测量,真正长期归档时,还应由资源层把该版本关联到已管理的字体资源身份。恢复旧材料时,如果资源已更新,应按版本迁移策略决定采用旧资源还是重新检查,而不是直接沿用旧的成功提示。快照中的规则身份同样需要有可追溯的实现约定。
数据复制与数据校验是两道不同步骤。复制可以让候选脱离页面对象,却不能让缺失字段变成有效内容;重新检查可以发现字符和版面问题,却不能代替结构版本的读取检查。当前流程先建独立候选、再检查候选、最后序列化,避免检查页面原文后又导出另一份被修改的对象。
快照文本的阅读格式可以变化,字段语义应保持稳定。归档系统若压缩空白或调整属性排列,读取结果仍应一致;如果需要防止内容被改动,应由业务归档协议对明确的字节或规范化数据计算摘要,并管理相应记录,不能把修订号当成内容摘要。修订号服务于当前编辑会话,两者用途不同。
检查快照复制时,可以在捕获后修改原模板,确认候选字段和角色行不跟随变化。检查提交时,分别覆盖开始修订不一致与结束修订不一致;空字段、过长文字和测量异常都应阻止输出。结构版本、规则身份和设计参数应同时进入回归用例,避免只检查标题文本。
八篇工程链路至此从原文归一化、字符规则、自然尺寸、区域布局、标题层级、字体缓存和批量定位,收束为一份可以解释和重建的版面快照。归档保留明确输入,当前检查保留当前上下文,两者的职责也随之清晰。
更多推荐

所有评论(0)