HarmonyOS摄影选片连载07:交付清单冻结与批注一致性核查【共创稿事节】
摄影棚交付不只是将几张满意的照片列成清单。照片是否已经具备可长期使用的应用内副本、批注仍指向哪一个原图坐标、意见是否处理完成、当前版本和交付时看到的版本是否一致,都会影响接收方复查。前几篇围绕P001~P003建立了相册选片会话,P001白花原图为4000×3000,已经记录从(600,450)到(1800,1200)的矩形和三个原图像素点;接着把构图、色彩、细节意见与这些稳定标记关联,并以意见修订号处理文本与状态变化。
这一阶段要回答新的问题:当摄影师选择“准备交付”时,系统怎样判断哪些素材可以进入清单;如果P002缺少本地原图副本,P003还有待复核意见,是否应该阻止整个会话生成可用清单;接收方日后打开清单,又应凭什么确认其中的批注和原图属于同一个版本。解决思路是逐资产核查、给出具体阻塞原因、冻结合格资产的版本快照。冻结清单是一份可复核的交付意图,不改变系统图库原件,也不擅自把待处理意见改为完成。

清单与活动工作区不能共用同一份可变对象
选片会话中的PhotoEntry表示当前可继续编辑的资产,Annotation表示原图像素空间内的点和矩形,ReviewNote表示面向目标标记的处理意见。把它们当前的数组引用直接放进交付清单,后面只要移动一个点或把意见重新打开,过去那份交付记录就会随之变化。交付时应复制必要字段形成不可变快照,记录源资产修订、几何批注修订、意见修订及生成时刻;以后要修订清单,应明确产生新版本,而不是在旧快照上静默覆盖。
目录在前面的model/PhotoEntry.ets和model/ReviewNote.ets之外增加model/DeliveryAsset.ets、service/DeliveryValidator.ets、service/FreezeManifestService.ets和pages/DeliveryCheckPage.ets。交付检查仅在已选择的P001~P003范围内进行,不重新扫描设备媒体库。每张资产都有稳定assetId;工作副本存储在应用空间,交付记录引用经过内部规范化的副本标识,而不记录暴露原始相册URI的外部页面地址。
export interface DeliveryAsset {
assetId: string;
displayName: string;
copyKey: string;
copyReady: boolean;
imageWidth: number;
imageHeight: number;
sourceRevision: number;
geometryRevision: number;
noteRevision: number;
}
export interface DeliveryMark {
assetId: string;
markId: string;
kind: 'POINT' | 'RECT';
x: number;
y: number;
width: number;
height: number;
}
export interface DeliveryNote {
noteId: string;
assetId: string;
targetId: string;
state: 'OPEN' | 'IN_PROGRESS' | 'RESOLVED';
noteRevision: number;
}
这里仍使用第六篇定义的稳定标记身份:point-1、point-2和region-main。一个坐标点已经移到新的位置,意见依然引用同一个targetId,不应把坐标字符串当作关联键。检查不需要读取完整原图像素或分析花卉内容;它关注的是副本可用性、像素坐标范围、目标引用和意见状态。这些条件足以覆盖最常见的“清单看起来齐全,打开后找不到批注”的交付缺陷。
每张资产应给出可解释的阻塞原因
P001的本地工作副本已经建立,三个点与一个矩形都有确定的原图坐标,当前意见在人工整理后可以进入交付候选;P002若仍停留在选择器URI阶段、尚未写入工作副本,就需要提示“原图副本待补”;P003若还有意见处于OPEN或IN_PROGRESS,则提示“意见待处理”。这三个状态是清单候选数据中的不同业务情形,不应简单用一个“无效图片”标签概括。
核查函数把错误收集成数组,让页面能够逐项展示。对矩形检查宽高均大于零并落在原图范围,对点检查坐标在边界内;如果有意见引用已删除的标记,就把该意见列为待修复对象。资产修订号必须为正数,避免把尚未建档的对象混进已冻结列表。为保证文档可重复检查,验证函数只读取参数并返回结果,不在其中执行文件写入或弹窗操作。
export interface DeliveryCheck {
assetId: string;
ready: boolean;
reasons: string[];
}
export function checkDelivery(
asset: DeliveryAsset, marks: DeliveryMark[], notes: DeliveryNote[]
): DeliveryCheck {
const reasons: string[] = [];
if (!asset.copyReady || !asset.copyKey) {
reasons.push('原图副本待补');
}
if (asset.imageWidth <= 0 || asset.imageHeight <= 0 ||
asset.sourceRevision < 1) {
reasons.push('原图元信息待核查');
}
const ownMarks = marks.filter((m: DeliveryMark) => m.assetId === asset.assetId);
for (const m of ownMarks) {
const valid = m.x >= 0 && m.y >= 0 &&
m.x + m.width <= asset.imageWidth &&
m.y + m.height <= asset.imageHeight &&
(m.kind === 'POINT' || (m.width > 0 && m.height > 0));
if (!valid) { reasons.push(`标记${m.markId}越界`); }
}
const markIds = ownMarks.map((m: DeliveryMark) => m.markId);
for (const note of notes.filter((n: DeliveryNote) => n.assetId === asset.assetId)) {
if (!markIds.includes(note.targetId)) {
reasons.push(`意见${note.noteId}找不到目标`);
}
if (note.state !== 'RESOLVED') {
reasons.push(`意见${note.noteId}待处理`);
}
}
return { assetId: asset.assetId, ready: reasons.length === 0, reasons };
}
对于POINT类型,宽高按零保存,此时x、y须满足原图范围;若项目约定坐标可以取到图像最右侧边界,应进一步按所选像素采样规则决定是否允许等于图像宽高。矩形的拖拽方向在第三篇已经归一化,核查阶段不重复猜测两个角点的先后关系。数据格式升级时,应先迁移旧版字段再核查,否则可能把旧数据缺少的新字段误判成用户尚未完成的编辑。
冻结时复制业务版本,拒绝混合快照
通过核查后,还不能直接把几张不同时间加载的记录拼成清单。假设在核查P001后,摄影师又将note-102重新打开;若冻结动作读取的还是此前的已解决状态,就会得到一个自相矛盾的交付包。清单服务应该取得一次明确的源快照,比较资产、几何和意见的版本三元组,校验通过后复制这些字段并分配新的清单版本。涉及共享后台时,最好由后台用版本条件检查和提交,客户端仅展示确认的快照结果。
冻结记录可包含资产显示序号,但序号只控制清单排版,不替代assetId。每次生成的条目按照会话中的既定显示顺序排列;若用户重新排序,应产生新清单修订,不要改变已经冻结的版本。下面的业务函数不依赖系统接口,对输入数组采用拷贝和排序,保证以后展开旧清单仍能按同一顺序找到对应资产。
export interface FrozenLine {
assetId: string;
order: number;
copyKey: string;
sourceRevision: number;
geometryRevision: number;
noteRevision: number;
}
export interface DeliveryManifest {
manifestId: string;
revision: number;
createdAt: number;
lines: FrozenLine[];
}
export function buildManifest(
manifestId: string, revision: number, assets: DeliveryAsset[],
checks: DeliveryCheck[], now: number
): DeliveryManifest {
const readyIds = checks.filter((c: DeliveryCheck) => c.ready)
.map((c: DeliveryCheck) => c.assetId);
const lines: FrozenLine[] = assets
.filter((a: DeliveryAsset) => readyIds.includes(a.assetId))
.map((a: DeliveryAsset, index: number): FrozenLine => ({
assetId: a.assetId, order: index + 1, copyKey: a.copyKey,
sourceRevision: a.sourceRevision,
geometryRevision: a.geometryRevision,
noteRevision: a.noteRevision
}));
return { manifestId, revision, createdAt: now, lines };
}
buildManifest()负责组装已经核查通过的候选行;实际持久化前还需重新确认数据版本未发生改变,否则给出“资产在核查后更新”的明确提示。清单只有P001时可以只冻结P001,并把P002与P003列入待补清单;也可以由业务规则要求整套会话都合格后才能整体冻结,两种策略都需要显示具体结果。这里选择“允许合格资产先冻结”,避免另外两张素材迟迟未完成时阻断已经整理好的P001。

清单页面保持当前选择与检查状态分离
界面需要同时展示“当前选中的候选资产”和“经核查可交付的资产”。它们并不相等:P002可以继续处于选中状态,却因为原图副本缺失而暂不能冻结。用户点击阻塞原因,应进入对应的原图副本页或意见页;返回后重新读取相关源修订,再刷新该行的核查结论。列表键使用assetId,不用当前索引,避免照片重新排序后提示被贴到另一张花卉缩略图上。
@Entry
@Component
struct DeliveryCheckPage {
@State private checks: DeliveryCheck[] = [];
@State private busy: boolean = false;
@State private selectedAssetId: string = '';
@State private warning: string = '';
private service: DeliveryService = createDeliveryService();
aboutToAppear(): void { this.refresh(); }
private async refresh(): Promise<void> {
if (this.busy) { return; }
this.busy = true;
try {
this.checks = await this.service.checkCurrentSession();
this.warning = '';
} catch (error) {
this.warning = '清单核查受阻,请重新获取';
} finally {
this.busy = false;
}
}
build() {
Column({ space: 12 }) {
Text('摄影棚 · 交付核查').fontSize(23)
Text(`可交付 ${this.checks.filter((c: DeliveryCheck) => c.ready).length} / ${this.checks.length}`)
if (this.warning) { Text(this.warning).fontColor(Color.Orange) }
List() {
ForEach(this.checks, (item: DeliveryCheck) => {
ListItem() {
Column({ space: 4 }) {
Text(item.assetId).fontSize(18)
Text(item.ready ? '可交付' : item.reasons.join('、'))
}.padding(12).width('100%')
}.onClick(() => { this.selectedAssetId = item.assetId; })
}, (item: DeliveryCheck) => item.assetId)
}.layoutWeight(1)
Button('重新核查').enabled(!this.busy)
.onClick(() => this.refresh())
}.height('100%').padding(16)
}
}
DeliveryService和createDeliveryService()是项目内的服务抽象,分别读取选片会话、工作副本和批注模型,再调用前面的核查函数。该页面用于交付前检查,真正的“冻结”按钮可以在确认页上附带本次可交付资产数量及各自版本,防止人员误以为三个素材都已经进入清单。对于尺寸不足、目标缺失或意见重新打开的资产,提供跳转修复入口比一个不可操作的错误提示更有价值。
轻量索引的恢复与交付检查路径
清单正文适合由独立业务存储维护,不宜直接把几张大图转换成字符串放进Preferences。Preferences可以保存最近打开的manifestId、筛选条件与显示顺序这些小型界面线索;在同一应用进程里按键读取,写入后等待flush()完成。交付清单的真实内容和图像文件仍由应用沙箱或业务仓储服务保存,恢复页面时先核对版本,再显示上次选中的资产。后续生成交换文件时,也应以已冻结版本为输入,而不是重新从活动编辑数组临时拼接。
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
export async function saveDeliveryView(
context: common.UIAbilityContext, manifestId: string,
selectedAssetId: string
): Promise<void> {
const store = await preferences.getPreferences(context,
{ name: 'photo_delivery_view' });
await store.put('last_manifest', manifestId);
await store.put('last_asset', selectedAssetId);
await store.flush();
}
调试可构造三个固定业务状态:P001原图副本和批注目标完整、意见已整理;P002缺少工作副本;P003有一条待处理意见。逐项检查返回原因是否可解释;把P001的一个点移动到图外,观察是否被检测;删除region-main但保留引用它的意见,检查关联失败;冻结前修改P001的意见修订号,观察版本比较能否拒绝旧快照;调整显示顺序后,检查既有清单的assetId及版本是否保持原样。HiLog可记录资产数量、通过数量、阻塞原因类别和清单修订号,避免写出原图外部URI和完整批注意见文本。

摄影素材交付的可靠性来自清楚区分“已选中”“已经核查”“可以冻结”和“已经交付”四个阶段。原图副本、批注位置、意见状态与源修订保持各自身份,才可以在以后查看交付清单时找到一致的内容。P001可以形成独立的合格候选,P002与P003则保留明确的补齐入口。下一阶段能够在这份冻结清单之上设计导出格式与交付归档,不必再临时猜测哪些批注属于哪个照片版本。
参考资料:ArkUI列表创建、Preferences接口、ArkTS语言概述。
更多推荐



所有评论(0)