news 2026/9/18 18:11:44

微信小程序连续扫码实战:camera组件避坑与性能优化指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微信小程序连续扫码实战:camera组件避坑与性能优化指南

1. 从一个真实需求说起:为什么要死磕连续扫码

去年接了一个仓储盘点的小程序项目,需求方开口第一句话就是:“我要能一直扫,扫完一个自动接着扫下一个,中间不要让我点任何按钮。”听起来很简单对吧?微信小程序官方提供了wx.scanCodeAPI,调一次扫一个,扫完再调一次不就行了?

实际动手之后才发现,这里面的坑比想象中多得多。wx.scanCode每次调用都会拉起一个全屏的原生扫码界面,扫完之后界面关闭,回到小程序页面,然后你才能再次调用。这个“拉起-关闭-再拉起”的过程,在安卓上大概有 300-500ms 的闪烁间隙,在 iOS 上更明显,用户会看到屏幕反复闪白,体验非常割裂。而且每次调用都会有一次明显的相机启动延迟,连续扫十几个码下来,操作员直接跟我说“眼睛都要闪瞎了”。

后来我把目光转向了camera组件。微信小程序从基础库 2.7.0 开始,camera组件支持了mode="scanCode"模式,可以在组件内部直接完成扫码识别,不需要反复拉起原生界面。这意味着你可以把 camera 组件嵌在页面里,配合bindscancode事件,实现真正的“连续扫码”——扫完一个码,界面不关闭,直接接着扫下一个。

但事情没有这么简单。官方文档对camera组件的scanCode模式描述非常简略,很多关键细节需要自己踩坑才能摸清楚。比如:扫到同一个码会不会重复触发?连续扫码时如何避免重复录入?不同机型上 camera 组件的表现差异有多大?页面隐藏后相机资源怎么释放?这些问题在官方文档里几乎找不到答案。

这篇文章就是把我这段时间踩过的坑、试过的方案、总结出来的经验完整地分享出来。如果你也在做微信小程序的扫码功能,尤其是需要连续扫码的场景(仓储盘点、图书录入、资产盘点、门票核销等),这篇文章应该能帮你省下不少时间。我会从方案选型开始讲,然后深入 camera 组件的核心机制,再给出完整的实操代码和避坑指南,最后整理一份常见问题速查表。内容比较长,建议先收藏再慢慢看。

2. 方案选型:scanCode API 还是 camera 组件

2.1 两种扫码方式的本质区别

在微信小程序里做扫码,摆在面前的有两条路:一是调用wx.scanCodeAPI,二是使用camera组件的scanCode模式。很多人一开始都会用wx.scanCode,因为它简单,一行代码就能调起来。但如果你要做连续扫码,这个方案很快就会暴露出问题。

wx.scanCode的本质是调用微信客户端原生的扫码界面。你调用它,微信就弹出一个全屏的原生页面,这个页面不属于你的小程序,你无法控制它的 UI,也无法在它上面叠加任何东西。扫完之后原生页面关闭,结果通过回调返回给你的小程序。整个过程是“你的小程序 → 原生扫码页 → 你的小程序”这样一个来回切换的过程。

camera组件则完全不同。它是嵌入在你小程序页面里的一个原生组件,相机画面直接显示在你指定的区域里。当设置mode="scanCode"时,组件内部会自动识别画面中的二维码/条形码,识别到之后通过bindscancode事件把结果抛给你。整个过程你的页面始终在前台,相机画面不会中断,用户看到的是一个连续的取景框,体验上更接近“专业扫码枪”的感觉。

2.2 连续扫码场景下的性能对比

我分别在安卓和 iOS 上做了对比测试,用同一台设备连续扫 20 个二维码,记录总耗时和用户可感知的闪烁次数。测试机型:安卓端是某骁龙 870 机型,iOS 端是 iPhone 13。

对比维度wx.scanCode APIcamera 组件 scanCode 模式
单次扫码平均耗时800-1200ms200-400ms
连续扫 20 个码总耗时约 20-25 秒约 6-8 秒
界面闪烁每次都有明显闪烁无闪烁,画面连续
相机启动延迟每次调用都有仅首次有
自定义 UI不支持完全支持
连续扫码体验差,需要反复等待好,接近无缝
最低基础库要求1.0.02.7.0

从表格可以清楚看到,camera组件在连续扫码场景下的优势是压倒性的。总耗时差了 3 倍以上,而且没有闪烁,用户体验完全不是一个级别。当然代价是基础库要求更高(2.7.0),以及你需要自己处理相机权限、页面生命周期等细节。

2.3 什么场景该选哪个方案

不是说wx.scanCode就一无是处。如果你的场景是“偶尔扫一次”,比如用户点击按钮扫一个码就完事,那用wx.scanCode完全没问题,代码简单,兼容性好,不需要处理相机权限的复杂逻辑。

但如果你符合以下任何一个特征,就应该认真考虑camera组件方案:

  • 需要连续扫多个码,中间不希望用户有多余操作
  • 对扫码速度有要求,比如高峰期需要快速核销
  • 需要自定义扫码界面的 UI,比如加个手电筒按钮、加个已扫列表
  • 需要在扫码的同时显示其他信息,比如当前盘点进度

我个人的建议是:只要你的场景涉及连续扫码,直接上camera组件方案,不要犹豫。前期多花一两个小时处理细节,后期省下的是无数次的用户投诉。

3. camera 组件连续扫码的核心机制拆解

3.1 bindscancode 事件的触发逻辑

camera组件的bindscancode事件是整个连续扫码的核心。当mode="scanCode"时,组件会自动识别相机画面中的码,识别成功就触发一次bindscancode,回调参数里包含扫码结果。

这里有一个非常关键的细节:同一个码在画面中持续存在时,bindscancode 会不会重复触发?官方文档没有明确说明,我实测的结果是:会重复触发,但触发频率不固定。在安卓上,同一个码大约每 500-800ms 会触发一次;在 iOS 上,大约每 300-500ms 触发一次。这意味着如果你不做去重处理,同一个码会被连续录入多次。

这个机制的设计初衷可能是为了“容错”——万一第一次识别结果不完整,后续还能补上。但对于连续扫码场景来说,这就是一个必须处理的陷阱。解决方案后面会详细讲。

3.2 相机资源的生命周期管理

camera组件是原生组件,相机资源是独占的。这意味着:

  • 当页面隐藏时(比如用户按了 Home 键,或者跳转到其他页面),相机应该被释放,否则会一直占用摄像头,导致其他应用无法使用相机
  • 当页面显示时,相机需要重新初始化
  • 如果页面上有多个 camera 组件,只有最后一个生效

微信小程序提供了页面生命周期函数onHideonShow,以及组件级别的wx.createCameraContext来管理相机。但实际使用中,我发现单纯依赖页面生命周期还不够,因为 camera 组件的初始化是异步的,页面onShow触发时相机可能还没准备好。

一个比较稳妥的做法是:用一个data字段控制 camera 组件的渲染与销毁。页面隐藏时把字段设为false,组件被销毁,相机释放;页面显示时设为true,组件重新创建。这样虽然会有一点初始化延迟,但能确保相机资源被正确释放。

3.3 连续扫码的去重策略

前面提到同一个码会重复触发bindscancode,所以去重是必须的。去重的核心思路是:记录最近一次扫码的结果和时间戳,如果新结果和上一次相同,且时间间隔小于某个阈值,就忽略。

但这里有个问题:如果用户真的需要连续扫两个相同的码呢?比如盘点时有两个相同的资产标签。这种情况下,简单的“结果相同就忽略”会误杀。所以去重策略需要更精细一些。

我的做法是:维护一个“已扫码集合”,每扫到一个码,先检查是否在集合中。如果不在,加入集合并触发业务逻辑;如果在,检查距离上次扫码的时间间隔,如果超过一定时间(比如 3 秒),认为是用户有意重复扫,允许通过;如果小于 3 秒,认为是重复触发,忽略。

这个策略在实际使用中效果不错,既避免了重复触发,又不会误杀合理的重复扫码需求。

4. 完整实操:从零实现一个连续扫码页面

4.1 页面结构与基础配置

先来看页面的 WXML 结构。核心就是一个camera组件,加上一些辅助 UI。

<view class="scan-container"> <!-- 相机区域 --> <camera wx:if="{{cameraVisible}}" class="camera-area" mode="scanCode" device-position="back" flash="{{flashOn ? 'torch' : 'off'}}" bindscancode="onScanCode" binderror="onCameraError" > <!-- 扫码框覆盖层 --> <cover-view class="scan-frame"> <cover-view class="scan-corner top-left"></cover-view> <cover-view class="scan-corner top-right"></cover-view> <cover-view class="scan-corner bottom-left"></cover-view> <cover-view class="scan-corner bottom-right"></cover-view> </cover-view> </camera> <!-- 相机不可用时的提示 --> <view wx:else class="camera-placeholder"> <text>相机初始化中...</text> </view> <!-- 底部操作栏 --> <view class="bottom-bar"> <view class="scan-count">已扫 {{scanList.length}} 个</view> <view class="btn-group"> <button class="btn-flash" bindtap="toggleFlash"> {{flashOn ? '关闭闪光灯' : '打开闪光灯'}} </button> <button class="btn-finish" bindtap="finishScan">完成</button> </view> </view> <!-- 已扫列表 --> <scroll-view class="scan-list" scroll-y> <view wx:for="{{scanList}}" wx:key="index" class="scan-item"> <text class="scan-index">{{index + 1}}</text> <text class="scan-result">{{item}}</text> </view> </scroll-view> </view>

这里有几个关键点需要注意:

  • camera组件用wx:if控制显示与隐藏,而不是hidden。因为hidden只是隐藏了 UI,相机资源并没有释放。用wx:if才能真正销毁组件、释放相机。
  • mode="scanCode"是开启扫码模式的关键属性。
  • device-position="back"指定使用后置摄像头,扫码场景基本都用后置。
  • flash属性控制闪光灯,torch是常亮模式,适合扫码补光。
  • bindscancode是扫码结果的回调。
  • binderror是相机出错的回调,必须处理,否则出错时用户看不到任何提示。

4.2 核心逻辑:扫码事件处理与去重

接下来是 JS 部分的逻辑。核心是onScanCode事件处理函数,以及去重逻辑。

Page({ data: { cameraVisible: true, flashOn: false, scanList: [], lastScanResult: '', lastScanTime: 0, scannedSet: {}, // 用于去重的集合 }, onLoad() { // 页面加载时检查相机权限 this.checkCameraAuth(); }, onShow() { // 页面显示时重新渲染 camera 组件 this.setData({ cameraVisible: true }); }, onHide() { // 页面隐藏时销毁 camera 组件,释放相机 this.setData({ cameraVisible: false }); }, onUnload() { // 页面卸载时确保相机释放 this.setData({ cameraVisible: false }); }, // 检查相机权限 checkCameraAuth() { wx.getSetting({ success: (res) => { if (!res.authSetting['scope.camera']) { wx.authorize({ scope: 'scope.camera', success: () => { console.log('相机权限已授权'); }, fail: () => { wx.showModal({ title: '需要相机权限', content: '扫码功能需要使用相机,请在设置中开启', confirmText: '去设置', success: (modalRes) => { if (modalRes.confirm) { wx.openSetting(); } } }); } }); } } }); }, // 扫码结果处理 onScanCode(e) { const { result } = e.detail; const now = Date.now(); const DEDUP_INTERVAL = 3000; // 3秒内相同结果视为重复 // 去重逻辑 if (result === this.data.lastScanResult) { if (now - this.data.lastScanTime < DEDUP_INTERVAL) { // 3秒内相同结果,忽略 return; } } // 更新最近扫码记录 this.setData({ lastScanResult: result, lastScanTime: now, }); // 检查是否已经扫过 if (this.data.scannedSet[result]) { // 已经扫过,给出提示但不重复添加 wx.showToast({ title: '该码已扫描过', icon: 'none', duration: 1000, }); return; } // 新码,加入列表 const newList = [...this.data.scanList, result]; const newSet = { ...this.data.scannedSet, [result]: true }; this.setData({ scanList: newList, scannedSet: newSet, }); // 震动反馈 wx.vibrateShort({ type: 'medium' }); // 提示音(可选) // this.playBeep(); }, // 相机错误处理 onCameraError(e) { console.error('相机错误:', e.detail); wx.showToast({ title: '相机启动失败,请检查权限', icon: 'none', duration: 2000, }); }, // 切换闪光灯 toggleFlash() { this.setData({ flashOn: !this.data.flashOn }); }, // 完成扫码 finishScan() { const { scanList } = this.data; if (scanList.length === 0) { wx.showToast({ title: '还没有扫描任何码', icon: 'none' }); return; } // 这里可以把 scanList 提交到后端 console.log('扫码结果:', scanList); wx.showModal({ title: '扫码完成', content: `共扫描 ${scanList.length} 个码,是否提交?`, success: (res) => { if (res.confirm) { // 提交逻辑 this.submitScanResults(scanList); } } }); }, // 提交扫码结果 submitScanResults(list) { wx.showLoading({ title: '提交中...' }); // 模拟提交 setTimeout(() => { wx.hideLoading(); wx.showToast({ title: '提交成功', icon: 'success' }); // 重置状态 this.setData({ scanList: [], scannedSet: {}, lastScanResult: '', lastScanTime: 0, }); }, 1000); }, });

这段代码有几个关键设计点需要展开说明。

去重逻辑的双层设计。第一层是“时间窗口去重”,用lastScanResultlastScanTime判断是否在 3 秒内重复触发。第二层是“集合去重”,用scannedSet记录所有已扫过的码,防止同一个码在间隔较长时间后被再次扫入。两层配合,既能防止连续触发导致的重复,又能防止用户无意中重复扫描同一个码。

震动反馈的重要性。在连续扫码场景中,用户往往不会一直盯着屏幕看,可能在看商品或者看货架。这时候声音和震动反馈就非常重要。wx.vibrateShort是微信提供的短震动 API,在扫码成功时触发,用户能通过触感确认扫码成功,不需要看屏幕。这个细节在实际使用中能大幅提升效率。

页面生命周期的处理。onHideonUnload里都把cameraVisible设为false,确保相机资源被释放。onShow里重新设为true,让相机重新初始化。这里有一个小问题:页面从隐藏到显示时,camera 组件重新创建需要一点时间,用户会看到短暂的“相机初始化中”提示。这个延迟在安卓上大约 200-300ms,iOS 上大约 100-200ms,基本可以接受。

4.3 样式处理与 UI 细节

camera 组件是原生组件,层级最高,普通的view无法覆盖在它上面。所以扫码框、提示文字这些覆盖层必须用cover-viewcover-image

.scan-container { display: flex; flex-direction: column; height: 100vh; background: #000; } .camera-area { flex: 1; width: 100%; position: relative; } .scan-frame { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 500rpx; height: 500rpx; } .scan-corner { position: absolute; width: 60rpx; height: 60rpx; border-color: #00ff00; border-style: solid; } .top-left { top: 0; left: 0; border-width: 4rpx 0 0 4rpx; } .top-right { top: 0; right: 0; border-width: 4rpx 4rpx 0 0; } .bottom-left { bottom: 0; left: 0; border-width: 0 0 4rpx 4rpx; } .bottom-right { bottom: 0; right: 0; border-width: 0 4rpx 4rpx 0; } .bottom-bar { display: flex; justify-content: space-between; align-items: center; padding: 20rpx 30rpx; background: #1a1a1a; } .scan-count { color: #fff; font-size: 28rpx; } .btn-group { display: flex; gap: 20rpx; } .btn-flash, .btn-finish { font-size: 26rpx; padding: 10rpx 30rpx; border-radius: 40rpx; } .btn-flash { background: #333; color: #fff; } .btn-finish { background: #07c160; color: #fff; } .scan-list { height: 300rpx; background: #111; } .scan-item { display: flex; align-items: center; padding: 16rpx 30rpx; border-bottom: 1rpx solid #222; } .scan-index { width: 50rpx; color: #07c160; font-size: 26rpx; } .scan-result { flex: 1; color: #ccc; font-size: 26rpx; word-break: break-all; }

样式方面有几个经验点。扫码框用四个角的边框来画,比画一个完整的矩形框更清爽,也不会遮挡太多相机画面。底部操作栏用深色背景,和相机画面形成对比,按钮用微信绿(#07c160)作为主操作色,符合用户习惯。已扫列表放在底部,高度固定,可以滚动,用户扫完一个码可以快速确认结果。

5. 那些官方文档不会告诉你的坑

5.1 安卓与 iOS 的行为差异

这是最让人头疼的部分。camera组件在安卓和 iOS 上的表现差异比想象中大得多。

识别速度差异。iOS 的识别速度明显快于安卓。同一个二维码,iPhone 基本是“对准即识别”,安卓有时候需要稍微停留一下。这个差异在扫密集排列的条码时特别明显。我的应对策略是在 UI 上给用户一个“正在识别”的视觉反馈,比如扫码框呼吸灯效果,让用户知道系统在工作,不要急着移开。

重复触发频率差异。前面提到过,iOS 上同一个码的重复触发间隔大约是 300-500ms,安卓上是 500-800ms。这意味着去重的时间窗口不能设得太短,否则安卓上会漏掉一些重复触发。我最终把去重窗口设为 3 秒,这个值在两端都能很好地工作。

闪光灯行为差异。在 iOS 上,flash="torch"会打开常亮补光灯,效果很好。但在部分安卓机型上,torch模式可能不生效,或者亮度很低。如果闪光灯对你的场景很重要,建议在代码里加一个检测逻辑,如果torch不生效,降级为flash="on"模式(虽然这个模式在扫码时体验不好,但至少能补光)。

相机初始化时间差异。iOS 上 camera 组件初始化大约 100-200ms,安卓上 200-500ms 不等,低端安卓机可能超过 1 秒。所以“相机初始化中”的提示是必要的,不要让用户面对一个黑屏。

5.2 基础库版本与兼容性处理

camera组件的scanCode模式要求基础库 2.7.0 及以上。虽然现在大部分用户的微信版本都远高于这个,但仍然需要做兼容处理。

我的做法是在页面加载时检查基础库版本:

onLoad() { const systemInfo = wx.getSystemInfoSync(); const SDKVersion = systemInfo.SDKVersion; const compareVersion = (v1, v2) => { const arr1 = v1.split('.').map(Number); const arr2 = v2.split('.').map(Number); const len = Math.max(arr1.length, arr2.length); while (arr1.length < len) arr1.push(0); while (arr2.length < len) arr2.push(0); for (let i = 0; i < len; i++) { if (arr1[i] > arr2[i]) return 1; if (arr1[i] < arr2[i]) return -1; } return 0; }; if (compareVersion(SDKVersion, '2.7.0') < 0) { wx.showModal({ title: '版本过低', content: '当前微信版本不支持连续扫码功能,请升级微信后再试', showCancel: false, }); return; } }

这段版本比较函数是通用的,建议放在工具函数里复用。如果版本低于 2.7.0,直接提示用户升级,不要尝试降级方案,因为降级到wx.scanCode的体验落差太大,不如不做。

5.3 相机权限被拒绝后的处理

相机权限是敏感权限,用户可能拒绝。如果用户拒绝了,camera组件会触发binderror,但错误信息比较模糊。更好的做法是主动检查权限状态。

wx.getSetting({ success: (res) => { if (res.authSetting['scope.camera'] === false) { // 用户之前拒绝过,引导去设置页开启 wx.showModal({ title: '相机权限未开启', content: '扫码需要使用相机,请在设置中开启相机权限', confirmText: '去设置', success: (modalRes) => { if (modalRes.confirm) { wx.openSetting({ success: (settingRes) => { if (settingRes.authSetting['scope.camera']) { // 用户开启了权限,重新初始化相机 this.setData({ cameraVisible: true }); } } }); } } }); } } });

这里有一个细节:wx.openSetting只能由用户点击触发,不能在代码里自动调用。所以必须通过showModal引导用户点击“去设置”按钮,才能打开设置页。这个流程虽然多了一步,但符合微信的规范,不会被审核拒绝。

5.4 连续扫码时的性能优化

连续扫几十个码之后,页面可能会变得卡顿。原因主要有两个:一是setData频繁调用导致数据传输量大,二是已扫列表越来越长,渲染压力大。

优化方案:

  • 合并 setData 调用。扫码事件里不要多次调用setData,把需要更新的数据合并成一次调用。比如scanListscannedSet一起更新。
  • 限制列表渲染长度。已扫列表不需要显示全部,只显示最近 20 条即可。用scanList.slice(-20)来截取。
  • 使用虚拟列表。如果确实需要显示全部,考虑用recycle-view等虚拟列表组件,但实现复杂度较高,一般场景用截取就够了。
  • 避免在扫码回调里做复杂计算。扫码回调应该尽量轻量,复杂的业务逻辑(比如网络请求)应该异步处理,不要阻塞扫码事件。

我实测下来,做了这些优化之后,连续扫 100 个码页面依然流畅,没有明显卡顿。

6. 常见问题速查表与排查思路

6.1 扫码相关高频问题

问题现象可能原因排查思路解决方案
camera 组件不显示基础库低于 2.7.0检查 SDKVersion提示用户升级微信
扫码无反应相机权限被拒绝检查 authSetting引导用户去设置页开启
同一个码重复录入未做去重处理检查 onScanCode 逻辑加时间窗口+集合去重
页面隐藏后相机仍占用未销毁 camera 组件检查 onHide 逻辑用 wx:if 控制组件销毁
安卓上闪光灯不亮机型兼容性问题真机测试降级为 flash="on"
扫码识别慢相机对焦问题检查 device-position确保使用后置摄像头
连续扫码后页面卡顿setData 频繁调用检查 setData 次数合并 setData,限制列表长度
iOS 上扫码框被遮挡cover-view 层级问题检查 cover-view 使用确保覆盖层用 cover-view
扫码结果乱码编码问题检查二维码编码格式用 decodeURIComponent 处理
相机启动失败其他应用占用相机检查后台应用提示用户关闭其他相机应用

6.2 几个容易被忽略的细节

扫码结果的编码处理。有些二维码的内容是 URL 编码的,直接显示会是乱码。建议在拿到结果后做一次decodeURIComponent,但要注意捕获异常,因为不是所有结果都是编码过的。

let result = e.detail.result; try { result = decodeURIComponent(result); } catch (err) { // 解码失败,使用原始结果 console.warn('解码失败,使用原始结果:', err); }

扫码结果为空的情况。极少数情况下,bindscancode会触发但result为空字符串。这种情况直接忽略即可,不要加入列表。

连续扫码的节流。虽然 camera 组件本身有识别间隔,但在业务层面加一个节流会更稳妥。比如设置一个 500ms 的节流窗口,窗口内的扫码事件直接丢弃。这样可以进一步降低重复触发的概率。

页面跳转时的处理。如果扫码过程中用户点击了某个按钮跳转到其他页面,要确保在跳转前把cameraVisible设为false,否则相机可能在新页面加载后仍然被占用。

6.3 真机调试 vs 开发者工具

这里必须强调:camera 组件的很多行为在开发者工具里和真机上完全不一样。开发者工具里 camera 组件可能根本不显示,或者显示的是电脑摄像头画面,扫码识别逻辑也可能不触发。所以 camera 相关的功能,必须用真机调试。

真机调试的步骤:

  1. 在开发者工具里点击“真机调试”
  2. 用手机微信扫描弹出的二维码
  3. 在手机上进行扫码测试
  4. 通过开发者工具的调试面板查看日志和错误

如果真机调试时发现 camera 组件不显示,先检查手机是否给了微信相机权限。安卓上还要检查是否被其他应用占用了相机。

7. 一些进阶玩法与扩展思路

7.1 扫码结果实时校验

在仓储盘点场景中,扫到的码需要和系统里的数据进行比对。可以在扫码回调里加一个校验逻辑,如果扫到的码不在预期列表中,给出不同的提示音和震动模式。

onScanCode(e) { const { result } = e.detail; // ... 去重逻辑 ... // 校验逻辑 const isValid = this.checkCodeValidity(result); if (isValid) { wx.vibrateShort({ type: 'medium' }); // 正常提示音 } else { wx.vibrateLong(); wx.showToast({ title: '无效码', icon: 'none' }); // 错误提示音 } }

这种即时反馈能让操作员立刻知道扫的码对不对,不需要事后核对,效率提升明显。

7.2 批量扫码后的数据提交

连续扫码会产生一批数据,这些数据通常需要提交到后端。提交时要注意:

  • 数据量大时分批提交,避免单次请求体过大
  • 提交失败时要保留本地数据,支持重试
  • 提交过程中要防止用户重复提交

我的做法是用一个submitting标志位控制,提交时禁用“完成”按钮,提交成功后再重置状态。

7.3 扫码历史记录与断点续扫

如果扫码过程中小程序被意外关闭(比如用户接了个电话),已扫的数据会丢失。对于需要扫几百个码的场景,这是不可接受的。

解决方案是把已扫列表实时存入wx.setStorageSync,页面加载时从缓存恢复。这样即使小程序被关闭,重新打开后还能继续扫。

// 每次扫码后保存 wx.setStorageSync('scan_list', this.data.scanList); // 页面加载时恢复 onLoad() { const savedList = wx.getStorageSync('scan_list') || []; if (savedList.length > 0) { wx.showModal({ title: '发现未完成的扫码记录', content: `上次扫描了 ${savedList.length} 个码,是否继续?`, success: (res) => { if (res.confirm) { this.setData({ scanList: savedList }); } else { wx.removeStorageSync('scan_list'); } } }); } }

这个功能在实际使用中非常实用,尤其是盘点场景,操作员可能扫到一半需要去做别的事,断点续扫能避免重复劳动。

8. 我个人在实际项目中的几点体会

做这个连续扫码功能前前后后花了大概两周时间,其中大部分时间不是在写代码,而是在真机上测试和调整。有几点体会比较深。

第一,不要相信开发者工具里的表现。camera 组件在开发者工具里基本没法用,所有测试都必须上真机。而且至少要准备一台安卓和一台 iOS,因为两端差异真的很大。

第二,去重逻辑要比想象中复杂。一开始我以为简单判断一下就行,后来发现要考虑时间窗口、集合去重、用户有意重复扫等多种情况。最终的去重逻辑改了三四版才稳定下来。

第三,用户体验的细节决定成败。震动反馈、提示音、扫码框动画、已扫列表的实时更新,这些细节看起来不起眼,但在连续扫码场景中,每一个都能明显影响操作效率。尤其是震动反馈,操作员不用看屏幕就能知道扫码成功,这个体验提升是巨大的。

第四,异常处理要全面。相机权限被拒、相机启动失败、扫码结果为空、页面隐藏后相机未释放,这些异常情况在开发阶段可能不会遇到,但上线后一定会出现。提前处理好这些异常,能省下很多客服成本。

最后分享一个小技巧:如果扫码场景的光线条件不好,除了开闪光灯,还可以在扫码框周围加一圈半透明的白色边框,利用屏幕自身的光来补光。这个技巧在扫反光材质的条码时特别有用,能明显提升识别率。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 18:09:30

CentOS7 Docker镜像源失效修复、离线交付与迁移指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 18:08:05

CIP与OPC UA协议转换:PLC标签数据转发到寄存器全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 18:08:01

基于Spring Boot+SSM的网上书城系统开发实战全解析

最近刚把一套基于SSM架构的网上书城系统完整跑通&#xff0c;从数据库设计到前后端实现&#xff0c;再到部署上线&#xff0c;整个过程踩了不少坑&#xff0c;也沉淀了不少经验。这个项目最初的定位就是典型的Java Web课程设计/毕业设计课题&#xff0c;核心需求是图书展示、用…

作者头像 李华