Initial commit
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<view class="qr-scanner">
|
||||
<!-- #ifdef WEB -->
|
||||
<view :id="videoContainerId" class="qr-scanner-video"></view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef WEB -->
|
||||
<camera
|
||||
class="qr-scanner-camera"
|
||||
mode="scanCode"
|
||||
device-position="back"
|
||||
@scancode="handleCameraScan"
|
||||
@error="handleCameraError"
|
||||
></camera>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// #ifdef WEB
|
||||
import { BrowserQRCodeReader } from '@zxing/browser'
|
||||
import type { IScannerControls } from '@zxing/browser'
|
||||
// #endif
|
||||
|
||||
type Props = {
|
||||
cooldown?: number
|
||||
active?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
cooldown: 5000,
|
||||
active: true
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'scan', result: string): void
|
||||
(e: 'error'): void
|
||||
}>()
|
||||
|
||||
let lastScannedAt = 0
|
||||
// #ifdef WEB
|
||||
const videoContainerId = `qr-scanner-${Date.now()}`
|
||||
let scannerControls: IScannerControls | null = null
|
||||
let scanVideo: HTMLVideoElement | null = null
|
||||
let isPageActive = false
|
||||
let restartTimer: number | null = null
|
||||
// #endif
|
||||
|
||||
function emitScan(result: string) {
|
||||
const now = Date.now()
|
||||
if (!props.active || !result || now - lastScannedAt < props.cooldown) return
|
||||
lastScannedAt = now
|
||||
emit('scan', result)
|
||||
}
|
||||
|
||||
function handleCameraScan(event: any) {
|
||||
emitScan(event.detail?.result as string ?? '')
|
||||
}
|
||||
|
||||
function handleCameraError() {
|
||||
emit('error')
|
||||
}
|
||||
|
||||
// #ifdef WEB
|
||||
function getScanVideo(): HTMLVideoElement | null {
|
||||
if (scanVideo != null) return scanVideo
|
||||
const container = document.getElementById(videoContainerId)
|
||||
if (container == null) return null
|
||||
|
||||
const video = document.createElement('video') as HTMLVideoElement
|
||||
video.autoplay = true
|
||||
video.muted = true
|
||||
video.playsInline = true
|
||||
video.setAttribute('style', 'display:block;width:100%;height:100%;object-fit:cover;background-color:#000000;')
|
||||
container.appendChild(video)
|
||||
scanVideo = video
|
||||
return video
|
||||
}
|
||||
|
||||
async function startWebScanner() {
|
||||
if (!isPageActive || !props.active || scannerControls != null) return
|
||||
const video = getScanVideo()
|
||||
if (video == null) {
|
||||
handleCameraError()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const reader = new BrowserQRCodeReader()
|
||||
scannerControls = await reader.decodeFromConstraints(
|
||||
{ video: { facingMode: 'environment' } },
|
||||
video,
|
||||
(result, _error, controls) => {
|
||||
if (!result) return
|
||||
controls.stop()
|
||||
scannerControls = null
|
||||
emitScan(result.getText())
|
||||
scheduleRestart()
|
||||
}
|
||||
)
|
||||
} catch (_error) {
|
||||
handleCameraError()
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleRestart() {
|
||||
if (!isPageActive || !props.active || restartTimer != null) return
|
||||
restartTimer = setTimeout(() => {
|
||||
restartTimer = null
|
||||
void startWebScanner()
|
||||
}, props.cooldown)
|
||||
}
|
||||
|
||||
function stopWebScanner() {
|
||||
if (restartTimer != null) {
|
||||
clearTimeout(restartTimer)
|
||||
restartTimer = null
|
||||
}
|
||||
if (scannerControls != null) {
|
||||
scannerControls.stop()
|
||||
scannerControls = null
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.active, (active) => {
|
||||
if (active) {
|
||||
void startWebScanner()
|
||||
} else {
|
||||
stopWebScanner()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
isPageActive = true
|
||||
void startWebScanner()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
isPageActive = false
|
||||
stopWebScanner()
|
||||
scanVideo = null
|
||||
})
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.qr-scanner { width: 100%; height: 224px; overflow: hidden; border-radius: 16px; border: 2px solid #6C63FF; background-color: #000000; }
|
||||
.qr-scanner-camera { width: 100%; height: 100%; }
|
||||
.qr-scanner-video { width: 100%; height: 100%; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user