|
|
@@ -39,6 +39,8 @@ import {
|
|
|
ensureLocationAuth
|
|
|
} from '@/utils/location'
|
|
|
import { verifyQrCode } from '@/api/inspection'
|
|
|
+// jsQR:纯 JS QR 识别库,作为 BarcodeDetector 不可用时的 fallback(iOS Safari < 17 / 老 WebView)
|
|
|
+import jsQR from 'jsqr'
|
|
|
|
|
|
const taskId = ref<number>(0)
|
|
|
const scanResult = ref<string>('')
|
|
|
@@ -80,19 +82,13 @@ function startScan() {
|
|
|
|
|
|
// ============================== H5 端扫码实现 ==============================
|
|
|
async function h5StartBarcodeScanner() {
|
|
|
- // 1. 能力检测
|
|
|
- const BarcodeDetectorCtor: any = (window as any).BarcodeDetector
|
|
|
- if (typeof BarcodeDetectorCtor !== 'function') {
|
|
|
- tipText.value = '当前浏览器不支持原生扫码,请点击下方"手动输入"'
|
|
|
- return
|
|
|
- }
|
|
|
- // 2. 检测摄像头权限 API
|
|
|
+ // 1. 检测摄像头权限 API
|
|
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
|
tipText.value = '浏览器不支持获取摄像头,请点击下方"手动输入"'
|
|
|
return
|
|
|
}
|
|
|
try {
|
|
|
- // 3. 申请摄像头(优先后置)
|
|
|
+ // 2. 申请摄像头(优先后置)
|
|
|
h5Stream = await navigator.mediaDevices.getUserMedia({
|
|
|
video: { facingMode: { ideal: 'environment' } },
|
|
|
audio: false
|
|
|
@@ -109,7 +105,7 @@ async function h5StartBarcodeScanner() {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // 4. 创建 <video> 元素并播放
|
|
|
+ // 3. 创建 <video> 元素并播放
|
|
|
const wrap = document.getElementById('h5-video-wrap')
|
|
|
if (!wrap) return
|
|
|
wrap.innerHTML = ''
|
|
|
@@ -128,27 +124,62 @@ async function h5StartBarcodeScanner() {
|
|
|
console.warn('[H5 扫码] video.play 失败', e)
|
|
|
}
|
|
|
|
|
|
- // 5. 循环 detect 二维码
|
|
|
- let detector: any
|
|
|
- try {
|
|
|
- detector = new BarcodeDetectorCtor({ formats: ['qr_code'] })
|
|
|
- } catch (e) {
|
|
|
- tipText.value = '浏览器不支持 QR 码识别,请点击下方"手动输入"'
|
|
|
+ // 4. 选 QR 识别方案:
|
|
|
+ // 优先 BarcodeDetector(Chrome/Edge/iOS 17+ 性能好),
|
|
|
+ // 不可用时 fallback jsQR(纯 JS,兼容老浏览器但稍慢)
|
|
|
+ const BarcodeDetectorCtor: any = (window as any).BarcodeDetector
|
|
|
+ if (typeof BarcodeDetectorCtor === 'function') {
|
|
|
+ let detector: any
|
|
|
+ try {
|
|
|
+ detector = new BarcodeDetectorCtor({ formats: ['qr_code'] })
|
|
|
+ } catch (e) {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ if (detector) {
|
|
|
+ const tick = async () => {
|
|
|
+ if (!h5Stream) return
|
|
|
+ try {
|
|
|
+ const codes = await detector.detect(video)
|
|
|
+ if (codes && codes.length && codes[0].rawValue) {
|
|
|
+ h5StopScanner()
|
|
|
+ scanResult.value = codes[0].rawValue
|
|
|
+ onScanSuccess(codes[0].rawValue)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ // 单帧失败忽略
|
|
|
+ }
|
|
|
+ h5RafId = requestAnimationFrame(tick)
|
|
|
+ }
|
|
|
+ h5RafId = requestAnimationFrame(tick)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. Fallback:jsQR(用 canvas 抽帧识别)
|
|
|
+ tipText.value = '正在用 jsQR 识别中…'
|
|
|
+ const canvas = document.createElement('canvas')
|
|
|
+ const ctx = canvas.getContext('2d', { willReadFrequently: true })
|
|
|
+ if (!ctx) {
|
|
|
+ tipText.value = '当前浏览器不支持 QR 码识别,请点击下方"手动输入"'
|
|
|
return
|
|
|
}
|
|
|
- const tick = async () => {
|
|
|
+ const tick = () => {
|
|
|
if (!h5Stream) return
|
|
|
- try {
|
|
|
- const codes = await detector.detect(video)
|
|
|
- if (codes && codes.length && codes[0].rawValue) {
|
|
|
- // 命中:停摄像头 + 触发回调
|
|
|
+ if (video.readyState === video.HAVE_ENOUGH_DATA) {
|
|
|
+ canvas.width = video.videoWidth
|
|
|
+ canvas.height = video.videoHeight
|
|
|
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
|
|
|
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
|
+ const code = jsQR(imageData.data, imageData.width, imageData.height, {
|
|
|
+ inversionAttempts: 'dontInvert'
|
|
|
+ })
|
|
|
+ if (code && code.data) {
|
|
|
h5StopScanner()
|
|
|
- scanResult.value = codes[0].rawValue
|
|
|
- onScanSuccess(codes[0].rawValue)
|
|
|
+ scanResult.value = code.data
|
|
|
+ onScanSuccess(code.data)
|
|
|
return
|
|
|
}
|
|
|
- } catch (e) {
|
|
|
- // 单帧 detect 失败忽略(视频流可能还没准备好)
|
|
|
}
|
|
|
h5RafId = requestAnimationFrame(tick)
|
|
|
}
|