更新记录

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 实现

安装和运行

  1. 在插件市场导入插件到项目的 uni_modules 目录。
  2. Android 首次使用、修改 utssdk/app-android 原生代码或更新依赖后,制作并安装自定义调试基座,或使用云端自定义打包。
  3. iOS 首次加入或更新原生插件后,重新制作 iOS 自定义基座或正式包。
  4. 真机运行,首次打开时授权相机权限。

使用者不需要手动安装 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 的 viewtextlist 渲染订单、结果和操作区;下方使用全局注册的 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: truemultiSelect: 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 fullscreenbottom-sheet
continuous boolean false 是否连续扫码
multiple boolean false 是否同帧识别多个码
multiSelect boolean true 多码时是否冻结画面并点选
multiSelectText string 内置提示 多码点选提示文字
resultMode string 单次 auto-close,连续 stay auto-closemanualstay
sheetHeight string/number - 半屏扫码高度
duplicateInterval number 2000 内容去重间隔,0 关闭去重
duplicateStrategy string ignore ignoreallowreplaceformat-content
formats string[] 全部支持码制 允许的码制
history boolean true 是否保存会话历史
album boolean false 是否显示相册识别入口
camera string back backfront
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 nonelinelaserradargrid
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_CODECODE_128CODE_39CODE_93EAN_13EAN_8UPC_AUPC_EITFPDF_417DATA_MATRIXAZTECCODABAR

结果与事件

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 可能为 urlwifiphonesmsemailgeocontactcalendarproducttext

事件列表

事件 说明
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. 为什么内嵌扫码区是黑色?

按顺序检查:

  1. 手机是否已授予相机权限。
  2. 是否正在运行最新自定义基座。
  3. app-nvue 中的 uts-smart-scanner-view 是否同时设置了明确宽和高。
  4. 是否有其他页面、原生相机或扫码会话占用摄像头。

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. 不会主动访问的能力

插件不会主动访问麦克风、通讯录、定位、蓝牙、短信、设备通讯录或业务服务器。业务方如需上传扫码结果,应在自己的回调中调用接口。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

Android:CAMERA(相机扫码)、VIBRATE(扫码成功震动,可关闭)。iOS:相机权限;启用相册识别时申请相册访问权限。

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集、不上传、不向任何服务器发送二维码、条形码、相册图片或识别结果。识别过程在设备本地完成,结果仅回传给调用方;可选识别历史仅由调用方在本地会话中管理。

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无广告

暂无用户评论。