diff --git a/inventory-web/src/components/QrScanner/index.vue b/inventory-web/src/components/QrScanner/index.vue
index 9987ecf..0f8b633 100644
--- a/inventory-web/src/components/QrScanner/index.vue
+++ b/inventory-web/src/components/QrScanner/index.vue
@@ -12,7 +12,7 @@
- 扫描成功,3秒后继续...
+ 扫描成功,2秒后继续...
@@ -50,6 +50,43 @@ const zoomMin = ref(1)
const zoomMax = ref(5)
const currentZoom = ref(1)
+// 音频上下文
+let audioCtx: AudioContext | null = null;
+
+// 提示音播放函数
+const playBeep = () => {
+ try {
+ const AudioContext = window.AudioContext || (window as any).webkitAudioContext;
+ if (!AudioContext) return;
+
+ if (!audioCtx) {
+ audioCtx = new AudioContext();
+ }
+
+ if (audioCtx.state === 'suspended') {
+ audioCtx.resume();
+ }
+
+ const oscillator = audioCtx.createOscillator();
+ const gainNode = audioCtx.createGain();
+
+ oscillator.connect(gainNode);
+ gainNode.connect(audioCtx.destination);
+
+ // 三角波,清脆响亮
+ oscillator.type = 'triangle';
+ oscillator.frequency.value = 1500;
+
+ gainNode.gain.setValueAtTime(1.0, audioCtx.currentTime);
+
+ oscillator.start();
+ oscillator.stop(audioCtx.currentTime + 0.1);
+
+ } catch (e) {
+ console.error("播放提示音失败:", e);
+ }
+};
+
const startScanning = async () => {
try {
html5QrCode = new Html5Qrcode(scannerElementId, {
@@ -63,16 +100,16 @@ const startScanning = async () => {
const config = {
fps: 20,
- // ★★★ 核心修改点 2:移除了 qrbox 属性 ★★★
- // 移除后,库默认会对每一帧的“全画面”进行解析,不再局限于中间区域
- // qrbox: { width: 300, height: 100 },
-
disableFlip: false,
videoConstraints: {
facingMode: "environment",
- // 保持高分辨率以支持微小条码
- width: { min: 1280, ideal: 3840, max: 3840 },
- height: { min: 720, ideal: 2160, max: 2160 },
+ // ★★★ 核心修改:设置为 2K (QHD) 分辨率 ★★★
+ // min: 1280x720 (保证低端机能启动)
+ // ideal: 2560x1440 (2K QHD,清晰度与性能的平衡点)
+ width: { min: 1280, ideal: 2560, max: 3840 },
+ height: { min: 720, ideal: 1440, max: 2160 },
+ // 16:9 的比例
+ aspectRatio: { ideal: 1.7777777778 },
focusMode: "continuous",
advanced: [{ focusMode: "macro" }, { zoom: 2.0 }]
}
@@ -84,14 +121,18 @@ const startScanning = async () => {
(decodedText) => {
if (isPaused.value) return
console.log(`Scan: ${decodedText}`)
+
isPaused.value = true
+
+ playBeep();
+
emit('decode', decodedText)
if (navigator.vibrate) navigator.vibrate(200);
setTimeout(() => {
isPaused.value = false
- }, 3000)
+ }, 2000)
},
(errorMessage) => {
// ignore
@@ -103,12 +144,14 @@ const startScanning = async () => {
} catch (err: any) {
let msg = '无法启动摄像头'
console.error("Scanner Error:", err)
+ if (err.name === 'OverconstrainedError') {
+ msg = '摄像头不支持 2K 分辨率,请尝试降低配置'
+ }
errorMsg.value = msg
emit('error', msg)
}
}
-// 检测硬件变焦能力
const checkZoomCapability = () => {
if (!html5QrCode) return
@@ -130,7 +173,6 @@ const checkZoomCapability = () => {
}
}
-// 处理滑块拖动
const handleZoom = () => {
if (!html5QrCode) return
@@ -154,9 +196,19 @@ const stopScanning = async () => {
console.error("Stop failed", e)
}
}
+
+ if (audioCtx) {
+ audioCtx.close();
+ audioCtx = null;
+ }
}
onMounted(() => {
+ try {
+ const AudioContext = window.AudioContext || (window as any).webkitAudioContext;
+ if (AudioContext) audioCtx = new AudioContext();
+ } catch(e) {}
+
setTimeout(() => {
startScanning()
}, 500)
@@ -178,7 +230,6 @@ onUnmounted(() => {
justify-content: center;
align-items: center;
overflow: hidden;
- /* 如果是全屏模式,这里不需要圆角,或者保持圆角视你的UI设计而定 */
border-radius: 0;
}
@@ -214,14 +265,12 @@ onUnmounted(() => {
z-index: 20;
}
-/* --- ★ 修改点 3:视觉层 CSS 更新 --- */
.focus-tip {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
- /* 移除了 border 和 box-shadow,不再显示红框和黑色遮罩 */
pointer-events: none;
z-index: 10;
display: flex;
@@ -230,23 +279,21 @@ onUnmounted(() => {
}
.focus-tip.success {
- background: rgba(103, 194, 58, 0.2); /* 成功时全屏微微泛绿 */
+ background: rgba(103, 194, 58, 0.2);
}
-/* 扫描线改为全屏宽度 */
.scan-line {
width: 100%;
height: 2px;
background: rgba(255, 0, 0, 0.5);
box-shadow: 0 0 4px rgba(255, 0, 0, 0.8);
position: absolute;
- /* 动画范围从 10% 到 90% */
animation: scan-move 2.5s infinite linear;
}
.scan-text {
position: absolute;
- bottom: 150px; /* 调整文字位置 */
+ bottom: 150px;
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
text-shadow: 0 1px 3px rgba(0,0,0,0.8);
@@ -275,7 +322,6 @@ onUnmounted(() => {
100% { top: 100%; opacity: 0; }
}
-/* 变焦控制器 */
.zoom-control {
position: absolute;
bottom: 80px;
diff --git a/inventory-web/src/layout/index.vue b/inventory-web/src/layout/index.vue
index e2909e7..a5d1950 100644
--- a/inventory-web/src/layout/index.vue
+++ b/inventory-web/src/layout/index.vue
@@ -22,7 +22,7 @@ import AppMain from './components/AppMain.vue'
}
.sidebar-container {
- width: 210px; /* 固定侧边栏宽度 */
+ width: 180px; /* 固定侧边栏宽度 */
height: 100%;
background-color: #304156; /* 侧边栏背景色 */
flex-shrink: 0; /* 防止被挤压 */
@@ -37,7 +37,7 @@ import AppMain from './components/AppMain.vue'
flex-direction: column;
overflow-y: auto; /* 关键:页面内容过多时,只在右侧区域滚动 */
background-color: #f0f2f5; /* 右侧灰色背景,让白色卡片更明显 */
- padding: 20px; /* 给内部页面留出边距 */
+ padding: 10px; /* 给内部页面留出边距 */
box-sizing: border-box;
}
\ No newline at end of file