移动端二维码扫一扫

文章展示了如何在Vue.js应用中集成二维码扫描功能,使用了@zxing/library和vue-qrcode-reader两个库。通过获取摄像头输入设备并设置扫描区域,实现实时识别二维码并处理扫描结果。
摘要由CSDN通过智能技术生成

第一,准备  插件安装

npm install @zxing/library  || yarn add  @zxing/library

zxing.vue 组件 

<template>

  <div class="page-scan">

    <LoadingVue :show="loading"></LoadingVue>

    <div class="scan-box">

      <video id="video" ref="video" class="scan-video" autoplay></video>

      <div class="qr-scanner">

        <div class="box">

          <div class="line"></div>

          <div class="angle"></div>

        </div>

      </div>

      <div class="scan-tip">{{ scanTextData.tipMsg }}</div>

    </div>

  </div>

</template>

<script>

import { BrowserMultiFormatReader } from "@zxing/library"

export default {

  name: "ScanCodePage",

  props: {

    show: {

      type: Boolean,

      default: false,

    },

  },

  data() {

    return {

      scanTextData: {

        codeReader: null,

        tipMsg: "将二维码置于屏幕中,即可识别",

        // 这个,就是当前调用的摄像头的索引,为什么是6,我会在后面说的 华为手机是鸿蒙系统有8个摄像头

        num: 5,

        // 这个就是扫描到的摄像头的数量

        videoLength: "",

      },

      hasBind: false,

    }

  },

  watch: {

    show(val) {

      if (!val) {

        // 关闭摄像头

        if (!document.getElementById("video")) {

          alert("请授权")

          return

        }

        const 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()

      }

    },

  },

  mounted() {

    this.scanTextData.codeReader = new BrowserMultiFormatReader()

    this.openScan() // 打开摄像头

  },

  destroyed() {

    this.scanTextData.codeReader.reset() // 重置摄像头

  },

  methods: {

    async openScan() {

      this.scanTextData.codeReader

        .getVideoInputDevices()

        .then((videoInputDevices) => {

          // 默认获取第一个摄像头设备id

          let firstDeviceId = videoInputDevices[0].deviceId

          this.loading = false

          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("ok", scanResult);

      this.$router.push({

        name: this.$route.query.next,

        query: {

          uuid: scanResult,

        },

      })

    },

  },

}

</script>

<style lang="scss" 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: 85vw;

  height: 85vw;

  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>

第二 

准备 : npm install @vue-qrcode-reader  || yarn add  @vue-qrcode-reader

qecode.vue

<template>

  <div class="scan">

    <LoadingVue :show="loading"></LoadingVue>

    <qrcode-stream

      :camera="camera"

      style="height: 100vh"

      @decode="onDecode"

      @init="onInit"

    >

      <div>

        <div class="qr-scanner">

          <div class="box">

            <div class="line"></div>

            <div class="angle"></div>

          </div>

        </div>

      </div>

    </qrcode-stream>

  </div>

</template>

<script>

import { QrcodeStream } from "vue-qrcode-reader"

export default {

  // 注册

  components: { QrcodeStream },

  data() {

    return {

      loading: true,

      camera: "auto",

      result: "", // 扫码结果信息

      error: "", // 错误信息

    }

  },

  created() {},

  methods: {

    // 回调扫描结果

    onDecode(result) {

      if (result !== "") {

        // this.$emit("ok", result);

        // alert(result)

        this.$router.push({

          name: this.$route.query.next,

          query: {

            uuid: result,

          },

        })

      }

    },

    // 检查是否调用摄像头

    async onInit(promise) {

      try {

        const { capabilities } = await promise

        console.log(

          "🚀 ~ file: cameracomponent.vue:47 ~ onInit ~ capabilities",

          capabilities

        )

        this.loading = false

      } 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: 85vw;

  height: 85vw;

  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>

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值