Files
scan-code-jssdk/src/services/provider/scan.js
iqudoo 4afa5ec8cb fix
2026-05-02 23:04:49 +08:00

461 lines
10 KiB
JavaScript

import { inRuntime, bridgeAsync } from "../bridge";
import { isSupportWebScan, startScanForWeb, stopScanForWeb, isSupportImageScan, startScanForImage, unlockScanBeep } from "../web";
import { isSupportWxScan, startScanForWx } from "../wx";
import { startScanner, stopScanner } from "../scanner";
import { getConfig } from "../config";
import { toAny } from "../../utils/toany";
import { printDebug } from "../../utils/logger";
let _scan_status = "ready";
let _scan_status_listener = null;
let _scan_listener_list = [];
let _scan_resolve = null;
let _scan_closing = false;
let _scan_next_start_time = 0;
const SCAN_RESTART_DELAY = 500;
function getScanRestartDelay() {
return toAny(getConfig("scanRestartDelay"), SCAN_RESTART_DELAY);
}
function parseBarcodeString(input) {
// 标准化的类型映射表:将各种变体映射到统一标识
// 这样即使传入 EAN_13、EAN-13、EAN13 都能匹配成功
const normalizedTypeMap = {
// EAN 系列
"ean13": true, "ean-13": true, "ean_13": true,
"ean8": true, "ean-8": true, "ean_8": true,
// UPC 系列
"upca": true, "upc-a": true, "upc_a": true,
"upce": true, "upc-e": true, "upc_e": true,
// CODE 系列
"code128": true, "code-128": true, "code_128": true,
"code39": true, "code-39": true, "code_39": true,
"code93": true, "code-93": true, "code_93": true,
// 其他常见类型
"itf": true, "itf14": true, "itf-14": true, "itf_14": true,
"codabar": true,
"pdf417": true, "pdf-417": true, "pdf_417": true,
"qrcode": true, "qr-code": true, "qr_code": true,
"datamatrix": true, "data-matrix": true, "data_matrix": true,
"aztec": true
};
// 健壮性检查
if (typeof input !== 'string') {
return input;
}
// 按第一个逗号分割
const commaIndex = input.indexOf(',');
if (commaIndex === -1) {
return input;
}
const possibleType = input.substring(0, commaIndex).trim();
const value = input.substring(commaIndex + 1).trim();
// 类型或值为空,返回原值
if (possibleType === "" || value === "") {
return input;
}
// 标准化类型(转小写,便于匹配)
const normalizedType = possibleType.toLowerCase();
// 检查是否为已知的条形码类型(支持多种分隔符变体)
const isKnownType = normalizedTypeMap.hasOwnProperty(normalizedType);
if (isKnownType) {
return value;
} else {
return input;
}
}
function __checkScanner() {
if (_scan_listener_list.length > 0) {
startScanner((result) => {
result = parseBarcodeString(result);
__scannerResult(result);
});
} else {
stopScanner();
}
}
function __match(result, match) {
let reg = null;
if (!!match && typeof match === "string") {
reg = new RegExp(match);
}
if (!!reg) {
return reg.test(result);
}
return true;
}
function __result(result) {
result = parseBarcodeString(result);
let matched = false;
for (let i = 0; i < _scan_listener_list.length; i++) {
const item = _scan_listener_list[i];
if (item.listener && __match(result, item.match)) {
matched = true;
item.listener({ result, key: item.key });
break;
}
}
if (matched) {
_scan_next_start_time = Date.now() + getScanRestartDelay();
}
return matched;
}
function __hasMatchedListener(result) {
for (let i = 0; i < _scan_listener_list.length; i++) {
const item = _scan_listener_list[i];
if (item.listener && __match(result, item.match)) {
return true;
}
}
return false;
}
function __scanning() {
if (_scan_status !== "scanning") {
_scan_status = "scanning";
if (_scan_status_listener) {
_scan_status_listener({ status: "scanning" });
}
}
}
function __closed() {
if (_scan_status !== "ready") {
_scan_status = "ready";
if (_scan_status_listener) {
_scan_status_listener({ status: "ready" });
}
}
}
function __finishScan() {
const resolve = _scan_resolve;
_scan_resolve = null;
__closed();
return resolve;
}
function __stopCurrentScan() {
if (inRuntime()) {
return bridgeAsync("stopScan").catch(() => {
// no thing
});
} else if (isSupportWebScan()) {
return stopScanForWeb().catch(() => {
// no thing
});
}
return Promise.resolve();
}
function __scannerResult(result) {
if (!__hasMatchedListener(result)) {
return;
}
if (isScanning()) {
const resolve = __finishScan();
_scan_closing = true;
__stopCurrentScan().then(() => {
setTimeout(() => {
_scan_closing = false;
}, 0);
});
__result(result);
resolve && resolve({
result
});
return;
}
__result(result);
}
function __startBridgeScan() {
return bridgeAsync("startScan", {
closeable: true
}).then(resp => {
if (!isScanning()) {
return resp;
}
if (!resp || !resp.result) {
return resp;
}
if (__result(resp.result)) {
return resp;
}
if (isScanning()) {
return __startBridgeScan();
}
return resp;
}).catch(err => {
if (!isScanning()) {
return err;
}
if (!err || !err.result) {
return err;
}
if (__result(err.result)) {
return err;
}
if (isScanning()) {
return __startBridgeScan();
}
return err;
});
}
function __startWxScan() {
return startScanForWx({
needResult: 1,
scanType: ["qrCode", "barCode"]
}).then(resp => {
if (!isScanning()) {
return resp;
}
if (!resp || !resp.result) {
return resp;
}
if (__result(resp.result)) {
return resp;
}
return resp;
}).catch(err => {
if (!isScanning()) {
return err;
}
if (!err || !err.result) {
return err;
}
if (__result(err.result)) {
return err;
}
return err;
});
}
function __startImageScan() {
return startScanForImage().then(resp => {
if (!isScanning()) {
return resp;
}
if (!resp || !resp.result) {
return resp;
}
if (__result(resp.result)) {
return resp;
}
if (isScanning()) {
return __startImageScan();
}
return resp;
}).catch(err => {
if (!isScanning()) {
return err;
}
if (!err || !err.result) {
return err;
}
if (__result(err.result)) {
return err;
}
if (isScanning()) {
return __startImageScan();
}
return err;
});
}
export function isScanning() {
return _scan_status === "scanning";
}
export function clear() {
for (let i = 0; i < _scan_listener_list.length; i++) {
const item = _scan_listener_list[i];
item.cancel();
}
_scan_listener_list.length = 0;
__checkScanner();
}
export function onScanListener(listener, key, match, level) {
if (!key || typeof key !== 'string') {
return;
}
if (typeof listener !== 'function') {
return;
}
let item = null;
for (let i = 0; i < _scan_listener_list.length; i++) {
const _i = _scan_listener_list[i];
if (_i.key === key) {
item = _i;
break;
}
}
if (item) {
item.level = level;
item.match = match;
item.listener = listener;
} else {
item = {
key,
match: match || "",
level: level || 0,
listener: listener,
cancel: () => {
const index = _scan_listener_list.indexOf(item);
if (index !== -1) {
const items = _scan_listener_list.splice(index, 1);
for (let i = 0; i < items.length; i++) {
const item = items[i];
item.listener && item.listener({ cancel: 1 });
}
}
}
};
_scan_listener_list.push(item);
}
// 根据level排序
_scan_listener_list.sort((a, b) => b.level - a.level);
__checkScanner();
return item;
}
export function offScanListener(listener) {
for (let i = 0; i < _scan_listener_list.length; i++) {
const _i = _scan_listener_list[i];
if (typeof listener === 'string') {
if (_i.key === listener) {
_i.cancel();
break;
}
} else if (_i.listener === listener) {
_i.cancel();
break;
}
}
__checkScanner();
}
export function setStatusListener(listener) {
if (typeof listener !== 'function') {
return;
}
_scan_status_listener = listener;
}
export function getStatus() {
return _scan_status;
}
export function stopScan() {
if (!isScanning()) {
return;
}
__stopCurrentScan().then(() => {
__closed();
});
}
export function startScan() {
if (isScanning() || _scan_closing || Date.now() < _scan_next_start_time) {
return;
}
Promise.resolve().then(() => {
__scanning();
let scannerPromise = new Promise(resolve => {
_scan_resolve = resolve;
});
let scanPromise = Promise.resolve();
if (inRuntime()) {
scanPromise = __startBridgeScan();
} else if (isSupportWxScan()) {
scanPromise = __startWxScan();
} else if (isSupportWebScan()) {
unlockScanBeep();
scanPromise = startScanForWeb(getConfig("webScanCanvasStyle"), __result);
} else if (isSupportImageScan()) {
scanPromise = __startImageScan();
} else {
printDebug("Not support scanner");
}
return Promise.race([scanPromise, scannerPromise]);
}).finally(() => {
_scan_resolve = null;
__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() {
if (!isSupportImageScan()) {
printDebug("Not support image scanner");
return;
}
if (isScanning() || _scan_closing || Date.now() < _scan_next_start_time) {
return;
}
Promise.resolve().then(() => {
__scanning();
return startScanForImage().then(resp => {
if (resp && resp.result) {
__result(resp.result);
}
throw resp.error;
}).catch(err => { });
}).finally(() => {
__closed();
});
}
export const supportList = [
{
name: "bridge",
support: !!inRuntime()
},
{
name: "wx",
support: !!isSupportWxScan()
},
{
name: "web",
support: !!isSupportWebScan()
},
{
name: "image",
support: !!isSupportImageScan()
},
{
name: "scanner",
support: true
}
];