VUE 调用PC摄像头,HTTP下也可以使用

14 篇文章 0 订阅
14 篇文章 0 订阅

由于内网使用,调用当前pc的摄像头,进行拍照上传功能,而浏览器由于安全策略考虑,只支持本地调用摄像头,服务器上禁止使用,新版chrome支持修改安全协议,代码如下:

chrome://flags/#unsafely-treat-insecure-origin-as-secure

然后,将该 flag 切换成 enable 状态;
在输入框中填写需要开启的域名或地址例:http://xxx.xxx.xx.xx:1234,如果有多个,则以逗号分隔;
重启浏览器后生效;

下面写了个子组件photo.vue,来调用,可以上传多张照片,通过elemenui支持查看

<template>
  <div>
    <el-dialog v-el-drag-dialog width="720px" :title="title" :close-on-click-modal="false" :visible.sync="dialogPhotoVisible">
      <div v-show="!noShow" class="pictureBox">
        <div>
          <div v-show="cameraShow" id="my_camera">
            <video id="videoCamera" :width="videoWidth" :height="videoHeight" autoplay></video>
            <canvas style="display:none;" id="canvasCamera" :width="videoWidth" :height="videoHeight"></canvas>
          </div>

          <div v-show="!cameraShow" class="camera_no">
            <div>
              <i class="el-icon-loading"/>
            </div>
            <div class="text">{{ loadingText }}</div>
          </div>
        </div>
        <div class="picList">
          <div v-for="(s, index) in listSrc" style="position: relative">
            <el-image
              style="width: 100%; height: 130px"
              :src="s.src"
              :preview-src-list="getSrcList(index)" lazy>
            </el-image>
            <i class="el-icon-error" @click="delClick(s)"></i>
          </div>

        </div>
      </div>
      <div v-show="noShow" class="camera_no_data">
        <div>
          <i class="el-icon-loading"/>
        </div>
        <div class="text">{{ loadingErrorText }}</div>
      </div>

      <div class="btn-group" v-show="!noShow">
        <div class="btn-cn" v-show="cameraShow">
          <el-button type="warning" :loading="saveLoading" @click="takeSnapShot()">拍照</el-button>
          <el-button v-if="listSrc.length !== 0" type="danger" :loading="saveLoading" @click="againTakePhoto()">清空</el-button>
          <el-button type="primary" :loading="saveLoading" @click="saveSnapShot()">上传</el-button>
        </div>
      </div>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogPhotoVisible = false">关 闭</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
  import { dataToFile } from '@/utils/index'
  import { uploadApplyFiles } from '@/api/common'

  export default {
    name: 'PhotoApply',
    data() {
      return {
        dialogPhotoVisible: false,
        videoWidth: 480,
        videoHeight: 360,
        imgSrc: "",
        thisCancas: null,
        thisContext: null,
        thisVideo: null,
        stream: null,
        src: '',
        listSrc: [],
        takePhoto: true,
        cameraShow: false,
        saveLoading: false,
        noShow: false,
        loadingText: '正在加载摄像头...',
        loadingErrorText: '正在加载摄像头...'
      }
    },
    props: {
      title: {
        type: String
      }
    },
    watch: {
      dialogPhotoVisible(val) {
        if (val) {
          this.$nextTick(() => {
            this.open()
          })
        } else {
          this.listSrc = []
          this.close()
        }
      }
    },
    beforeDestroy() {
      this.listSrc = []
    },
    mounted() {
    },
    methods: {
      getSrcList(index){
        const srcs = this.listSrc.map((v) => { return v.src })
        return srcs.slice(index).concat(srcs.slice(0,index))
      },
      takeSnapShot() {
        const _this = this
        const timestamp = new Date().getTime()
        const fileName = _this.applyID + '_' + timestamp
        // canvas画图
        _this.thisContext.drawImage(
          _this.thisVideo,
          0,
          0,
          _this.videoWidth,
          _this.videoHeight
        );
        // 获取图片base64链接
        const image = _this.thisCancas.toDataURL("image/jpeg");
        _this.src = image
        _this.listSrc.push({
          src: _this.src,
          file: dataToFile(_this.src, fileName),
          title: fileName
        })

        _this.takePhoto = false
      },
      delClick(src) {
        this.listSrc = this.listSrc.filter((v) => {return src.src !== v.src})
      },
      againTakePhoto() {
        this.src = ''
        this.listSrc = []
      },
      saveSnapShot() {
        if (this.listSrc.length === 0) {
          this.$message.warning('请上传相关数据')
          return
        }
        // 拍照
        const _this = this
        // 调用后台接口
        _this.saveLoading = true
        _this.loadingText = '正在上传图片,请稍后...'
        let formData = new FormData()
        _this.listSrc.forEach((v) => {
          formData.append("images", v.file)
        })
        // 上传自己可以自己写
        uploadApplyFiles(formData).then(res => {
          // const data = res.message
          _this.$message.success('上传图片成功')
          _this.dialogPhotoVisible = false
        }).catch(() => {
          setTimeout(() => {
            // 失败
            _this.src = ''
            _this.saveLoading = false
            _this.takePhoto = true
            _this.cameraShow = true
          }, 500)
        })
      },
      close() {
        this.src = ''
        this.listSrc = []
        this.saveLoading = false
        this.cameraShow = false
        this.takePhoto = true
        this.loadingText = '正在关闭摄像头...'
        this.stopNavigator()
      },
      open() {
        this.cameraShow = false
        this.loadingText = '正在打开摄像头...'

        this.getCompetence()
        setTimeout(() => {
          this.cameraShow = true
        }, 1500)
      },
      // 调用权限(打开摄像头功能)
      getCompetence() {
        const _this = this;
        _this.thisCancas = document.getElementById("canvasCamera");
        _this.thisContext = this.thisCancas.getContext("2d");
        _this.thisVideo = document.getElementById("videoCamera");
        _this.thisVideo.style.display = 'block';
        // 获取媒体属性,旧版本浏览器可能不支持mediaDevices,我们首先设置一个空对象
        if (navigator.mediaDevices === undefined) {
          navigator.mediaDevices = {};
        }
        // 一些浏览器实现了部分mediaDevices,我们不能只分配一个对象
        // 使用getUserMedia,因为它会覆盖现有的属性。
        // 这里,如果缺少getUserMedia属性,就添加它。
        if (navigator.mediaDevices.getUserMedia === undefined) {
          navigator.mediaDevices.getUserMedia = function(constraints) {
            // 首先获取现存的getUserMedia(如果存在)
            const getUserMedia =
              navigator.webkitGetUserMedia ||
              navigator.mozGetUserMedia ||
              navigator.getUserMedia;
            // 有些浏览器不支持,会返回错误信息
            // 保持接口一致
            if (!getUserMedia) {//不存在则报错
              return Promise.reject(
                _this.$message.error("浏览器没有打开摄像头的权限!")
              );
            }
            // 否则,使用Promise将调用包装到旧的navigator.getUserMedia
            return new Promise(function(resolve, reject) {
              getUserMedia.call(navigator, constraints, resolve, reject);
            });
          };
        }
        let constraints = {
          audio: false,
          video: {
            width: this.videoWidth,
            height: this.videoHeight,
            transform: "scaleX(-1)"
          }
        };
        navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
          _this.stream = stream
          // 旧的浏览器可能没有srcObject
          if ("srcObject" in _this.thisVideo) {
            _this.thisVideo.srcObject = stream;
          } else {
            // 避免在新的浏览器中使用它,因为它正在被弃用。
            _this.thisVideo.src = window.URL.createObjectURL(stream);
          }
          _this.thisVideo.onloadedmetadata = function(e) {
            _this.thisVideo.play();
          };
        }).catch(err => {
          console.log(err);
          this.$nextTick(() => {
            this.noShow = true
            this.loadingErrorText = '浏览器没有打开摄像头的权限!在chrome浏览器的地址栏中输入: chrome://flags/#unsafely-treat-insecure-origin-as-secure,将该 flag 切换成 enable 状态;\n' +
              '在输入框中填写需要开启的域名或地址,如果有多个,则以逗号分隔;\n' +
              '重启浏览器后生效。'
          })
        });
      },
      // 关闭摄像头
      stopNavigator() {
        if (this.stream) {
          if (this.stream.getVideoTracks) {
            let tracks = this.stream.getVideoTracks();
            if (tracks && tracks[0] && tracks[0].stop) tracks[0].stop();
          } else if (this.stream.stop) {
            this.stream.stop();
          }
        }
      }
    }
  }
</script>

<style scoped lang="scss">
  .pictureBox {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }

  .btn-cn {
    width: 100%;
    text-align: center;
    margin-top: 20px;
    margin-bottom: 20px;
  }

  #my_camera {
    border: 1px solid #606266;
  }

  .picList{
    width: 200px;
    overflow-x: hidden;
    overflow-y: scroll;
    height: 360px;
    .el-image{
      width: 180px;
      height: 130px;
      margin: 0px 0px 10px 10px;
    }
  }
  .camera_no_data{
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: 50px;
    width: 100%;
    margin-bottom: 20px;
    background: #fff;
    height: 360px;
    border: 1px solid #606266;
    .el-icon-loading {
      font-size: 40px;
    }
    .text {
      font-size: 20px;
      margin-top: 30px;
    }
  }
  .camera_no {
    position: relative;
    background: #fff;
    width: 480px;
    height: 360px;
    border: 1px solid #606266;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;

    .el-icon-loading {
      font-size: 40px;
    }

    .text {
      font-size: 20px;
      margin-top: 30px;
    }
  }
  ::v-deep .el-image-viewer__mask {
    opacity: .9;
    background: #fff;
  }
  .el-icon-error{
    position: absolute;
    right: 0;
    top: 0;
    font-size: 25px;
    color: yellow;
    cursor: pointer;
  }
</style>

dataToFile.js

/**
 * 将base64转换成file
 */
export function dataToFile(base64, fileName){

  const parts = base64.split(';base64,')
  const contentType = parts[0].split(':')[1]
  const raw = window.atob(parts[1])
  const rawLength = raw.length

  const uInt8Array = new Uint8Array(rawLength)

  for (let i = 0; i < rawLength; ++i) {
    uInt8Array[i] = raw.charCodeAt(i)
  }
  return new File([uInt8Array], fileName, {type: contentType, lastModified: Date.now()})
}

父组件里调用

<photo red="photo" :title="'图片拍照上传'" />

import photo from '......../photo.vue'

components:{
    photo
},

 methods: {
    openClick() {
        this.$refs['photo'].dialogPhotoVisible = true
    }
 }

根据网上的博客来封装了一下,由于关闭不了摄像头,自己给改装了一下

原文:

https://blog.csdn.net/RussW0/article/details/104694368/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值