当前位置: 首页 > java >正文

手机 浏览器调用摄像头扫描二维码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>

http://www.xdnf.cn/news/18279.html

相关文章:

  • 2026 济南淀粉深加工展览会亮点:玉米科技与未来产业发展
  • 03-dockerfile
  • C++继承中的虚函数机制:从单继承到多继承的深度解析
  • 【ansible】2.实施ansible playbook
  • 机器学习算法核心总结
  • 静/动态库 IIC(arm) day58
  • 医疗问答应用:UniApp + Node.js + DeepSeek API
  • Objective-C 版本的 LiveEventBus 效果
  • 直流无刷(BLDC)电机、单相直流无刷电机、三相直流无刷电机、单相直流无刷电机驱动芯片
  • 齐次线性方程组最小二乘解
  • 从零开始学AI——13
  • Docker复杂安装--最详细的MySQL主从复制与Redis集群安装、主从复制、主从扩容主从缩容实战版
  • java线程池相关知识
  • XR(AR/VR/MR)芯片方案,Soc VS “MCU+协处理器”?
  • 【动态规划、dp】P4933 大师
  • pnpm : 无法加载文件 C:\Program Files\nodejs\pnpm.ps1,因为在此系统上禁止运行脚本。
  • C++之多态(从0到1的突破)
  • Python如何将两个列表转化为一个字典
  • 基于STM32的APP遥控视频水泵小车设计
  • Codeforces MIN = GCD
  • Python爬虫实战:研究dark-fantasy,构建奇幻文学数据采集分析系统
  • BM25 vs TF-IDF:经典文本检索方法的对比
  • 【39】OpenCV C++实战篇——直线拟合、直线测距、平行线段测距;(边缘检测,剔除噪点,轮廓检测,渐进概率霍夫直线)
  • Django管理后台结合剪映实现课件视频生成应用
  • MySQL架构
  • MySQL实战45讲 24-25
  • hadoop技术栈(九)Hbase替代方案
  • Linux 进程间通信(IPC):信号、共享内存
  • Vue3 el-table实现 将子表字段动态显示在主表行尾
  • MySQL 三大日志:redo log、undo log、binlog 详解