461 lines
10 KiB
JavaScript
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
|
|
}
|
|
]; |