技术支持(@zxing/library,webrtc-adapter)
运行条件必须是https
运行条件必须是https
运行条件必须是https
<template>
<div class="page-scan" style="flex: 1; position: relative;">
<!-- 扫码区域 -->
<div class="QrCode" ref="videoContainer" style="height: 100%;">
<!-- <video id="kankan" ref="video" height="100%" autoplay playsinline muted style="object-fit: cover;"></video> -->
</div>
<!-- 扫码样式遮罩 -->
<div class="qr-scanner">
<div class="box">
<div class="line"></div>
<div class="angle"></div>
</div>
<div class="mask1"></div>
<div class="mask2"></div>
<div class="mask3"></div>
<div class="mask4"></div>
</div>
</div>
</template>
<script>
import 'webrtc-adapter'
import {
BrowserMultiFormatReader
} from '@zxing/library'
export default {
name: 'ScanCodePage',
data() {
return {
codeReader: null,
scanning: false,
videoEl: null, // 保存原生 video 引用
}
},
mounted() {
this.codeReader = new BrowserMultiFormatReader()
this.openScan()
},
beforeUnmount() {
this.stopScan()
},
methods: {
async openScan() {
try {
// 1. 创建原生 video 元素
const video = document.createElement('video')
video.setAttribute('id', 'kankan')
video.setAttribute('autoplay', true)
video.setAttribute('playsinline', true)
video.setAttribute('muted', true)
video.style.width = '100%'
video.style.height = '100%'
video.style.objectFit = 'cover'
// 2. 挂载到页面上
this.$refs.videoContainer.appendChild(video)
this.videoEl = video
// 3. 获取摄像头设备
const devices = await this.codeReader.listVideoInputDevices()
if (!devices.length) {
// this.$toast.fail('未找到摄像头设备')
return
}
let deviceId = devices[0].deviceId
if (devices.length > 1) {
const backCamera = devices.find(d => d.label.toLowerCase().includes('back'))
if (backCamera) {
deviceId = backCamera.deviceId
} else {
deviceId = devices[1].deviceId
}
}
// 4. 启动扫码
this.codeReader.reset()
this.scanning = true
this.codeReader.decodeFromInputVideoDeviceContinuously(
deviceId,
video,
(result, err) => {
uni.showToast({
title: '扫码中',
icon: 'none'
})
if (result) {
uni.showToast({
title: '扫码成功',
icon: 'none'
})
this.stopScan()
}
if (err && err.name !== 'NotFoundException') {
uni.showToast({
title: '扫码失败',
icon: 'none'
})
// this.$toast.fail(err.message || err)
}
}
)
} catch (error) {
// this.$toast.fail(error.message || error)
}
},
stopScan() {
if (this.scanning) {
this.codeReader.reset()
this.scanning = false
// 清理 video 元素
if (this.videoEl && this.videoEl.parentNode) {
this.videoEl.parentNode.removeChild(this.videoEl)
}
this.videoEl = null
}
},
},
}
</script>
<style scoped lang="scss">
.page-scan {
position: relative;
height: 100vh;
display: flex;
flex-direction: column;
}
.QrCode {
flex: 1;
position: relative;
}
video {
width: 100%;
height: 100%;
object-fit: cover;
}
.qr-scanner {
background-size: 3rem 3rem;
background-position: -1rem -1rem;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9;
width: 100%;
height: 100%;
}
.qr-scanner .box {
width: 75vw;
height: 75vw;
max-height: 75vh;
max-width: 75vh;
position: relative;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
overflow: hidden;
}
.qr-scanner .line {
height: calc(100% - 2px);
width: 100%;
background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #3F90FD 211%);
border-bottom: 3px solid #3F90FD;
transform: translateY(-100%);
animation: radar-beam 2s infinite;
animation-timing-function: cubic-bezier(0.53, 0, 0.43, 0.99);
animation-delay: 1.4s;
}
.qr-scanner .box:after,
.qr-scanner .box:before,
.qr-scanner .angle:after,
.qr-scanner .angle:before {
content: '';
display: block;
position: absolute;
width: 3vw;
height: 3vw;
border: 0.2rem solid transparent;
}
.qr-scanner .box:after,
.qr-scanner .box:before {
top: 0;
border-top-color: #3F90FD;
}
.qr-scanner .angle:after,
.qr-scanner .angle:before {
bottom: 0;
border-bottom-color: #3F90FD;
}
.qr-scanner .box:before,
.qr-scanner .angle:before {
left: 0;
border-left-color: #3F90FD;
}
.qr-scanner .box:after,
.qr-scanner .angle:after {
right: 0;
border-right-color: #3F90FD;
}
@keyframes radar-beam {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(0);
}
}
.back-btn {
position: fixed;
top: 20px;
left: 20px;
padding: 6px 12px;
background: rgba(0, 0, 0, 0.4);
border: none;
border-radius: 4px;
color: white;
font-size: 14px;
z-index: 999;
cursor: pointer;
}
.back-btn:hover {
background: rgba(0, 0, 0, 0.6);
}
</style>
由于uniapp不支持原生video标签,只能自己创建