摄像头逻辑进行修改,更改分辨率进行快速的读取识别

This commit is contained in:
dxc
2026-02-06 11:28:48 +08:00
parent e027ebd4a9
commit 489e62e55b
7 changed files with 788 additions and 708 deletions

View File

@ -4,9 +4,16 @@
<div v-if="errorMsg" class="error-msg">{{ errorMsg }}</div>
<div class="focus-tip" v-if="!errorMsg">
<div class="focus-tip" v-if="!errorMsg && !isPaused">
<div class="scan-line"></div>
<div class="scan-text">将条码对准取景</div>
<div class="scan-text">将条码横向填满红</div>
</div>
<div class="focus-tip success" v-if="isPaused">
<div class="scan-text-success">
<el-icon><CircleCheckFilled /></el-icon>
扫描成功3秒后继续...
</div>
</div>
</div>
</template>
@ -14,9 +21,13 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import { Html5Qrcode, Html5QrcodeSupportedFormats } from 'html5-qrcode'
import { CircleCheckFilled } from '@element-plus/icons-vue' // 引入图标用于成功提示
// 定义事件
const emit = defineEmits(['decode', 'error'])
const errorMsg = ref('')
const isPaused = ref(false) // ★ 新增:控制暂停状态
let html5QrCode: Html5Qrcode | null = null
const scannerElementId = "qr-reader"
@ -34,17 +45,15 @@ const startScanning = async () => {
// 2. 启动配置
const config = {
fps: 25,
qrbox: { width: 250, height: 100 }, // 扫描区域设置
// ★★★ 修复点1移除 aspectRatio让画面自适应容器长宽比 ★★★
// aspectRatio: 1.0,
fps: 20,
qrbox: { width: 320, height: 60 },
disableFlip: false,
videoConstraints: {
facingMode: "environment",
// 限制分辨率,保证速度
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 480, ideal: 720, max: 1080 },
focusMode: "continuous"
width: { min: 1280, ideal: 1920, max: 3840 },
height: { min: 720, ideal: 1080, max: 2160 },
focusMode: "continuous",
advanced: [{ focusMode: "macro" }]
}
}
@ -53,8 +62,21 @@ const startScanning = async () => {
{ facingMode: "environment" },
config,
(decodedText) => {
// ★ 核心修改:如果处于暂停冷却期,直接忽略后续扫描结果
if (isPaused.value) return
console.log(`Scan: ${decodedText}`)
// 1. 锁定状态
isPaused.value = true
// 2. 发送数据
emit('decode', decodedText)
// 3. 开启 3 秒倒计时解锁
setTimeout(() => {
isPaused.value = false
}, 3000)
},
(errorMessage) => {
// ignore
@ -65,7 +87,8 @@ const startScanning = async () => {
const errStr = err.toString()
if (errStr.includes('Permission')) msg = '请允许摄像头权限'
else if (errStr.includes('Secure')) msg = '需要 HTTPS 或 localhost'
else if (errStr.includes('NotFound')) msg = '未检测到摄像头'
else if (errStr.includes('NotFound')) msg = '未检测到后置摄像头'
else if (errStr.includes('OverconstrainedError')) msg = '摄像头不支持高分辨率'
console.error("Scanner Error:", err)
errorMsg.value = msg
@ -89,7 +112,7 @@ const stopScanning = async () => {
onMounted(() => {
setTimeout(() => {
startScanning()
}, 300)
}, 500)
})
onUnmounted(() => {
@ -107,17 +130,15 @@ onUnmounted(() => {
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden; /* 关键:裁剪多余画面 */
border-radius: 12px; /* 保持圆角 */
overflow: hidden;
border-radius: 12px;
}
.scanner-box {
width: 100%;
height: 100%;
position: relative;
}
/* ★★★ 修复点2强制 Video 元素填满容器 ★★★ */
:deep(#qr-reader) {
width: 100%;
height: 100%;
@ -128,17 +149,11 @@ onUnmounted(() => {
:deep(#qr-reader video) {
width: 100% !important;
height: 100% !important;
object-fit: cover !important; /* 关键:保持比例铺满,裁剪多余部分 */
border-radius: 12px;
object-fit: cover !important;
display: block !important;
border-radius: 12px;
}
/* 隐藏 html5-qrcode 可能自带的遮罩层,我们用自己的 */
:deep(#qr-reader__scan_region) {
display: none !important;
}
/* 自定义错误提示 */
.error-msg {
position: absolute;
top: 50%;
@ -158,62 +173,56 @@ onUnmounted(() => {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 250px;
height: 120px;
/* 使用半透明黑边框模拟遮罩效果,突出中间区域 */
box-shadow: 0 0 0 2000px rgba(0, 0, 0, 0.6);
border: 1px solid rgba(255, 255, 255, 0.6);
border-radius: 8px;
width: 320px;
height: 60px;
border: 2px solid rgba(255, 0, 0, 0.6);
border-radius: 6px;
pointer-events: none;
box-shadow: 0 0 0 2000px rgba(0, 0, 0, 0.6);
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s;
}
/* 四个角的装饰 */
.focus-tip::before,
.focus-tip::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
border-color: #409EFF;
border-style: solid;
border-width: 0;
}
.focus-tip::before {
top: -1px; left: -1px;
border-top-width: 3px;
border-left-width: 3px;
}
.focus-tip::after {
bottom: -1px; right: -1px;
border-bottom-width: 3px;
border-right-width: 3px;
/* ★ 扫描成功时的绿色框样式 */
.focus-tip.success {
border-color: #67c23a; /* 绿色边框 */
background: rgba(103, 194, 58, 0.1);
}
/* 红色扫描线动画 */
.scan-line {
width: 90%;
width: 95%;
height: 2px;
background: #ff0000;
box-shadow: 0 0 4px #ff0000;
animation: scan-move 2s infinite linear;
position: absolute;
animation: scan-move 1.5s infinite ease-in-out;
}
.scan-text {
position: absolute;
bottom: -30px;
color: #fff;
font-size: 12px;
opacity: 0.8;
bottom: -35px;
color: rgba(255, 255, 255, 0.9);
font-size: 13px;
white-space: nowrap;
text-shadow: 0 1px 2px #000;
}
.scan-text-success {
color: #67c23a;
font-size: 16px;
font-weight: bold;
display: flex;
align-items: center;
gap: 5px;
text-shadow: 0 1px 3px rgba(0,0,0,0.8);
}
@keyframes scan-move {
0% { transform: translateY(-40px); opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { transform: translateY(40px); opacity: 0; }
0% { top: 10%; opacity: 0.5; }
50% { top: 90%; opacity: 1; }
100% { top: 10%; opacity: 0.5; }
}
</style>