Newer
Older
BJgas-metering-front / src / views / person / photograph.vue
liyaguang on 28 Apr 2023 5 KB feat(*): 人员管理模块完成
<!--
  Description: 人员管理-人脸拍照组件
  Author: 李亚光
  Date: 2023-04-23
 -->
<script lang="ts" setup name="photograph">
import { ElMessage } from 'element-plus'
import { uploadApi } from '@/api/person'
// 父组件传来的拍照状态 true 拍照 false 关闭拍照
const props = defineProps({
  tackPhoto: {
    type: Boolean,
    default: false,
  },
})
// 父组件 传来的 关闭拍照事件
const emits = defineEmits(['off'])
// 保存图片的loading
const loadingbut = ref(false)
// 是否展示预览图片
const preViewVisible = ref(false)
// 拍照图片
const blobFile = ref<File>()
const mediaStreamTrack = ref('')
const canvasRef = ref()
const dialogFormVisible = ref(false)
// 监听接收到拍摄状态后,开始调取摄像头权限
watch(() => props.tackPhoto, (newVal) => {
  if (newVal) {
    var video = document.querySelector('video')
    // 兼容代码
    window.URL = (window.URL || window.webkitURL || window.mozURL || window.msURL)

    if (navigator.mediaDevices === undefined) {
      navigator.mediaDevices = {}
    }
    if (navigator.mediaDevices.getUserMedia === undefined) {
      navigator.mediaDevices.getUserMedia = function (constraints) {
        var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia
        if (!getUserMedia) {
          ElMessage.warning('浏览器未开启权限,请先开启摄像头权限')
          return Promise.reject(new Error('getUserMedia is not implemented in this browser'))
        }
        return new Promise((resolve, reject) => {
          getUserMedia.call(navigator, constraints, resolve, reject)
        })
      }
    }
    // 摄像头调用配置
    var mediaOpts = {
      audio: false,
      // video: true,
      video: { facingMode: 'environment' }, // 或者 "user"
      // video: { width: 1280, height: 720 }
      // video: { facingMode: { exact: "environment" } }// 或者 "user"
    }
    navigator.mediaDevices.getUserMedia(mediaOpts).then((stream: any) => {
      mediaStreamTrack.value = stream
      video = document.querySelector('video') as HTMLVideoElement
      if ('srcObject' in video) {
        video.srcObject = stream
      }
      else {
        video.src = (window.URL && window.URL.createObjectURL(stream)) || stream
      }
      video.play()
    }).catch((err) => {
      console.log(err)
    })
  }
}, {
  deep: true,
  immediate: true,
})

//  blob转文件对象
const dataURLtoFile = (dataurl: string, filename: string) => {
  var arr = dataurl.split(',')
  var mime = arr[0].match(/:(.*?);/)[1]
  var bstr = atob(arr[1])
  var n = bstr.length
  var u8arr = new Uint8Array(n)
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n)
  }
  return new File([u8arr], filename, { type: mime })
}
// 点击拍照截图画面
const takePhone = () => {
  const canvas = document.querySelector('#canvas-photo')
  const video = document.querySelector('video')
  canvas.getContext('2d').drawImage(video, 0, 0, 400, 300)
  // 转为本地路径
  const dataurl = canvas.toDataURL('image/jpeg')
  // 用时间戳作为文件名
  const filenname = `${Date.now().toString()}.png`
  // 将blob文件转为文件对象
  blobFile.value = dataURLtoFile(dataurl, filenname)
  preViewVisible.value = true
}
//  关闭弹窗
const close = () => {
  const canvas = document.querySelector('#canvas-photo')
  // 获取图片base64链接
  // var image = canvas.toDataURL('image/png')
  canvas.getContext('2d').clearRect(0, 0, 400, 300)// 清除画布
  // 关闭摄像头
  mediaStreamTrack.value.getVideoTracks().forEach((track) => {
    track.stop()
  })
  blobFile.value = null
  // 告诉父组件关闭摄像头
  emits('off', '')
}
// 保存图片
const takePhoneUpfile = () => {
  console.log(blobFile.value)
  if (!blobFile.value) {
    ElMessage.warning('请先拍照,再点击保存')
    return false
  }
  // loadingbut.value = true
  const formData = new FormData()
  formData.append('file', blobFile.value)// 图片内容
  // 上传图片
  uploadApi(formData).then((res) => { // 后台接口
    loadingbut.value = false
    blobFile.value = null
    const canvas = document.querySelector('#canvas-photo')
    // 获取图片base64链接
    canvas.getContext('2d').clearRect(0, 0, 400, 300)// 清除画布
    // 关闭摄像头
    mediaStreamTrack.value.getVideoTracks().forEach((track) => {
      track.stop()
    })
    // 告诉父组件关闭摄像头,并且把返回结果传出去
    emits('off', res.data)
    dialogFormVisible.value = false
  }, (error) => {
    loadingbut.value = false
    window.console.log(error)
  })
}
const initDialog = () => {
  dialogFormVisible.value = true
}
defineExpose({ initDialog })
</script>

<template>
  <el-dialog v-model="dialogFormVisible" title="人脸注册" append-to-body width="900px" @close="close">
    <div class="camera-box" style="width: 900px;">
      <el-row :gutter="20">
        <el-col :span="12">
          <div style="text-align: center;font-size: 14px;font-weight: bold;margin-bottom: 10px;">摄像头</div>
          <!-- 这里就是摄像头显示的画面 -->
          <video id="video-photo" width="400" height="300"></video>
          <div class="iCenter">
            <el-button type="primary" style="margin-top: 10px;" @click="takePhone">拍照</el-button>
          </div>
        </el-col>
        <el-col :span="12">
          <div style="text-align: center;font-size: 14px;font-weight: bold;margin-bottom: 10px;">拍摄效果</div>
          <!-- 这里是点击拍照显示的图片画面 -->
          <canvas ref="canvasRef" id='canvas-photo' width='400' height='300' style="display: block;"></canvas>
          <el-button :loading="loadingbut" type='primary' @click="takePhoneUpfile"
            style="margin-top: 10px;">保存</el-button>
        </el-col>
      </el-row>
    </div>
  </el-dialog>
</template>

<style>
.camera-box #canvas {
  border: 1px solid #DCDFE6;
}
</style>