拍摄身份证时,相机显示身份证正反面轮廓图

拍摄身份证时,相机显示身份证正反面轮廓图

项目需求是牌神身份证时在用户摄像机界面需要添加身份证正反面轮廓图,当时项目使用的是vue2+vant的h5项目,vant不支持修改相机,此时想起用原生方式(navigator.mediaDevices.getUserMedia),掉摄像机的api

需要实现的效果图

完整代码如下显示:

<template>
  <div>
    <!-- capture="environment" -->
    <!-- :after-read="afterRead" -->
    <van-uploader v-if="this.deviceType === 'PC端'"
      accept="image/*"
      :capture = "cameraTag?'camera':null"
      :preview-image="false"
      :max-count="1"
      :disabled="disabled"
      :before-read="screenshot ? uploadChange : ''"
      :after-read="screenshot ? '' : afterRead"
    >
      <div class="upload_button">
        <van-loading v-show="loading" class="loading" />
        <img class="show_img" v-if="value" :src="value" alt="" />
        <template v-else>
          <div class="camera_icon">
            <svg-icon iconClass="camera"></svg-icon>
            <p>{{ imgText }}</p>
          </div>
          <img
            :style="`width:${width};height:${height};`"
            :src="buttonImage"
            alt=""
          />
        </template>
      </div>
    </van-uploader>

    <div class="image-container" @click="takePictures" ref="openCamera" v-if="this.deviceType === '手机端'">
      <img class="show_img" v-if="value" :src="value" alt="" style="width: 100%;"/>
      <img v-else
        style="width:100%;height:100%;"
        :src="buttonImage"
        alt=""
      />
    </div>
    <div class="v-cropper-layer" ref="layer">
      <div class="layer-header">
        <button class="cancel" @click="cancelHandle">取消</button>
        <button class="confirm" @click="confirmHandle">裁剪</button>
      </div>
      <img ref="cropperImg" />
    </div>
    
    <!-- 弹出相机或相册 -->
    <van-popup v-model="showPicker" position="bottom">
      <section class="sectPopup">
        <div @click="takePictures">拍摄</div>
        <div class="xiangce">
          从相册选择
          <label for="fileupload" class="custom-file-upload">
            选择文件
          </label>
          <input type="file" id="fileupload" size="100" accept="image/*" capture="environment" @change="handleFileUpload"/>
        </div>
        <div @click="showPicker = false">取消</div>
      </section>
    </van-popup>
    <!-- 弹出相机 -->
    <van-popup v-model="show" position="bottom" :style="{ width: '100%', height: '100%' }">
      <div class="header">
        <van-nav-bar class="title" left-arrow title="身份证拍照" :fixed="true" @click-left="closeCamera" />
      </div>

      <div id="cameraContainer" style="height: 100%; overflow-x: hidden;">
        <div class="container" style="height: 100%; overflow-x: hidden;">
          <video style="height: 100%;overflow-x: hidden;" ref="video" id="video-fix" autoplay webkit-playsinline playsinline class="camera-video"></video>
          <div class="photograph" @click="takePhoto">
            <div id="captureButton" >
              <div class="cap-inner"></div>
            </div>
          </div>
          <!-- 正反面轮廓图 -->
          <div class="front">
            <img v-if="buttonImage.includes('renxiang')" src="@/assets/images/zheng.png" alt="">
            <img v-else src="@/assets/images/fan.png" alt="">
          </div>
        </div>
      </div>
    </van-popup>
  </div>
</template>

<script>
import $api from "@/api/index";
import { Toast } from "vant";
import Compressor from 'compressorjs';
import Cropper from "cropperjs";
import "cropperjs/dist/cropper.min.css";
export default {
  props: {
    value: {
      type: String,
      default: "",
    },
    width: {
      type: String,
      default: "100%",
    },
    height: {
      type: String,
      default: "100%",
    },
    buttonImage: {
      type: String,
      default: require("@/assets/images/ic_other@1x.png"),
    },
    imgText: {
      type: String,
      default: "",
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    cameraTag: {
      type: Boolean,
      default: false,
    },
    screenshot: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      loading: false,
      cropper: {},
      filename: "",
      show: false,
      showPicker: false,
      deviceType: null
    };
  },
  mounted() {
    this.init();
    //根据不同路由跳转不同页面
    if( this.isMobile() ){
      console.log("手机端")
      this.deviceType = '手机端'
      Toast('手机端')
    }else{
      console.log("PC端")
      this.deviceType = 'PC端'
      Toast('PC端')
    }
  },
  methods: {
    // 校验当前操作环境是pc还是移动
    isMobile(){
      let flag = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)
      return flag
    },
    async openCamera() {
      this.showPicker = true
    },
    // 打开相机摄像头
    async takePictures() {
      this.showPicker = false
      this.show = true;
      const stream = await navigator.mediaDevices.getUserMedia({video: { facingMode: "environment" }})
      const videoRef = this.$refs.video;
      videoRef.srcObject = stream
    },
    // 选择文件进行上传
    handleFileUpload(event) {
      console.log('this.index', this.index);
      this.showPicker = false
      var that = this
      const file = event.target.files[0]; // 获取选择的文件
      new Compressor(file, {
        quality: 0.6,
        async success(result) {
          const params = new FormData();
          params.append("file", result, result.name);
          
          const resultData = await $api.tool.uploadFile(params);
          if (resultData.success) {
            that.showPicker = false
            that.$emit("change", resultData.data.filePath, result);
            console.log('that.index', that.index);
          }
        },error(err) {
          console.error(err.message);
        }
      })
    },
    // 关闭摄像头
    closeCamera() {
      const video = this.$refs.video;
      if (video && video.srcObject) {
        const tracks = video.srcObject.getTracks();
        tracks.forEach((track) => track.stop());
      }
      this.show = false;
    },
    // 拍照
    async takePhoto() {
      var that = this
      const video = this.$refs.video;
      const canvas = document.createElement('canvas');
      if (video && canvas) {
        const context = canvas.getContext('2d');
        if (context) {
          // 设置画布尺寸与视频一致
          canvas.width = video.videoWidth;
          canvas.height = video.videoHeight;
          context.drawImage(video, 0, 0);
          // 将图像数据转换为二进制格式
          const blob = await this.dataURLToBlob(canvas.toDataURL('image/png'));
           // 将 Blob 转换为 File 对象
          const file = new File([blob], 'photo.png', { type: 'image/png' });
          
        // 创建 FormData 对象并添加图像数据
        const formData = new FormData();
        formData.append('file', blob, '001.png'); 
        // 发送请求
        const resultData = await $api.tool.uploadFile(formData);
        if (resultData.success) {
          that.$emit("change", resultData.data.filePath, file);
        }
          Toast.success('拍照成功');
          this.closeCamera();
        }
      }
    },
    // 将 Data URL 转换为 Blob
    dataURLToBlob(dataURL) {
      const byteString = atob(dataURL.split(',')[1]);
      const mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0];
      const ab = new ArrayBuffer(byteString.length);
      const ia = new Uint8Array(ab);
      for (let i = 0; i < byteString.length; i++) {
          ia[i] = byteString.charCodeAt(i);
      }
      return new Blob([ab], { type: mimeString });
    },
    // 上传
   async afterRead(file) {
     this.compressImage(file.file,this)
      // const params = new FormData();
      // params.append("file", file.file);
      // this.loading = true;
      // const result = await $api.tool.uploadFile(params);
      // if (result.success) {
      //   this.$emit("change", result.data.filePath, file);
      // }
      this.loading = false;
    },
    compressImage(file,zone) {//作用于变了
      new Compressor(file, {
        quality: 0.6,
        async success(result) {
          // result.name=file.name
          console.log("result********1" + result.name)
          const params = new FormData();
          // params.append("file", result, result.name);
          params.append("file", file);
          console.log('params', params);
          
          const resultData = await $api.tool.uploadFile(params);
          if (resultData.success) {
            zone.$emit("change", resultData.data.filePath, result);
          }
        },error(err) {
          console.error(err.message);
        },
      });
    },
    // 截图上传
    confirmHandle(file) {
      let cropBox = this.cropper.getCropBoxData();
      let cropCanvas = this.cropper.getCroppedCanvas({
        width: cropBox.width,
        height: cropBox.height,
      });
      cropCanvas.toBlob(async (imgData) => {
        const params = new FormData();
        let fileImg = new window.File([imgData], this.filename, {
          type: "image/jpeg",
        });
        params.append("file", fileImg);
        this.loading = true;
        const result = await $api.tool.uploadFile(params);
        if (result.success) {
          this.$emit("change", result.data.filePath, file);
          this.cancelHandle();
        }
        this.loading = false;
      }, "image/jpeg");
    },
    // 初始化裁剪插件
    init() {
      let cropperImg = this.$refs["cropperImg"];
      this.cropper = new Cropper(cropperImg, {
        dragMode: "move",
      });
    },
    // 选择上传文件
    uploadChange(e) {
      console.log(e, "select");
      let file = e;
      this.filename = file["name"];
      let URL = window.URL || window.webkitURL;
      this.$refs["layer"].style.display = "block";
      this.cropper.replace(URL.createObjectURL(file));
    },
    // 取消上传
    cancelHandle() {
      this.cropper.reset();
      this.$refs["layer"].style.display = "none";
      // this.$refs["file"].value = "";
    },
    // base64转file
    dataURLtoBlob(dataurl) {
      var arr = dataurl.split(","),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new Blob([u8arr], { type: mime });
    },
  },
};
</script>

<style lang="scss" scoped>
.camera_icon {
  width: 100%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 10vw;
  color: $common-color;
  text-align: center;

  p {
    font-size: 4vw;
    margin: 0;
  }
}
.upload_button {
  width: 45vw;
  height: 30vw;
  .loading {
    position: absolute;
    transform: translate(50%, -50%);
    top: 50%;
    right: 50%;
  }
  .show_img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 1vw;
  }
}

// .v-simple-cropper {
//   .file {
//     display: none;
//   }
.v-cropper-layer {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background: #fff;
  z-index: 99999;
  display: none;
  .layer-header {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 99999;
    background: #fff;
    width: 100%;
    height: 11vw;
    padding: 0 2vw;
    box-sizing: border-box;
  }
  .cancel,
  .confirm {
    line-height: 10vw;
    font-size: 4vw;
    background: inherit;
    border: 0;
    outline: 0;
    float: left;
  }
  .confirm {
    float: right;
  }
  img {
    position: inherit !important;
    border-radius: inherit !important;
    float: inherit !important;
  }
}

.container {
  position: relative;
  height: 100%;
  width: 100%;

  .photograph {
    position: absolute;
    width: 20vw;
    height: 20vw; 
    border-radius: 50%;
    background: #fff;
    z-index: 10;
    left: 50%;
    transform: translateX(-50%); 
    bottom: 8vw;
  }

  .front {
    width: 80%;
    height: 70%; 
    position: absolute;
    bottom: calc(8vw + 20vw + 8vw); 
    left: 50%;
    transform: translateX(-50%); 

    img {
      width: 100%;
      height: 100%;
    }
  }
}
.sectPopup {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 13vw;
  font-size: 4vw;
  div {
    width: 100%;
    text-align: center;
  }
  .xiangce {
    border-top: 0.5vw solid #e7e5e5;
    width: 100%;
    border-bottom: 3vw solid #f1f1f1;
    position: relative;
    overflow: hidden;
    .custom-file-upload {
      display: inline-block;
      padding: 1.33333vw 2.66667vw;
      cursor: pointer;
      border-radius: 0.53333vw;
      color: transparent;
      font-weight: bold;
      width: 100%;
      position: absolute;
      left: 0;
    }

    input[type="file"] {
      display: none; 
    }
  }
}

.image-container {
  height: 27vw;
  overflow: hidden;
  position: relative;
  .image-container img {
    position: absolute;
    top: 50%; 
    left: 50%; 
    width: auto; 
    height: auto; 
    min-width: 100%; 
    min-height: 100%; 
    transform: translate(-50%, -50%); 
  }
}
#captureButton {
  position: absolute;
  bottom: 5vw;
  left: 50%;
  -webkit-transform: translateX(-50%);
  transform: translateX(-50%);
  background-color: rgb(241 241 241 / 80%);
  border-radius: 50%;
  width: 30vw;
  height: 30vw;
  max-width: 10vw;
  max-height: 10vw;
  display: flex;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  cursor: pointer;
}





::v-deep .van-dialog {
  height: 100%;
}
::v-deep .van-dialog__content {
  height: 100%;
}
::v-deep .van-nav-bar{
  background: transparent !important;
}
::v-deep .van-nav-bar__title {
  color: #fff;
}
// }
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值