vue2 

"@zxing/library": "^0.19.1", "vue-qrcode-reader": "^3.1.0",

  1. 扫码方式有三种,第一微信jssdk自带的扫一扫功能,优点:无兼容性问题,只要微信能扫一扫的场景,这个api都能扫。缺点:只能在微信环境使用,浏览器环境中不能使用,另外一点,需要后端配合出接口,获取当前公众号的appid等信息。

第二种:直接用原生插件实现,发现两种插件 vue-qrcode-reader 和 @zxing/library,两个插件都可以在项目中使用,但是vue-qrcode-reader在苹果手机中无法使用,第一次扫码无反应调不起摄像头,需要返回第二次进入才会正常(暂未找到解决方法),@zxing/library安卓ios都能正常调用起摄像头,但是对于华为手机,因为华为手机有6个摄像头,正常手机就2个摄像头,所以对于华为手机要兼容判断下。代码中已展示。!!!还有二维码如果参数太多,生成的二维码密度就会越多,正常大小扫不出来,所以需要放大二维码,参数不能传太多,影响扫码准确性。!!! zxing/library 和vue-qrcode-reade 库兼容问题在装有 iOS < 14.3 相机访问仅适用于本机 Safari,而不适用于其他浏览器 (Chrome,…) 或使用 UIWebView 或 WKWebView 的应用。这不是这个库的限制,而是苹果有限的 WebRTC 支持的限制,但是 iOS 14.3(2020 年 12 月发布)现在也支持第三方浏览器中的 WebRTC。浏览器层使用的是 MediaDevices Web API,这是旧版浏览器不支持的。您可以使用 WebRTC 适配器等外部 polyfill 来增加浏览器兼容性。另外,请注意,该库使用的是 TypedArray 等,这在较旧的浏览器(例如 Android 4 默认浏览器)中不可用。Int32ArrayUint8ClampedArray您可以使用 core-js 添加对这些浏览器的支持。 

  1. @zxing/library 的 使用方法 ,可直接复制使用

调用 <Browser @getScanResult="getResult" />复制代码

<template>   <div>     <div>       <video ref="video" id="video" autoplay></video>       <div>         <div>           <div></div>           <div></div>         </div>       </div>       <div>{{ scanTextData.tipMsg }}</div>     </div>   </div> </template> <script> import { BrowserMultiFormatReader } from "@zxing/library"; export default {   name: "scanCodePage",   data() {     return {       scanTextData: {         codeReader: null,         tipMsg: "识别二维码",         // 这个,就是当前调用的摄像头的索引,为什么是6,我会在后面说的 华为手机是鸿蒙系统有8个摄像头         num: 5,         // 这个就是扫描到的摄像头的数量         videoLength: ""       },       hasBind: false     };   },   props: {     show: {       type: Boolean,       default: false     }   },   mounted() {     this.scanTextData.codeReader = new BrowserMultiFormatReader();     this.openScan(); // 打开摄像头   },   watch: {     show(val) {       if (!val) {         // 关闭摄像头         if (!document.getElementById("video")) {           alert("请授权");           return;         }         let thisVideo = document.getElementById("video");         thisVideo.srcObject.getTracks()[0].stop();         this.scanTextData.codeReader.reset();       } else {         if (this.scanTextData.codeReader === null) {           this.scanTextData.codeReader = new BrowserMultiFormatReader();         }         this.openScan();       }     }   },   methods: {     async openScan() {       this.scanTextData.codeReader         .getVideoInputDevices()         .then(videoInputDevices => {           // 默认获取第一个摄像头设备id           let firstDeviceId = videoInputDevices[0].deviceId;           console.log(             "手机摄像头的数量",             videoInputDevices.length,             videoInputDevices           );           // 获取第一个摄像头设备的名称           const videoInputDeviceslablestr = JSON.stringify(             videoInputDevices[0].label           );           if (videoInputDevices.length > 1) {             // 华为手机有6个摄像头,前三个是前置,后三个是后置,第6个摄像头最清晰             if (videoInputDevices.length > 5) {               firstDeviceId = videoInputDevices[5].deviceId;             } else {               // 判断是否后置摄像头               if (videoInputDeviceslablestr.indexOf("back") > -1) {                 firstDeviceId = videoInputDevices[0].deviceId;               } else {                 firstDeviceId = videoInputDevices[1].deviceId;               }             }           }           this.decodeFromInputVideoFunc(firstDeviceId);         })         .catch(err => {           console.error(err);         });     },     decodeFromInputVideoFunc(firstDeviceId) {       this.scanTextData.codeReader.reset();       this.scanTextData.codeReader.decodeFromInputVideoDeviceContinuously(         firstDeviceId,         "video",         (result, err) => {           if (result && result.text) {             this.handleResult(result.text);           }           if (err && !err) {             console.log(err);             this.$toast.fail(err);           }         }       );     },     async handleResult(scanResult) {       console.log(scanResult);       // TODO 逻辑处理或直接返回扫码结果       this.$emit("getScanResult", scanResult);     }   },   destroyed() {     this.scanTextData.codeReader.reset(); // 重置摄像头   } }; </script> <style scoped> .scan-box {   position: fixed;   top: 0;   left: 0;   height: 100%;   width: 100vw;   background-image: linear-gradient(       0deg,       transparent 24%,       rgba(32, 255, 77, 0.1) 25%,       rgba(32, 255, 77, 0.1) 26%,       transparent 27%,       transparent 74%,       rgba(32, 255, 77, 0.1) 75%,       rgba(32, 255, 77, 0.1) 76%,       transparent 77%,       transparent     ),     linear-gradient(       90deg,       transparent 24%,       rgba(32, 255, 77, 0.1) 25%,       rgba(32, 255, 77, 0.1) 26%,       transparent 27%,       transparent 74%,       rgba(32, 255, 77, 0.1) 75%,       rgba(32, 255, 77, 0.1) 76%,       transparent 77%,       transparent     );   background-size: 3rem 3rem;   background-position: -1rem -1rem; } .scan-video {   height: 100vh;   width: 100vw;   object-fit: cover; } .qr-scanner .box {   width: 213px;   height: 213px;   position: absolute;   left: 50%;   top: 50%;   transform: translate(-50%, -50%);   overflow: hidden;   border: 0.1rem solid rgba(0, 255, 51, 0.2);   /* background: url('http://resource.beige.world/imgs/gongconghao.png') no-repeat center center; */ } .qr-scanner .line {   height: calc(100% - 2px);   width: 100%;   background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #00ff33 211%);   border-bottom: 3px solid #00ff33;   transform: translateY(-100%);   animation: radar-beam 2s infinite alternate;   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: #00ff33; } .qr-scanner .angle:after, .qr-scanner .angle:before {   bottom: 0;   border-bottom-color: #00ff33; } .qr-scanner .box:before, .qr-scanner .angle:before {   left: 0;   border-left-color: #00ff33; } .qr-scanner .box:after, .qr-scanner .angle:after {   right: 0;   border-right-color: #00ff33; } @keyframes radar-beam {   0% {     transform: translateY(-100%);   }   100% {     transform: translateY(0);   } } .scan-tip {   width: 100vw;   text-align: center;   margin-bottom: 5vh;   color: white;   font-size: 5vw;   position: absolute;   bottom: 50px;   left: 0;   color: #fff; } .page-scan {   overflow-y: hidden; } </style>

复制代码

 

3. vue-qrcode-reader 使用方法复制代码

<template>   <div>     <qrcode-stream       :camera="camera"       @decode="onDecode"       @init="onInit"       style="height: 100vh;"     >       <div>         <div>           <div>             <div></div>             <div></div>           </div>         </div>       </div>     </qrcode-stream>   </div> </template> <script> // 下载插件 // cnpm install --save  vue-qrcode-reader // 引入 import { QrcodeStream } from "vue-qrcode-reader"; export default {   // 注册   components: { QrcodeStream },   data() {     return {       camera: "auto",       result: "", // 扫码结果信息       error: "" // 错误信息     };   },   created() {},   methods: {     //回调扫描结果     onDecode(result) {       if (result !== "") {         this.$emit("ok", result);       }     },     // 检查是否调用摄像头     async onInit(promise) {       try {         const { capabilities } = await promise;         console.log(           "🚀 ~ file: cameracomponent.vue:47 ~ onInit ~ capabilities",           capabilities         );       } catch (error) {         // console.log()         this.$toast(error.name);         if (error.name === "NotAllowedError") {           this.error = "ERROR: 您需要授予相机访问权限";         } else if (error.name === "NotFoundError") {           this.error = "ERROR: 这个设备上没有摄像头";         } else if (error.name === "NotSupportedError") {           this.error = "ERROR: 所需的安全上下文(HTTPS、本地主机)";         } else if (error.name === "NotReadableError") {           this.error = "ERROR: 相机被占用";         } else if (error.name === "OverconstrainedError") {           this.error = "ERROR: 安装摄像头不合适";         } else if (error.name === "StreamApiNotSupportedError") {           this.error = "ERROR: 此浏览器不支持流API";         } else if (error.name === "InsecureContextError") {           this.error =             "ERROR: 仅允许在安全上下文中访问摄像机。使用HTTPS或本地主机,而不是HTTP。";         } else {           this.error = "ERROR:摄像机错误";         }         this.$emit("err", this.error);       }     }   } }; </script> <style scoped> .error {   font-weight: bold;   color: red; } </style> <style scoped> .scan {   width: 100vw;   border-color: #585858;   position: fixed;   top: 0;   left: 0; } .qrcode-stream-camera {   width: 100%;   height: 100%;   display: block;   -o-object-fit: cover;   object-fit: cover;   position: absolute;   top: 0%;   left: 0%; } .qr-scanner {   background-image: linear-gradient(       0deg,       transparent 24%,       rgba(32, 255, 77, 0.1) 25%,       rgba(32, 255, 77, 0.1) 26%,       transparent 27%,       transparent 74%,       rgba(32, 255, 77, 0.1) 75%,       rgba(32, 255, 77, 0.1) 76%,       transparent 77%,       transparent     ),     linear-gradient(       90deg,       transparent 24%,       rgba(32, 255, 77, 0.1) 25%,       rgba(32, 255, 77, 0.1) 26%,       transparent 27%,       transparent 74%,       rgba(32, 255, 77, 0.1) 75%,       rgba(32, 255, 77, 0.1) 76%,       transparent 77%,       transparent     );   background-size: 3rem 3rem;   background-position: -1rem -1rem;   width: 100%;   /* height: 100%; */   height: 100vh;   position: relative;   /* background-color: #1110;*/   /* background-color: #111; */ } .qr-scanner .box {   width: 213px;   height: 213px;   position: absolute;   left: 50%;   top: 50%;   transform: translate(-50%, -50%);   overflow: hidden;   border: 0.1rem solid rgba(0, 255, 51, 0.2);   /* background: url('http://resource.beige.world/imgs/gongconghao.png') no-repeat center center; */ } .qr-scanner .line {   height: calc(100% - 2px);   width: 100%;   background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #00ff33 211%);   border-bottom: 3px solid #00ff33;   transform: translateY(-100%);   animation: radar-beam 2s infinite alternate;   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: #00ff33; } .qr-scanner .angle:after, .qr-scanner .angle:before {   bottom: 0;   border-bottom-color: #00ff33; } .qr-scanner .box:before, .qr-scanner .angle:before {   left: 0;   border-left-color: #00ff33; } .qr-scanner .box:after, .qr-scanner .angle:after {   right: 0;   border-right-color: #00ff33; } @keyframes radar-beam {   0% {     transform: translateY(-100%);   }   100% {     transform: translateY(0);   } } </style>

复制代码