案例ProofCarousel / EvidencePage / 任务PC-1808。本文的数据、日志、DevEco 界面与手机屏幕均为独立构造的技术示意不是实际编译、识别或真机测试结果。示例展示的是轮播状态治理不宣称设备内建 AI 证据能力。一、证据少了一张当前页不能跟着换人内容审核工作台很容易出现这样的小矛盾服务端返回四份候选证据前端准备轮播展示但其中一份图片地址失效。直接把四个条目都塞进 Swiper第二张可能变成空白若先按下标选中第三张再把失效项删掉原来的第三张又会变成第二张。用户刚刚打开的内容忽然换了问题既不像网络故障也不像控件崩溃定位起来格外麻烦。我把这个问题单独抽成 ProofCarousel。它并不连接真实 AI 模型只处理一份受控示例清单。任务号固定为PC-1808原始记录为 E-01、E-02、E-03、E-04只有 E-02 标记为 failed其余三条标记为 ready。初始选中的是 E-03。筛掉 E-02 后界面应该显示 E-03 与2 / 3而不是显示「第三张」这样脱离数据的身份。工程上要承认一个事实业务记录 ID 是身份Swiper 的 index 只是当时容器中的位置。这里还有另一层边界。所谓 failed 在 Demo 中只是静态业务标记不等于系统图片组件加载回调已经证明资源不可用。正式接入时图片解码失败、下载超时和内容审核失败应拆成不同原因必须由上游给出可复核的错误分类。本文刻意不借一个漂亮的截图宣称完成了链路实测。封面表现的是「证据变少但选中对象不漂移」的关系左侧留给工程问题右侧卡片强调内容之间存在固定身份。后面的实现都围绕一个判断展开——保留的是 E-03而不是数字 2。二、把可展示清单变成唯一数据入口先不要急着调整 Swiper 属性。当前实现把原始清单与展示清单明确分离source 保存全部来源记录visible 保存通过条件的记录activeId 保存当前业务身份。列表索引只在渲染前计算。这样能把「失败项被过滤」与「控件下标变化」拆开测试也能避免把一张失败证据当成占位页参与页数统计。下面这段纯函数只解决清单过滤与索引归位不知道 ArkUI也不管理网络或图片资源。状态仅取 ready、failed 两类是为了保持案例足够集中。调用方传入 E-03 时会得到过滤后的索引 1如果身份已消失回退到索引 0如果没有可用卡片返回 -1交由 UI 显示空态。interfaceEvidenceItem{id:string;title:string;status:string;}functionusableCards(source:EvidenceItem[]):EvidenceItem[]{constunique:SetstringnewSetstring();returnsource.filter((item:EvidenceItem){if(item.status!ready||unique.has(item.id))returnfalse;unique.add(item.id);returntrue;});}functionlocateCard(cards:EvidenceItem[],wantedId:string):number{if(cards.length0)return-1;constatcards.findIndex((item:EvidenceItem)item.idwantedId);returnat0?at:0;}这里刻意加了一个 ID 去重门禁。生产环境若出现同一证据被两个批次重复推送ForEach 使用相同 key 会让渲染身份变得不可靠。去重时保留第一条只是 Demo 的业务约定涉及真实审核时还应根据版本号选择可信来源。Set 在函数返回后自然释放不需要开发者显式 dispose但如果上游持有大尺寸图片缓冲过滤掉卡片并不意味着相关底层资源一定已释放。三、Swiper 负责位置记录 ID 负责连续性接下来才是页面层。官方 Swiper 文档明确支持 index、onChange、loop 等能力并说明越界 index 会采用默认位置。它不会替业务辨别 E-03 在新清单中的位置。因此在 EvidencePage 中我让 onChange 回写 activeId展示数量根据 visibleCards 计算不把 onChange 的序号保存成长期业务标识。EntryComponentstruct EvidencePage{Stateprivatesource:EvidenceItem[][{id:E-01,title:构图参考,status:ready},{id:E-02,title:过期缩略图,status:failed},{id:E-03,title:主体细节,status:ready},{id:E-04,title:场景补充,status:ready}];StateprivateactiveId:stringE-03;StateprivatepageIndex:number1;privatevisibleCards():EvidenceItem[]{returnusableCards(this.source);}build(){Column({space:14}){Text(ProofCarousel · PC-1808).fontSize(22)Text(AI 证据展示示例数据).fontSize(16)Swiper(){ForEach(this.visibleCards(),(card:EvidenceItem){Column({space:12}){Text(card.id).fontSize(26).fontWeight(FontWeight.Bold)Text(card.title).fontSize(18)}.width(100%).height(260).justifyContent(FlexAlign.Center)},(card:EvidenceItem)card.id)}.index(this.pageIndex).loop(false).indicator(true).duration(0).onChange((index:number){constcardsthis.visibleCards();if(index0indexcards.length){this.pageIndexindex;this.activeIdcards[index].id;}})Text(${this.pageIndex1}/${this.visibleCards().length})}.padding(20)}}这段是缩减后的组件骨架使用了固定高度的文字卡片并不发起图片请求。把远端 Image、重试按钮和占位渲染接进去之前先用受控状态确保核心路径清楚。这里将切换时长设为零专门回避演示时「边播动画边改子项」的干扰产品若启用动画应等切换稳定后再应用清单变更。不要从本段示例推导出 Swiper 可以在任意过渡帧安全替换数据源。ForEach的 key 使用 E-03 这样的稳定 ID而不是 index。这样 E-02 被移除后E-03 虽然从原始清单下标 2 变成展示清单下标 1组件身份仍能对应业务对象。但这也不是绝对的滚动恢复保证页切换与状态更新仍由调用顺序管理不能只靠稳定 key 解掉所有竞态。DevEco 白色界面图示展示 EvidencePage.ets 的关键状态以及右侧示意模拟器的E-03 / 2 / 3。底部 HiLog 文字为人工编写的示例事件并非导出的真实日志。四、清单更新必须成组提交假设下一批可用证据只剩 E-03、E-04仍以 E-03 为中心。危险做法是先给 source 赋值然后依赖旧 pageIndex 等待 Swiper 自己恢复用户可能看到瞬时的错误计数。更稳妥的方式是先计算 nextVisible 与 nextIndex再一次性更新相关业务状态对于没有证据的空态pageIndex 保持零只是内部安全默认值屏幕不应假装存在第 1 张。// 放在 EvidencePage 组件内仅在轮播切换结束后调用。privateapplyEvidenceBatch(next:EvidenceItem[]):void{constnextVisible:EvidenceItem[]usableCards(next);constnextIndex:numberlocateCard(nextVisible,this.activeId);this.sourcenext.slice();this.pageIndexnextIndex0?0:nextIndex;this.activeIdnextIndex0?:nextVisible[nextIndex].id;}这段代码没有伪造任何后台接口next 由上层数据适配器提供。它说明的是同一份快照先算完再赋值不是多次赋值的真正数据库事务。大批量加载时还要建立请求批次号只有最新批次可以调用 applyEvidenceBatch否则一个较慢的旧请求仍可能把用户正在查看的证据重新带回来。页面离开时应取消图片下载订阅和异步回调避免在已销毁页面上更新状态。还有一个不太显眼的取舍如果用户当前看的 E-03 恰好变成 failed我们选第一张剩余可用证据而不是强行把下标钳制到末尾。原因在于业务身份已经不在了任意所谓「最近位置」都会掩盖身份变化。正式产品需要明确提示「当前证据不可展示已切换到其他证据」尤其不能让审核人员误认为原图内容被悄悄替换。五、只对准一个截图验收不扩大结论手机示意图固定采用三个 ready 条目、一个 failed 条目顶部显示项目 ProofCarousel、任务 PC-1808中间卡片 E-03 / 主体细节下方页数为2 / 3脚注说明E-02 已排除。它表达的是一个预期状态不是截图式测试报告。我们应把屏幕文字和文档中的示例值核对一致而不是靠拟真的模拟器边框冒充完成验证。如果要把这一页放到真实工程里我会先写几组自动化状态断言源数据四条且 E-02 failed 时usableCards 应返回三条activeIdE-03 应定位索引 1删除 E-01 后 activeId 仍是 E-03但索引应回到 0全部 failed 时应显示空态重复 ID 不应产生重复卡片。这些是建议的验收样例不是已执行的运行结果。六、发布前需要补齐的地方首先要决定失败判据。网络请求失败时允许重试审核规则失败时可能永久禁止展示两种情况不应共用一个粗糙的 failed。其次要控制图片缓存与页面生命周期避免已失效卡片继续持有较大的位图。再次要考虑无障碍场景页码变化应能用清晰的文本表达不能只依赖轮播圆点。最后是排序规则如果同一 ID 的多条记录来自不同来源应先定义版本选择顺序再执行过滤。工程上更值得保留的是一套小小的约束当前对象始终以 ID 识别当前位置始终在可展示快照内计算清单更新在过渡稳定后进行截图和日志只能证明示意数据一致不能证明真实加载、审核或性能结果。到这里Demo 的边界就算写清了下一步才值得接上真正的图片资源和可回溯的错误上报。七、参考与边界华为官方 Swiper APIindex、onChange、loop、duration 等https://developer.huawei.com/consumer/en/doc/harmonyos-references-V13/ts-container-swiper-V13华为 HarmonyOS 文档中心https://developer.huawei.com/consumer/cn/doc/本文代码按 ArkTS 组件示例组织未在目标 SDK 下进行编译或真机调试具体属性签名和 API 版本须以目标项目所装 SDK 为准。涉及 AI 证据的名称均为业务示例没有调用真实 AI 开放能力。