2 changed files with 333 additions and 150 deletions
@ -1,150 +0,0 @@ |
|||
|
|||
<!doctype html> |
|||
<html lang="en"> |
|||
|
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|||
<meta name="author" content="ZXing for JS"> |
|||
|
|||
<title>ZXing TypeScript | Decoding from camera stream</title> |
|||
|
|||
<!-- <link rel="stylesheet" rel="preload" as="style" onload="this.rel='stylesheet';this.onload=null" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">--> |
|||
<!-- <link rel="stylesheet" rel="preload" as="style" onload="this.rel='stylesheet';this.onload=null" href="https://unpkg.com/normalize.css@8.0.0/normalize.css">--> |
|||
<!-- <link rel="stylesheet" rel="preload" as="style" onload="this.rel='stylesheet';this.onload=null" href="https://unpkg.com/milligram@1.3.0/dist/milligram.min.css">--> |
|||
</head> |
|||
|
|||
<body> |
|||
|
|||
<main class="wrapper" style="padding-top:2em"> |
|||
|
|||
<section class="container" id="demo-content"> |
|||
<div> |
|||
<a class="button" id="startButton">扫描</a> |
|||
<a class="button" id="resetButton">重置</a> |
|||
</div> |
|||
|
|||
<div> |
|||
<video id="video" width="300" height="200" style="border: 1px solid gray"></video> |
|||
</div> |
|||
|
|||
<div id="sourceSelectPanel" style="display:none"> |
|||
<label for="sourceSelect">选择摄像头: </label> |
|||
<select id="sourceSelect" style="max-width:400px"> |
|||
</select> |
|||
</div> |
|||
|
|||
<div style="display: table"> |
|||
<label for="decoding-style"> 扫描次数:</label> |
|||
<select id="decoding-style" size="1"> |
|||
<option value="once">扫描一次</option> |
|||
<option value="continuously">持续扫描</option> |
|||
</select> |
|||
</div> |
|||
|
|||
<label>结果:</label> |
|||
<pre><code id="result"></code></pre> |
|||
</section> |
|||
|
|||
</main> |
|||
|
|||
<script type="text/javascript" src="https://unpkg.com/@zxing/library@latest"></script> |
|||
<script type="text/javascript"> |
|||
function decodeOnce(codeReader, selectedDeviceId) { |
|||
codeReader.decodeFromInputVideoDevice(selectedDeviceId, 'video').then((result) => { |
|||
console.log(result); |
|||
document.getElementById('result').textContent = result.text |
|||
}).catch((err) => { |
|||
console.error(err); |
|||
document.getElementById('result').textContent = err |
|||
}) |
|||
} |
|||
|
|||
function decodeContinuously(codeReader, selectedDeviceId) { |
|||
codeReader.decodeFromInputVideoDeviceContinuously(selectedDeviceId, 'video', (result, err) => { |
|||
if (result) { |
|||
// properly decoded qr code |
|||
console.log('Found QR code!', result); |
|||
document.getElementById('result').textContent = result.text |
|||
} |
|||
|
|||
if (err) { |
|||
if (err instanceof ZXing.NotFoundException) { |
|||
console.log('No QR code found.') |
|||
} |
|||
|
|||
if (err instanceof ZXing.ChecksumException) { |
|||
console.log('A code was found, but it\'s read value was not valid.') |
|||
} |
|||
|
|||
if (err instanceof ZXing.FormatException) { |
|||
console.log('A code was found, but it was in a invalid format.') |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
|
|||
window.addEventListener('load', function () { |
|||
let selectedDeviceId; |
|||
const codeReader = new ZXing.BrowserQRCodeReader(); |
|||
console.log('ZXing 初始化'); |
|||
|
|||
codeReader.getVideoInputDevices() |
|||
.then((videoInputDevices) => { |
|||
const sourceSelect = document.getElementById('sourceSelect') |
|||
// selectedDeviceId = videoInputDevices[0].deviceId; |
|||
if (videoInputDevices.length >= 1) { |
|||
videoInputDevices.forEach((element) => { |
|||
const sourceOption = document.createElement('option'); |
|||
console.log(element.label); |
|||
if(element.label.includes("front")){ |
|||
sourceOption.text = "前置摄像头"; |
|||
sourceOption.value = element.deviceId; |
|||
}else if (element.label.includes("back")){ |
|||
sourceOption.text = "后置摄像头"; |
|||
sourceOption.selected=true; |
|||
sourceOption.value = element.deviceId; |
|||
selectedDeviceId = element.deviceId; |
|||
} |
|||
sourceSelect.appendChild(sourceOption) |
|||
}); |
|||
|
|||
sourceSelect.onchange = () => { |
|||
selectedDeviceId = sourceSelect.value; |
|||
}; |
|||
|
|||
const sourceSelectPanel = document.getElementById('sourceSelectPanel') |
|||
sourceSelectPanel.style.display = 'block' |
|||
} |
|||
|
|||
document.getElementById('startButton').addEventListener('click', () => { |
|||
|
|||
const decodingStyle = document.getElementById('decoding-style').value; |
|||
|
|||
if (decodingStyle === "once") { |
|||
// 如果只扫描一次 |
|||
decodeOnce(codeReader, selectedDeviceId); |
|||
} else { |
|||
// 扫描多次 |
|||
decodeContinuously(codeReader, selectedDeviceId); |
|||
} |
|||
|
|||
console.log(`Started decode from camera with id ${selectedDeviceId}`) |
|||
}); |
|||
|
|||
document.getElementById('resetButton').addEventListener('click', () => { |
|||
codeReader.reset(); |
|||
document.getElementById('result').textContent = ''; |
|||
console.log('Reset.') |
|||
}) |
|||
|
|||
}) |
|||
.catch((err) => { |
|||
console.error(err) |
|||
}) |
|||
}) |
|||
</script> |
|||
|
|||
</body> |
|||
|
|||
</html> |
|||
@ -0,0 +1,333 @@ |
|||
<!DOCTYPE html> |
|||
<html xmlns:th="http://www.w3.org/1999/xhtml"> |
|||
<html xmlns:th="http://www.thymeleaf.org"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>扫码</title> |
|||
<link rel="stylesheet" href="/static/lib/layui-v2.6.3/css/layui.css" media="all"> |
|||
<link rel="stylesheet" href="/static/css/layuimini.css?v=2.0.4.2" media="all"> |
|||
<link rel="stylesheet" href="/static/css/themes/default.css" media="all"> |
|||
<link rel="stylesheet" href="/static/lib/font-awesome-4.7.0/css/font-awesome.min.css" media="all"> |
|||
<!--[if lt IE 9]> |
|||
<script src="/static/js/html5.min.js"></script> |
|||
<script src="/static/js/respond.min.js"></script> |
|||
<![endif]--> |
|||
<!-- vue相关--> |
|||
<script src="../static/js/vue/vue.js"></script> |
|||
<script src="../static/js/vue/vue-router.js"></script> |
|||
<script src="../static/lib/http-vue-loader/src/httpVueLoader.js"></script> |
|||
<script src="../static/js/VueQrcodeReader.umd.min.js"></script> |
|||
<style> |
|||
.validation-success, |
|||
.validation-failure, |
|||
.validation-pending { |
|||
position: absolute; |
|||
width: 100%; |
|||
height: 100%; |
|||
|
|||
background-color: rgba(255, 255, 255, .8); |
|||
text-align: center; |
|||
font-weight: bold; |
|||
font-size: 1.4rem; |
|||
padding: 10px; |
|||
|
|||
display: flex; |
|||
flex-flow: column nowrap; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.validation-success { |
|||
color: green; |
|||
} |
|||
|
|||
.validation-failure { |
|||
color: red; |
|||
} |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<div id="app"> |
|||
<qrcode-stream :camera="camera" @decode="onDecode" @init="onInit" :track="paintBoundingBox"> |
|||
<div v-if="validationPending" class="validation-pending"> |
|||
Long validation in progress... |
|||
</div> |
|||
</qrcode-stream> |
|||
</div> |
|||
<script src="/static/lib/layui-v2.6.3/layui.js" charset="utf-8"></script> |
|||
<script> |
|||
Vue.use(httpVueLoader); |
|||
|
|||
var vue = new Vue({ |
|||
data() { |
|||
return { |
|||
isValid: undefined, |
|||
camera: 'auto', |
|||
result: '', |
|||
error: '', |
|||
materialList: [], |
|||
depository: null, |
|||
place: null |
|||
|
|||
} |
|||
}, |
|||
computed: { |
|||
validationPending() { |
|||
return this.isValid === undefined |
|||
&& this.camera === 'off' |
|||
}, |
|||
}, |
|||
methods: { |
|||
onDecode(result) { |
|||
let params = {}; // 用于暂存扫描结果 |
|||
this.result = result; |
|||
let parse = JSON.parse(result); |
|||
vue.turnCameraOff(); // 暂停扫描 |
|||
if (parse.did !== undefined) { |
|||
// 如果扫描的是仓库二维码 |
|||
this.depository = parse;// 将扫描结果保存到vue中 |
|||
params.depository = this.depository; |
|||
params.place = this.place; |
|||
params.materialList = this.materialList; |
|||
if (this.materialList.length > 0) { |
|||
// 如果有物料 |
|||
this.temporaryScanValue(params); // 将数据暂存至redis中 |
|||
this.chooseInOrOut(); // 弹出选择框 |
|||
} else { |
|||
// 如果没有 |
|||
layer.confirm("暂未选择物料,是否继续扫描", { |
|||
btn:["继续","取消"] |
|||
},function () { // 继续 |
|||
vue.turnCameraOn(); // 继续扫描 |
|||
layer.close(layer.index); // 关闭弹窗 |
|||
},function () { // 取消 |
|||
vue.temporaryScanValue(params); // 将数据暂存 |
|||
vue.chooseInOrOut(); // 弹出选择框 |
|||
}) |
|||
} |
|||
} |
|||
else if (parse.pid !== undefined) { |
|||
// 如果扫描的是库位二维码 |
|||
this.place = parse; // 将扫描结果保存到vue中 |
|||
params.depository = this.depository; |
|||
params.place = this.place; |
|||
params.materialList = this.materialList; |
|||
if (this.materialList.length > 0) { |
|||
// 如果有物料 |
|||
this.temporaryScanValue(params); // 将数据暂存至redis中 |
|||
this.chooseInOrOut(); // 弹出选择框 |
|||
} else { |
|||
// 如果没有 |
|||
layer.confirm("当前并未扫描物料,是否继续扫描", |
|||
{btn:["继续","取消"]}, |
|||
function () { // 继续扫描 |
|||
vue.turnCameraOn(); // 继续扫描 |
|||
layer.close(layer.index); // 关闭弹窗 |
|||
}, |
|||
function () { |
|||
vue.temporaryScanValue(params); // 将数据暂存 |
|||
vue.chooseInOrOut(); // 弹出选择框 |
|||
} |
|||
) |
|||
} |
|||
} |
|||
else if (parse.mid !== undefined) { |
|||
// 如果扫描的是物料二维码 |
|||
this.materialList.push(parse); |
|||
layer.confirm("是否继续扫描", |
|||
{ |
|||
btn: ["继续", "取消"] |
|||
}, |
|||
function () { // 继续扫描物料 |
|||
vue.turnCameraOn(); // 继续扫描 |
|||
layer.close(layer.index); // 关闭弹窗 |
|||
}, |
|||
function () { |
|||
// 不扫描物料 |
|||
params.materialList = vue.materialList; |
|||
params.depository = vue.depository; |
|||
params.place = vue.place; |
|||
vue.temporaryScanValue(params); // 将物料暂存 |
|||
if (vue.depository !== '' || vue.place !== '') { |
|||
// 如果已经扫描了仓库或库位 |
|||
// 弹出选择框 |
|||
vue.chooseInOrOut(); |
|||
} |
|||
else { |
|||
// 如果没有扫描仓库或库位 |
|||
layer.confirm("暂未扫描仓库,是否继续该操作", |
|||
{ |
|||
btn: ["继续", "取消"] |
|||
}, |
|||
function () {// 继续 |
|||
// 弹出选择框 |
|||
vue.chooseInOrOut(); |
|||
}, |
|||
function () { // 取消当前操作 |
|||
vue.turnCameraOn(); // 继续扫描 |
|||
layer.close(layer.index); // 关闭弹窗 |
|||
} |
|||
) |
|||
} |
|||
} |
|||
) |
|||
} |
|||
}, |
|||
/*layer.confirm("请选择入库|出库",{ |
|||
btn:["入库","出库"]}, |
|||
function () { |
|||
layer.open({ |
|||
type: 2, |
|||
title: '入库', |
|||
skin: 'layui-layer-rim', |
|||
maxmin: true, |
|||
shadeClose: true, //点击遮罩关闭层 |
|||
area: ['100%', '100%'], |
|||
move : '.layui-layer-title', |
|||
fixed:false, |
|||
content: '/application_in_back?mid='+material.mid, |
|||
}) |
|||
},function () { |
|||
let req = {}; |
|||
req.code = material.code; |
|||
req.type = "out"; |
|||
layui.$.ajax({ |
|||
url: "/material/findMatrialByCode", |
|||
type: "get", |
|||
dataType: 'json', |
|||
data:(req), |
|||
contentType: "application/json;charset=utf-8", |
|||
success: function (d) { |
|||
var d = d.data; |
|||
if(d !== null){ |
|||
if(d == null) { |
|||
layer.msg("没有该编码,请确认是否输入正确"); |
|||
}else{ |
|||
layer.open({ |
|||
type: 2, |
|||
title: '出库', |
|||
skin: 'layui-layer-rim', |
|||
maxmin: true, |
|||
shadeClose: true, //点击遮罩关闭层 |
|||
area: ['100%', '100%'], |
|||
move : '.layui-layer-title', |
|||
fixed:false, |
|||
content: '/application_out_back?code='+d.code+"&depositoryCode="+d.depositoryCode, |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
)*/ |
|||
async onInit(promise) { |
|||
try { |
|||
await promise.then(this.resetValidationState) |
|||
} catch (error) { |
|||
if (error.name === 'NotAllowedError') { |
|||
this.error = "ERROR: you need to grant camera access permission" |
|||
} else if (error.name === 'NotFoundError') { |
|||
this.error = "ERROR: no camera on this device" |
|||
} else if (error.name === 'NotSupportedError') { |
|||
this.error = "ERROR: secure context required (HTTPS, localhost)" |
|||
} else if (error.name === 'NotReadableError') { |
|||
this.error = "ERROR: is the camera already in use?" |
|||
} else if (error.name === 'OverconstrainedError') { |
|||
this.error = "ERROR: installed cameras are not suitable" |
|||
} else if (error.name === 'StreamApiNotSupportedError') { |
|||
this.error = "ERROR: Stream API is not supported in this browser" |
|||
} else if (error.name === 'InsecureContextError') { |
|||
this.error = 'ERROR: Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.'; |
|||
} else { |
|||
this.error = `ERROR: Camera error (${error.name})` |
|||
} |
|||
console.log(this.error) |
|||
} |
|||
} |
|||
, |
|||
resetValidationState() { |
|||
this.isValid = undefined |
|||
} |
|||
, |
|||
// 绘制二维码跟踪框 |
|||
paintBoundingBox(detectedCodes, ctx) { |
|||
for (const detectedCode of detectedCodes) { |
|||
const {boundingBox: {x, y, width, height}} = detectedCode; |
|||
ctx.lineWidth = 2; |
|||
ctx.strokeStyle = '#007bff'; |
|||
ctx.strokeRect(x, y, width, height) |
|||
} |
|||
} |
|||
, |
|||
// 打开相机 |
|||
turnCameraOn() { |
|||
this.camera = 'auto' |
|||
} |
|||
, |
|||
// 关闭相机 |
|||
turnCameraOff() { |
|||
this.camera = 'off' |
|||
}, |
|||
// 将扫描到的数据暂存到redis |
|||
temporaryScanValue(params) { |
|||
layui.$.ajax({ |
|||
url: "/material/temporaryValue", |
|||
type: 'post', |
|||
dataType: 'json', |
|||
contentType: "application/json;charset=utf-8", |
|||
data: JSON.stringify(params) |
|||
}); |
|||
}, |
|||
// 弹出入库|出库选择框 |
|||
chooseInOrOut(){ |
|||
layer.confirm("请选择入库|出库", { |
|||
btn: ["入库"] |
|||
}, |
|||
function () { // 选择入库 |
|||
layer.open({ |
|||
type: 2, |
|||
title: '入库', |
|||
skin: 'layui-layer-rim', |
|||
maxmin: true, |
|||
shadeClose: true, //点击遮罩关闭层 |
|||
area: ['100%', '100%'], |
|||
move: '.layui-layer-title', |
|||
fixed: false, |
|||
content: '/application_in_scanQrCode', |
|||
end:function () { |
|||
var index = parent.layer.getFrameIndex(window.name); |
|||
parent.layer.close(index); |
|||
} |
|||
}) |
|||
}, |
|||
function () { // 选择出库 |
|||
layui.$.ajax({ // 判断当前物料是否存在库存 |
|||
url:"/material/IsMaterialExist", |
|||
type:"get", |
|||
dataType: 'json', |
|||
contentType: "application/json;charset=utf-8", |
|||
success:function (d) { |
|||
layer.open({ |
|||
type: 2, |
|||
title: '出库', |
|||
skin: 'layui-layer-rim', |
|||
maxmin: true, |
|||
shadeClose: true, //点击遮罩关闭层 |
|||
area: ['100%', '100%'], |
|||
move: '.layui-layer-title', |
|||
fixed: false, |
|||
content: '/application_Out_scanQrCode', |
|||
end:function () { |
|||
var index = parent.layer.getFrameIndex(window.name); |
|||
parent.layer.close(index); |
|||
} |
|||
}) |
|||
} |
|||
}); |
|||
|
|||
}) |
|||
} |
|||
} |
|||
}).$mount('#app') |
|||
</script> |
|||
</body> |
|||
</html> |
|||
Loading…
Reference in new issue