更新记录
1.0.0(2026-07-13)
- 新增 Android CameraX + Google ML Kit 原生扫码引擎。
- 新增 iOS AVCaptureSession 原生扫码引擎。
- 支持二维码、常见一维条码、PDF417、Data Matrix 和 Aztec。
- 支持单次扫码、连续扫码、会话历史、内容去重和重复事件通知。
- 支持多码识别,检测到多个码后冻结预览并由用户点选结果。
- 支持相册识别、自动缩放、双指缩放、自动对焦、点击对焦、手电筒和前后摄像头切换。
- 支持扫描框区域限制、提示音、震动、原生 Toast 和 UI 配色定制
平台兼容性
uni-app(5.14)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | × | × | √ | √ | √ | √ | × |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| × | × | × | × | × | × | × | × | × | × | × | × |
UTS Smart Scanner 智能扫码插件
UTS Smart Scanner 是面向 App 的企业级原生扫码插件。它不是只返回一次结果的简单***,而是提供连续扫码、多码定格点选、相册识别、去重、缩放、聚焦和同页原生相机嵌入能力,适合仓储、物流、PDA、收银、核销和设备绑定。
插件信息
| 项目 | 内容 |
|---|---|
| 插件名称 | UTS智能扫码 |
| 插件类型 | UTS 插件 / API 插件 / 原生视图组件 |
| 当前版本 | 0.1.0 |
| Android 引擎 | CameraX + Google ML Kit |
| iOS 引擎 | AVCaptureSession + MetadataOutput / Vision |
| 支持平台 | App-Android、App-iOS |
| 是否申请权限 | 相机;启用相册识别时申请 iOS 相册权限 |
| 是否采集数据 | 否,识别在设备本地完成 |
更新记录见 changelog.md。
适用场景
- 扫描二维码、商品条码、物流码、设备码、ISBN、Data Matrix、PDF417、Aztec。
- 仓储入库、出库、盘点、收银、PDA 连续录入。
- 一张海报、货架或屏幕出现多个码时,让用户点击确认目标码。
- 业务表单或订单页面上方展示业务信息,下方同屏显示实时相机预览。
- 从相册图片中识别二维码或条形码。
不适用场景
- H5、微信小程序、支付宝小程序及其他小程序。
- OCR 文字识别、身份证识别、银行卡识别、人脸识别。
- 普通
.vue页面中直接把 Vue 元素盖在实时原生相机画面上。 - 扫描结果需要上传云端后才能返回的业务流程。插件只负责本地识别,上传由业务代码实现。
平台兼容性
| 平台 | 支持情况 | 说明 |
|---|---|---|
| App-Android | 支持 | 最低 Android 5.0;CameraX + ML Kit |
| App-iOS | 支持 | AVCaptureSession 原生扫码 |
| app-vue | 支持 | 使用 scan() 或 Scan.open() 打开原生扫码页 |
| app-nvue | 支持 | 额外支持 uts-smart-scanner-view 真内嵌相机 |
| uni-app x app-uvue | 支持 | 使用 uts-scanner 原生视图组件 |
| H5 | 不支持 | 无原生相机识别实现 |
| 各类小程序 | 不支持 | 无原生 UTS 实现 |
| 鸿蒙 | 暂不支持 | 当前版本未提供 HarmonyOS 实现 |
安装和运行
- 在插件市场导入插件到项目的
uni_modules目录。 - Android 首次使用、修改
utssdk/app-android原生代码或更新依赖后,制作并安装自定义调试基座,或使用云端自定义打包。 - iOS 首次加入或更新原生插件后,重新制作 iOS 自定义基座或正式包。
- 真机运行,首次打开时授权相机权限。
使用者不需要手动安装 CameraX、ML Kit 或 Android SDK。插件已声明原生依赖。只有开发者需要在本机制作 Android 自定义基座时,才需要在 HBuilderX“设置 - 运行配置”中配置 Gradle、JDK 和 Android SDK。
接入方式
| 页面与需求 | 使用方式 | 是否同屏显示业务区和相机 |
|---|---|---|
普通 .vue,单次识别后返回 |
scan() |
否 |
普通 .vue,连续扫码或半屏扫码 |
Scan.open() |
半屏时业务区可见,但不能点击 |
普通 uni-app .nvue |
uts-smart-scanner-view |
是 |
uni-app x .uvue |
uts-scanner |
是 |
mode: 'embedded' 只供 nvue / uvue 原生视图组件内部使用。普通 .vue 页面不能把 CameraX / AVCapture 预览直接插入 Vue DOM。
快速开始:单次扫码
适合登录、设备绑定、核销、邀请二维码和轻量商品识别。
import { scan } from '@/uni_modules/uni-scan-pro'
async function scanOnce() {
try {
const result = await scan({
formats: ['QR_CODE', 'CODE_128', 'EAN_13'],
album: true,
autoZoom: true,
scanRectOnly: true,
beep: true,
vibrate: { duration: 30 },
ui: {
title: '扫描二维码或条形码',
subTitle: '请将码置于取景框内',
cornerColor: '#22c55e',
lineColor: '#22c55e'
}
})
console.log('内容:', result.rawValue)
console.log('码制:', result.format)
} catch (error) {
if (error.code === 'USER_CANCEL') return
uni.showToast({ title: error.message || '扫码失败', icon: 'none' })
}
}
调用后打开全屏原生扫码页。识别成功时返回 ScanResult;用户关闭扫码页时 Promise 进入 catch,错误码为 USER_CANCEL。
连续扫码:仓储、物流、PDA
连续扫码不会在每次识别后关闭页面。同一内容会按 duplicateInterval 去重,默认 2000ms。
import { Scan } from '@/uni_modules/uni-scan-pro'
let scanner = null
function startInventoryScan() {
scanner = Scan.open({
mode: 'bottom-sheet',
sheetHeight: '58%',
continuous: true,
resultMode: 'stay',
formats: ['CODE_128', 'EAN_13', 'QR_CODE'],
duplicateInterval: 2000,
duplicateStrategy: 'ignore',
history: true,
toast: { text: '已录入,请继续扫码' },
beep: true,
vibrate: { duration: 25 }
}, onScannerEvent)
}
function onScannerEvent(eventJson) {
const event = JSON.parse(eventJson)
if (event.event === 'success') {
console.log('录入成功:', event.result.rawValue)
// 在这里调用入库、盘点或核销接口。
}
if (event.event === 'duplicate') {
console.log('重复码已忽略:', event.result.rawValue)
}
if (event.event === 'error') {
console.error(event.error)
}
}
function stopInventoryScan() {
scanner?.close('page-unload')
scanner = null
}
推荐生命周期:
进入业务页 -> Scan.open() -> 连续收到 success -> 提交完成 / 离开页面 -> close()
pause() 会暂停相机和识别但保留会话;resume() 恢复;close() 会释放相机资源。
多码识别:冻结后点选
适合一张画面中出现多个二维码或条形码。
import { scan } from '@/uni_modules/uni-scan-pro'
async function chooseOneCode() {
const result = await scan({
multiple: true,
multiSelect: true,
multiSelectText: '识别到多个码,请点击一个继续',
formats: ['QR_CODE', 'CODE_128', 'EAN_13', 'DATA_MATRIX'],
album: true
})
console.log('用户选择:', result.rawValue)
}
multiple: true:开启同一帧多码识别。multiSelect: true:默认值。检测到多个码时冻结预览,显示对应标记,用户点选后才返回结果。multiSelect: false:不进入点选界面,直接返回识别结果数组。
多码点选时不会显示中间扫描框,以免遮挡可点击的结果标记。
半屏扫码
Scan.open({
mode: 'bottom-sheet',
sheetHeight: '56%',
sheetDimAmount: 0.06,
continuous: true,
resultMode: 'stay'
}, onScannerEvent)
半屏扫码器是独立原生页面。上方业务内容仍可见,但扫码器打开期间不要期望上层 Vue 页面能响应点击。需要真正的“上方业务区 + 下方实时相机”请使用 app-nvue 或 uni-app x 原生视图。
普通 uni-app:app-nvue 真内嵌相机
创建 .nvue 页面。上方用 nvue 的 view、text、list 渲染订单、结果和操作区;下方使用全局注册的 uts-smart-scanner-view 直接承载原生相机。
原生组件必须指定宽和高。下例用
windowHeight计算下半屏高度。nvue 通过ref调用方法时不要依赖同步返回值。
<!-- pages/inbound/scan.nvue -->
<template>
<view class="page">
<view class="business-zone">
<text class="title">入库单 AR-0726</text>
<text>已扫 {{ count }} 件</text>
<text>{{ lastResult }}</text>
</view>
<uts-smart-scanner-view
ref="scanner"
class="scanner"
:style="{ height: scannerHeight + 'px' }"
:optionsjson="scannerOptionsText"
:active="scannerActive"
@event="onScannerEvent"
/>
</view>
</template>
<script>
export default {
data() {
return {
scannerActive: true,
scannerHeight: 360,
count: 0,
lastResult: '等待扫码',
scannerOptionsText: JSON.stringify({
continuous: true,
resultMode: 'stay',
formats: ['QR_CODE', 'CODE_128', 'EAN_13'],
duplicateInterval: 2000,
autoZoom: true,
scanRectOnly: true,
beep: true,
toast: { text: '已录入,请继续扫码' }
})
}
},
onLoad() {
this.scannerHeight = Math.max(300, uni.getSystemInfoSync().windowHeight - 275)
},
methods: {
onScannerEvent(payload) {
const eventJson = typeof payload.get === 'function'
? payload.get('eventJson')
: payload.eventJson || (payload.detail && payload.detail.eventJson)
if (!eventJson) return
const event = JSON.parse(eventJson)
if (event.event !== 'success') return
const result = event.result || event.results[0]
this.count += event.results ? event.results.length : 1
this.lastResult = result.displayValue || result.rawValue
},
pauseScanner() {
this.scannerActive = false
this.$refs.scanner.pause()
},
resumeScanner() {
this.scannerActive = true
this.$refs.scanner.resume()
}
},
onUnload() {
const scanner = this.$refs.scanner
if (scanner) scanner.pause()
}
}
</script>
<style>
.page { flex: 1; }
.business-zone { padding: 24rpx; }
.title { font-size: 34rpx; }
.scanner { width: 750rpx; background-color: #000000; }
</style>
可调用的方法:start()、pause()、resume()、toggleFlashlight()、switchCamera()、clearHistory()。
uni-app x:uvue 真内嵌相机
在 .uvue 页面使用 uts-scanner,业务区域和相机区域由 uvue 排版。
<template>
<view class="page">
<view class="business-area">
<text class="title">入库任务 #A1024</text>
<text>请扫描待入库商品条码</text>
</view>
<uts-scanner
class="scanner"
:continuous="true"
:formats="['CODE_128', 'EAN_13', 'QR_CODE']"
:duplicate-interval="2000"
:options="scannerOptions"
@success="onScanSuccess"
@error="onScanError"
/>
</view>
</template>
<script setup lang="uts">
const scannerOptions = {
toast: { text: '已录入,请继续扫码' },
beep: true,
vibrate: { duration: 25 },
ui: {
cornerColor: '#22c55e',
lineColor: '#22c55e'
}
}
function onScanSuccess(event: UTSJSONObject) {
console.log(event['result'])
}
function onScanError(event: UTSJSONObject) {
console.error(event)
}
</script>
<style>
.page { height: 100%; }
.business-area { padding: 24px; }
.title { font-size: 18px; }
.scanner { width: 100%; height: 420px; }
</style>
扫描框、扫描线、多码标记均由原生层绘制。不要依赖 Vue z-index 覆盖实时相机画面。
API 说明
scan(options)
打开单次扫码页,成功时返回 Promise<ScanResult>;multiple: true 且 multiSelect: false 时可能返回 ScanResult[]。
const result = await scan({
formats: ['QR_CODE', 'EAN_13'],
album: true
})
Scan.open(options, callback)
创建可主动控制的扫码会话,适合连续扫码、半屏扫码和业务流程扫描。
const controller = Scan.open({
continuous: true,
resultMode: 'stay'
}, (eventJson) => {
console.log(JSON.parse(eventJson))
})
ScannerController
| 方法 | 说明 |
|---|---|
start() / resume() |
开始或恢复相机识别 |
pause() |
暂停相机和识别,保留会话 |
close(reason?) / stop(reason?) |
关闭会话并释放相机 |
openAlbum() |
打开相册识别,需设置 album: true |
toggleFlashlight() / toggleTorch() |
切换手电筒 |
switchCamera() |
切换前后摄像头 |
getHistory() |
获取当前会话识别历史,返回 JSON 字符串 |
clearHistory() |
清空历史和去重记录 |
on(eventName, callback) |
订阅指定事件,返回 token |
off(token?) |
取消订阅;不传 token 时清空所有订阅 |
ScannerOptions
| 配置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
mode |
string | fullscreen |
fullscreen、bottom-sheet |
continuous |
boolean | false |
是否连续扫码 |
multiple |
boolean | false |
是否同帧识别多个码 |
multiSelect |
boolean | true |
多码时是否冻结画面并点选 |
multiSelectText |
string | 内置提示 | 多码点选提示文字 |
resultMode |
string | 单次 auto-close,连续 stay |
auto-close、manual、stay |
sheetHeight |
string/number | - | 半屏扫码高度 |
duplicateInterval |
number | 2000 |
内容去重间隔,0 关闭去重 |
duplicateStrategy |
string | ignore |
ignore、allow、replace、format-content |
formats |
string[] | 全部支持码制 | 允许的码制 |
history |
boolean | true |
是否保存会话历史 |
album |
boolean | false |
是否显示相册识别入口 |
camera |
string | back |
back、front |
flashlight |
boolean | false |
初始手电筒状态 |
autoZoom |
boolean | false |
码太小时自动放大 |
pinchZoom |
boolean | true |
双指缩放 |
autoFocus |
boolean | true |
自动连续对焦 |
tapFocus |
boolean | true |
点击画面对焦 |
scanRectOnly |
boolean | false |
仅识别扫描框内的码 |
beep |
boolean/string | false |
提示音开关或音频路径 |
vibrate |
boolean/object | false |
true 或 { duration: 30 } |
toast |
boolean/object | 连续扫码开启 | false 或 { enable, text } |
animation |
string | line |
none、line、laser、radar、grid |
ui |
object | {} |
原生 UI 外观配置 |
ui 外观配置
ui: {
backgroundColor: '#000000',
maskColor: 'rgba(0, 0, 0, 0.48)',
borderColor: '#ffffff',
cornerColor: '#7cf0d2',
lineColor: '#ffcc5c',
lineWidth: 2,
radius: 12,
title: '扫描商品码',
subTitle: '请将条形码置于取景框内',
albumText: '从相册识别',
flashOffText: '打开手电筒',
flashOnText: '关闭手电筒',
choiceColor: '#21d4c2'
}
支持码制:QR_CODE、CODE_128、CODE_39、CODE_93、EAN_13、EAN_8、UPC_A、UPC_E、ITF、PDF_417、DATA_MATRIX、AZTEC、CODABAR。
结果与事件
ScanResult
{
"id": "smart-scanner-xxx",
"text": "6901234567892",
"rawValue": "6901234567892",
"displayValue": "6901234567892",
"type": "product",
"format": "EAN_13",
"timestamp": 1782870398724,
"bounds": {
"left": 12,
"top": 30,
"width": 180,
"height": 76
}
}
type 可能为 url、wifi、phone、sms、email、geo、contact、calendar、product 或 text。
事件列表
| 事件 | 说明 |
|---|---|
ready |
扫码会话已创建 |
start |
相机或相册识别已启动 |
detect |
检测到码,尚未完成去重 |
success |
识别结果已通过去重并被接受 |
duplicate |
命中去重规则并被忽略 |
pause / resume |
会话暂停或恢复 |
close |
会话关闭,包含 reason |
torchChange |
手电筒状态变化 |
cameraChange |
前后摄像头切换完成 |
zoomChange |
自动或手势缩放变化 |
focusChange |
点击对焦结果 |
historyClear |
会话历史已清空 |
error |
权限、相机或相册识别错误 |
常见问题
1. 为什么提示“找不到名称 camera”或 mlkit?
Android 原生依赖没有进入当前 UTS 编译 classpath。检查 HBuilderX“设置 - 运行配置”,完成 Gradle、JDK、Android SDK 配置后,重新制作 Android 自定义基座。
2. 为什么提示 ActivityNotFoundException?
正在运行标准基座或旧自定义基座。插件的原生 Activity 和 CameraX 依赖只会打进重新制作后的自定义基座。
3. 为什么内嵌扫码区是黑色?
按顺序检查:
- 手机是否已授予相机权限。
- 是否正在运行最新自定义基座。
- app-nvue 中的
uts-smart-scanner-view是否同时设置了明确宽和高。 - 是否有其他页面、原生相机或扫码会话占用摄像头。
4. 为什么连续扫码没有反应?
相同内容在 duplicateInterval 内会触发 duplicate 而不是 success。连续业务建议给用户显示“已录入,请继续扫码”的 Toast,并在回调中处理 duplicate 事件。
5. 为什么多码没有直接返回?
multiple: true 默认开启 multiSelect。插件会先冻结画面让用户点选。需要直接拿到该帧结果时设置 multiSelect: false。
6. 为什么相册识别没有打开?
确认已设置 album: true,并在 iOS 授予相册权限。调用 Controller 的 openAlbum() 前也需要保持会话未关闭。
示例工程
示例工程覆盖:
- 单次扫码
- 微信风格全屏扫码
- 半屏连续扫码
- PDA 连续扫码和去重
- 多码冻结点选
- 相册识别
- app-nvue 上方业务区与下方原生相机真内嵌
- 商品入库流程
权限与隐私说明
1. 本插件需要申请的系统权限
CAMERA:打开相机扫码。- iOS 相册权限:仅在启用相册识别时申请。
VIBRATE:Android 成功震动反馈。
2. 数据采集和传输
插件不包含广告,不采集、存储或上传二维码、条形码、相册图片和识别结果。识别在设备本地完成,结果只回传给调用插件的业务代码。
3. 不会主动访问的能力
插件不会主动访问麦克风、通讯录、定位、蓝牙、短信、设备通讯录或业务服务器。业务方如需上传扫码结果,应在自己的回调中调用接口。

收藏人数:
购买普通授权版(
试用
使用 HBuilderX 导入示例项目
赞赏(0)
下载 21
赞赏 0
下载 12423859
赞赏 1934
赞赏
京公网安备:11010802035340号