功能
- 支持通过摄像头实时扫描二维码,可切换前置、后置摄像头,扫描成功或失败有相应提示及事件触发。
- 能选择本地图片进行二维码扫描,同样有对应成功、失败反馈。
- 界面有关闭、切换摄像头、选择相册等交互按钮。
知识点
- 组件使用:在HTML中引入组件,绑定相关自定义事件,布局界面元素并设置样式,含动画效果展示。
- 组件实现:
- 引入
Html5Qrcode
等库,在data
中定义相关数据。 - 利用生命周期钩子做初始化、销毁等操作。
- 实现多个方法,如获取摄像头、关闭组件、切换摄像头、启动/停止扫描、使用本地图片扫描及对应扫描结果处理等方法。
- 引入
-
使用
-
<BarScan v-if="qrcodeShow" ref="qrcode" @ok="getResult" @err="geterror" @close="qrcodeShow = false" ></BarScan>
-
组件源码
-
<template> <div class="qrcode"> <div class="close" @click="closeComponents"> <img src="@/static/取消.svg" alt="" style="height:20px"> </div> <rjLoad v-if="!loaded" style='z-index: 99999;'></rjLoad> <div id="reader" style="height: 100vh;width:100%;"></div> <!-- 使用本地图片或者切换摄像头 --> <div class="switch"> <div class="switch-item" @click="switchCamera"> <div class="img-box"> <img src="@/static/切换.svg" alt=""> </div> <p>切换</p> </div> <div class="switch-item" @click="useLocalImage"> <div class="img-box"> <img src="@/static/相册.svg" alt=""> </div> <p>相册</p> </div> </div> </div> </template> <script> import { Html5Qrcode } from "html5-qrcode"; import rjLoad from "@/components/rj-load/RjLoad" import { conforms } from "lodash"; export default { components: { rjLoad }, data() { return { html5QrCode: null, loaded: false, type:true, } }, created() { this.getCameras() }, beforeDestroy() { try{ this.stop() }catch(e){ //TODO handle the exception } }, mounted() { }, onShow(){ this.getCameras() }, methods: { getCameras() { this.loaded = false Html5Qrcode.getCameras() .then((devices) => { if (devices && devices.length) { this.html5QrCode = new Html5Qrcode("reader"); this.start(); } }) .catch((err) => { // handle err this.html5QrCode = new Html5Qrcode("reader"); this.error = " 请授予相机访问权限 " uni.showToast({ title: this.error, icon: "error", duration: 2000, }) this.$emit("err", this.error) }); }, closeComponents() { this.$emit("close") }, switchCamera(){ this.type=!this.type try{ this.stop() }catch(e){ //TODO handle the exception } this.getCameras() }, async start() { let type = 'environment' if(!this.type){ type='user' } console.log(type) //environment后置 user前置 console.log('screenWidth',screen.width) console.log('screenHeight',screen.height) await this.html5QrCode .start( // environment后置摄像头 user前置摄像头 也可以传递获取摄像头时的id { facingMode: type }, { fps: 10, qrbox: { width: screen.width, height: screen.height }, aspectRatio: screen.height/screen.width // canvasWidth: screen.width, // canvasHeight: screen.height, }, (decodedText, decodedResult) => { this.$emit("ok", decodedText) uni.showToast({ title: "扫描成功", icon: "success", duration: 2000, }); } ).then(()=>{ console.log('启动成功') setTimeout(()=>{ this.loaded = true },500) }) .catch((err) => { console.log(err,'err') this.$emit("err", err) }); }, stop() { this.html5QrCode.stop().then((ignore) => { // QR Code scanning is stopped. console.log("QR Code scanning stopped."); }) .catch((err) => { // Stop failed, handle it. console.log("Unable to stop scanning."); }); }, //使用本地文件 useLocalImage() { const _that = this //使用本地文件需要先关闭摄像头 try{ this.stop() }catch(e){ //TODO handle the exception } console.log('选择文件') uni.chooseFile({ count: 1, //默认100 extension: ['.jpg', '.png', '.jpeg'], success: _that.successFn, fail: (err) =>{ console.log(err) } }); }, successFn(res) { // if (res.tempFiles[0].size / 1024 / 1024 > 20) { // this.$refs.uToast.show({ // title: '附件大小不能超过20M', // type: 'warning', // }) // return; // } const _this = this this.html5QrCode .scanFile(res.tempFiles[0], true) .then((decodedText) => { console.log('扫描成q功wqwqw,',res) _this.$emit("ok", decodedText) res.innerText = "扫码成功结果:\n" + decodedText; uni.showToast({ title: "扫描成功", icon: "success", }) }) .catch((err) => { console.log('扫描失败,',err) res.innerText = "扫码失败:\n" + err; uni.showToast({ title: "未能识别到二维码", icon: "error", }) this.$emit("err", err) }); } } } </script> <style lang="scss" scoped> .qrcode { position: fixed; z-index: 2000; top: 0; left: 0; height: 100%; width: 100%; background: rgba($color: #000000, $alpha: 0.48); } .close { position: absolute; top: 10px; right: 10px; background-color: #fff; font-size: 16px; z-index: 2001; height: 30px; width: 30px; display: flex; justify-content: center; align-items: center; text-align: center; border-radius: 50%; background: rgba($color: #000000, $alpha: 0.48); cursor: pointer; } #reader { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000; background-color: rgba(0, 0, 0, 0.3); /* Optional: to darken the background */ display: flex; align-items: center; justify-content: center; } #reader::before { content: ""; position: absolute; z-index: 1001; top: -4px; /* 初始位置在视图外 */ left: 50%; width: 70%; transform: translate(-50%,0); height: 8px; /* 蓝条的高度 */ border-radius: 60%; box-shadow: 0 -10px 30px rgba(0, 123, 255, 0.8); background-color: rgba(0, 123, 255, 0.8); /* 蓝色条的颜色和透明度 */ animation: scanAnimation 2s linear infinite; } @keyframes scanAnimation { 0% { top: 25%; /* 蓝条从视图上方开始 */ opacity: 0; } 50% { opacity: 1; } 100% { top: 75%; /* 蓝条移到视图下方 */ opacity: 0; } } .switch { position: absolute; z-index: 10001; bottom: 10px; left:0; box-sizing: border-box; color: #fff; width: 100%; display: flex; justify-content: space-between; align-items: center; font-size: 14px; padding: 10px 45px; .switch-item { display:flex; flex-direction: column; justify-content: center; align-items: center; color:#fff; border-radius: 5px; .img-box{ height: 40px; width: 40px; display: flex; justify-content: center; align-items: center; border-radius: 50%; background: rgba($color: #000000, $alpha: 0.48); margin-bottom: 4px; img{ height: 20px; } } } } button { display: block; width: 100%; margin: 6px; outline: none; height: 40px; line-height: 40px; color: #fff; background-color: #26a2ff; text-align: center; border-radius: 4px; border: none; cursor: pointer; } #upload-input { opacity: 0; filter: alpha(opacity=0); display: inline-block; width: 100%; height: 100%; } #upload-text { position: relative; bottom: 40px; user-select: none; } </style>
不懂可以找我
https://www.goofish.com/item?spm=a21ybx.personal.feeds.3.5e926ac2cyz0bJ&id=872662421016&categoryId=50023914