You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
135 lines
4.6 KiB
135 lines
4.6 KiB
<style scoped>
|
|
.scan-area {
|
|
width: 100%;
|
|
height: 18.15rem;
|
|
}
|
|
</style>
|
|
|
|
<template>
|
|
<div class="scan-area">
|
|
<qrcode-stream @click="handleClick" :paused="scanPause" :track="paintOutLineAndCenterText" @error="handleError" @camera-on="onCameraOn">
|
|
<div v-if="!cameraReady">
|
|
loading...
|
|
</div>
|
|
</qrcode-stream>
|
|
<!-- <audio ref="audioRef" src="/src/assets/Di.mp3"></audio> -->
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
// import Vconsole from "vconsole";
|
|
import { QrcodeStream } from "vue-qrcode-reader";
|
|
import {ref} from 'vue'
|
|
const audioRef = ref(null);
|
|
const errorInfo = ref('');
|
|
const cameraReady = ref(false);
|
|
const scanPause = ref(false);
|
|
const reactAngles = ref<any>([]);
|
|
const onCameraOn = function () {
|
|
cameraReady.value = true;
|
|
// new Vconsole();
|
|
}
|
|
const handleError = function (err) {
|
|
errorInfo.value = `[${err.name}]: `
|
|
if (err.name === 'NotAllowedError') {
|
|
errorInfo.value += '您需要授予相机访问权限'
|
|
} else if (err.name === 'NotFoundError') {
|
|
errorInfo.value = '此设备上没有检测到摄像头';
|
|
} else if (err.name === 'NotReadableError') {
|
|
errorInfo.value = '相机被占用'
|
|
} else if (err.name === 'NotSupportedError') {
|
|
errorInfo.value = 'secure context required (HTTPS, localhost)'
|
|
} else if (err.name === 'OverconstrainedError') {
|
|
errorInfo.value += ''
|
|
} else if (err.name === 'StreamApiNotSupportedError') {
|
|
errorInfo.value += 'Stream API is not supported in this browser'
|
|
} else if (err.name === 'InsecureContextError') {
|
|
errorInfo.value += 'Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.'
|
|
} else {
|
|
errorInfo.value += err.message
|
|
}
|
|
console.log(errorInfo.value);
|
|
errorInfo.value = '';
|
|
}
|
|
|
|
const paintOutLineAndCenterText = (detectedCodes, ctx) => {
|
|
// 暂停页面
|
|
scanPause.value = true;
|
|
// 播放语音
|
|
// audioRef.value.play();
|
|
// 画边框和文本
|
|
reactAngles.value.splice();
|
|
for (const detectedCode of detectedCodes) {
|
|
// 画边框
|
|
ctx.lineWidth = 1;
|
|
const [firstPoint, ...otherPoints] = detectedCode.cornerPoints
|
|
ctx.strokeStyle = 'red'
|
|
ctx.beginPath()
|
|
ctx.moveTo(firstPoint.x, firstPoint.y)
|
|
for (const { x, y } of otherPoints) {
|
|
ctx.lineTo(x, y)
|
|
}
|
|
ctx.lineTo(firstPoint.x, firstPoint.y)
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
// 画文字
|
|
const { boundingBox, rawValue } = detectedCode
|
|
const centerX = boundingBox.x + boundingBox.width / 2
|
|
const centerY = boundingBox.y + boundingBox.height / 2
|
|
const fontSize = Math.max(12, (50 * boundingBox.width) / ctx.canvas.width)
|
|
console.log(boundingBox.width, ctx.canvas.width)
|
|
ctx.lineWidth = 2;
|
|
ctx.font = `${fontSize}px sans-serif`
|
|
ctx.textAlign = 'center'
|
|
ctx.strokeStyle = '#35e5a7'
|
|
ctx.strokeText('>', centerX, centerY)
|
|
// 配置矩形内容
|
|
if (detectedCode.cornerPoints.length==4) {
|
|
reactAngles.value.push({
|
|
leftTop: {x: firstPoint.x, y:firstPoint.y},
|
|
rightTop: {x: otherPoints[0].x, y:otherPoints[0].y},
|
|
rightBottom: {x: otherPoints[1].x, y:otherPoints[1].y},
|
|
leftBottom: {x: otherPoints[2].x, y:otherPoints[2].y},
|
|
text: detectedCode.rawValue
|
|
});
|
|
}
|
|
}
|
|
};
|
|
// 获取事件的发生位置
|
|
const getEventPosition = (ev) => {
|
|
var x, y;
|
|
if (ev.layerX || ev.layerX == 0) {
|
|
x = ev.layerX;
|
|
y = ev.layerY;
|
|
} else if (ev.offsetX || ev.offsetX == 0) { // Opera
|
|
x = ev.offsetX;
|
|
y = ev.offsetY;
|
|
}
|
|
return {x: x, y: y};
|
|
};
|
|
|
|
const handleClick = function (event) {
|
|
// 1. 获取点击点的位置
|
|
var eventPosition = getEventPosition(event);
|
|
// 2. 判断在不在已有的矩形内部,在的话获取二维码解析的结果,并进行业务处理
|
|
var resolveText = handleClickRectAngle(eventPosition);
|
|
if (resolveText != undefined) {
|
|
alert(resolveText);
|
|
}
|
|
// 3. 业务处理完,如果决定跳转就跳转,不决定跳转的话就关闭暂停
|
|
scanPause.value = false;
|
|
}
|
|
|
|
// 判断一个点在不在某个矩形内部的算法,后面可以采取凸包算法,优化判断的实现,不过这里已经能实现了,没必要深究
|
|
const handleClickRectAngle = function (eventPosition) {
|
|
for (let i = 0; i < reactAngles.value.length; i++) {
|
|
let reactAngle = reactAngles.value[i];
|
|
if ( ((eventPosition.x >= Math.min(reactAngle.leftTop.x, reactAngle.leftBottom.x)) && (eventPosition.x <= Math.max(reactAngle.rightTop.x, reactAngle.rightBottom.x)))
|
|
&& ((eventPosition.y >= Math.min(reactAngle.leftTop.y, reactAngle.rightTop.y)) && (eventPosition.y <= Math.max(reactAngle.leftBottom.y, reactAngle.rightBottom.y)))
|
|
) {
|
|
return reactAngle.text;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
</script>
|