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

<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>