<template>
<div class="Qqrcode">
<m-button class="scanBtn" size="small" type="primary" @click="show=true">
<ScanOutlined />
</m-button>
<m-popup position="top" overlay-class="overlayClass" round :style="{ height: '60%' }" v-model:visible="show"
@opened="openEvent" @close="stopScan">
<div class="scan-qrcode" ref="wrapperRef">
<video id="video" ref="videoRef" />
<div class="mask" ref="maskRef"></div>
</div>
</m-popup>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import { BrowserMultiFormatReader } from '@zxing/library';
import { ScanOutlined } from '@nancal-icon/icons-vue';
const videoRef = ref(null);
const maskRef = ref(null);
const show = ref(false);
let qrCodeReader = null;
let selectedDeviceId = '';
const emits = defineEmits(['update:value', 'ok']);
const props = defineProps({
value: {
type: String,
default: '',
},
});
function scanner() {
qrCodeReader.decodeFromVideoDevice(selectedDeviceId, videoRef.value, (result, err) => {
//if (result && result.resultPoints) {
// drawBoundingBox(result.resultPoints); // 绘制框
//}
if (result && result.text) {
emits('update:value', result.text);
emits('ok', result.text);
show.value = false;
}
if (err) { }
})
}
const drawBoundingBox = (points) => {
const canvas = canvasRef.value;
const canvasDom = canvas.getBoundingClientRect();
const ctx = canvas.getContext("2d");
// 清空画布
ctx.clearRect(0, 0, canvasDom.width, canvasDom.height);
// 设置绘制样式
ctx.fillStyle = "red";
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
// 遍历点并绘制
points.forEach((point) => {
ctx.beginPath();
ctx.arc(point.x, point.y, 5, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
});
};
// 停止视频流
const stopScan = () => {
if (qrCodeReader) {
qrCodeReader.reset(); // 停止二维码扫描
}
}
const openEvent = () => {
nextTick(() => {
scanner();
});
}
const getRearCameraDeviceId = async () => {
!navigator.mediaDevices ||!navigator.mediaDevices.enumerateDevices
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
console.error('当前浏览器不支持获取媒体设备列表');
return null;
}
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(device => device.kind === "videoinput");
// 尝试找到一个包含 "back" 或 "rear" 的摄像头设备
const rearCamera = videoDevices.find(device =>
device.label.toLowerCase().includes("back") ||
device.label.toLowerCase().includes("rear")
);
if (rearCamera) {
return rearCamera.deviceId; // 返回后置摄像头的 deviceId
} else {
console.warn("未找到后置摄像头,默认使用第一个摄像头");
return videoDevices[0]?.deviceId; // 如果没有找到后置摄像头,返回第一个设备
}
} catch (error) {
if (error.name === 'NotAllowedError') {
console.error('用户拒绝了摄像头权限');
} else if (error.name === 'NotFoundError') {
console.error('未找到摄像头设备');
} else {
console.error('无法获取摄像头设备:', error);
}
return null;
}
};
onMounted(async () => {
qrCodeReader = new BrowserMultiFormatReader(); // 初始化二维码读取器
selectedDeviceId = await getRearCameraDeviceId();
})
</script>
<style lang="less" scoped>
@keyframes translateYAnimation {
0% {
top: 10%;
filter: drop-shadow(10px 10px 10px rgba(105, 120, 254, 1));
}
50% {
top: 90%;
filter: drop-shadow(10px 10px 20px rgba(105, 120, 254, 1));
}
100% {
top: 10%;
filter: drop-shadow(10px 10px 10px rgba(105, 120, 254, 1));
}
}
.scan-qrcode {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
video {
width: 100%;
height: 100%;
object-fit: cover;
}
.mask {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 100%;
width: 60vw;
height: 60vw;
border-radius: 6px;
outline: rgba(0, 0, 0, .25) solid 20vmax;
&::before {
content: '';
display: block;
position: absolute;
top: 0;
left: 50%;
width: 120%;
height: 3px;
transform: translate(-50%);
background-image: linear-gradient(to left, rgba(105, 120, 254, 0), rgba(105, 120, 254, 0.9) 50%, rgba(105, 120, 254, 0));
filter: drop-shadow(10px 10px 10px rgba(105, 120, 254, 1));
animation: translateYAnimation 4s linear infinite;
}
}
}
.Qqrcode {
--m-overlay-bg-color: #0000000e;
}
</style>

04-18
1715
1715

被折叠的 条评论
为什么被折叠?



