修改条形码为二维码,同时对于扫码展示部分进行修改

This commit is contained in:
dxc
2026-02-09 14:48:09 +08:00
parent bdee5fb27a
commit fdf22b9973
5 changed files with 402 additions and 205 deletions

View File

@ -6,7 +6,7 @@
<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">
@ -15,25 +15,43 @@
扫描成功3秒后继续...
</div>
</div>
<div v-if="hasZoom" class="zoom-control">
<span class="zoom-icon">-</span>
<input
type="range"
:min="zoomMin"
:max="zoomMax"
step="0.1"
v-model="currentZoom"
@input="handleZoom"
/>
<span class="zoom-icon">+</span>
<div class="zoom-value">{{ currentZoom }}x</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import { Html5Qrcode, Html5QrcodeSupportedFormats } from 'html5-qrcode'
import { CircleCheckFilled } from '@element-plus/icons-vue' // 引入图标用于成功提示
import { CircleCheckFilled } from '@element-plus/icons-vue'
// 定义事件
const emit = defineEmits(['decode', 'error'])
const errorMsg = ref('')
const isPaused = ref(false) // ★ 新增:控制暂停状态
const isPaused = ref(false)
let html5QrCode: Html5Qrcode | null = null
const scannerElementId = "qr-reader"
// 变焦控制状态
const hasZoom = ref(false)
const zoomMin = ref(1)
const zoomMax = ref(5)
const currentZoom = ref(1)
const startScanning = async () => {
try {
// 1. 实例化
html5QrCode = new Html5Qrcode(scannerElementId, {
useBarCodeDetectorIfSupported: true,
formatsToSupport: [
@ -43,37 +61,34 @@ const startScanning = async () => {
verbose: false
})
// 2. 启动配置
const config = {
fps: 20,
qrbox: { width: 320, height: 60 },
// ★★★ 核心修改点 2移除了 qrbox 属性 ★★★
// 移除后,库默认会对每一帧的“全画面”进行解析,不再局限于中间区域
// qrbox: { width: 300, height: 100 },
disableFlip: false,
videoConstraints: {
facingMode: "environment",
width: { min: 1280, ideal: 1920, max: 3840 },
height: { min: 720, ideal: 1080, max: 2160 },
// 保持高分辨率以支持微小条码
width: { min: 1280, ideal: 3840, max: 3840 },
height: { min: 720, ideal: 2160, max: 2160 },
focusMode: "continuous",
advanced: [{ focusMode: "macro" }]
advanced: [{ focusMode: "macro" }, { zoom: 2.0 }]
}
}
// 3. 启动
await html5QrCode.start(
{ facingMode: "environment" },
config,
(decodedText) => {
// ★ 核心修改:如果处于暂停冷却期,直接忽略后续扫描结果
if (isPaused.value) return
console.log(`Scan: ${decodedText}`)
// 1. 锁定状态
isPaused.value = true
// 2. 发送数据
emit('decode', decodedText)
// 3. 开启 3 秒倒计时解锁
if (navigator.vibrate) navigator.vibrate(200);
setTimeout(() => {
isPaused.value = false
}, 3000)
@ -82,20 +97,52 @@ const startScanning = async () => {
// ignore
}
)
checkZoomCapability()
} catch (err: any) {
let msg = '无法启动摄像头'
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('OverconstrainedError')) msg = '摄像头不支持高分辨率'
console.error("Scanner Error:", err)
errorMsg.value = msg
emit('error', msg)
}
}
// 检测硬件变焦能力
const checkZoomCapability = () => {
if (!html5QrCode) return
try {
const videoTrack = html5QrCode.getRunningTrackCameraCapabilities() as MediaTrackCapabilities;
// @ts-ignore
if (videoTrack && 'zoom' in videoTrack) {
hasZoom.value = true
// @ts-ignore
zoomMin.value = videoTrack.zoom.min || 1
// @ts-ignore
zoomMax.value = videoTrack.zoom.max || 5
// @ts-ignore
currentZoom.value = videoTrack.zoom.min || 1
}
} catch (e) {
console.warn("无法获取变焦能力", e)
}
}
// 处理滑块拖动
const handleZoom = () => {
if (!html5QrCode) return
try {
html5QrCode.applyVideoConstraints({
advanced: [{ zoom: Number(currentZoom.value) }]
})
} catch (e) {
console.error("变焦失败", e)
}
}
const stopScanning = async () => {
if (html5QrCode) {
try {
@ -131,7 +178,8 @@ onUnmounted(() => {
justify-content: center;
align-items: center;
overflow: hidden;
border-radius: 12px;
/* 如果是全屏模式这里不需要圆角或者保持圆角视你的UI设计而定 */
border-radius: 0;
}
.scanner-box {
@ -151,7 +199,6 @@ onUnmounted(() => {
height: 100% !important;
object-fit: cover !important;
display: block !important;
border-radius: 12px;
}
.error-msg {
@ -167,62 +214,98 @@ onUnmounted(() => {
z-index: 20;
}
/* --- 视觉辅助线 --- */
/* --- ★ 修改点 3视觉层 CSS 更新 --- */
.focus-tip {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 320px;
height: 60px;
border: 2px solid rgba(255, 0, 0, 0.6);
border-radius: 6px;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* 移除了 border 和 box-shadow不再显示红框和黑色遮罩 */
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.success {
border-color: #67c23a; /* 绿色边框 */
background: rgba(103, 194, 58, 0.1);
background: rgba(103, 194, 58, 0.2); /* 成功时全屏微微泛绿 */
}
/* 扫描线改为全屏宽度 */
.scan-line {
width: 95%;
width: 100%;
height: 2px;
background: #ff0000;
box-shadow: 0 0 4px #ff0000;
background: rgba(255, 0, 0, 0.5);
box-shadow: 0 0 4px rgba(255, 0, 0, 0.8);
position: absolute;
animation: scan-move 1.5s infinite ease-in-out;
/* 动画范围从 10% 到 90% */
animation: scan-move 2.5s infinite linear;
}
.scan-text {
position: absolute;
bottom: -35px;
color: rgba(255, 255, 255, 0.9);
font-size: 13px;
white-space: nowrap;
text-shadow: 0 1px 2px #000;
bottom: 150px; /* 调整文字位置 */
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
text-shadow: 0 1px 3px rgba(0,0,0,0.8);
background: rgba(0,0,0,0.3);
padding: 4px 10px;
border-radius: 4px;
}
.scan-text-success {
color: #67c23a;
font-size: 16px;
color: #fff;
font-size: 20px;
font-weight: bold;
display: flex;
align-items: center;
gap: 5px;
text-shadow: 0 1px 3px rgba(0,0,0,0.8);
gap: 10px;
text-shadow: 0 2px 4px rgba(0,0,0,0.8);
background: rgba(103, 194, 58, 0.9);
padding: 15px 30px;
border-radius: 50px;
}
@keyframes scan-move {
0% { top: 10%; opacity: 0.5; }
50% { top: 90%; opacity: 1; }
100% { top: 10%; opacity: 0.5; }
0% { top: 0%; opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { top: 100%; opacity: 0; }
}
/* 变焦控制器 */
.zoom-control {
position: absolute;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
width: 80%;
max-width: 300px;
display: flex;
align-items: center;
gap: 10px;
background: rgba(0, 0, 0, 0.5);
padding: 10px 20px;
border-radius: 30px;
z-index: 50;
color: white;
}
.zoom-control input[type=range] {
flex: 1;
height: 4px;
}
.zoom-icon {
font-size: 20px;
font-weight: bold;
}
.zoom-value {
font-size: 14px;
width: 30px;
text-align: right;
}
</style>