代码基于vue3,chrome版本122.0.6261.131,未进行其他浏览器的兼容性验证。拷贝到工程文件中进行简单调整即可复用。
<template>
<div>
<el-dialog title="现场拍照" :model-value="props.isShowTakePhoto" width="900px" :before-close="handleClose">
<div class="photo-content">
<div class="left-content" v-loading="isOpening || isDetecting" :element-loading-text="isOpening ? '正在启用摄像头' : '正在进行人脸检测'">
<video ref="videoElement" class="video-area"></video>
<div class="photo-area">
<canvas ref="canvasElement" style="display: none"></canvas>
<div v-if="photoURL">
<img :src="photoURL" alt="Photo" />
<div class="img-bottom-text">当前照片</div>
</div>
</div>
<SvgIcon name="take-photo-text" class="take-photo-text" @click="takePhoto" />
</div>
</div>
<template #footer>
<el-button type="primary" @click="toUse" :disabled="isCapturing" style="width: 140px">使用</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import { ElDialog, ElButton, ElMessage, vLoading } from 'element-plus';
import { DetectFace } from '@/api/index.js';
import SvgIcon from '@/components/SvgIcon';
const videoElement = ref(null);
const canvasElement = ref(null);
const photoURL = ref(null);
const isCapturing = ref(false);
let mediaStream;
let isOpening = ref(false);
const props = defineProps({
isShowTakePhoto: {
type: Boolean,
default: false,
},
});
watch(
() => props.isShowTakePhoto,
(newV) => {
if (!newV) {
photoURL.value = null;
closeCamera();
}
},
);
onMounted(async () => {
try {
isOpening.value = true;
mediaStream = await navigator.mediaDevices.getUserMedia({ video: true });
videoElement.value.srcObject = mediaStream;
videoElement.value.play();
isOpening.value = false;
} catch (error) {
// console.error('Error accessing media devices: ', error);
}
});
const takePhoto = () => {
isCapturing.value = true;
const context = canvasElement.value.getContext('2d');
canvasElement.value.width = videoElement.value.videoWidth;
canvasElement.value.height = videoElement.value.videoHeight;
context.drawImage(videoElement.value, 0, 0, canvasElement.value.width, canvasElement.value.height);
photoURL.value = canvasElement.value.toDataURL('image/png');
isCapturing.value = false;
};
const emit = defineEmits(['closeTakePhoto']);
const handleClose = () => {
closeCamera();
emit('closeTakePhoto');
};
const closeCamera = () => {
// 释放摄像头
mediaStream && mediaStream.getTracks().forEach((track) => track.stop());
};
let isDetecting = ref(false);
const toUse = () => {
if (!photoURL.value) {
ElMessage.warning('请先拍照');
return;
}
let photoFile = dataURLtoBlob(photoURL.value);
// 进行人脸检测
const formData = new FormData();
formData.append('image', photoFile);
isDetecting.value = true;
DetectFace(formData)
.then((res) => {
// 人脸检测成功
emit('setPhoto', photoFile, photoURL.value);
handleClose();
})
.catch(() => {
// 未检测到人脸
})
.finally(() => {
isDetecting.value = false;
});
};
/**
* Base64字符串转二进制流
* @param {String} dataurl Base64字符串(字符串包含Data URI scheme,例如:data:image/png;base64, )
*/
const 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,
});
};
// return { videoElement, canvasElement, photoURL, takePhoto, isCapturing };
</script>
<style lang="less" scoped>
.photo-content {
display: flex;
justify-content: space-around;
align-items: center;
width: 100%;
.left-content {
position: relative;
}
.video-area {
width: 500px;
height: 375px;
border-radius: @borderRadius;
}
}
.photo-area {
position: absolute;
top: 0;
right: -155px;
img {
width: 133px;
height: 100px;
border-top-right-radius: @borderRadius;
border-top-left-radius: @borderRadius;
}
}
.img-bottom-text {
// width: 100px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
top: -6px;
background-color: #e2eefa;
border-bottom-left-radius: @borderRadius;
border-bottom-right-radius: @borderRadius;
}
.take-photo-text {
width: 64px;
height: 64px;
color: var(--el-color-primary);
position: absolute;
left: 218px;
bottom: 20px;
cursor: pointer;
}
.take-photo-text:hover {
box-shadow: 0px 0px 12px var(--el-color-primary);
border-radius: 100px;
}
</style>