plus实现扫码功能

文章详细介绍了如何在移动端和PC端分别封装扫码组件。移动端使用的是barcode.vue,依赖于plus.barcode,而PC端则利用ZXing库的BrowserMultiFormatReader进行二维码和条形码的扫描。组件提供了开始扫描、关闭扫描以及结果处理等功能。
摘要由CSDN通过智能技术生成

移动端扫码封装barcode.vue扫描组件

<template>
  <div class="scan">
    <div id="bcid">
      <div style="height: 40%"></div>
      <p class="tip">...载入中...</p>
    </div>
  </div>
</template>

<script type='text/ecmascript-6'>
import { Toast } from 'vant';
let scan = null;
let ws = null;
let view = null;
export default {
  props: {
    success: {
      type: Function,
      default () {
        return null;
      },
    },
  },
  data () {
    return {
      codeUrl: "",
    };
  },
  mounted () {
    //await this.waitplus();
    this.$nextTick(() => this.startRecognize());
  },
  beforeDestroy () {
    console.log("barcode unmount");
    this.closeScan();
  },
  methods: {
    async waitplus () {
      return new Promise((resolve, reject) => {
        setInterval(() => {
          if (!window.plus) return;
          plus.screen.lockOrientation('portrait-primary');//竖屏
          resolve();
        }, 10);
        setTimeout(() => {
          reject();
        }, 60000);
      })
    },
    // 创建扫描控件
    startRecognize () {
      let that = this;
      if (!window.plus) return;
      plus.screen.lockOrientation('portrait-primary');
      ws = plus.webview.currentWebview();
      scan = new plus.barcode.Barcode('bcid', [window.plus.barcode.QR], {
        position: 'absolute',
        scanbarColor: '#2E49C0',
        frameColor: '#2E49C0'
      });
      scan.onmarked = onmarked;
      this.startScan();
      function onmarked (type, result, file) {
        switch (type) {
          case plus.barcode.QR:
            type = "QR";
            break;
          case plus.barcode.EAN13:
            type = "EAN13";
            break;
          case plus.barcode.EAN8:
            type = "EAN8";
            break;
          default:
            type = "其它" + type;
            break;
        }
        result = result.replace(/\n/g, "");
        that.codeUrl = result;
        //alert(result);
        that.success(result);
      }
    },

    // 开始扫描
    startScan () {
      if (!window.plus) return;
      scan.start();
    },
    // 关闭条码识别控件
    closeScan () {
      if (!window.plus) return;
      scan.cancel();
      scan.close();
    },
  },
};
</script>
<style lang="less">
.scan {
  height: 100%;
  background: #000;
  #bcid {
    width: 100%;
    position: absolute;
    left: 0;
    right: 0;
    top: 1.3rem;
    bottom: 0;
    text-align: center;
    color: #fff;
    background: #000;
  }
}
</style>

PC端扫码封装

<template>
  <div>
    <slot name="header">
      <header>
        <div
          class="refresh"
          @click="decodeOnceFromConstraintsFunc(switchPerspective)"
          v-if="showToggleBtnCopy"
        >
          <svg class="icon" aria-hidden="true">
            <use xlink:href="#icon-shuaxin"></use>
          </svg>
        </div>
        <div class="toggle" @click="toggle" v-if="showToggleBtnCopy">
          <svg class="icon" aria-hidden="true">
            <use xlink:href="#icon-iconqiehuanjingtou"></use>
          </svg>
        </div>
      </header>
    </slot>
    <div class="page" v-if="parseType === 'Camera'">
      <video
        id="video"
        :class="{ video: !videoWidth && !videoHeight }"
        :width="videoWidth"
        :height="videoHeight"
        autoplay
      ></video>
      <slot>
        <div
          class="scanBox"
          :style="{ width: scanBoxWidth, height: scanHeight }"
          v-if="parseType === 'Camera' && showScanBoxCopy"
        >
          <div class="frame upperLeft"></div>
          <div class="frame upperRight"></div>
          <div class="frame lowerRight"></div>
          <div class="frame lowerLeft"></div>
          <div class="pointerBox">
            <div class="pointer"></div>
          </div>
          <div v-show="tipShow" class="tip">{{ tipMsg }}</div>
        </div>
      </slot>
    </div>
    <!-- 选择图片 -->
    <input
      type="file"
      id="choose"
      accept="image/*"
      v-if="parseType === 'Image' && inputId === 'choose' && showDefaultInput"
    />

    <slot name="other"></slot>
  </div>
</template>
<script>
import "./assets/svg/iconfont";
// BrowserQRCodeReader只读取二维码 速度要比 BrowserMultiFormatReader快
import { BrowserMultiFormatReader } from "@zxing/library";
export default {
  name: "parser-code",
  props: {
    parseType: {
      // 传入需要解码的类型  摄像头还是静态图 Camera 或 Image
      type: String,
      default: "Camera",
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        let result = ["Camera", "Image"].indexOf(value) !== -1;
        if (!result)
          throw new Error("parseType can only be 'Camera' or 'Image'");
        return true;
      },
    },
    // 如果选择使用静态图  那么可以传入一个input的id,这个input类型需要是file类型,否则采用组件默认的input
    inputId: {
      type: String,
      default: "choose",
    },
    // 如果不想要默认的input框展示可以传入showDefaultInput,然后通过组件下的parseStaticImg方法传入图片路径来获取解析后的值
    showDefaultInput: {
      type: Boolean,
      default: true,
    },
    showScanBox: {
      type: Boolean,
      default: false,
    },
    // 是否展示切换镜头按钮
    showToggleBtn: {
      type: Boolean,
      default: false,
    },
    videoWidth: {
      typeof: Number,
      default: null,
    },
    videoHeight: {
      typeof: Number,
      default: null,
    },
    scanBoxWidth: {
      typeof: Number,
      default: null,
    },
    scanHeight: {
      typeof: Number,
      default: null,
    },
    success: {
      type: Function,
      default () {
        return null
      },
    },
    fail: {
      type: Function,
      default () {
        return null
      },
    },
    // 获取摄像头失败回调
    getVideoFail: {
      type: Function,
      default () {
        return null
      },
    },
  },
  data () {
    return {
      codeReader: null,
      tipMsg: "扫描二维码",
      tipShow: true,
      video: null,
      img: null,
      reader: null,
      back: true,
      showScanBoxCopy: this.showScanBox,
      showToggleBtnCopy: this.showToggleBtn,
      inputDom: null,
    };
  },
  computed: {
    switchPerspective () {
      return this.back
        ? { video: { facingMode: { exact: "environment" } } }
        : { video: { facingMode: "user" } };
    },
  },
  created () {
    if (this.parseType === "Camera") {
      this.openScan();
    } else {
      this.reader = new FileReader();
      let _this = this;
      this.reader.onload = function (e) {
        if (!e.target.result)
          throw new Error("[FileReader]:Image parsing failed");
        _this.parseStaticImg(e.target.result); // 'data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...'
      };
    }
  },
  mounted () {
    // 防止获取不到父组件中的元素
    if (this.parseType === "Image") {
      this.$nextTick(() => {
        this.inputDom = document.querySelector(`#${this.inputId}`);
        this.inputDom &&
          this.inputDom.addEventListener("change", this.selectImg(this), false);
      });
    }
  },
  beforeDestroy () {
    // 页面关闭前,结束调用摄像头
    this.codeReader.reset()
  },
  methods: {
    async openScan () {
      this.codeReader = await new BrowserMultiFormatReader();
      this.codeReader
        .getVideoInputDevices()
        .then((videoInputDevices) => {
          if (videoInputDevices.length > 1) {
            this.showScanBoxCopy = true;
            this.tipShow = true;
            this.showToggleBtnCopy = true;
            //this.tipMsg = "扫描装备条码";
            this.decodeOnceFromConstraintsFunc(this.switchPerspective);
          } else if (videoInputDevices.length > 0) {
            this.showScanBoxCopy = true;
            this.tipShow = true;
            this.showToggleBtnCopy = true;
            //this.tipMsg = "扫描装备条码";
            const selectedDeviceId = videoInputDevices[0].deviceId;
            this.codeReader.decodeFromInputVideoDeviceContinuously(selectedDeviceId, 'video', (result, err) => {
              if (result) {
                this.textContent = result.text;
                this.success(result.text)
                //关闭
              }
              if (err && !(err)) {
                console.error(err);
              }
            });

          } else {
            return alert("请允许获取摄像头权限后再进行二维码扫描");
          }
        })
        .catch((err) => {
          this.showScanBoxCopy = false;
          this.tipShow = false;
          this.getVideoFail(err) || alert('调取用户摄像头失败');
        });
    },
    toggle () {
      //摄像头切换时,需要先重置codeReader,关闭已打开摄像头,否则操作失败
      this.back = !this.back;
      this.decodeOnceFromConstraintsFunc(this.switchPerspective);
    },
    async decodeOnceFromConstraintsFunc (constraints) {
      // 摄像头读取重置
      this.codeReader.decodeOnceFromConstraints(
        constraints,
        "video"
      )
      const stream = await navigator.mediaDevices.getUserMedia(constraints);
      await this.codeReader.reset();
      this.video = await this.codeReader.attachStreamToVideo(stream, "video");
      await this.decoding(this.video)
    },
    async decoding (video) {
      let result = this.codeReader.decodeOnce(video)
      result.then((result) => {
        this.tipShow = true;
        this.tipMsg = "正在尝试识别...";
        if (result) {
          if (result.text) {
            this.success(result.text)
            this.tipMsg = "扫描装备条码";
            this.decoding(this.video)
          }
        }
      }, (err) => {
        if (err) {
          if (err.message === 'Video stream has ended before any code could be detected.') return
          this.tipMsg = "识别失败";
          this.fail(err);
          setTimeout(() => { this.tipShow = false }, 2000)
        }
      });
    },
    // 返回一个promise对象,如果要获取解析后的值可以通过调用then方法来获取
    async parseStaticImg (imgSrc) {
      this.codeReader = await new BrowserMultiFormatReader();
      let res = this.codeReader
        .decodeFromImage(undefined, imgSrc)
        .then((result) => {
          this.success(result.text)
          return result.text;
        })
        .catch((err) => {
          this.fail(err);
        });
      return res;
    },
    selectImg (_this) {
      return function () {
        let file = this.files[0];
        if (
          file.type !== "image/jpeg" &&
          file.type !== "image/png" &&
          file.type !== "image/gif"
        ) {
          this.value = "";
          //this.outerHTML = this.outerHTML;
          alert("不是有效的图片文件!");
          return;
        }
        this.value = "";
        // 以DataURL的形式读取文件: 该方法是一个异步方法,所以上面采用onload监听文件读取完成
        _this.reader.readAsDataURL(file);
      };
    },
  },
};
</script>

<style scoped>
body {
  margin: 0;
}
/* 头部 */
header {
  position: absolute;
  top: 2%;
  left: 0;
  right: 0;
  display: flex;
  height: 40px;
  justify-content: flex-end;
  z-index: 999;
}
.toggle,
.refresh {
  width: 30px;
  height: 30px;
  margin-right: 5%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.refresh {
  margin-right: 2%;
}
/*vjs-fluid 自适video 长宽*/
.video {
  height: 100%;
  width: 100%;
  /* 等比例放大,填充满父级 */
  object-fit: cover;
}
.tip {
  position: absolute;
  left: 50%;
  top: 120%;
  transform: translate(-50%, 0);
  white-space: nowrap;
  color: rgba(222, 220, 222, 1);
  font-size: 16px;
}
/* common */
.page {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0px;
}
.scanBox {
  position: absolute;
  left: 50%;
  top: 40%;
  transform: translate(-50%, -50%);
  /* 扫描框变大,相机离太远不易得出结果,所以选择放大扫描框 */
  height: 40%;
  width: 85%;
}
.frame {
  position: absolute;
  width: 15px;
  height: 15px;
  border: 3px solid transparent;
  background: transparent;
}
.upperLeft {
  top: 0;
  left: 0;
  border-left-color: rgba(66, 133, 244, 1);
  border-top-color: rgba(66, 133, 244, 1);
}
.upperRight {
  top: 0;
  right: 0;
  border-right-color: rgba(66, 133, 244, 1);
  border-top-color: rgba(66, 133, 244, 1);
}
.lowerRight {
  bottom: 0;
  right: 0;
  border-bottom-color: rgba(66, 133, 244, 1);
  border-right-color: rgba(66, 133, 244, 1);
}
.lowerLeft {
  bottom: 0;
  left: 0;
  border-left-color: rgba(66, 133, 244, 1);
  border-bottom-color: rgba(66, 133, 244, 1);
}
.pointerBox {
  position: absolute;
  top: 0;
  left: 0;
  width: 98%;
  height: 100%;
  overflow: hidden;
}
.pointer {
  height: 3px;
  background-image: linear-gradient(
    to right,
    transparent 0%,
    rgba(66, 133, 244, 1) 50%,
    transparent 100%
  );
  transform: translateY(-3px);
  animation: move 3.5s linear infinite;
}
@keyframes move {
  0% {
    transform: translateY(-3px);
  }
  100% {
    /* scanBox变高扫描线运动范围增大 */
    transform: translateY(calc(45vh - 3px));
  }
}
.icon {
  width: 3em;
  height: 3em;
  vertical-align: -0.15em;
  fill: white;
  overflow: hidden;
}
</style>

使用组件

<template>
  <div>
    <!-- PC端扫码 -->
    <!-- <ParserCode v-if="!showShare" :success="success" /> -->
    <!-- 移动端扫码 -->
    <barcode v-if="!showShare" :success="success" />
    <van-share-sheet
      v-model="showShare"
      :options="options"
      safe-area-inset-bottom
      get-container="body"
      @select="clickOptions"
    />
  </div>
</template>

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值