
说明本文的 ThresholdBench、任务TB-1606、文档DOC-07、示例置信度和 HiLog 都是为解释状态机而设计的受控演示数据配图是独立绘制的 DevEco/手机界面示意不是真实编译、真机运行或 OCR 服务评测结果。代码应在项目采用的 SDK 与设备上重新编译、联调。一、阈值往上拖为什么不该立刻算成一笔配置修改做 OCR 质检页面时一个容易被轻描淡写的交互是“把低置信度阈值从 70 调到 75”。从界面看无非一个滑块、一行待复核数量。但只要把它接到批量校对流程上就会出现两个不同含义手指滑动时人想试探筛查范围点击确认后人才真正接受这次策略变化。把这两个动作都写到同一个变量里后面的批次审核、历史回显、撤销操作就无法判断当前看到的是预览还是正式值。我把这个问题缩到一个名为ThresholdBench的单页 Demo页面叫ReviewThresholdPage示例任务TB-1606文档DOC-07。这里没有调用 OCR 引擎输入是一组固定的 12 行置信度92、68、86、59、74、96、83、62、88、71、90、77。正式阈值是70界面上正在预览的草稿阈值是75按照“严格小于阈值就加入人工复核队列”的规则75 对应5 行待复核。数值是演示样本不代表任何真实识别准确率。我更在意的是阈值背后的责任边界。用户拖到 75 后退出页面产品究竟应该保留 70 还是 75如果没有确认按钮答案并不明确。当前设计明确规定savedThreshold只代表已确认值draftThreshold只代表本次界面试探pendingCount是依草稿临时计算的投影状态文本只有“待提交”“确认中”“已确认”三类。页面关闭时未提交的草稿直接丢弃重新进入则用已经保存的业务配置初始化。实际项目若要跨进程持久化再由独立仓储层处理不让 Slider 事件直接承担落盘职责。封面中的滑块是这个工程问题的视觉提示并非已经跑通 OCR 识别的截图。技术方向落在 ArkUI 的Slider事件语义和业务状态分层而不是模型本身。二、把“预览”做成纯计算而不是边拖边请求第一段代码先把业务输入固定下来。缺测值、无穷大、负值或超过 100 的置信度都不应混进合格统计。界面里的 75 也不能只是一个看着正确、实际未经规整的浮点数我们约定以 5 为步长提交前再执行一次钳制。待复核采用而非这样 75 分恰好在阈值上时仍保留在普通列表验收时不会因为边界取值含糊而出现一条差异。interfaceOcrLine{id:string;confidence:number;}constDEMO_ROWS:OcrLine[][{id:L01,confidence:92},{id:L02,confidence:68},{id:L03,confidence:86},{id:L04,confidence:59},{id:L05,confidence:74},{id:L06,confidence:96},{id:L07,confidence:83},{id:L08,confidence:62},{id:L09,confidence:88},{id:L10,confidence:71},{id:L11,confidence:90},{id:L12,confidence:77}];functionsafeThreshold(value:number):number{if(!Number.isFinite(value))return70;returnMath.max(0,Math.min(100,Math.round(value/5)*5));}functioncountPending(rows:OcrLine[],threshold:number):number{returnrows.filter((row:OcrLine)Number.isFinite(row.confidence)row.confidence0row.confidence100row.confidencethreshold).length;}这里返回的只是 UI 所需的整数不对行对象排序也不改变它们的原始置信度。坏数据被排除不意味着可以假装它已经识别成功真正的导出或复核服务应将无效行单独计入异常队列。示例数组存在模块内离开页面不需要关闭文件句柄或网络连接但如果真实项目将其换成订阅的数据源应在页面消失时取消订阅不要依靠一次性的 Demo 数据结构推断生产环境的生命周期。三、Slider 的 Moving 和 End不是同一个业务事件第二段代码是页面的核心。华为文档给Slider提供了value/min/max/steponChange回调中还能拿到SliderChangeMode其中Moving表示拖动中的值变化End表示交互结束。这里不把End解读为“用户批准保存”。手指离开滑块只说明交互完成是否采用新阈值仍要通过显式确认表达。这样即使用户在滑动时看见“5 行待复核”正式配置也不会悄悄改变。EntryComponentstruct ReviewThresholdPage{StatesavedThreshold:number70;StatedraftThreshold:number75;StatependingCount:number5;Statestatus:string待提交;privateticket:number0;privatepreview(value:number):void{this.ticket1;// 使尚未完成的旧确认回执失效this.draftThresholdsafeThreshold(value);this.pendingCountcountPending(DEMO_ROWS,this.draftThreshold);this.statusthis.draftThresholdthis.savedThreshold?已确认:待提交;}build(){Column({space:18}){Text(ThresholdBench · TB-1606).fontSize(22)Text(DOC-07 | 数据版本 v4)Text(正式阈值${this.savedThreshold}/ 预览${this.draftThreshold})Slider({value:this.draftThreshold,min:0,max:100,step:5}).onChange((value:number,mode:SliderChangeMode){if(modeSliderChangeMode.Moving||modeSliderChangeMode.End||modeSliderChangeMode.Click)this.preview(value);})Text(待复核${this.pendingCount}/ 12 行)Text(this.status)Button(确认本次阈值).onClick((){this.confirm();})}.padding(20).width(100%)}}这里直接在草稿变化时计算 12 行的筛查数量没有使用定时器也没有把每一帧手势都发往后端。对于几千行、几万行的数据集应改成索引结构、后台计算或节流投影但优化之前需要先确立两个变量的含义否则只会更快地制造错误。Slider的触摸开始或结束属于组件交互语义savedThreshold的改变则是应用层行为两个层次不能混用。DevEco 示意图刻意把工程目录、ReviewThresholdPage.ets、右侧模拟器和底部 HiLog 分开。图中TB-1606、DOC-07、70/75、5/12 与正文一致日志文字也是演示构造的期望输出不是实际采集的性能证据。四、确认按钮要提交快照不能提交一个不断变化的引用确认按钮看起来只需要一次赋值但真实业务一般要等仓储层或者接口回执。如果点击确认后用户继续把阈值拖回 65旧确认异步返回时仍写入 75就会出现“界面已经预览另一个值正式配置却在后台改掉”的错乱。因此第三段代码给每次草稿变化一个单调递增的ticket。真正的持久化接口尚未接入这里用Promise.resolve()构造可读的异步边界演示并不暗示已经完成服务端保存。// 放在 ReviewThresholdPage 组件内privateasyncconfirm():Promisevoid{if(this.status确认中)return;constcurrentTicket:numberthis.ticket;constsnapshot:numbersafeThreshold(this.draftThreshold);this.status确认中;try{awaitPromise.resolve();// 仅演示异步回执不是落盘/网络调用if(currentTicket!this.ticket)return;this.savedThresholdsnapshot;this.status已确认;}catch(_){if(currentTicketthis.ticket)this.status待提交;}}ticket仅在当前组件实例内生效不能当成分布式版本号更不是事务 ID。生产系统应该连同DOC-07、数据版本v4、操作者上下文一起传给仓储或服务端由服务端独立校验权限与版本冲突。try/catch只处理提交路径本身的异常不能吞掉版本不一致后的用户反馈旧回执被忽略时应按真实产品需求提示“本次配置已变更请重新确认”。五、怎样检查“手指松开后仍未保存”这个边界我给这页设计了五条可以手工检查的验收动作。先以已确认 70 进入页面筛查集合应是低于 70 的 68、59、62 三行。然后拖到 75预览集合增加 74、71界面出现 5/12但正式值仍显示 70状态是“待提交”。第三步不点确认就返回页面再进入时应按正式设置回到 70而不是从上一轮未完成操作恢复 75。第四步点击确认后只在有效回执到达时正式值才变成 75。第五步要在确认仍挂起时继续拖动旧票据不能覆盖新草稿。这些属于预期验收步骤而不是本次已经执行并通过的真机测试。若 Slider 接了无障碍步长、键盘或外部设置同步还要补测非触摸输入如果新的配置来自其他设备组件本地ticket不足以决定胜负必须引入业务修订号。还有一个细节Moving可能在一次拖动期间多次回调日志不应该逐帧写到线上监控更不应该让每次事件都造成落盘以免制造大量无意义 IO。纯屏幕示意在顶部保留 10:24、5G、Wi-Fi、信号条和电量 86%屏幕中间固定显示ThresholdBench、TB-1606、DOC-07、正式 70、预览 75、待复核 5/12、待提交。图片是给读者复核状态含义的辅助证据而不是模拟器或设备真实画面。六、收尾输入控件不用负责替业务做决定这次拆开的并不只是两个数字而是用户试探、可见预览、显式确认、正式版本四个相互关联又不等价的阶段。给他们各自命名后异常处理就有了落点无效分数不污染筛查、滑动回调不触发提交、并发回执不反写新草稿、页面退出不私自保存。对于 OCR、图像超分参数、分类门槛等具有“预览后才决定”特征的场景这个小模式都值得保留。本例真正有把握的结论是状态设计而不是任何 OCR 准确率或运行性能。生产落地前还要补齐真实 OCR 行数据映射、持久化仓储、权限、可访问性、错误提示以及对应 SDK 编译和设备验收。参考华为官方 Slider APIhttps://developer.huawei.com/consumer/en/doc/harmonyos-references-V14/ts-basic-components-slider-V14以及 ArkTS 基础语言介绍https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/introduction-to-arkts。