更新记录
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-PLUS;APP-ANDROID/APP-IOS是 uni-app x 进一步新增的条件,放在传统项目中会把原生 facade 导入和调用全部裁掉。插件 metadata 仍只声明传统 App 的 Android/iOS 支持。 - 内置界面目前只有中文;宿主可设置标题、提示和主题色,不能替换原生页面布局。
安装与构建
- 将完整的
uni_modules/nj-scan/放入传统 uni-app Vue 3 项目。 - 使用 HBuilderX 4.81 或更高版本;本插件当前发布验证使用 HBuilderX 5.15。
- 这是包含 Kotlin/Swift 与第三方原生依赖的 UTS 插件,标准运行基座不包含它。调试时请制作包含本插件的自定义基座,发布时使用原生 App 云打包或等价的完整原生构建,不能只运行 Web/小程序构建。
- iOS 构建需要 CocoaPods 能解析锁定的
GoogleMLKit/BarcodeScanning 6.0.0。云打包或离线打包后应保存最终 Pod 解析结果,并确认插件的PrivacyInfo.xcprivacy被归档。 - 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-core、camera-camera2、camera-lifecycle、camera-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_STORAGE、WRITE_EXTERNAL_STORAGE或READ_MEDIA_IMAGES。插件通过 AndroidXPickVisualMedia优先使用系统、系统扩展或兼容 Photo Picker,并在这些路径都不可用时回退到ACTION_OPEN_DOCUMENT;只接受成功返回的一个明确image/*URI,不取得整个照片库权限,也不持久保存 URI 授权。 - Android 9(API 28)及以上使用系统
ImageDecoder直接按 URI 采样为软件位图,兼容 Photo Picker 提供方;API 21–27 使用BitmapFactory、EXIF 方向修正和一次有界空流重试。两条路径均在后台解码,并继续执行 2048/4096 px 渐进识别。 INTERNET与ACCESS_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 种 BarcodeFormat:CODE_128、CODE_39、CODE_93、CODABAR、DATA_MATRIX、EAN_13、EAN_8、ITF、QR_CODE、UPC_A、UPC_E、PDF417、AZTEC。
frame 与 full 的界面和识别范围
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 |
按 rawValue、displayValue、rawBytesBase64 的顺序取首个可用值 |
rawValue |
string? |
ML Kit 原始文本值 |
displayValue |
string? |
适合显示的文本值 |
rawBytesBase64 |
string? |
原始字节 Base64;用于无损交付非 UTF-8 条码 |
format |
BarcodeFormat |
13 种公开格式之一 |
valueType |
BarcodeValueType |
UNKNOWN、CONTACT_INFO、EMAIL、ISBN、PHONE、PRODUCT、SMS、TEXT、URL、WIFI、GEO、CALENDAR_EVENT 或 DRIVER_LICENSE |
source |
'camera' \| 'album' |
camera 表示实时相机,album 表示系统单图选择器 |
没有任何文本/字节候选或无法映射格式的条码不会回调空结果。插件不返回 ML Kit 原生对象;结构化 Wi-Fi、联系人等内容由宿主根据 valueType 和原始内容自行处理。
ScanCompleteResult
| 字段 | 类型 | 说明 |
|---|---|---|
reason |
ScanCompleteReason |
success、cancel、programmatic、error 或 interrupted |
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 接收继承 IUniError 的 ScanFail 对象:errSubject 固定为 nj-scan,errCode 是下表稳定数字码,errMsg 是不包含条码数据的中文错误信息(可附安全的内部详情);本插件不附带额外错误数据或底层异常,因此 data 与 cause 固定为 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() 仍会打开全屏降级页。用户可以:
- 点击“从相册选择”,通过系统单图选择器选一张图片;不需要先授予相机权限。
- 取消选择时直接回到扫码页,不触发
success、fail或complete。 - 图片无条码或加载失败时留在扫码页,可再次选择。
- 图片只有一个结果时按当前一次性/连续模式处理;多码时先显示中文候选列表,由用户选择一个。
- 点击“前往设置”授予相机权限;返回前台后插件会重新检查并在已授权时切回实时扫码。
图片按 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 除插件直接声明的 CAMERA、VIBRATE 外,还会由 ML Kit/GoogleDataTransport 合并 INTERNET、ACCESS_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 表单应覆盖相应标识、诊断、性能、交互和其他配置/类别数据。两家商店的最终填写责任属于宿主开发者。
官方说明:
- ML Kit Terms & Privacy
- ML Kit Android Data Disclosure
- ML Kit iOS Data Disclosure
- Android Barcode Scanning
- Apple:Describing data use in privacy manifests
- DCloud:iOS 隐私清单
故障排查
导入时报找不到 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.json 的 dependencies-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。

收藏人数:
购买普通授权版(
试用
赞赏(0)
下载 539
赞赏 4
下载 12441660
赞赏 1934
赞赏
京公网安备:11010802035340号