优化++
This commit is contained in:
16
README.md
16
README.md
@@ -61,9 +61,6 @@ IScan.config({
|
|||||||
// 自动选择可用扫码方式:桥接 -> 微信 -> Web 摄像头 -> 图片识别
|
// 自动选择可用扫码方式:桥接 -> 微信 -> Web 摄像头 -> 图片识别
|
||||||
IScan.startScan();
|
IScan.startScan();
|
||||||
|
|
||||||
// 仅打开 Web 视频扫码
|
|
||||||
IScan.scanVideo();
|
|
||||||
|
|
||||||
// 仅选择图片识别
|
// 仅选择图片识别
|
||||||
IScan.scanImage();
|
IScan.scanImage();
|
||||||
|
|
||||||
@@ -126,6 +123,11 @@ interface ScanConfigOptions {
|
|||||||
| `webScanImageFallbackOnVideoError` | 摄像头不可用或打开失败时,是否自动弹出拍照/选图(适用于部分安卓内置浏览器) | `true` |
|
| `webScanImageFallbackOnVideoError` | 摄像头不可用或打开失败时,是否自动弹出拍照/选图(适用于部分安卓内置浏览器) | `true` |
|
||||||
| `webScanVideoAccessTimeout` | 打开摄像头超时(毫秒),超时后走图片回退 | `10000` |
|
| `webScanVideoAccessTimeout` | 打开摄像头超时(毫秒),超时后走图片回退 | `10000` |
|
||||||
| `webScanVideoReadyTimeout` | 摄像头已开但无画面超时(毫秒),超时后走图片回退 | `8000` |
|
| `webScanVideoReadyTimeout` | 摄像头已开但无画面超时(毫秒),超时后走图片回退 | `8000` |
|
||||||
|
| `webScanCameraPermissionDialogEnabled` | `startScan` 走 Web 摄像头前是否先展示权限说明弹窗 | `true` |
|
||||||
|
| `webScanCameraPermissionTitle` | 权限说明弹窗标题 | `需要使用摄像头` |
|
||||||
|
| `webScanCameraPermissionMessage` | 权限说明弹窗正文 | 见类型定义默认值 |
|
||||||
|
| `webScanCameraPermissionConfirmText` | 确认按钮文案 | `继续` |
|
||||||
|
| `webScanCameraPermissionCancelText` | 取消按钮文案 | `取消` |
|
||||||
| `scanBeepAudio` | 扫码成功提示音地址(任意模式匹配成功时播放) | 内置提示音 |
|
| `scanBeepAudio` | 扫码成功提示音地址(任意模式匹配成功时播放) | 内置提示音 |
|
||||||
| `scanBeepEnabled` | 扫码成功是否播放提示音 | `true` |
|
| `scanBeepEnabled` | 扫码成功是否播放提示音 | `true` |
|
||||||
| `initWechatJssdk` | 微信 JSSDK 初始化配置,仅微信环境生效 | 无 |
|
| `initWechatJssdk` | 微信 JSSDK 初始化配置,仅微信环境生效 | 无 |
|
||||||
@@ -292,14 +294,6 @@ console.log(IScan.getStatus());
|
|||||||
IScan.startScan();
|
IScan.startScan();
|
||||||
```
|
```
|
||||||
|
|
||||||
### `scanVideo(): void`
|
|
||||||
|
|
||||||
直接开启 Web 摄像头扫码。扫码结果通过 `onScanListener` 回调。
|
|
||||||
|
|
||||||
```js
|
|
||||||
IScan.scanVideo();
|
|
||||||
```
|
|
||||||
|
|
||||||
### `scanImage(): void`
|
### `scanImage(): void`
|
||||||
|
|
||||||
直接选择图片进行识别。识别结果通过 `onScanListener` 回调。
|
直接选择图片进行识别。识别结果通过 `onScanListener` 回调。
|
||||||
|
|||||||
@@ -207,7 +207,6 @@
|
|||||||
<p>点击开始后,会按桥接、微信、Web 摄像头、图片识别的顺序选择可用扫码方式。</p>
|
<p>点击开始后,会按桥接、微信、Web 摄像头、图片识别的顺序选择可用扫码方式。</p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button onclick="startScan()" class="btn">开始扫码</button>
|
<button onclick="startScan()" class="btn">开始扫码</button>
|
||||||
<button onclick="scanVideo()" class="btn secondary">开启视频扫码</button>
|
|
||||||
<button onclick="scanImage()" class="btn secondary">选择图片识别</button>
|
<button onclick="scanImage()" class="btn secondary">选择图片识别</button>
|
||||||
<button onclick="stopScan()" class="btn secondary">停止扫码</button>
|
<button onclick="stopScan()" class="btn secondary">停止扫码</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -247,7 +246,6 @@
|
|||||||
|
|
||||||
IScan.startScan();
|
IScan.startScan();
|
||||||
IScan.scanImage();
|
IScan.scanImage();
|
||||||
IScan.scanVideo();
|
|
||||||
IScan.stopScan();</pre>
|
IScan.stopScan();</pre>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
@@ -349,10 +347,6 @@ IScan.stopScan();</pre>
|
|||||||
IScan.scanImage();
|
IScan.scanImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
function scanVideo() {
|
|
||||||
IScan.scanVideo();
|
|
||||||
}
|
|
||||||
|
|
||||||
ready();
|
ready();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
132
dist/index.d.ts
vendored
132
dist/index.d.ts
vendored
@@ -45,6 +45,58 @@ interface ScanConfigOptions {
|
|||||||
* 2. 结束扫码的方法名称为:stopScan
|
* 2. 结束扫码的方法名称为:stopScan
|
||||||
*/
|
*/
|
||||||
bridgeName?: string,
|
bridgeName?: string,
|
||||||
|
/**
|
||||||
|
* 桥接扫码超时(毫秒),超时后回退 Web/图片识别,默认 5000
|
||||||
|
*/
|
||||||
|
bridgeScanTimeout?: number,
|
||||||
|
/**
|
||||||
|
* 是否允许 H5 摄像头扫码:true 强制开启(仍需有媒体 API),false 强制关闭
|
||||||
|
*/
|
||||||
|
webScanCameraEnabled?: boolean,
|
||||||
|
/**
|
||||||
|
* @deprecated 请用 webScanCameraEnabled;保留兼容
|
||||||
|
*/
|
||||||
|
webScanCameraInWechat?: boolean,
|
||||||
|
/**
|
||||||
|
* @deprecated 摄像头权限已后置到 startScan,请用 webScanVideoAccessTimeout
|
||||||
|
*/
|
||||||
|
webScanCameraProbeTimeout?: number,
|
||||||
|
/**
|
||||||
|
* startScan 走 Web 摄像头前是否展示权限说明弹窗,默认 true
|
||||||
|
*/
|
||||||
|
webScanCameraPermissionDialogEnabled?: boolean,
|
||||||
|
/**
|
||||||
|
* 摄像头权限说明弹窗标题
|
||||||
|
*/
|
||||||
|
webScanCameraPermissionTitle?: string,
|
||||||
|
/**
|
||||||
|
* 摄像头权限说明弹窗正文
|
||||||
|
*/
|
||||||
|
webScanCameraPermissionMessage?: string,
|
||||||
|
/**
|
||||||
|
* 摄像头权限说明弹窗确认按钮文案
|
||||||
|
*/
|
||||||
|
webScanCameraPermissionConfirmText?: string,
|
||||||
|
/**
|
||||||
|
* 摄像头权限说明弹窗取消按钮文案
|
||||||
|
*/
|
||||||
|
webScanCameraPermissionCancelText?: string,
|
||||||
|
/**
|
||||||
|
* 选图后延迟读取 file 对象(毫秒),微信/安卓 WebView 默认 100
|
||||||
|
*/
|
||||||
|
webScanFileReadDelay?: number,
|
||||||
|
/**
|
||||||
|
* 强制使用 WASM ponyfill 识别(微信/部分 WebView 建议开启,默认微信内自动开启)
|
||||||
|
*/
|
||||||
|
webScanPreferPonyfill?: boolean,
|
||||||
|
/**
|
||||||
|
* WASM 文件完整 URL;未配置时相对 SDK 脚本地址解析 lib/reader.wasm
|
||||||
|
*/
|
||||||
|
webScanWasmUrl?: string,
|
||||||
|
/**
|
||||||
|
* WASM 基准路径(SDK 脚本 URL 或目录);用于 async/defer 加载时修正 reader.wasm 路径
|
||||||
|
*/
|
||||||
|
webScanWasmBaseUrl?: string,
|
||||||
/**
|
/**
|
||||||
* webScan是否启用,默认启用
|
* webScan是否启用,默认启用
|
||||||
*/
|
*/
|
||||||
@@ -101,6 +153,41 @@ interface ScanConfigOptions {
|
|||||||
* 摄像头已打开但长时间无画面时触发图片回退(毫秒),默认 8000
|
* 摄像头已打开但长时间无画面时触发图片回退(毫秒),默认 8000
|
||||||
*/
|
*/
|
||||||
webScanVideoReadyTimeout?: number,
|
webScanVideoReadyTimeout?: number,
|
||||||
|
/**
|
||||||
|
* 图片/回退识别时 detect 超时(毫秒),默认 15000
|
||||||
|
*/
|
||||||
|
webScanDetectTimeout?: number,
|
||||||
|
/**
|
||||||
|
* 图片识别是否优先使用 ZXing ponyfill(原生 detect 在部分 WebView 可能卡住),默认 true
|
||||||
|
*/
|
||||||
|
webScanImagePreferPonyfill?: boolean,
|
||||||
|
/**
|
||||||
|
* 移动端图片回退是否使用 capture 拍照(false 为相册选图,兼容性更好),默认 false
|
||||||
|
*/
|
||||||
|
webScanImagePreferCapture?: boolean,
|
||||||
|
/**
|
||||||
|
* 图片识别是否优先 canvas 解码(安卓 WebView 建议开启),默认在安卓/微信内自动开启
|
||||||
|
*/
|
||||||
|
webScanImageDetectPreferCanvas?: boolean,
|
||||||
|
/**
|
||||||
|
* ZXing WASM 加载超时(毫秒),默认 20000
|
||||||
|
*/
|
||||||
|
webScanPrepareTimeout?: number,
|
||||||
|
/**
|
||||||
|
* 选图等待超时(毫秒),默认 120000
|
||||||
|
*/
|
||||||
|
webScanChooseImageTimeout?: number,
|
||||||
|
/**
|
||||||
|
* 选图方式:button=显示「选择图片」按钮(安卓/微信默认,需用户点击);auto=自动弹出系统选图
|
||||||
|
*/
|
||||||
|
webScanImagePickerMode?: 'auto' | 'button',
|
||||||
|
webScanImagePickerTitle?: string,
|
||||||
|
webScanImagePickerButtonText?: string,
|
||||||
|
webScanImagePickerCancelText?: string,
|
||||||
|
/**
|
||||||
|
* 单次扫码会话超时(毫秒),超时后状态恢复 ready,默认 90000
|
||||||
|
*/
|
||||||
|
scanSessionTimeout?: number,
|
||||||
/**
|
/**
|
||||||
* 扫码成功提示音地址,默认使用内置提示音;任意识别模式匹配成功时播放
|
* 扫码成功提示音地址,默认使用内置提示音;任意识别模式匹配成功时播放
|
||||||
*/
|
*/
|
||||||
@@ -161,6 +248,16 @@ interface ScanResult {
|
|||||||
key: string
|
key: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫码错误
|
||||||
|
*/
|
||||||
|
interface ScanErrorInfo {
|
||||||
|
error: string,
|
||||||
|
key: string,
|
||||||
|
source?: string,
|
||||||
|
cancel?: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听key
|
* 监听key
|
||||||
*/
|
*/
|
||||||
@@ -197,6 +294,22 @@ type ScanStatus = "scanning" | "ready";
|
|||||||
*/
|
*/
|
||||||
type ScanResultCallback = (result: ScanResult) => any;
|
type ScanResultCallback = (result: ScanResult) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听扫码错误回调
|
||||||
|
*/
|
||||||
|
type ScanErrorCallback = (error: ScanErrorInfo) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫码错误监听信息
|
||||||
|
*/
|
||||||
|
interface ScanErrorListenerInfo {
|
||||||
|
key?: string;
|
||||||
|
match?: string;
|
||||||
|
level?: number;
|
||||||
|
listener: ScanErrorCallback;
|
||||||
|
cancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听状态回调
|
* 监听状态回调
|
||||||
*/
|
*/
|
||||||
@@ -227,6 +340,19 @@ interface IScan {
|
|||||||
* @param callback 监听回调,或监听key
|
* @param callback 监听回调,或监听key
|
||||||
*/
|
*/
|
||||||
offScanListener(callback: ScanResultCallback | string): void;
|
offScanListener(callback: ScanResultCallback | string): void;
|
||||||
|
/**
|
||||||
|
* 添加监听扫码错误(如图片识别失败)
|
||||||
|
* @param callback 错误回调
|
||||||
|
* @param key 监听 key
|
||||||
|
* @param match 可选正则,匹配 error 文本后回调
|
||||||
|
* @param level 优先级
|
||||||
|
*/
|
||||||
|
onScanErrorListener(callback: ScanErrorCallback, key: string, match?: string, level?: number): ScanErrorListenerInfo;
|
||||||
|
/**
|
||||||
|
* 取消监听扫码错误
|
||||||
|
* @param callback 监听回调,或监听 key
|
||||||
|
*/
|
||||||
|
offScanErrorListener(callback: ScanErrorCallback | string): void;
|
||||||
/**
|
/**
|
||||||
* 获取扫码状态
|
* 获取扫码状态
|
||||||
* @returns ScanStatus
|
* @returns ScanStatus
|
||||||
@@ -240,14 +366,12 @@ interface IScan {
|
|||||||
* 开启扫码
|
* 开启扫码
|
||||||
*/
|
*/
|
||||||
startScan(): void;
|
startScan(): void;
|
||||||
/**
|
|
||||||
* 开启视频扫码
|
|
||||||
*/
|
|
||||||
scanVideo(): void;
|
|
||||||
/**
|
/**
|
||||||
* 选择图片进行识别
|
* 选择图片进行识别
|
||||||
*/
|
*/
|
||||||
scanImage(): void;
|
scanImage(): void;
|
||||||
|
/** 由业务/原生传入已选图片 File 识别(WebView input.files 异常时使用) */
|
||||||
|
scanImageFromFile(file: File | Blob): void;
|
||||||
/**
|
/**
|
||||||
* 清除全部监听
|
* 清除全部监听
|
||||||
*/
|
*/
|
||||||
|
|||||||
7
dist/index.html
vendored
7
dist/index.html
vendored
@@ -161,7 +161,7 @@
|
|||||||
|
|
||||||
section {
|
section {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}</style></head><body><main class="page"><section class="hero"><h1>IScan 通用扫码SDK</h1><p>统一接入桥接扫码、微信小程序、微信 JSSDK 扫码、Web 摄像头扫码、图片识别和扫码枪输入。</p></section><section class="grid"><div class="card"><h2>功能说明</h2><ul class="feature-list"><li>支持桥接扫码、微信 JSSDK 扫码、Web 摄像头扫码、图片识别和扫码枪输入。</li><li>浏览器环境使用WASM库识别二维码和条形码。</li><li>扫码结果由监听规则统一回调,支持正则匹配和优先级排序。</li></ul></div><div class="card"><h2>当前状态</h2><p>SDK 状态:<span id="status" class="status">loading</span></p><p>运行环境:</p><pre id="output" class="panel"></pre></div></section><section class="card"><h2>操作</h2><p>点击开始后,会按桥接、微信、Web 摄像头、图片识别的顺序选择可用扫码方式。</p><div class="actions"><button onclick="startScan()" class="btn">开始扫码</button> <button onclick="scanVideo()" class="btn secondary">开启视频扫码</button> <button onclick="scanImage()" class="btn secondary">选择图片识别</button> <button onclick="stopScan()" class="btn secondary">停止扫码</button></div></section><section class="card"><h2>扫码结果</h2><pre id="result" class="panel result"></pre></section><section class="card"><h2>错误信息</h2><pre id="error" class="panel error"></pre><p style="color: #919191">错误信息可能来源于扫码结果、扫码过程、扫码初始化等。</p></section><section class="card code"><h2>接入方式</h2><pre>IScan.config({
|
}</style></head><body><main class="page"><section class="hero"><h1>IScan 通用扫码SDK</h1><p>统一接入桥接扫码、微信小程序、微信 JSSDK 扫码、Web 摄像头扫码、图片识别和扫码枪输入。</p></section><section class="grid"><div class="card"><h2>功能说明</h2><ul class="feature-list"><li>支持桥接扫码、微信 JSSDK 扫码、Web 摄像头扫码、图片识别和扫码枪输入。</li><li>浏览器环境使用WASM库识别二维码和条形码。</li><li>扫码结果由监听规则统一回调,支持正则匹配和优先级排序。</li></ul></div><div class="card"><h2>当前状态</h2><p>SDK 状态:<span id="status" class="status">loading</span></p><p>运行环境:</p><pre id="output" class="panel"></pre></div></section><section class="card"><h2>操作</h2><p>点击开始后,会按桥接、微信、Web 摄像头、图片识别的顺序选择可用扫码方式。</p><div class="actions"><button onclick="startScan()" class="btn">开始扫码</button> <button onclick="scanImage()" class="btn secondary">选择图片识别</button> <button onclick="stopScan()" class="btn secondary">停止扫码</button></div></section><section class="card"><h2>扫码结果</h2><pre id="result" class="panel result"></pre></section><section class="card"><h2>错误信息</h2><pre id="error" class="panel error"></pre><p style="color: #919191">错误信息可能来源于扫码结果、扫码过程、扫码初始化等。</p></section><section class="card code"><h2>接入方式</h2><pre>IScan.config({
|
||||||
webScanEnabled: true,
|
webScanEnabled: true,
|
||||||
webScanCanvasEnabled: true,
|
webScanCanvasEnabled: true,
|
||||||
webScanCloseButtonStyle: "background: rgba(27, 99, 244, 0.88);",
|
webScanCloseButtonStyle: "background: rgba(27, 99, 244, 0.88);",
|
||||||
@@ -182,7 +182,6 @@
|
|||||||
|
|
||||||
IScan.startScan();
|
IScan.startScan();
|
||||||
IScan.scanImage();
|
IScan.scanImage();
|
||||||
IScan.scanVideo();
|
|
||||||
IScan.stopScan();</pre></section></main><script>(function () {
|
IScan.stopScan();</pre></section></main><script>(function () {
|
||||||
output(window.navigator.userAgent);
|
output(window.navigator.userAgent);
|
||||||
window.onerror = function (message, source, lineno, colno, err) {
|
window.onerror = function (message, source, lineno, colno, err) {
|
||||||
@@ -279,8 +278,4 @@ IScan.stopScan();</pre></section></main><script>(function () {
|
|||||||
IScan.scanImage();
|
IScan.scanImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
function scanVideo() {
|
|
||||||
IScan.scanVideo();
|
|
||||||
}
|
|
||||||
|
|
||||||
ready();</script><script src="index.js"></script></body></html>
|
ready();</script><script src="index.js"></script></body></html>
|
||||||
2
dist/index.js
vendored
2
dist/index.js
vendored
File diff suppressed because one or more lines are too long
16
dist/index.md
vendored
16
dist/index.md
vendored
@@ -61,9 +61,6 @@ IScan.config({
|
|||||||
// 自动选择可用扫码方式:桥接 -> 微信 -> Web 摄像头 -> 图片识别
|
// 自动选择可用扫码方式:桥接 -> 微信 -> Web 摄像头 -> 图片识别
|
||||||
IScan.startScan();
|
IScan.startScan();
|
||||||
|
|
||||||
// 仅打开 Web 视频扫码
|
|
||||||
IScan.scanVideo();
|
|
||||||
|
|
||||||
// 仅选择图片识别
|
// 仅选择图片识别
|
||||||
IScan.scanImage();
|
IScan.scanImage();
|
||||||
|
|
||||||
@@ -126,6 +123,11 @@ interface ScanConfigOptions {
|
|||||||
| `webScanImageFallbackOnVideoError` | 摄像头不可用或打开失败时,是否自动弹出拍照/选图(适用于部分安卓内置浏览器) | `true` |
|
| `webScanImageFallbackOnVideoError` | 摄像头不可用或打开失败时,是否自动弹出拍照/选图(适用于部分安卓内置浏览器) | `true` |
|
||||||
| `webScanVideoAccessTimeout` | 打开摄像头超时(毫秒),超时后走图片回退 | `10000` |
|
| `webScanVideoAccessTimeout` | 打开摄像头超时(毫秒),超时后走图片回退 | `10000` |
|
||||||
| `webScanVideoReadyTimeout` | 摄像头已开但无画面超时(毫秒),超时后走图片回退 | `8000` |
|
| `webScanVideoReadyTimeout` | 摄像头已开但无画面超时(毫秒),超时后走图片回退 | `8000` |
|
||||||
|
| `webScanCameraPermissionDialogEnabled` | `startScan` 走 Web 摄像头前是否先展示权限说明弹窗 | `true` |
|
||||||
|
| `webScanCameraPermissionTitle` | 权限说明弹窗标题 | `需要使用摄像头` |
|
||||||
|
| `webScanCameraPermissionMessage` | 权限说明弹窗正文 | 见类型定义默认值 |
|
||||||
|
| `webScanCameraPermissionConfirmText` | 确认按钮文案 | `继续` |
|
||||||
|
| `webScanCameraPermissionCancelText` | 取消按钮文案 | `取消` |
|
||||||
| `scanBeepAudio` | 扫码成功提示音地址(任意模式匹配成功时播放) | 内置提示音 |
|
| `scanBeepAudio` | 扫码成功提示音地址(任意模式匹配成功时播放) | 内置提示音 |
|
||||||
| `scanBeepEnabled` | 扫码成功是否播放提示音 | `true` |
|
| `scanBeepEnabled` | 扫码成功是否播放提示音 | `true` |
|
||||||
| `initWechatJssdk` | 微信 JSSDK 初始化配置,仅微信环境生效 | 无 |
|
| `initWechatJssdk` | 微信 JSSDK 初始化配置,仅微信环境生效 | 无 |
|
||||||
@@ -292,14 +294,6 @@ console.log(IScan.getStatus());
|
|||||||
IScan.startScan();
|
IScan.startScan();
|
||||||
```
|
```
|
||||||
|
|
||||||
### `scanVideo(): void`
|
|
||||||
|
|
||||||
直接开启 Web 摄像头扫码。扫码结果通过 `onScanListener` 回调。
|
|
||||||
|
|
||||||
```js
|
|
||||||
IScan.scanVideo();
|
|
||||||
```
|
|
||||||
|
|
||||||
### `scanImage(): void`
|
### `scanImage(): void`
|
||||||
|
|
||||||
直接选择图片进行识别。识别结果通过 `onScanListener` 回调。
|
直接选择图片进行识别。识别结果通过 `onScanListener` 回调。
|
||||||
|
|||||||
30
src/_core.js
30
src/_core.js
@@ -1,12 +1,19 @@
|
|||||||
import './polyfill';
|
import './polyfill';
|
||||||
import {
|
import {
|
||||||
supportList,
|
supportList,
|
||||||
onScanListener, offScanListener, setStatusListener, getStatus,
|
onScanListener, offScanListener,
|
||||||
startScan, stopScan, scanVideo, scanImage, clear
|
onScanErrorListener, offScanErrorListener,
|
||||||
|
setStatusListener, getStatus,
|
||||||
|
startScan, stopScan, scanImage, scanImageFromFile, clear
|
||||||
} from './services/provider/scan';
|
} from './services/provider/scan';
|
||||||
import { setConfig, getVersion } from './services/config';
|
import { setConfig, getVersion } from './services/config';
|
||||||
import { initWxJssdk } from './services/wx';
|
import { initWxJssdk } from './services/wx';
|
||||||
import { printDebug } from './utils/logger';
|
import {
|
||||||
|
isSupportWebScan,
|
||||||
|
prepareWebScanBarcodeDetector,
|
||||||
|
isSupportImageScan
|
||||||
|
} from './services/web';
|
||||||
|
import { printDebug, printWarn } from './utils/logger';
|
||||||
|
|
||||||
let _readyPromise = null;
|
let _readyPromise = null;
|
||||||
let _calledReady = false;
|
let _calledReady = false;
|
||||||
@@ -23,18 +30,25 @@ function config(config) {
|
|||||||
return _readyPromise;
|
return _readyPromise;
|
||||||
}
|
}
|
||||||
_readyPromise = Promise.resolve().then(() => {
|
_readyPromise = Promise.resolve().then(() => {
|
||||||
|
return initWxJssdk().catch(err => {
|
||||||
|
printDebug('init wx jssdk failed:', err && err.message ? err.message : err);
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
printDebug('-------------------------------------');
|
printDebug('-------------------------------------');
|
||||||
printDebug('sdk_version:', getVersion());
|
printDebug('sdk_version:', getVersion());
|
||||||
printDebug('support_list:', supportList.map(item => item.name + ':' + item.support).join(', '));
|
printDebug('support_list:', supportList.map(item => item.name + ':' + item.support).join(', '));
|
||||||
printDebug('-------------------------------------');
|
printDebug('-------------------------------------');
|
||||||
initWxJssdk();
|
if (isSupportWebScan() || isSupportImageScan()) {
|
||||||
return Promise.resolve().then(() => {
|
return prepareWebScanBarcodeDetector().catch(err => {
|
||||||
|
printWarn('prepare barcode detector failed:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
_calledReady = true;
|
_calledReady = true;
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
_readyPromise = null;
|
_readyPromise = null;
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
})
|
|
||||||
return _readyPromise;
|
return _readyPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,11 +56,13 @@ export default Object.assign({}, {
|
|||||||
config,
|
config,
|
||||||
onScanListener,
|
onScanListener,
|
||||||
offScanListener,
|
offScanListener,
|
||||||
|
onScanErrorListener,
|
||||||
|
offScanErrorListener,
|
||||||
setStatusListener,
|
setStatusListener,
|
||||||
getStatus,
|
getStatus,
|
||||||
startScan,
|
startScan,
|
||||||
stopScan,
|
stopScan,
|
||||||
scanVideo,
|
|
||||||
scanImage,
|
scanImage,
|
||||||
|
scanImageFromFile,
|
||||||
clear,
|
clear,
|
||||||
});
|
});
|
||||||
@@ -13,8 +13,12 @@ import { createUUID } from "./utils/uuid";
|
|||||||
import {
|
import {
|
||||||
dispatchEmbedScanResult,
|
dispatchEmbedScanResult,
|
||||||
acknowledgeEmbedScanConsumed,
|
acknowledgeEmbedScanConsumed,
|
||||||
|
dispatchEmbedScanError,
|
||||||
} from "./services/provider/scan";
|
} from "./services/provider/scan";
|
||||||
import { setEmbedScanResultForwarder } from "./services/embedScanBridge";
|
import {
|
||||||
|
setEmbedScanResultForwarder,
|
||||||
|
setEmbedScanErrorForwarder,
|
||||||
|
} from "./services/embedScanBridge";
|
||||||
|
|
||||||
const EMBED_SOURCE = "IScanEmbed";
|
const EMBED_SOURCE = "IScanEmbed";
|
||||||
const EMBED_V = 1;
|
const EMBED_V = 1;
|
||||||
@@ -25,6 +29,8 @@ const embedChildSources = new Set();
|
|||||||
const EMBED_LISTENER_METHODS = new Set([
|
const EMBED_LISTENER_METHODS = new Set([
|
||||||
"onScanListener",
|
"onScanListener",
|
||||||
"offScanListener",
|
"offScanListener",
|
||||||
|
"onScanErrorListener",
|
||||||
|
"offScanErrorListener",
|
||||||
"clear",
|
"clear",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -105,6 +111,26 @@ function hydrateEmbedParams(params, messageSource, targetOrigin) {
|
|||||||
return params.map(walk);
|
return params.map(walk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function broadcastScanErrorToEmbedChildren(error) {
|
||||||
|
if (embedChildSources.size === 0 || error == null || error === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
embedChildSources.forEach((source) => {
|
||||||
|
try {
|
||||||
|
source.postMessage(
|
||||||
|
{
|
||||||
|
source: EMBED_SOURCE,
|
||||||
|
v: EMBED_V,
|
||||||
|
kind: "forwardScanError",
|
||||||
|
error,
|
||||||
|
},
|
||||||
|
"*"
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function broadcastScanResultToEmbedChildren(result) {
|
function broadcastScanResultToEmbedChildren(result) {
|
||||||
if (embedChildSources.size === 0 || result == null || result === "") {
|
if (embedChildSources.size === 0 || result == null || result === "") {
|
||||||
return;
|
return;
|
||||||
@@ -198,6 +224,12 @@ function embedChildOnMessage(ev) {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (data.kind === "forwardScanError") {
|
||||||
|
if (typeof data.error === "string") {
|
||||||
|
dispatchEmbedScanError(data.error);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (data.kind === "invokeResult") {
|
if (data.kind === "invokeResult") {
|
||||||
const pending = pendingInvokes[data.id];
|
const pending = pendingInvokes[data.id];
|
||||||
if (!pending) {
|
if (!pending) {
|
||||||
@@ -307,6 +339,7 @@ export function installEmbedHost(lib) {
|
|||||||
}
|
}
|
||||||
embedHostInstalled = true;
|
embedHostInstalled = true;
|
||||||
setEmbedScanResultForwarder(broadcastScanResultToEmbedChildren);
|
setEmbedScanResultForwarder(broadcastScanResultToEmbedChildren);
|
||||||
|
setEmbedScanErrorForwarder(broadcastScanErrorToEmbedChildren);
|
||||||
window.addEventListener("message", (ev) => {
|
window.addEventListener("message", (ev) => {
|
||||||
const data = ev.data;
|
const data = ev.data;
|
||||||
if (!isEmbedMessage(data)) {
|
if (!isEmbedMessage(data)) {
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
|
import './polyfill';
|
||||||
import core from "./_core";
|
import core from "./_core";
|
||||||
import _global from './polyfill/_global';
|
import _global from './polyfill/_global';
|
||||||
import { exportSDK, installEmbedHost } from './_export';
|
import { exportSDK, installEmbedHost } from './_export';
|
||||||
|
import { setSdkScriptSrc } from './services/web';
|
||||||
|
|
||||||
|
if (typeof document !== 'undefined' && document.currentScript && document.currentScript.src) {
|
||||||
|
setSdkScriptSrc(document.currentScript.src);
|
||||||
|
}
|
||||||
|
|
||||||
const IScan = exportSDK(core, null, "config", "setStatusListener", "onScanListener",
|
const IScan = exportSDK(core, null, "config", "setStatusListener", "onScanListener",
|
||||||
"offScanListener", "stopScan", "startScan", "scanImage", "clear");
|
"offScanListener", "onScanErrorListener", "offScanErrorListener", "stopScan", "startScan", "scanImage", "scanImageFromFile", "clear");
|
||||||
|
|
||||||
installEmbedHost(core);
|
installEmbedHost(core);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { polyfill } from 'es6-promise';
|
import { polyfill } from 'es6-promise';
|
||||||
|
|
||||||
|
// Object.hasOwn (ES2022) — barcode-detector / ZXing 依赖,旧版 WebView 无此方法
|
||||||
|
if (typeof Object.hasOwn !== 'function') {
|
||||||
|
Object.hasOwn = function hasOwn(object, key) {
|
||||||
|
return Object.prototype.hasOwnProperty.call(object, key);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Object.assign
|
// Object.assign
|
||||||
if (typeof Object.assign != 'function') {
|
if (typeof Object.assign != 'function') {
|
||||||
// Must be writable: true, enumerable: false, configurable: true
|
// Must be writable: true, enumerable: false, configurable: true
|
||||||
|
|||||||
@@ -1,12 +1,24 @@
|
|||||||
/** 父页识别到扫码结果时,向嵌入 iframe 转发的回调(由 installEmbedHost 注册) */
|
/** 父页识别到扫码结果时,向嵌入 iframe 转发的回调(由 installEmbedHost 注册) */
|
||||||
let embedScanResultForwarder = null;
|
let embedScanResultForwarder = null;
|
||||||
|
/** 父页图片识别失败时,向嵌入 iframe 转发的回调 */
|
||||||
|
let embedScanErrorForwarder = null;
|
||||||
|
|
||||||
export function setEmbedScanResultForwarder(fn) {
|
export function setEmbedScanResultForwarder(fn) {
|
||||||
embedScanResultForwarder = typeof fn === "function" ? fn : null;
|
embedScanResultForwarder = typeof fn === "function" ? fn : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setEmbedScanErrorForwarder(fn) {
|
||||||
|
embedScanErrorForwarder = typeof fn === "function" ? fn : null;
|
||||||
|
}
|
||||||
|
|
||||||
export function forwardEmbedScanResultIfNeeded(result) {
|
export function forwardEmbedScanResultIfNeeded(result) {
|
||||||
if (embedScanResultForwarder && result != null && result !== "") {
|
if (embedScanResultForwarder && result != null && result !== "") {
|
||||||
embedScanResultForwarder(result);
|
embedScanResultForwarder(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function forwardEmbedScanErrorIfNeeded(error) {
|
||||||
|
if (embedScanErrorForwarder && error != null && error !== "") {
|
||||||
|
embedScanErrorForwarder(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,28 +7,83 @@ import {
|
|||||||
startScanForImage,
|
startScanForImage,
|
||||||
unlockScanBeep,
|
unlockScanBeep,
|
||||||
playScanBeep,
|
playScanBeep,
|
||||||
isWebScanImageFallbackEnabled
|
isWebScanImageFallbackEnabled,
|
||||||
|
canUseWebCameraScan,
|
||||||
|
shouldSkipWebCameraProbe,
|
||||||
|
cleanupWebScanResiduals,
|
||||||
|
detectImageFileForScan
|
||||||
} from "../web";
|
} from "../web";
|
||||||
import { isSupportWxScan, startScanForWx } from "../wx";
|
import { isSupportWxScan, startScanForWx, isWxEnv } from "../wx";
|
||||||
import { startScanner, stopScanner } from "../scanner";
|
import { startScanner, stopScanner } from "../scanner";
|
||||||
import { getConfig } from "../config";
|
import { getConfig } from "../config";
|
||||||
import { toAny } from "../../utils/toany";
|
import { toAny } from "../../utils/toany";
|
||||||
import { printDebug } from "../../utils/logger";
|
import { printDebug, printWarn } from "../../utils/logger";
|
||||||
import { forwardEmbedScanResultIfNeeded } from "../embedScanBridge";
|
import { forwardEmbedScanResultIfNeeded, forwardEmbedScanErrorIfNeeded } from "../embedScanBridge";
|
||||||
|
|
||||||
let _scan_status = "ready";
|
let _scan_status = "ready";
|
||||||
let _scan_status_listener = null;
|
let _scan_status_listener = null;
|
||||||
let _scan_listener_list = [];
|
let _scan_listener_list = [];
|
||||||
|
let _scan_error_listener_list = [];
|
||||||
let _scan_resolve = null;
|
let _scan_resolve = null;
|
||||||
let _scan_closing = false;
|
let _scan_closing = false;
|
||||||
let _scan_next_start_time = 0;
|
let _scan_next_start_time = 0;
|
||||||
|
|
||||||
const SCAN_RESTART_DELAY = 500;
|
const SCAN_RESTART_DELAY = 500;
|
||||||
|
const BRIDGE_SCAN_TIMEOUT = 5000;
|
||||||
|
const SCAN_SESSION_TIMEOUT = 90000;
|
||||||
|
|
||||||
function getScanRestartDelay() {
|
function getScanRestartDelay() {
|
||||||
return toAny(getConfig("scanRestartDelay"), SCAN_RESTART_DELAY);
|
return toAny(getConfig("scanRestartDelay"), SCAN_RESTART_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBridgeScanTimeout() {
|
||||||
|
const timeout = getConfig("bridgeScanTimeout");
|
||||||
|
return typeof timeout === "number" && timeout > 0 ? timeout : BRIDGE_SCAN_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getScanSessionTimeout() {
|
||||||
|
const timeout = getConfig("scanSessionTimeout");
|
||||||
|
return typeof timeout === "number" && timeout > 0 ? timeout : SCAN_SESSION_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
function withScanSessionTimeout(scanPromise) {
|
||||||
|
return Promise.race([
|
||||||
|
scanPromise,
|
||||||
|
new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
printWarn("scan session timeout");
|
||||||
|
resolve({ cancel: 1, scanTimeout: true });
|
||||||
|
}, getScanSessionTimeout());
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __fallbackScanAfterBridgeFailure(err) {
|
||||||
|
if (!isScanning()) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
printWarn("bridge scan unavailable, fallback:", err);
|
||||||
|
return __startNonBridgeScan(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __fallbackScanAfterWxFailure(err) {
|
||||||
|
if (!isScanning()) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
printWarn("wx scan unavailable, fallback to web/image scan:", err);
|
||||||
|
return __startNonBridgeScan(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __startNonBridgeScan(err) {
|
||||||
|
if (isSupportWebScan()) {
|
||||||
|
return __startWebScan();
|
||||||
|
}
|
||||||
|
if (isSupportImageScan()) {
|
||||||
|
return __startImageScan();
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
function parseBarcodeString(input) {
|
function parseBarcodeString(input) {
|
||||||
// 标准化的类型映射表:将各种变体映射到统一标识
|
// 标准化的类型映射表:将各种变体映射到统一标识
|
||||||
// 这样即使传入 EAN_13、EAN-13、EAN13 都能匹配成功
|
// 这样即使传入 EAN_13、EAN-13、EAN13 都能匹配成功
|
||||||
@@ -120,6 +175,68 @@ function __result(result) {
|
|||||||
return matched;
|
return matched;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeScanError(raw) {
|
||||||
|
if (typeof raw === "string") {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
if (raw && raw.error != null && raw.error !== "") {
|
||||||
|
return String(raw.error);
|
||||||
|
}
|
||||||
|
if (raw && raw.message) {
|
||||||
|
return String(raw.message);
|
||||||
|
}
|
||||||
|
if (raw == null || raw === "") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return String(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __scanError(raw, meta) {
|
||||||
|
const error = normalizeScanError(raw);
|
||||||
|
if (!error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
forwardEmbedScanErrorIfNeeded(error);
|
||||||
|
let matched = false;
|
||||||
|
for (let i = 0; i < _scan_error_listener_list.length; i++) {
|
||||||
|
const item = _scan_error_listener_list[i];
|
||||||
|
if (item.listener && __match(error, item.match)) {
|
||||||
|
matched = true;
|
||||||
|
item.listener(Object.assign({
|
||||||
|
error,
|
||||||
|
key: item.key
|
||||||
|
}, meta || {}));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matched;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __notifyImageScanFailure(raw, meta) {
|
||||||
|
if (raw && raw.cancel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const error = normalizeScanError(raw);
|
||||||
|
const notified = __scanError(raw, Object.assign({ source: "image" }, meta || {}));
|
||||||
|
if (!notified && error) {
|
||||||
|
printWarn("image scan failed:", error);
|
||||||
|
}
|
||||||
|
return notified;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __notifyWebScanFailure(raw, meta) {
|
||||||
|
if (raw && raw.cancel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const payload = raw && raw.error != null ? raw.error : raw;
|
||||||
|
const error = normalizeScanError(payload);
|
||||||
|
const notified = __scanError(payload, Object.assign({ source: "web" }, meta || {}));
|
||||||
|
if (!notified && error) {
|
||||||
|
printWarn("web scan failed:", error);
|
||||||
|
}
|
||||||
|
return notified;
|
||||||
|
}
|
||||||
|
|
||||||
function __hasMatchedListener(result) {
|
function __hasMatchedListener(result) {
|
||||||
for (let i = 0; i < _scan_listener_list.length; i++) {
|
for (let i = 0; i < _scan_listener_list.length; i++) {
|
||||||
const item = _scan_listener_list[i];
|
const item = _scan_listener_list[i];
|
||||||
@@ -178,6 +295,15 @@ export function dispatchEmbedScanResult(raw) {
|
|||||||
return __scannerResult(result);
|
return __scannerResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父页通过 postMessage 将识别错误投递到嵌入 iframe 时调用。
|
||||||
|
* @returns {boolean} 是否有监听消费了该错误
|
||||||
|
*/
|
||||||
|
export function dispatchEmbedScanError(raw) {
|
||||||
|
const error = normalizeScanError(raw);
|
||||||
|
return __scanError(error, { source: "image" });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 嵌入 iframe 已消费扫码结果时通知父页结束当前识别(关闭摄像头/UI 等)。
|
* 嵌入 iframe 已消费扫码结果时通知父页结束当前识别(关闭摄像头/UI 等)。
|
||||||
*/
|
*/
|
||||||
@@ -222,7 +348,7 @@ function __scannerResult(result) {
|
|||||||
function __startBridgeScan() {
|
function __startBridgeScan() {
|
||||||
return bridgeAsync("startScan", {
|
return bridgeAsync("startScan", {
|
||||||
closeable: true
|
closeable: true
|
||||||
}).then(resp => {
|
}, getBridgeScanTimeout()).then(resp => {
|
||||||
if (!isScanning()) {
|
if (!isScanning()) {
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
@@ -238,10 +364,10 @@ function __startBridgeScan() {
|
|||||||
return resp;
|
return resp;
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
if (!isScanning()) {
|
if (!isScanning()) {
|
||||||
return err;
|
throw err;
|
||||||
}
|
}
|
||||||
if (!err || !err.result) {
|
if (!err || !err.result) {
|
||||||
return err;
|
throw err;
|
||||||
}
|
}
|
||||||
if (__result(err.result)) {
|
if (__result(err.result)) {
|
||||||
return err;
|
return err;
|
||||||
@@ -261,6 +387,9 @@ function __startWxScan() {
|
|||||||
if (!isScanning()) {
|
if (!isScanning()) {
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
if (resp && resp.error && !resp.result) {
|
||||||
|
throw resp.error;
|
||||||
|
}
|
||||||
if (!resp || !resp.result) {
|
if (!resp || !resp.result) {
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
@@ -270,43 +399,50 @@ function __startWxScan() {
|
|||||||
return resp;
|
return resp;
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
if (!isScanning()) {
|
if (!isScanning()) {
|
||||||
return err;
|
throw err;
|
||||||
}
|
|
||||||
if (!err || !err.result) {
|
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
if (err && err.result) {
|
||||||
if (__result(err.result)) {
|
if (__result(err.result)) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
return err;
|
return err;
|
||||||
|
}
|
||||||
|
return __fallbackScanAfterWxFailure(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function __startWebScan() {
|
function __startWebScan(useImageScan = false) {
|
||||||
return startScanForWeb(__result).then(resp => {
|
return startScanForWeb(__result, __scanError).then(resp => {
|
||||||
if (!isScanning()) {
|
if (!isScanning()) {
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
if (!resp || !resp.result) {
|
if (!resp || !resp.result) {
|
||||||
|
if (resp && resp.success === false) {
|
||||||
|
__notifyWebScanFailure(resp);
|
||||||
|
}
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
if (__result(resp.result)) {
|
__result(resp.result);
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
if (isScanning()) {
|
|
||||||
return __startWebScan();
|
|
||||||
}
|
|
||||||
return resp;
|
return resp;
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
if (!isScanning()) {
|
if (!isScanning()) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
if (err && err.cancel || err && err.imageFallbackUsed) {
|
if (err && err.cancel) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
if (isWebScanImageFallbackEnabled()) {
|
if (err && err.scanTimeout) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (err && err.imageFallbackUsed) {
|
||||||
|
__notifyWebScanFailure(err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (isWebScanImageFallbackEnabled() && useImageScan) {
|
||||||
return __startImageScan();
|
return __startImageScan();
|
||||||
}
|
}
|
||||||
|
__notifyWebScanFailure(err);
|
||||||
|
printWarn("web scan failed:", err);
|
||||||
return err;
|
return err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -316,29 +452,27 @@ function __startImageScan() {
|
|||||||
if (!isScanning()) {
|
if (!isScanning()) {
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
if (resp && resp.cancel) {
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
if (!resp || !resp.result) {
|
if (!resp || !resp.result) {
|
||||||
|
__notifyImageScanFailure(resp);
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
if (__result(resp.result)) {
|
__result(resp.result);
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
if (isScanning()) {
|
|
||||||
return __startImageScan();
|
|
||||||
}
|
|
||||||
return resp;
|
return resp;
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
if (!isScanning()) {
|
if (!isScanning()) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
if (!err || !err.result) {
|
if (err && err.cancel) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
if (__result(err.result)) {
|
if (err && err.result) {
|
||||||
|
__result(err.result);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
if (isScanning()) {
|
__notifyImageScanFailure(err);
|
||||||
return __startImageScan();
|
|
||||||
}
|
|
||||||
return err;
|
return err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -353,6 +487,11 @@ export function clear() {
|
|||||||
item.cancel();
|
item.cancel();
|
||||||
}
|
}
|
||||||
_scan_listener_list.length = 0;
|
_scan_listener_list.length = 0;
|
||||||
|
for (let i = 0; i < _scan_error_listener_list.length; i++) {
|
||||||
|
const item = _scan_error_listener_list[i];
|
||||||
|
item.cancel();
|
||||||
|
}
|
||||||
|
_scan_error_listener_list.length = 0;
|
||||||
__checkScanner();
|
__checkScanner();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,6 +555,68 @@ export function offScanListener(listener) {
|
|||||||
__checkScanner();
|
__checkScanner();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createScanErrorListenerItem(listener, key, match, level) {
|
||||||
|
const item = {
|
||||||
|
key,
|
||||||
|
match: match || "",
|
||||||
|
level: level || 0,
|
||||||
|
listener: listener,
|
||||||
|
cancel: () => {
|
||||||
|
const index = _scan_error_listener_list.indexOf(item);
|
||||||
|
if (index !== -1) {
|
||||||
|
const items = _scan_error_listener_list.splice(index, 1);
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const removed = items[i];
|
||||||
|
removed.listener && removed.listener({ cancel: 1, key: removed.key });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onScanErrorListener(listener, key, match, level) {
|
||||||
|
if (!key || typeof key !== 'string') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof listener !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let item = null;
|
||||||
|
for (let i = 0; i < _scan_error_listener_list.length; i++) {
|
||||||
|
const _i = _scan_error_listener_list[i];
|
||||||
|
if (_i.key === key) {
|
||||||
|
item = _i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item) {
|
||||||
|
item.level = level;
|
||||||
|
item.match = match;
|
||||||
|
item.listener = listener;
|
||||||
|
} else {
|
||||||
|
item = createScanErrorListenerItem(listener, key, match, level);
|
||||||
|
_scan_error_listener_list.push(item);
|
||||||
|
}
|
||||||
|
_scan_error_listener_list.sort((a, b) => b.level - a.level);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function offScanErrorListener(listener) {
|
||||||
|
for (let i = 0; i < _scan_error_listener_list.length; i++) {
|
||||||
|
const _i = _scan_error_listener_list[i];
|
||||||
|
if (typeof listener === 'string') {
|
||||||
|
if (_i.key === listener) {
|
||||||
|
_i.cancel();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (_i.listener === listener) {
|
||||||
|
_i.cancel();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function setStatusListener(listener) {
|
export function setStatusListener(listener) {
|
||||||
if (typeof listener !== 'function') {
|
if (typeof listener !== 'function') {
|
||||||
return;
|
return;
|
||||||
@@ -428,11 +629,15 @@ export function getStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function stopScan() {
|
export function stopScan() {
|
||||||
|
cleanupWebScanResiduals();
|
||||||
if (!isScanning()) {
|
if (!isScanning()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const resolve = __finishScan();
|
||||||
|
_scan_closing = true;
|
||||||
__stopCurrentScan().then(() => {
|
__stopCurrentScan().then(() => {
|
||||||
__closed();
|
resolve && resolve({ cancel: 1 });
|
||||||
|
_scan_closing = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,45 +653,23 @@ export function startScan() {
|
|||||||
});
|
});
|
||||||
let scanPromise = Promise.resolve();
|
let scanPromise = Promise.resolve();
|
||||||
if (inRuntime()) {
|
if (inRuntime()) {
|
||||||
scanPromise = __startBridgeScan();
|
scanPromise = __startBridgeScan().catch(__fallbackScanAfterBridgeFailure);
|
||||||
} else if (isSupportWxScan()) {
|
} else if (isSupportWxScan()) {
|
||||||
scanPromise = __startWxScan();
|
scanPromise = __startWxScan();
|
||||||
} else if (isSupportWebScan()) {
|
} else if (isSupportWebScan()) {
|
||||||
scanPromise = __startWebScan();
|
scanPromise = __startWebScan(true);
|
||||||
} else if (isSupportImageScan()) {
|
} else if (isSupportImageScan()) {
|
||||||
scanPromise = __startImageScan();
|
scanPromise = __startImageScan();
|
||||||
} else {
|
} else {
|
||||||
printDebug("Not support scanner");
|
printWarn("Not support scanner");
|
||||||
}
|
}
|
||||||
return Promise.race([scanPromise, scannerPromise]);
|
return withScanSessionTimeout(Promise.race([scanPromise, scannerPromise]));
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
_scan_resolve = null;
|
_scan_resolve = null;
|
||||||
__closed();
|
__closed();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function scanVideo() {
|
|
||||||
if (!isSupportWebScan()) {
|
|
||||||
printDebug("Not support video scanner");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isScanning() || _scan_closing || Date.now() < _scan_next_start_time) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
unlockScanBeep();
|
|
||||||
Promise.resolve().then(() => {
|
|
||||||
__scanning();
|
|
||||||
return startScanForWeb(__result).then(resp => {
|
|
||||||
if (resp && resp.result) {
|
|
||||||
__result(resp.result);
|
|
||||||
}
|
|
||||||
throw resp.error;
|
|
||||||
}).catch(err => { });
|
|
||||||
}).finally(() => {
|
|
||||||
__closed();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function scanImage() {
|
export function scanImage() {
|
||||||
if (!isSupportImageScan()) {
|
if (!isSupportImageScan()) {
|
||||||
printDebug("Not support image scanner");
|
printDebug("Not support image scanner");
|
||||||
@@ -496,14 +679,49 @@ export function scanImage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
unlockScanBeep();
|
unlockScanBeep();
|
||||||
Promise.resolve().then(() => {
|
|
||||||
__scanning();
|
__scanning();
|
||||||
return startScanForImage().then(resp => {
|
withScanSessionTimeout(__startImageScan()).catch(err => {
|
||||||
if (resp && resp.result) {
|
if (err && err.cancel) {
|
||||||
__result(resp.result);
|
return;
|
||||||
}
|
}
|
||||||
throw resp.error;
|
__notifyImageScanFailure(err);
|
||||||
}).catch(err => { });
|
}).finally(() => {
|
||||||
|
__closed();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 由原生/业务传入已选图片 File,避免 WebView 无法从 input.files 取文件 */
|
||||||
|
export function scanImageFromFile(file) {
|
||||||
|
if (!isSupportImageScan()) {
|
||||||
|
printDebug("Not support image scanner");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isScanning() || _scan_closing || Date.now() < _scan_next_start_time) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unlockScanBeep();
|
||||||
|
cleanupWebScanResiduals();
|
||||||
|
__scanning();
|
||||||
|
withScanSessionTimeout(
|
||||||
|
detectImageFileForScan(file).then(resp => {
|
||||||
|
if (!isScanning()) {
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
if (resp && resp.cancel) {
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
if (!resp || !resp.result) {
|
||||||
|
__notifyImageScanFailure(resp);
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
__result(resp.result);
|
||||||
|
return resp;
|
||||||
|
})
|
||||||
|
).catch(err => {
|
||||||
|
if (err && err.cancel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
__notifyImageScanFailure(err);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
__closed();
|
__closed();
|
||||||
});
|
});
|
||||||
@@ -512,19 +730,30 @@ export function scanImage() {
|
|||||||
export const supportList = [
|
export const supportList = [
|
||||||
{
|
{
|
||||||
name: "bridge",
|
name: "bridge",
|
||||||
support: !!inRuntime()
|
get support() {
|
||||||
|
return !!inRuntime();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wx",
|
name: "wx",
|
||||||
support: !!isSupportWxScan()
|
get support() {
|
||||||
|
return !!isSupportWxScan();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "web",
|
name: "web",
|
||||||
support: !!isSupportWebScan()
|
get support() {
|
||||||
|
if (shouldSkipWebCameraProbe()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !!canUseWebCameraScan();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "image",
|
name: "image",
|
||||||
support: !!isSupportImageScan()
|
get support() {
|
||||||
|
return !!isSupportImageScan();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "scanner",
|
name: "scanner",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
132
types/index.d.ts
vendored
132
types/index.d.ts
vendored
@@ -45,6 +45,58 @@ interface ScanConfigOptions {
|
|||||||
* 2. 结束扫码的方法名称为:stopScan
|
* 2. 结束扫码的方法名称为:stopScan
|
||||||
*/
|
*/
|
||||||
bridgeName?: string,
|
bridgeName?: string,
|
||||||
|
/**
|
||||||
|
* 桥接扫码超时(毫秒),超时后回退 Web/图片识别,默认 5000
|
||||||
|
*/
|
||||||
|
bridgeScanTimeout?: number,
|
||||||
|
/**
|
||||||
|
* 是否允许 H5 摄像头扫码:true 强制开启(仍需有媒体 API),false 强制关闭
|
||||||
|
*/
|
||||||
|
webScanCameraEnabled?: boolean,
|
||||||
|
/**
|
||||||
|
* @deprecated 请用 webScanCameraEnabled;保留兼容
|
||||||
|
*/
|
||||||
|
webScanCameraInWechat?: boolean,
|
||||||
|
/**
|
||||||
|
* @deprecated 摄像头权限已后置到 startScan,请用 webScanVideoAccessTimeout
|
||||||
|
*/
|
||||||
|
webScanCameraProbeTimeout?: number,
|
||||||
|
/**
|
||||||
|
* startScan 走 Web 摄像头前是否展示权限说明弹窗,默认 true
|
||||||
|
*/
|
||||||
|
webScanCameraPermissionDialogEnabled?: boolean,
|
||||||
|
/**
|
||||||
|
* 摄像头权限说明弹窗标题
|
||||||
|
*/
|
||||||
|
webScanCameraPermissionTitle?: string,
|
||||||
|
/**
|
||||||
|
* 摄像头权限说明弹窗正文
|
||||||
|
*/
|
||||||
|
webScanCameraPermissionMessage?: string,
|
||||||
|
/**
|
||||||
|
* 摄像头权限说明弹窗确认按钮文案
|
||||||
|
*/
|
||||||
|
webScanCameraPermissionConfirmText?: string,
|
||||||
|
/**
|
||||||
|
* 摄像头权限说明弹窗取消按钮文案
|
||||||
|
*/
|
||||||
|
webScanCameraPermissionCancelText?: string,
|
||||||
|
/**
|
||||||
|
* 选图后延迟读取 file 对象(毫秒),微信/安卓 WebView 默认 100
|
||||||
|
*/
|
||||||
|
webScanFileReadDelay?: number,
|
||||||
|
/**
|
||||||
|
* 强制使用 WASM ponyfill 识别(微信/部分 WebView 建议开启,默认微信内自动开启)
|
||||||
|
*/
|
||||||
|
webScanPreferPonyfill?: boolean,
|
||||||
|
/**
|
||||||
|
* WASM 文件完整 URL;未配置时相对 SDK 脚本地址解析 lib/reader.wasm
|
||||||
|
*/
|
||||||
|
webScanWasmUrl?: string,
|
||||||
|
/**
|
||||||
|
* WASM 基准路径(SDK 脚本 URL 或目录);用于 async/defer 加载时修正 reader.wasm 路径
|
||||||
|
*/
|
||||||
|
webScanWasmBaseUrl?: string,
|
||||||
/**
|
/**
|
||||||
* webScan是否启用,默认启用
|
* webScan是否启用,默认启用
|
||||||
*/
|
*/
|
||||||
@@ -101,6 +153,41 @@ interface ScanConfigOptions {
|
|||||||
* 摄像头已打开但长时间无画面时触发图片回退(毫秒),默认 8000
|
* 摄像头已打开但长时间无画面时触发图片回退(毫秒),默认 8000
|
||||||
*/
|
*/
|
||||||
webScanVideoReadyTimeout?: number,
|
webScanVideoReadyTimeout?: number,
|
||||||
|
/**
|
||||||
|
* 图片/回退识别时 detect 超时(毫秒),默认 15000
|
||||||
|
*/
|
||||||
|
webScanDetectTimeout?: number,
|
||||||
|
/**
|
||||||
|
* 图片识别是否优先使用 ZXing ponyfill(原生 detect 在部分 WebView 可能卡住),默认 true
|
||||||
|
*/
|
||||||
|
webScanImagePreferPonyfill?: boolean,
|
||||||
|
/**
|
||||||
|
* 移动端图片回退是否使用 capture 拍照(false 为相册选图,兼容性更好),默认 false
|
||||||
|
*/
|
||||||
|
webScanImagePreferCapture?: boolean,
|
||||||
|
/**
|
||||||
|
* 图片识别是否优先 canvas 解码(安卓 WebView 建议开启),默认在安卓/微信内自动开启
|
||||||
|
*/
|
||||||
|
webScanImageDetectPreferCanvas?: boolean,
|
||||||
|
/**
|
||||||
|
* ZXing WASM 加载超时(毫秒),默认 20000
|
||||||
|
*/
|
||||||
|
webScanPrepareTimeout?: number,
|
||||||
|
/**
|
||||||
|
* 选图等待超时(毫秒),默认 120000
|
||||||
|
*/
|
||||||
|
webScanChooseImageTimeout?: number,
|
||||||
|
/**
|
||||||
|
* 选图方式:button=显示「选择图片」按钮(安卓/微信默认,需用户点击);auto=自动弹出系统选图
|
||||||
|
*/
|
||||||
|
webScanImagePickerMode?: 'auto' | 'button',
|
||||||
|
webScanImagePickerTitle?: string,
|
||||||
|
webScanImagePickerButtonText?: string,
|
||||||
|
webScanImagePickerCancelText?: string,
|
||||||
|
/**
|
||||||
|
* 单次扫码会话超时(毫秒),超时后状态恢复 ready,默认 90000
|
||||||
|
*/
|
||||||
|
scanSessionTimeout?: number,
|
||||||
/**
|
/**
|
||||||
* 扫码成功提示音地址,默认使用内置提示音;任意识别模式匹配成功时播放
|
* 扫码成功提示音地址,默认使用内置提示音;任意识别模式匹配成功时播放
|
||||||
*/
|
*/
|
||||||
@@ -161,6 +248,16 @@ interface ScanResult {
|
|||||||
key: string
|
key: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫码错误
|
||||||
|
*/
|
||||||
|
interface ScanErrorInfo {
|
||||||
|
error: string,
|
||||||
|
key: string,
|
||||||
|
source?: string,
|
||||||
|
cancel?: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听key
|
* 监听key
|
||||||
*/
|
*/
|
||||||
@@ -197,6 +294,22 @@ type ScanStatus = "scanning" | "ready";
|
|||||||
*/
|
*/
|
||||||
type ScanResultCallback = (result: ScanResult) => any;
|
type ScanResultCallback = (result: ScanResult) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听扫码错误回调
|
||||||
|
*/
|
||||||
|
type ScanErrorCallback = (error: ScanErrorInfo) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫码错误监听信息
|
||||||
|
*/
|
||||||
|
interface ScanErrorListenerInfo {
|
||||||
|
key?: string;
|
||||||
|
match?: string;
|
||||||
|
level?: number;
|
||||||
|
listener: ScanErrorCallback;
|
||||||
|
cancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 监听状态回调
|
* 监听状态回调
|
||||||
*/
|
*/
|
||||||
@@ -227,6 +340,19 @@ interface IScan {
|
|||||||
* @param callback 监听回调,或监听key
|
* @param callback 监听回调,或监听key
|
||||||
*/
|
*/
|
||||||
offScanListener(callback: ScanResultCallback | string): void;
|
offScanListener(callback: ScanResultCallback | string): void;
|
||||||
|
/**
|
||||||
|
* 添加监听扫码错误(如图片识别失败)
|
||||||
|
* @param callback 错误回调
|
||||||
|
* @param key 监听 key
|
||||||
|
* @param match 可选正则,匹配 error 文本后回调
|
||||||
|
* @param level 优先级
|
||||||
|
*/
|
||||||
|
onScanErrorListener(callback: ScanErrorCallback, key: string, match?: string, level?: number): ScanErrorListenerInfo;
|
||||||
|
/**
|
||||||
|
* 取消监听扫码错误
|
||||||
|
* @param callback 监听回调,或监听 key
|
||||||
|
*/
|
||||||
|
offScanErrorListener(callback: ScanErrorCallback | string): void;
|
||||||
/**
|
/**
|
||||||
* 获取扫码状态
|
* 获取扫码状态
|
||||||
* @returns ScanStatus
|
* @returns ScanStatus
|
||||||
@@ -240,14 +366,12 @@ interface IScan {
|
|||||||
* 开启扫码
|
* 开启扫码
|
||||||
*/
|
*/
|
||||||
startScan(): void;
|
startScan(): void;
|
||||||
/**
|
|
||||||
* 开启视频扫码
|
|
||||||
*/
|
|
||||||
scanVideo(): void;
|
|
||||||
/**
|
/**
|
||||||
* 选择图片进行识别
|
* 选择图片进行识别
|
||||||
*/
|
*/
|
||||||
scanImage(): void;
|
scanImage(): void;
|
||||||
|
/** 由业务/原生传入已选图片 File 识别(WebView input.files 异常时使用) */
|
||||||
|
scanImageFromFile(file: File | Blob): void;
|
||||||
/**
|
/**
|
||||||
* 清除全部监听
|
* 清除全部监听
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user