340 lines
9.4 KiB
JavaScript
340 lines
9.4 KiB
JavaScript
import { createUUID } from "../../utils/uuid";
|
|
import { getConfig } from "../config";
|
|
import scanBeepAudio from "../../../res/scan_beep.ogg";
|
|
|
|
const scanWeb = {
|
|
uuid: null,
|
|
finish: true
|
|
}
|
|
|
|
const DEFAULT_SCAN_BEEP_AUDIO = scanBeepAudio;
|
|
|
|
function removeEl(id) {
|
|
try {
|
|
let el = document.getElementById(id);
|
|
document.body.removeChild(el);
|
|
} catch (error) {
|
|
}
|
|
}
|
|
|
|
function createEl(tagName, id, style, appendChild) {
|
|
let el = document.getElementById(id);
|
|
if (!el) {
|
|
el = document.createElement(tagName);
|
|
el.id = id;
|
|
el.style = style;
|
|
appendChild && document.body.appendChild(el);
|
|
}
|
|
return el;
|
|
}
|
|
|
|
function canvasDrawLine(context, width, begin, end, color) {
|
|
context.beginPath();
|
|
context.moveTo(width - begin.x, begin.y);
|
|
context.lineTo(width - end.x, end.y);
|
|
context.lineWidth = 4;
|
|
context.strokeStyle = color;
|
|
context.stroke();
|
|
}
|
|
|
|
function getBarcodeFormats(scanType) {
|
|
let formats = [];
|
|
if (!scanType) {
|
|
scanType = ["qrCode", "barCode"];
|
|
}
|
|
if (scanType.includes('qrCode')) {
|
|
formats.push('qr_code');
|
|
}
|
|
if (scanType.includes('barCode')) {
|
|
formats.push(
|
|
'ean_13',
|
|
'ean_8',
|
|
'code_128',
|
|
'code_39',
|
|
'codabar',
|
|
'upc_a',
|
|
'upc_e',
|
|
'itf',
|
|
'aztec',
|
|
'data_matrix',
|
|
'pdf417'
|
|
);
|
|
}
|
|
return formats;
|
|
}
|
|
|
|
function createBarcodeDetector(scanType) {
|
|
return Promise.resolve().then(() => {
|
|
if (typeof BarcodeDetector === 'undefined') {
|
|
throw new Error("BarcodeDetector is not supported");
|
|
}
|
|
const formats = getBarcodeFormats(scanType);
|
|
if (BarcodeDetector.getSupportedFormats) {
|
|
return BarcodeDetector.getSupportedFormats().then(supportedFormats => {
|
|
const supported = formats.filter(format => supportedFormats.indexOf(format) !== -1);
|
|
if (!supported.length) {
|
|
throw new Error("No supported barcode formats");
|
|
}
|
|
return new BarcodeDetector({ formats: supported });
|
|
});
|
|
}
|
|
return new BarcodeDetector({ formats });
|
|
});
|
|
}
|
|
|
|
function drawBarcode(context, width, barcode) {
|
|
const cornerPoints = barcode.cornerPoints;
|
|
if (cornerPoints && cornerPoints.length) {
|
|
for (let i = 0; i < cornerPoints.length; i++) {
|
|
canvasDrawLine(context, width, cornerPoints[i], cornerPoints[(i + 1) % cornerPoints.length], "#FF3B58");
|
|
}
|
|
return;
|
|
}
|
|
if (barcode.boundingBox) {
|
|
const rect = barcode.boundingBox;
|
|
const points = [
|
|
{ x: rect.x, y: rect.y },
|
|
{ x: rect.x + rect.width, y: rect.y },
|
|
{ x: rect.x + rect.width, y: rect.y + rect.height },
|
|
{ x: rect.x, y: rect.y + rect.height }
|
|
];
|
|
for (let i = 0; i < points.length; i++) {
|
|
canvasDrawLine(context, width, points[i], points[(i + 1) % points.length], "#FF3B58");
|
|
}
|
|
}
|
|
}
|
|
|
|
function playScanBeep() {
|
|
if (getConfig("webScanBeepEnabled") === false) {
|
|
return;
|
|
}
|
|
const audioSrc = getConfig("webScanBeepAudio") || DEFAULT_SCAN_BEEP_AUDIO;
|
|
if (!audioSrc || typeof Audio === 'undefined') {
|
|
return;
|
|
}
|
|
try {
|
|
const audio = new Audio(audioSrc);
|
|
const playPromise = audio.play();
|
|
playPromise && playPromise.catch && playPromise.catch(() => {
|
|
// no thing
|
|
});
|
|
} catch (e) {
|
|
}
|
|
}
|
|
|
|
export function isSupportWebScan() {
|
|
return typeof navigator !== 'undefined'
|
|
&& navigator.mediaDevices
|
|
&& navigator.mediaDevices.getUserMedia
|
|
&& typeof BarcodeDetector !== 'undefined';
|
|
}
|
|
|
|
export function isSupportImageScan() {
|
|
return typeof document !== 'undefined'
|
|
&& typeof BarcodeDetector !== 'undefined'
|
|
&& typeof URL !== 'undefined'
|
|
&& URL.createObjectURL;
|
|
}
|
|
|
|
export function stopScanForWeb() {
|
|
return Promise.resolve().then(() => {
|
|
scanWeb.uuid = null;
|
|
})
|
|
}
|
|
|
|
function chooseImageFile() {
|
|
return new Promise(resolve => {
|
|
const input = document.createElement("input");
|
|
input.type = "file";
|
|
input.accept = "image/*";
|
|
input.style.display = "none";
|
|
let finished = false;
|
|
let finish = file => {
|
|
if (finished) {
|
|
return;
|
|
}
|
|
finished = true;
|
|
removeEl("__webscan_image_input__");
|
|
resolve(file);
|
|
};
|
|
input.id = "__webscan_image_input__";
|
|
input.onchange = () => {
|
|
finish(input.files && input.files[0]);
|
|
};
|
|
input.oncancel = () => {
|
|
finish(null);
|
|
};
|
|
document.body.appendChild(input);
|
|
input.click();
|
|
});
|
|
}
|
|
|
|
function detectImageFile(detector, file) {
|
|
if (!file) {
|
|
return Promise.resolve(null);
|
|
}
|
|
if (typeof createImageBitmap !== 'undefined') {
|
|
return createImageBitmap(file).then(image => {
|
|
return detector.detect(image).then(barcodes => {
|
|
image.close && image.close();
|
|
return barcodes && barcodes[0];
|
|
}).catch(err => {
|
|
image.close && image.close();
|
|
throw err;
|
|
});
|
|
});
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
const image = new Image();
|
|
const url = URL.createObjectURL(file);
|
|
image.onload = () => {
|
|
detector.detect(image).then(barcodes => {
|
|
URL.revokeObjectURL(url);
|
|
resolve(barcodes && barcodes[0]);
|
|
}).catch(err => {
|
|
URL.revokeObjectURL(url);
|
|
reject(err);
|
|
});
|
|
};
|
|
image.onerror = err => {
|
|
URL.revokeObjectURL(url);
|
|
reject(err);
|
|
};
|
|
image.src = url;
|
|
});
|
|
}
|
|
|
|
export function startScanForImage() {
|
|
return createBarcodeDetector(getConfig("webScanType")).then(detector => {
|
|
return chooseImageFile().then(file => detectImageFile(detector, file));
|
|
}).then(code => {
|
|
if (code && code.rawValue) {
|
|
return {
|
|
result: code.rawValue
|
|
};
|
|
}
|
|
return {
|
|
success: false,
|
|
error: "未识别到二维码或条形码"
|
|
};
|
|
});
|
|
}
|
|
|
|
export function startScanForWeb(canvasStyle, onResult) {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
scanWeb.uuid = createUUID();
|
|
scanWeb.finish = false;
|
|
let videoEl = createEl("video",
|
|
"__webscan_video__",
|
|
"display: none", false);
|
|
let canvasEnabled = getConfig("webCanvasEnabled") !== false;
|
|
let canvasDisplay = "";
|
|
let canvasBaseStyle = canvasStyle || "position: fixed; width: 300px; height: 240px; top: 0; left: 0; z-index: 9999;";
|
|
let canvasEl = createEl("canvas",
|
|
"__webscan_canvas__",
|
|
canvasBaseStyle + " display: none;", true);
|
|
canvasDisplay = canvasEl.style.display;
|
|
canvasEl.style.cssText = canvasBaseStyle;
|
|
canvasDisplay = canvasEl.style.display;
|
|
canvasEl.style.display = "none";
|
|
let context = canvasEl.getContext("2d");
|
|
let currentUuid = scanWeb.uuid;
|
|
videoEl.width = 300;
|
|
videoEl.height = 300;
|
|
videoEl.uuid = scanWeb.uuid;
|
|
createBarcodeDetector(getConfig("webScanType")).then(detector => {
|
|
return navigator.mediaDevices.getUserMedia({
|
|
video: {
|
|
facingMode: "environment"
|
|
}
|
|
}).then(stream => {
|
|
return {
|
|
detector,
|
|
stream
|
|
};
|
|
});
|
|
}).then(function (options) {
|
|
const detector = options.detector;
|
|
const stream = options.stream;
|
|
videoEl.srcObject = stream;
|
|
videoEl.setAttribute("playsinline", true); // iOS使用
|
|
videoEl.play();
|
|
canvasEl.style.display = "none";
|
|
let detecting = false;
|
|
let displayed = false;
|
|
let closed = false;
|
|
let close = () => {
|
|
if (closed) {
|
|
return;
|
|
}
|
|
closed = true;
|
|
try {
|
|
stream.getTracks()[0].stop();
|
|
} catch (_e) { }
|
|
};
|
|
let tick = () => {
|
|
try {
|
|
if (videoEl.readyState === videoEl.HAVE_ENOUGH_DATA && !detecting) {
|
|
canvasEl.height = videoEl.videoHeight;
|
|
canvasEl.width = videoEl.videoWidth;
|
|
context.setTransform(-1, 0, 0, 1, canvasEl.width, 0);
|
|
context.drawImage(videoEl, 0, 0, canvasEl.width, canvasEl.height);
|
|
context.setTransform(1, 0, 0, 1, 0, 0);
|
|
if (canvasEnabled && !displayed) {
|
|
displayed = true;
|
|
canvasEl.style.display = canvasDisplay || "";
|
|
}
|
|
detecting = true;
|
|
detector.detect(videoEl).then(barcodes => {
|
|
const code = barcodes && barcodes[0];
|
|
if (code && code.rawValue && scanWeb.uuid == currentUuid) {
|
|
if (!onResult || !onResult(code.rawValue)) {
|
|
return;
|
|
}
|
|
drawBarcode(context, canvasEl.width, code);
|
|
playScanBeep();
|
|
scanWeb.uuid = null;
|
|
scanWeb.finish = true;
|
|
close();
|
|
resolve({
|
|
result: code.rawValue
|
|
})
|
|
}
|
|
}).catch(() => {
|
|
}).finally(() => {
|
|
detecting = false;
|
|
});
|
|
}
|
|
} catch (e) {
|
|
detecting = false;
|
|
}
|
|
if (scanWeb.uuid == currentUuid) {
|
|
requestAnimationFrame(() => {
|
|
tick()
|
|
});
|
|
} else {
|
|
if (!scanWeb.finish) {
|
|
reject({ cancel: 1 });
|
|
scanWeb.finish = true;
|
|
}
|
|
close();
|
|
}
|
|
if (scanWeb.finish) {
|
|
close();
|
|
}
|
|
};
|
|
requestAnimationFrame(() => {
|
|
tick();
|
|
});
|
|
}).catch(err => {
|
|
reject({ error: err });
|
|
});
|
|
} catch (e) {
|
|
reject({ error: e });
|
|
}
|
|
}).finally(() => {
|
|
removeEl("__webscan_video__");
|
|
removeEl("__webscan_canvas__");
|
|
})
|
|
} |