更新记录

1.0.0(2026-07-20)

首次发布


平台兼容性

uni-app(4.81)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
× × × × 5.0 15 ×
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
× × × × × × × × × × × ×

nj-scan 全屏扫码

nj-scan 是传统 uni-app Vue 3 App 的 UTS API 插件。宿主调用 scan(options) 后,插件打开原生全屏扫码页;页面内提供相机扫码、闪光灯、双指缩放、系统单图选择、无相机权限降级和中文无障碍标签。默认收到第一个结果后结束,也可开启连续扫码。

兼容性与重要限制

  • 仅支持传统 uni-app Vue 3 的 Android/iOS App;不支持 Vue 2、不支持 uni-app x,也不支持 Harmony、Web、小程序、nvue 或快应用。
  • Android API 21 及以上;iOS 15.0 及以上。
  • 最低 HBuilderX 4.81。内部事件监听依赖 4.27 起提供的 @UTSJS.keepAlive;最低版本进一步提升到 4.81,是因为这是 DCloud 说明的传统 uni-app 核心适配 Android 16 KB 页面的首个版本。发布构建仍须检查最终 APK/AAB 的 ZIP 与全部 64 位 ELF 对齐。
  • Google 当前文档的支持基线为 API 23。插件固定版本的 AAR 清单审计表明依赖最低版本不高于 API 21,但 API 21–22 属于项目自维护兼容范围,不代表 Google 当前厂商支持承诺;发布前必须覆盖相应真机。
  • 公开入口固定为 @/uni_modules/nj-scan/js_sdk/nj-scan.js。DCloud 会把包根解析到平台 UTS 入口,因此不要从 @/uni_modules/nj-scan 直接导入;包根的 _native* 方法属于内部桥接,不是公共 API。
  • 传统 uni-app 的 App 条件编译标识是 APP-PLUSAPP-ANDROID / APP-IOS 是 uni-app x 进一步新增的条件,放在传统项目中会把原生 facade 导入和调用全部裁掉。插件 metadata 仍只声明传统 App 的 Android/iOS 支持。
  • 内置界面目前只有中文;宿主可设置标题、提示和主题色,不能替换原生页面布局。

安装与构建

  1. 将完整的 uni_modules/nj-scan/ 放入传统 uni-app Vue 3 项目。
  2. 使用 HBuilderX 4.81 或更高版本;本插件当前发布验证使用 HBuilderX 5.15。
  3. 这是包含 Kotlin/Swift 与第三方原生依赖的 UTS 插件,标准运行基座不包含它。调试时请制作包含本插件的自定义基座,发布时使用原生 App 云打包或等价的完整原生构建,不能只运行 Web/小程序构建。
  4. iOS 构建需要 CocoaPods 能解析锁定的 GoogleMLKit/BarcodeScanning 6.0.0。云打包或离线打包后应保存最终 Pod 解析结果,并确认插件的 PrivacyInfo.xcprivacy 被归档。
  5. Android 构建必须保留 minSdkVersion 21,并在最终消费 App 的合并清单和 APK/AAB 中复核依赖贡献的权限、组件及 Android 16 KB 对齐。

插件目录、平台配置和原生混编要求以 DCloud UTS API 插件文档 为准。

直接依赖已固定如下;升级任何一项都需要重新做兼容性、隐私和真机审计:

平台 固定依赖
Android com.google.mlkit:barcode-scanning:17.3.0(bundled model)
Android CameraX 1.4.1:camera-corecamera-camera2camera-lifecyclecamera-view
Android androidx.activity:activity-ktx:1.9.3
Android androidx.exifinterface:exifinterface:1.3.7
iOS GoogleMLKit/BarcodeScanning 6.0.0

权限与申请时机

插件只会在宿主按钮触发并调用 scan() 后检查相机权限;导入插件、应用启动和仅浏览宿主页面时不会申请相机权限。

Android

  • 插件直接声明 CAMERA 与普通权限 VIBRATE。相机和闪光灯硬件 feature 均为 required="false",所以没有摄像头的设备仍可安装并进入相册降级页。
  • 首次状态未决定时只发起一次相机权限申请;拒绝或永久拒绝后不会循环申请。插件仅在宿主应用的私有偏好中保存一个“已请求过相机权限”的布尔值,用于让后续扫码会话直接进入降级页;它不包含权限结果、图片或条码内容,也不会上传。扫码页仍保留“从相册选择”“前往设置”和“关闭”。
  • 不声明 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGEREAD_MEDIA_IMAGES。插件通过 AndroidX PickVisualMedia 优先使用系统、系统扩展或兼容 Photo Picker,并在这些路径都不可用时回退到 ACTION_OPEN_DOCUMENT;只接受成功返回的一个明确 image/* URI,不取得整个照片库权限,也不持久保存 URI 授权。
  • Android 9(API 28)及以上使用系统 ImageDecoder 直接按 URI 采样为软件位图,兼容 Photo Picker 提供方;API 21–27 使用 BitmapFactory、EXIF 方向修正和一次有界空流重试。两条路径均在后台解码,并继续执行 2048/4096 px 渐进识别。
  • INTERNETACCESS_NETWORK_STATE 来自 ML Kit 的传递依赖合并,供诊断/使用遥测和可能的维护通信使用,不是插件业务代码自建网络服务。最终 App 仍会包含它们,详见隐私章节。

iOS

  • 插件只合并 NSCameraUsageDescription = 用于扫描二维码和条形码。;仅在调用 scan() 后按系统状态请求相机权限。
  • 相册使用 PHPickerViewController 选择一张图片,不调用 PhotoKit 全库授权,也不添加 NSPhotoLibraryUsageDescription。因此不请求整个照片库权限。
  • 相机权限被拒绝、受限或没有后置相机时仍展示全屏降级页;用户仍可从相册扫码,也可点击“前往设置”。从设置回到前台后插件会重新检查相机权限。

快速开始(一次性扫码)

默认 continuous: false,首个有效结果会触发 success,原生页面完成释放后触发 complete({ reason: 'success' })。不要从包根导入。

// #ifdef APP-PLUS
import { scan, closeScan } from '@/uni_modules/nj-scan/js_sdk/nj-scan.js'

export default {
  methods: {
    startOneShotScan() {
      scan({
        continuous: false,
        scanArea: 'frame',
        success: (result) => {
          this.latestResult = result
        },
        fail: (error) => {
          this.latestError = error
        },
        complete: (summary) => {
          this.latestCompletion = summary
        },
      })
    },
    stopScan() {
      closeScan()
    },
  },
}
// #endif

同时只能有一个活动会话。已有会话时再次调用 scan(),第二次调用会按 fail(9011002) → complete({ reason: 'error', count: 0, durationMs: 0 }) 返回,首个会话不受影响。调用 closeScan() 是幂等操作:有活动会话时以 programmatic 原因结束,无活动会话时不做任何事。

选项与默认值

scan(options) 要求传入对象;scan({}) 使用全部默认值。插件会在调用时同步复制全部标量、格式数组和回调引用,调用方随后修改原对象不会改变当前会话。

选项 类型 默认值 说明
continuous boolean false false 为一次性;true 为连续扫码
duplicateIntervalMs number 1500 必须是 0...60000 的有限整数;0 关闭时间去重
formats BarcodeFormat[] 全部 13 种 只接受下方白名单;空数组非法
scanArea 'frame' \| 'full' frame 实时相机按扫描框中心过滤,或接受全画面;相册始终分析整张图
initialTorch boolean false 相机支持时首次尝试打开闪光灯,失败仅提示并降级
orientation 'portrait' \| 'auto' portrait 锁定竖屏或跟随设备旋转
vibrate boolean true 每个已接受的分析批次最多轻震一次
beep boolean false 每个已接受的分析批次最多提示一次
title string 扫一扫 原生页标题
hint string 将二维码或条码放入框内 扫描区提示
themeColor string #4CD964 只接受 #RRGGBB
success (result) => void 每个已接受结果触发;一次性最多一次
fail (error) => void 仅参数、busy、展示失败、引擎初始化或致命内部错误触发
complete (summary) => void 每个已接受会话或预检失败恰好一次

支持的 13 种 BarcodeFormatCODE_128CODE_39CODE_93CODABARDATA_MATRIXEAN_13EAN_8ITFQR_CODEUPC_AUPC_EPDF417AZTEC

framefull 的界面和识别范围

  • frame 是可见的正方形扫描框,也是实时相机的实际识别 ROI。框外有半透明遮罩和四角标记,锥形扫描线及柔光完整地在正方形内移动;条码中心必须落在这个正方形 ROI 内才会被实时相机接受。
  • full 接受实时相机的完整画面,不绘制框外遮罩、扫描框或四角标记;仍保留锥形扫描线及柔光,并让其在顶部/底部控件之间按水平 24 dp/pt、垂直 12 dp/pt 的边距移动。full 不是“显示全屏但仍按隐藏扫描框过滤”。
  • 相册始终分析所选完整图片,不受 scanArea 影响。

两端关闭、相册和闪光灯均为无可见文字的圆形图标控件:关闭为 44 dp/pt,相册和闪光灯为 56 dp/pt;前往设置仍是文字按钮。三个图标控件都有中文 TalkBack/VoiceOver 标签,闪光灯会按已确认的开关状态切换图标、中文状态和值,并仅在开启成功后使用主题色。图标来自 Google Material Symbols Rounded,随插件本地打包,来源记录在 THIRD_PARTY_NOTICES.md,完整 Apache License 2.0 位于 licenses/MaterialSymbols-Apache-2.0.txt;运行时不会下载图标。

结果、错误与完成

ScanResult

字段 类型 说明
text string rawValuedisplayValuerawBytesBase64 的顺序取首个可用值
rawValue string? ML Kit 原始文本值
displayValue string? 适合显示的文本值
rawBytesBase64 string? 原始字节 Base64;用于无损交付非 UTF-8 条码
format BarcodeFormat 13 种公开格式之一
valueType BarcodeValueType UNKNOWNCONTACT_INFOEMAILISBNPHONEPRODUCTSMSTEXTURLWIFIGEOCALENDAR_EVENTDRIVER_LICENSE
source 'camera' \| 'album' camera 表示实时相机,album 表示系统单图选择器

没有任何文本/字节候选或无法映射格式的条码不会回调空结果。插件不返回 ML Kit 原生对象;结构化 Wi-Fi、联系人等内容由宿主根据 valueType 和原始内容自行处理。

ScanCompleteResult

字段 类型 说明
reason ScanCompleteReason successcancelprogrammaticerrorinterrupted
count number 当前会话实际派发的 success 次数
durationMs number 从接受有效调用到完成清理的毫秒数

完成原因:

  • success:一次性扫码成功。
  • cancel:用户点关闭、Android 返回键或 iOS 关闭控件。
  • programmatic:宿主调用 closeScan()
  • error:不可恢复错误;顺序为 fail → complete
  • interrupted:宿主 Activity/控制器被系统销毁。

一次性成功顺序为 success → complete;致命错误顺序为 fail → complete;连续模式是零次或多次 success 后再 complete。参数错误、busy 或尚未进入原生会话的失败,其 completion 固定为 { reason: 'error', count: 0, durationMs: 0 }。调用方回调抛出的异常会被隔离,不会阻止清理或后续回调。

错误码

fail 接收继承 IUniErrorScanFail 对象:errSubject 固定为 nj-scanerrCode 是下表稳定数字码,errMsg 是不包含条码数据的中文错误信息(可附安全的内部详情);本插件不附带额外错误数据或底层异常,因此 datacause 固定为 null

错误码 名称 说明
9011001 INVALID_OPTIONS 参数非法,如未知/空格式数组、非整数去重时间、越界值或非法颜色
9011002 SCANNER_BUSY 已有活动扫码会话
9011003 PRESENTATION_FAILED 找不到宿主展示页面或原生扫码页启动失败
9011004 MLKIT_INITIALIZATION_FAILED ML Kit 扫码器无法初始化
9011005 INTERNAL_ERROR 其他必须终止会话的内部错误

相机无权限、没有后置摄像头、单帧失败、相册加载失败、照片没有条码或闪光灯失败都是可恢复问题,只在扫码页以中文提示,不调用 fail

连续扫码

连续模式会按 format + 内容duplicateIntervalMs 窗口内去重,逐个触发 success,并在页面顶部显示累计数量。宿主可随时调用 closeScan();完成原生清理后收到 complete({ reason: 'programmatic' })

// #ifdef APP-PLUS
import { scan, closeScan } from '@/uni_modules/nj-scan/js_sdk/nj-scan.js'

const scannedResults = []
let latestError = null
let latestCompletion = null

export function startContinuousScan() {
  scan({
    continuous: true,
    duplicateIntervalMs: 1500,
    scanArea: 'full',
    formats: ['QR_CODE', 'EAN_13', 'CODE_128'],
    success: (result) => {
      scannedResults.push(result)
    },
    fail: (error) => {
      latestError = error
    },
    complete: (summary) => {
      latestCompletion = summary
    },
  })
}

export function stopContinuousScan() {
  closeScan()
}
// #endif

无相机权限时使用相册

相机权限被拒绝、受限或设备没有后置相机时,scan() 仍会打开全屏降级页。用户可以:

  1. 点击“从相册选择”,通过系统单图选择器选一张图片;不需要先授予相机权限。
  2. 取消选择时直接回到扫码页,不触发 successfailcomplete
  3. 图片无条码或加载失败时留在扫码页,可再次选择。
  4. 图片只有一个结果时按当前一次性/连续模式处理;多码时先显示中文候选列表,由用户选择一个。
  5. 点击“前往设置”授予相机权限;返回前台后插件会重新检查并在已授权时切回实时扫码。

图片按 EXIF 方向处理,先将最长边缩到不超过 2048 px;若无结果且原图更大,再以 4096 px 重试。处理期间不把照片复制到插件持久目录。

生命周期、资源与隐私

会话与资源

  • 同时只允许一个会话;closeScan() 幂等。
  • 切入后台会暂停相机与分析、关闭闪光灯并撤销扫码页的常亮;暂停后不再启动新的相册解码、ML Kit 推理或 4096 px 重试,在途阶段只做安全释放或延迟到真正恢复前台后继续。回到前台后重新检查权限并恢复;打开相册和关闭页面时同样会关闭闪光灯。
  • Android 等待在途 ML Kit Task 结束后关闭 scanner;iOS 排空推理队列后释放 scanner 引用。晚到结果会被终态门丢弃。
  • 插件自有代码不会把条码内容写入日志;示例只把结果显示在页面中,不写入控制台。

隐私与第三方 SDK

扫码引擎提供方为 Google LLC。Android 使用 com.google.mlkit:barcode-scanning:17.3.0,iOS 使用 GoogleMLKit/BarcodeScanning 6.0.0

边界必须分开理解:

  • 图片像素、相机帧、条码原始值、显示值和原始字节不发送到 Google;推理在设备端完成,结果只返回宿主。插件业务代码不记录、不持久化、不上传这些内容,也没有自有服务器。
  • Google ML Kit 仍会经 HTTPS 发送设备/应用信息、每安装标识、性能、API 配置、输入输出大小、功能版本、条码格式与内容类型类别(不含条码载荷)、初始化、检测、释放事件及错误码,用于诊断、使用分析、维护改进和滥用检测。Google 组件可能在本地保存每安装标识和待发送遥测。
  • Android 17.3.0 的识别模型随包内置,正常使用无需首次下载模型;SDK 仍可能联系 Google 获取错误修复、模型更新或硬件加速兼容信息,所以不能描述成“零网络”。Google 没有公开的 standalone ML Kit 遥测关闭开关,也没有公布固定服务器域名。
  • Google Android 披露称相关数据经 HTTPS 且不向第三方共享;锁定 iOS 隐私清单将收集项标为不关联身份且不用于跟踪。最终合规答案仍由宿主结合其他 SDK 和业务行为确认。

Android 最终消费 App 除插件直接声明的 CAMERAVIBRATE 外,还会由 ML Kit/GoogleDataTransport 合并 INTERNETACCESS_NETWORK_STATE,以及 MlKitInitProvider、ML Kit 组件发现 Service、GoogleDataTransport 上传调度 Service/Receiver 和 GoogleApiActivity 等非导出或受保护组件。Library harness 的中间 manifest 不等于最终 App 合并结果,发布时必须检查最终包。

iOS 插件自带 PrivacyInfo.xcprivacy。它声明插件自身计时使用的 SystemBootTime / 35F9.1,并合并锁定 Google 6.0.0 清单中的 Disk Space、File Timestamp、System Boot Time、User Defaults Required Reason API 以及六类 collected data。真实 XCArchive/IPA 必须用 Xcode Privacy Report 复核清单是否保留;不能依赖宿主或其他 SDK 的清单替插件兜底。

宿主收到 ScanResult 后,如果保存、上传、分享、关联账号或用于分析,属于宿主自己的数据处理,必须在用户协议、隐私政策、DCloud 市场声明、App Store 隐私标签和 Google Play Data safety 中另行披露。建议依据锁定清单保守填写 App Store 的 Device ID、Other Data Types、Other Diagnostic Data、Other User Content、Performance Data、Product Interaction;Play 表单应覆盖相应标识、诊断、性能、交互和其他配置/类别数据。两家商店的最终填写责任属于宿主开发者。

官方说明:

故障排查

导入时报找不到 scan

确认使用显式 facade:

import { scan, closeScan } from '@/uni_modules/nj-scan/js_sdk/nj-scan.js'

不要直接导入包根。DCloud 的 uni_modules resolver 会把包根选择到平台 UTS 内部桥接,那里只导出 _nativeScan_nativeCloseScan

标准基座中没有插件或原生类

重新制作包含 nj-scan 的自定义基座,或执行完整云打包。仅编译页面资源不会加入 Kotlin、Swift、CameraX、CocoaPods 或 ML Kit。

提示自定义基座 SDK 与 HBuilderX 不一致

使用当前 HBuilderX 重新制作自定义基座后再做发布验收。原生依赖与平台配置没有变化时,开发阶段可以把新编译的 UTS/Kotlin/Swift 代码同步到已有基座做快速回归;但出现 SDK 版本不一致提示的旧基座不能作为最终兼容性证据。

iOS Pod 解析失败

确认 CocoaPods 网络/源可用,且最终解析顶层依赖仍是 GoogleMLKit/BarcodeScanning 6.0.0。不要把 iOS config.jsondependencies-pods 改成通用 dependencies,后者不会被 HBuilderX 按本插件预期处理。

Android 21/22 构建成功但设备异常

API 21/22 是项目自维护范围。先核对依赖未被宿主冲突解析升级,再在对应厂商真机执行权限、CameraX、Photo Picker fallback、旋转图片和资源释放清单;构建清单兼容不等于 OEM 运行兼容。

有相册权限但没有相机权限

无需额外处理:调用 scan() 后使用降级页“从相册选择”即可。插件使用系统单图选择器,不依赖全库照片权限;宿主已有相册权限也不会改变这个申请时机。

企业网络需要 allowlist

Google 没有公布稳定、完整的 ML Kit 域名清单。请针对真实设备和最终发行包做动态网络观察,并考虑 Google Play services、重定向和服务端配置变化,不能把某次二进制中观察到的端点当成长期合同。

测试与发布状态

源码仓库提供 Python 发布/UI/制品完整性契约测试、两个 Node 行为 harness、纯 Kotlin/Swift 策略与界面几何测试、Android 原生 library harness、CocoaPods lint harness 和依赖级 Android 16 KB 检查。2026-07-20 当前源码的新鲜自动证据为 Python 145/145、facade 31/31、runtime probe 8/8、两端几何可执行文件通过、Android clean assembleDebug lintDebug 通过(lint 0 error/2 warning)、iOS Pod validation 通过,以及 HBuilderX 5.15 Android/iOS mixed compile 通过。iOS mixed compile 已把 12 个 PNG 原样放入生成资源暂存目录,但标准基座 compile-only 没有生成可检查的宿主 .app,所以不能据此认定 Bundle.main 已在自定义基座或最终包中加载这些图标。

这些证据覆盖源码契约、确定性几何、PNG 透明度/内容、原生编译与项目级混编;不代替重新制作并安装包含当前界面修改的 Android/iOS 自定义基座。正方形框、锥形/柔光扫描线、full 无框外观、图标触控、闪光灯状态,以及 TalkBack/VoiceOver 仍须在两端真机观看和操作;最终签名 APK/AAB/IPA、完整设备/系统矩阵和商店门也仍未完成。详见 RELEASE_CHECKLIST.md

本版本当前不能仅凭 README 或分层 harness 宣称为发布候选。两端真实 facade + UTS/原生混编已经通过,Android 15 真机的既有专项冒烟和用户重建基座后“此前双端相机/相册问题不再复现”的报告也已保留;请继续执行同目录的 RELEASE_CHECKLIST.md,其中当前 UI polish 双端自定义基座外观/触控/无障碍复测、100+100 会话、稳定 callback ID、最终 APK/AAB 与 XCArchive/IPA、Android 16 KB 整包检查、隐私报告和完整系统/设备矩阵仍保持开放,直到有可复核证据后再逐项勾选。

插件版本为 1.0.0;公开变更见 changelog.md

隐私、权限声明

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

Android:CAMERA、VIBRATE;ML Kit 传递依赖会合并 INTERNET、ACCESS_NETWORK_STATE,用于诊断、使用遥测及可能的维护通信。iOS:相机用途说明;系统单图选择器不申请照片库权限。

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

图片/相机帧和条码结果仅在设备端处理,结果返回调用方;本插件业务代码不向插件作者或 Google 上传图片/条码结果,也不持久化这些内容。Android 仅在宿主应用私有偏好中保存一个相机权限请求布尔状态,用于避免重复弹窗;该状态不含权限结果、图片或条码内容且不会上传。Google ML Kit 会经 HTTPS 向 Google 服务器发送设备/应用信息、每安装标识、性能、API 配置、输入输出大小/功能版本、条码格式与内容类型类别(不含条码原文)、使用事件(初始化/检测/释放)及错误码,用于诊断、使用分析、维护改进和滥用检测;Google 组件可能在设备上保存每安装标识及待发送遥测。Android 官方称相关遥测不向第三方共享,iOS 清单将数据标记为不关联身份且不用于跟踪。SDK 还可能联网获取错误修复、模型更新或硬件加速兼容信息;Android 扫码模型已随包内置。Google 未公布固定服务器域名,也未提供独立版 ML Kit 的公开遥测关闭开关;本插件无自有服务器。

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

暂无用户评论。