手机 浏览器调用摄像头扫描二维码Quagga
注:需用谷歌浏览器才能调用权限
1. 引入依赖:npm install @ericblade/quagga2
<template><el-button color="#188ae2" @click="handleScan" class="scan-btn" :disabled="isInitializing || isScanning">{{t('btn.scan') }}</el-button><!-- 条形码扫描器的占位符 --><div id="interactive" ref="interactiveRef" class="viewport" style="display: none;"></div></template><script setup>import { ref, onBeforeUnmount } from 'vue'
import Quagga from '@ericblade/quagga2'; // 引用
const interactiveRef = ref < HTMLElement | null > (null)
const isInitializing = ref(false)
const isScanning = ref(false)
const lastScanTime = ref(0)
let onDetectedHandler = null// 统一停止/清理
const stopScan = () => {try { Quagga.stop() } catch { }const el = interactiveRef.value || (document.querySelector('#interactive') || null)if (el) el.style.display = 'none'if (onDetectedHandler) {Quagga.offDetected(onDetectedHandler)onDetectedHandler = null}isScanning.value = falseisInitializing.value = false
}const handleScan = async () => {// 防抖:1s 内不重复触发const now = Date.now()if (now - lastScanTime.value < 1000) returnlastScanTime.value = now// ✅ 修复:不要写成 if (!isMobile.value); 末尾多分号if (!isMobile.value) {toast('不是手机端登录', 'warning')return}// 避免重复初始化/启动if (isInitializing.value || isScanning.value) {toast('正在打开摄像头…', 'info')return}const container = interactiveRef.value || (document.querySelector('#interactive') | null)if (!container) {toast('找不到扫码容器 #interactive', 'error')return}// 显示并给尺寸(没有尺寸很难识别)container.style.display = 'block'container.style.width = '100%'container.style.height = '60vh'isInitializing.value = truetry {await new Promise((resolve, reject) => {Quagga.init({inputStream: {name: 'Live',type: 'LiveStream',target: container,constraints: {facingMode: 'environment',width: { min: 640 },height: { min: 480 },},},decoder: { readers: ['code_128_reader', 'ean_reader'] },locate: true,},(err) => (err ? reject(err) : resolve()),)})Quagga.start()isScanning.value = truetoast('摄像头已启动', 'success')} catch (e) {console.error('摄像头/初始化失败', e)toast('无法启动摄像头:请使用 HTTPS 并允许相机权限', 'error')stopScan()return} finally {isInitializing.value = false}// 识别一次即关闭(做一次性处理)anyonDetectedHandler = (data) => {if (!isScanning.value) returnconst code = data?.codeResult?.codeif (code) {trackingNumber.value = code // ✅ 把结果写回输入框toast('已识别:' + code, 'success')// todo 在这里添加扫描后需调用的逻辑stopScan()}}Quagga.onDetected(onDetectedHandler)
}// 清理资源
onBeforeUnmount(() => {stopScan()
});</script>