盘库操作初设计

This commit is contained in:
dxc
2026-02-06 10:16:37 +08:00
parent c1ddb8093f
commit e027ebd4a9
15 changed files with 1227 additions and 30 deletions

View File

@ -0,0 +1,219 @@
<template>
<div class="qr-scanner-container">
<div id="qr-reader" class="scanner-box"></div>
<div v-if="errorMsg" class="error-msg">{{ errorMsg }}</div>
<div class="focus-tip" v-if="!errorMsg">
<div class="scan-line"></div>
<div class="scan-text">将条码对准取景框</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import { Html5Qrcode, Html5QrcodeSupportedFormats } from 'html5-qrcode'
const emit = defineEmits(['decode', 'error'])
const errorMsg = ref('')
let html5QrCode: Html5Qrcode | null = null
const scannerElementId = "qr-reader"
const startScanning = async () => {
try {
// 1. 实例化
html5QrCode = new Html5Qrcode(scannerElementId, {
useBarCodeDetectorIfSupported: true,
formatsToSupport: [
Html5QrcodeSupportedFormats.CODE_128,
Html5QrcodeSupportedFormats.QR_CODE
],
verbose: false
})
// 2. 启动配置
const config = {
fps: 25,
qrbox: { width: 250, height: 100 }, // 扫描区域设置
// ★★★ 修复点1移除 aspectRatio让画面自适应容器长宽比 ★★★
// aspectRatio: 1.0,
disableFlip: false,
videoConstraints: {
facingMode: "environment",
// 限制分辨率,保证速度
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 480, ideal: 720, max: 1080 },
focusMode: "continuous"
}
}
// 3. 启动
await html5QrCode.start(
{ facingMode: "environment" },
config,
(decodedText) => {
console.log(`Scan: ${decodedText}`)
emit('decode', decodedText)
},
(errorMessage) => {
// ignore
}
)
} 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 = '未检测到摄像头'
console.error("Scanner Error:", err)
errorMsg.value = msg
emit('error', msg)
}
}
const stopScanning = async () => {
if (html5QrCode) {
try {
if (html5QrCode.isScanning) {
await html5QrCode.stop()
}
html5QrCode.clear()
} catch (e) {
console.error("Stop failed", e)
}
}
}
onMounted(() => {
setTimeout(() => {
startScanning()
}, 300)
})
onUnmounted(() => {
stopScanning()
})
</script>
<style scoped>
.qr-scanner-container {
width: 100%;
height: 100%;
position: relative;
background-color: #000;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden; /* 关键:裁剪多余画面 */
border-radius: 12px; /* 保持圆角 */
}
.scanner-box {
width: 100%;
height: 100%;
position: relative;
}
/* ★★★ 修复点2强制 Video 元素填满容器 ★★★ */
:deep(#qr-reader) {
width: 100%;
height: 100%;
border: none !important;
padding: 0 !important;
}
:deep(#qr-reader video) {
width: 100% !important;
height: 100% !important;
object-fit: cover !important; /* 关键:保持比例铺满,裁剪多余部分 */
border-radius: 12px;
display: block !important;
}
/* 隐藏 html5-qrcode 可能自带的遮罩层,我们用自己的 */
:deep(#qr-reader__scan_region) {
display: none !important;
}
/* 自定义错误提示 */
.error-msg {
position: absolute;
top: 50%;
left: 0;
width: 100%;
text-align: center;
color: #fff;
background: rgba(245, 108, 108, 0.85);
padding: 15px;
transform: translateY(-50%);
z-index: 20;
}
/* --- 视觉辅助线 --- */
.focus-tip {
position: absolute;
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;
pointer-events: none;
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
}
/* 四个角的装饰 */
.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;
}
/* 红色扫描线动画 */
.scan-line {
width: 90%;
height: 2px;
background: #ff0000;
box-shadow: 0 0 4px #ff0000;
animation: scan-move 2s infinite linear;
}
.scan-text {
position: absolute;
bottom: -30px;
color: #fff;
font-size: 12px;
opacity: 0.8;
}
@keyframes scan-move {
0% { transform: translateY(-40px); opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { transform: translateY(40px); opacity: 0; }
}
</style>