支持扫码这件事,说起来很单,真做到稳定好用却不容易
果只是粗暴的实现,前端常会这样做:
- 打摄像头;
- 不停抓当前帧;
- 识别到任何像条码的东西就返回
问题也随之来:
- 权限报错么提示?
- 不同浏览器的测能力么兼容?
- 为么用户明明把码放在框里,却还扫到背景里的别的码?
- 连续扫描场景下,么避免同帧疯触发结果?
Odoo 这套条码前端实现,恰好就是围绕这些真实问题展的
scanBarcode() 先把扫码过程包装成个 Promise,不是裸弹窗调用
barcode_dialog.js 里的 scanBarcode(env, facingMode) 很得看
它不是单 dialog.add(...) 后就完事,是:
- 先创建个 Promise;
- 把
resolve / reject存起来; - 再打
BarcodeDialog; - 在
onResult时 resolve,在onError时 reject
这样设计的好处非常直接:
上业务拿到的是等待次扫码结果的步接口,不是打个含摄像头的 UI 组件
这让扫码能力更像个前端服务,不是某个页面私有的弹窗辑
二Dialog 只负责入口和失败兜底,不负责识别细
BarcodeDialog 自己做的事情其实很少:
- 判断当前浏览器是否支持扫码能力;
- 若支持,就载
BarcodeVideoScanner; - 若不支持或运行失败,就显示错误界面
这里特别得注意的是:
onResult() 会先 close() 再把结果回传
这说明 Odoo 明确把拿到首个有效结果并关闭扫码界面当成默认用户路径,不是让扫描器继续在后台活
对于单次扫码业务,这是非常合理的默认
三测器不是写死种实现,是原生 API 优先,ZXing 回
BarcodeVideoScanner 在 onWillStart() 里会先判断:
- 浏览器是否有
BarcodeDetector - 没有的话,再
loadJS()引入 ZXing 库
然后统构 this.detector
这步非常关键,因为它表达的不是支持或不支持扫码,是:
在浏览器能力不致的前提下,前端要尽量为同种业务接口提供兼容实现
对业务方来说,理想的状不是知道底用了哪个库,是无论原生还是 ZXing,上都继续只关心 onResult()
四媒体流生命周期处理得很干:拿不到权限就报错,卸载时定停流
onMounted() 里会调用:
browser.navigator.mediaDevices.getUserMedia({ video: { facingMode }, audio: false })
果失败,源码会按错误类型映射成更友好的提示:
NotFoundError-> 没找到设备NotAllowedError-> 要授权
在组件卸载或常出时,又会执行 cleanStreamAndTimeout():
- 清理
setTimeout - 停止有媒体 track
这比很多临时扫码实现强太多
不少项目里,扫码弹窗关掉后摄像头还亮,根源就是没有把媒体流生命周期认真收口
五真正的关键不只是识别,是识别边界
很多人第次看 BarcodeVideoScanner 时,会把注意力都放在 detector.detect(...) 上
但这段实现真正厉害的地方,在于它没有默认整张视频画面都算有效区域
BarcodeVideoScanner 会过 CropOverlay 拿到 overlayInfo,然后在 detectCode() 里做两类边界控制:
1)ZXing 路径:直接把裁剪区传给 detector
onResize() 里若测到当前是 ZXing 回实现,就会:
this.detector.setCropArea(this.adaptValuesWithRatio(this.overlayInfo, true))
也就是说,裁剪区会在测器就被当成扫描边界
2)原生 BarcodeDetector 路径:识别后再按 bounding box 过滤
若用的是原生 API,Odoo 会对返回结果个查 boundingBox 是否落在 overlay 范围内,超出就跳过
这个分支设计非常成熟,因为它承认不同测后端的能力不完全样:
- 有的能在测前裁剪;
- 有的只能在测后过滤
但上要得到致行为:只有框内的码才算有效
六CropOverlay 不是纯视觉蒙,是可以持久化的几何约束
crop_overlay.js 这部分尤其得前端同学细看
它会:
- 在
setupCropRect()里计算默认裁剪区; - 从
localStorage恢复用户上次调整的位置; - 用 CSS 变量更新可视框位置;
- 再过
executeOnResizeCallback()把几何信息回传给扫描器
这说明 CropOverlay 的地位不是画个半明框,是:
维护份用户可调整浏览器可记忆扫描器可消费的裁剪几何状
它甚至还特别处理了 iOS 分支样式,这说明官方是把移动端扫码体验当真来做的,不是桌面端顺手兼容下
七连续扫描并不是无限触发,delayBetweenScan 用来流下次识别
barcodeDetected(barcode) 里有段很重要:
- 若配置了
delayBetweenScan,先把scanPaused = true - 延迟结束后再恢复测循环
这在连续扫码或批量收货类业务里尤其重要
否则个码只要还停留在镜头中,同帧或连续帧就可能反复触发,前端和业务辑都会被刷爆
以 Odoo 这里不是识别到就直回调,是给出了可配置流边界
八二容易踩的坑
误区 1:把扫码界面和扫码结果死在个页面组件里
更好的方式是像 scanBarcode() 这样先抽象成 Promise 能力
误区 2:整张画面都算有效区域
这会让背景里的条码包装箱上的其他码起干扰结果
误区 3:只做 Chrome happy path,不虑回实现
现实设备和浏览器环境远比本地测试复
误区 4:关闭弹窗后不清理 stream / timeout
摄像头残留占用就是这样来的
九结论
Odoo 的条码扫描前端之以靠谱,不是因为它能打摄像头,是因为它把扫码拆成了几职责清晰的前端协议:
scanBarcode()暴露 Promise 风格能力接口;BarcodeDialog管入口与失败兜底;BarcodeVideoScanner管测器装配媒体流和扫描循环;CropOverlay管用户可视可调的真实裁剪边界
以真正得学的不是何调用相机,是:
何让摄像头测器裁剪区和业务结果在同条链路里协同工作,不是互相打架
DISCUSSION
评论区