Vue 项目 PC 扫码组件

template 结构中

<template>
  <div>
    <el-dialog
      customClass="dialog"
      title="扫描二维码"
      :visible.sync="dialogDisplay"
      :modal-append-to-body="false"
      :append-to-body="true"
      :before-close="closeCamera">
      <div class="container">
        <div class="scan-wrap">
          <div v-if="!showCanvas">获取不到摄像头</div>
          <canvas
            :width="canvasWidth"
            :height="canvasHeight"
            id="canvas"
            v-show="showCanvas"
            ref="canvasElement"
          ></canvas>
          <div class="output" :style="{width:styleSetting}" v-if="showCanvas">
            <p v-show="!outputData">未监测到二维码</p>
            <p v-show="outputData">结果:{{outputData}}</p>
          </div>
        </div>
        <ul class="ul-wrap" :style="{height: canvasHeight + 'px'}" v-if="multiple">
          <li class="title">二维码列表</li>
          <li v-if="codeList.length === 0">None</li>
          <li v-for="(item,i) in codeList" :key="i">{{item}}</li>
        </ul>
      </div>
      <span slot="footer" class="dialog-footer" v-if="multiple">
        <el-button @click="cancel">取 消</el-button>
        <el-button type="primary" @click="closeCamera">确 定</el-button>
        <el-button type="primary" @click="openScan">继 续</el-button>
      </span>
    </el-dialog>
  </div>
</template>

js 代码


<script>
import jsQR from 'jsqr'

let animationFrameId = null

export default {
  props: {
    show: {  // 展示扫码的 弹窗
      type: Boolean,
      required: true
    },
    multiple: { // 支持列表扫码
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      styleSetting: '',
      whetherDisplay: '',
      video: null,
      showCanvas: true,
      canvas2d: undefined,
      outputData: undefined,
      canvasWidth: 400,
      canvasHeight: 200,
      codeList: []
    }
  },
  created () {
    if (!this.multiple) {
      this.canvasHeight = 400
      this.styleSetting = this.canvasWidth + 'px'
    }
  },
  computed:{
    dialogDisplay () {
      if (this.show) {
        this.video = document.createElement('video')
        this.openScan()
      }
      return this.show
    }
  },
  methods: {
    cancel () {
      this.codeList = []
      this.closeCamera()
    },
    openScan () {
      const videoParam = { video: { facingMode: "environment" } }
      navigator.mediaDevices.getUserMedia(videoParam).then((stream) => {
        this.video.srcObject = stream
        this.video.setAttribute('playsinline', true) // required to tell iOS safari we don't want fullscreen
        this.video.setAttribute('autoplay', true) // required to tell iOS safari we don't want fullscreen
        this.video.play()
        animationFrameId = requestAnimationFrame(this.tick)
      }).catch(err => {
        this.showCanvas = false
      })
    },
    // 关闭摄像头
    closeCamera () {
      if (this.video.srcObject) {
        this.video.srcObject.getTracks().forEach(function (track) {
          track.stop()
        })
      }
      // 关闭摄像头的同时,将输出传给父组件
      this.$emit('getCodeList', this.codeList)
      this.codeList = []

    },
    tick () {
      if (this.video.readyState === this.video.HAVE_ENOUGH_DATA) {
        this.whetherDisplay = this.multiple
        if (this.whetherDisplay) {
          this.canvasHeight = this.video.videoHeight / this.video.videoWidth * this.canvasWidth
        }
        if (!this.whetherDisplay) {
          this.canvasWidth = this.video.videoWidth / this.video.videoHeight * this.canvasHeight
        }
        !this.canvas2d && (this.canvas2d = this.$refs.canvasElement.getContext('2d'))
        this.canvas2d.drawImage(this.video, 0, 0, this.canvasWidth, this.canvasHeight)
        var imageData = this.canvas2d.getImageData(0, 0, this.canvasWidth, this.canvasHeight)
        var code = null
        try {
          code = jsQR(imageData.data, imageData.width, imageData.height, {
            inversionAttempts: 'dontInvert'
          })
        } catch (err) {
          code = null
          this.outputData = undefined
        }
        if (code) {
          this.drawLine(code.location.topLeftCorner, code.location.topRightCorner, '#FF3B58')
          this.drawLine(code.location.topRightCorner, code.location.bottomRightCorner, '#FF3B58')
          this.drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, '#FF3B58')
          this.drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, '#FF3B58')
          this.outputData = code.data
          if (!this.whetherDisplay) {
            this.codeList.push(code.data)
            this.closeCamera()
          } else if (this.whetherDisplay) {
            this.codeList.push(code.data)
          }
          return cancelAnimationFrame(animationFrameId)
        } else {
          this.outputData = undefined
        }
      }
      if (this.show) {
        requestAnimationFrame(this.tick)
      } else {
        cancelAnimationFrame(animationFrameId)
      }
    },
    drawLine (begin, end, color) {
      this.canvas2d.beginPath()
      this.canvas2d.moveTo(begin.x, begin.y)
      this.canvas2d.lineTo(end.x, end.y)
      this.canvas2d.lineWidth = 4
      this.canvas2d.strokeStyle = color
      this.canvas2d.stroke()
    }
  },
}
</script>

style 样式

<style lang="scss" scoped>
/deep/ .dialog {
  width: 800px;
} 
.container {
  display: flex;
  .scan-wrap {
    margin: 0 auto;
    .output {
      text-align: center;
    }
    .active{
      display: flex;
      justify-content: center;
      align-items: center;
    }
  }
  .ul-wrap {
    flex: 1;
    margin-left: 10px;
    border: 1px solid #bbb;
    overflow: auto;
    text-align: center;
    position: relative;
    .title {
      height: 30px;
      line-height: 30px;
      font-size: 16px;
      font-weight: 500;
      background: #e0e0e0;
      position: sticky;
      top: 0;
    }
    li {
      border-bottom: 1px solid #bbb;
      min-height: 28px;
      line-height: 28px;
      &:nth-child(odd) {
        background-color: #f5f5f5;
      }
    }
  }
}
</style>

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清云随笔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值