HarmonyOS摄影选片连载08:交付版本比对与清单归档【共创稿事节】
摄影棚把选好的照片交给后期人员时,麻烦往往出现在“已经发过一份”之后。第一份交付清单记录P001白花原图4000×3000,包含三个点和一个矩形区域,意见已经整理;P002紫花缺少原图副本,P003粉花仍有待复核意见,因此这两张保留在工作区。过了一段时间,P002的副本补齐,P003的意见也得到处理。若此时直接覆盖第一份交付清单,接收方看到的照片数量和批注修订便与先前版本不同,无法解释哪些信息是后来新增的。
前面的七篇已经完成系统相册选择、应用内工作副本、原图像素坐标、缩放平移逆变换、撤销重做、意见关联和逐资产交付核查。本篇为摄影选片系列收尾:从冻结快照生成独立的交付版本,比较两个版本的资产变化,形成具有可追溯修订的清单文件,再提供归档预览。设计的重点不是把当前所有照片统一打包,而是确保每次交付都有明确的资产身份、来源修订、批注集合和差异说明。

交付版本不是活动工作区的别名
活动工作区的PhotoEntry允许重新选图、调整排序和修改批注;交付清单描述的是某一刻已经满足条件的资产快照。P001的源资产ID保持P001,但一个版本中的批注修订号可能为7,另一个版本则可能为8。若把这两个版本指向同一个可变对象,后来的编辑就会改变过去的交付记录。工程继续沿用DeliveryValidator.ets和FreezeManifestService.ets,新增model/DeliveryRevision.ets、service/ManifestDiff.ets、service/ManifestWriter.ets及pages/DeliveryArchivePage.ets。
每条交付资产保存稳定assetId、本地副本的相对名称、源图宽高、图像修订号、批注修订号、意见修订号及最终意见状态。清单本身有独立的版本序号、冻结时间和来源会话标识。这里保留的是后期交付必需的元数据,原图URI不应直接显示在普通清单上;清单中的文件名也不应根据列表当前行号生成,因为重新排序可能造成接收方对错文件。
export interface DeliveryItem {
assetId: string;
copyName: string;
width: number;
height: number;
imageRevision: number;
annotationRevision: number;
noteRevision: number;
annotationIds: string[];
}
export interface DeliveryManifest {
manifestRevision: number;
sourceSession: string;
createdAt: number;
items: DeliveryItem[];
}
export function normalizeItem(item: DeliveryItem): DeliveryItem {
const ids = Array.from(new Set<string>(item.annotationIds));
ids.sort((a: string, b: string) => a.localeCompare(b));
return { ...item, annotationIds: ids };
}
冻结前需要再次检查源修订
第七篇已经把P001判定为可交付,把P002和P003列为待处理。这里不把“在上次页面中显示可交付”直接理解为“现在依然可交付”。冻结之前,应重新读取每个候选资产的副本索引与意见修订,并和用户预览时的快照逐项比较。如果某张照片的标记被删除,或者意见被重新打开,冻结动作应返回具体的版本差异,不应该用旧缓存继续生成交付文件。
同一个会话可以拥有多个交付修订,但每个修订必须有唯一的资产ID集合。确认冻结时先复制资产对象,排序批注ID,再按稳定资产ID排序。源图宽高、工作副本名称、修订号都是清单中的必需字段;有缺失的字段要指出是哪一项,而不是悄悄填零。P001的三个点与矩形应只引用明确存在的目标标记,原图像素坐标应继续保存在批注数据里,不受交付预览采用的手机或平板尺寸影响。
export function freezeDelivery(items: DeliveryItem[], previousRevision: number,
session: string, now: number): DeliveryManifest {
const keys = new Set<string>();
const frozen: DeliveryItem[] = items.map((entry: DeliveryItem) => {
if (entry.assetId.length === 0 || entry.copyName.length === 0) {
throw new Error('资产身份或副本名称缺失');
}
if (keys.has(entry.assetId)) throw new Error('重复资产:' + entry.assetId);
if (entry.width <= 0 || entry.height <= 0) throw new Error('原图尺寸异常');
keys.add(entry.assetId);
return normalizeItem(entry);
});
frozen.sort((a: DeliveryItem, b: DeliveryItem) => a.assetId.localeCompare(b.assetId));
return { manifestRevision: previousRevision + 1, sourceSession: session,
createdAt: now, items: frozen };
}
归档不是发布成功的标记,而是“这份内容已经按某个版本固定下来”。如果用户在冻结之后再次编辑P001的批注,活动工作区继续变化,但旧交付修订仍然列出原来的批注版本。新的交付需要再走一次逐资产核查,生成新的清单修订。可以允许同一资产在多个版本中出现,却不应该把旧版文件原地替换成新版。由此接收方可以明确知道自己拿到的是哪一个版本,哪些照片首次出现,以及哪些批注发生过修改。

版本差异必须基于稳定身份比较
比较两个清单时不能把数组下标当作资产身份。假设第一份清单只有P001,第二份包含P001与P002,如果后期人员把P002排到列表首位,按行号比较会误报P001被删除并新增另一张照片。正确的做法是以assetId建立字典,再分别找出新增、移除和修订变化。对于同一资产,图像修订、批注修订与意见修订独立比较;仅排序变化不构成内容改变,批注ID列表则先规范排序再进行比较。
差异结果也应区分“新增资产”和“同一资产更新”。P002补齐副本后首次进入清单,属于新增;P001的批注修订从7变成8,属于更新;P003尚未通过意见核查时不能因为出现在工作区就进入交付。对于每条更新,可以记录修订号前后变化,具体批注文本仍通过授权的详情页查看。这样既能让交接人员快速浏览变更,又避免在报告中重复散布不必要的原图路径和意见全文。
export type DeltaKind = 'ADDED' | 'REMOVED' | 'MODIFIED';
export interface DeliveryDelta { assetId: string; kind: DeltaKind; }
export function compareDelivery(oldItems: DeliveryItem[], newItems: DeliveryItem[]): DeliveryDelta[] {
const oldMap = new Map<string, DeliveryItem>();
const newMap = new Map<string, DeliveryItem>();
oldItems.forEach((item: DeliveryItem) => oldMap.set(item.assetId, item));
newItems.forEach((item: DeliveryItem) => newMap.set(item.assetId, item));
const result: DeliveryDelta[] = [];
newMap.forEach((item: DeliveryItem, id: string) => {
const before = oldMap.get(id);
if (!before) { result.push({ assetId: id, kind: 'ADDED' }); return; }
if (JSON.stringify(normalizeItem(before)) !== JSON.stringify(normalizeItem(item))) {
result.push({ assetId: id, kind: 'MODIFIED' });
}
});
oldMap.forEach((_item: DeliveryItem, id: string) => {
if (!newMap.has(id)) result.push({ assetId: id, kind: 'REMOVED' });
});
return result.sort((a: DeliveryDelta, b: DeliveryDelta) => a.assetId.localeCompare(b.assetId));
}
清单文件写入应用空间并保留版本
在工程中可以先把清单序列化为JSON文件,放在应用的filesDir下,再由独立的用户触发流程完成实际交付。JSON内容至少包含清单版本、来源会话、冻结时间和资产数组;如果原图和批注资料需要一同交付,应在创建交付集合时明确文件对应关系,不能只凭一个短文件名假定接收方拥有全部素材。清单文件应采用唯一的修订文件名;发生写入异常时,不更新界面上的“当前归档版本”,保留此前版本以供继续查看。
Core File Kit提供应用文件的打开、写入和关闭操作。为了避免旧文件尾部残留,使用新的修订文件名而不是直接覆盖旧文件;对于中途断电、空间不足等场景,还应在更高层引入临时文件与完成标记策略。下方的文件写入函数承担清单JSON本身,图片副本的整理和授权导出应是明确的后续步骤。应用沙箱路径适合保存工作版本,但接收方获取文件仍需要经过符合场景的授权和用户操作。
import { fileIo } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
export function saveManifest(context: common.UIAbilityContext,
manifest: DeliveryManifest): string {
const path = context.filesDir + '/delivery_' + manifest.manifestRevision + '.json';
const file = fileIo.openSync(path, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
try {
const content = JSON.stringify(manifest);
fileIo.writeSync(file.fd, content);
} finally {
fileIo.closeSync(file);
}
return path;
}
持久化还需要定义失败处理的先后顺序。若原图副本丢失,应先标记对应资产不可交付,再决定是否生成一个只包含其余合格资产的新版本;若JSON写入过程中报错,不能因为此前已有旧文件就把当前版本标为归档完成。若用户取消交付,已经冻结的历史修订可以继续保留,活动会话的选择与批注也无需自动清空。这样取消、失败与成功三种结果都不会改变业务上已经确认的旧档案。
ArkUI列表只负责呈现版本与差异
交付归档页面左侧可展示版本序号及冻结时间,右侧显示该版本的资产清单;窄屏页面通过单栏跳转进入详情。无论界面形态怎样变化,选中的都是清单版本加资产ID,而不是列表位置。List与ForEach使用稳定键,支持按“新增、更新、移除、无变化”筛选。若用户从版本7切到版本8,页面应先清除旧版本的悬浮详情,再用版本8的资产集合验证焦点是否存在;选择P001时可以显示其原图尺寸和批注修订,而不在日志中暴露完整图片URI。
如果某个版本只有P001,页面应明确显示“交付资产1项、待补齐2项”,不能把P002、P003误放在已交付列表里。第二次交付若P002已经通过核查,差异页面应该显示“新增P002”,而不是把工作区所有照片统一标为已交付。这样摄影师和后期人员从版本差异中能够了解进展,而不会混淆“进入会话”“完成批注”和“进入交付清单”的阶段。页面筛选偏好可另行存储,不参与冻结文件的内容计算。
用确定的数据核查归档逻辑
准备两份可比较的清单:第一份包含P001,图像修订2、批注修订7、意见修订5;第二份包含P001和已补齐的P002,P001批注修订升至8。差异算法应得出“P001更新、P002新增”,不应受到输入数组排序变化的影响。进一步只调整P001的显示顺序,检查差异是否依然为空;删除P001的一个标记,检查新的批注修订是否变化;把P003的待复核意见直接改成完成但不更新对应修订,应由冻结前的核查拒绝不一致的内容。
还要覆盖异常分支:重复assetId阻止冻结,尺寸字段缺失时指出对应资产,保存文件失败时保持旧归档的可读性,加载新版本超时后仍能返回旧版本详情。快速切换归档版本时,利用查询代次忽略晚到结果;应用转入后台时保留编辑状态但不自动发布新的交付。HiLog记录版本号、合格资产数、差异分类计数、写入阶段和异常类型即可,避免输出外部图库URI、原始批注意见与交付者的个人信息。

至此,摄影选片系列构建了从相册选片到工作副本、原图坐标批注、手势还原、意见追踪、交付核查和版本归档的连续链路。最重要的工程原则是让稳定资产身份贯穿整个过程,同时让每一次冻结都保存自己的修订信息。照片是否完成交付不由当前缩略图或列表勾选决定,而由通过核查并固定下来的版本清单决定。这样即使后期继续编辑工作区,旧清单依然能够被准确对照,新版本也能说明具体增加或更改了什么。
更多推荐


所有评论(0)