修改拍照上传逻辑,避免平板不可以调用照相机
This commit is contained in:
@ -3,13 +3,54 @@
|
||||
<div v-if="error" class="error-message">
|
||||
{{ error }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<video ref="videoRef" autoplay playsinline class="video-preview"></video>
|
||||
|
||||
<div v-else class="media-box">
|
||||
<video
|
||||
v-show="!imgSrc"
|
||||
ref="videoRef"
|
||||
autoplay
|
||||
playsinline
|
||||
class="media-content"
|
||||
></video>
|
||||
|
||||
<img
|
||||
v-if="imgSrc"
|
||||
:src="imgSrc"
|
||||
class="media-content preview-img"
|
||||
alt="Photo Preview"
|
||||
/>
|
||||
|
||||
<canvas ref="canvasRef" style="display: none;"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="camera-actions">
|
||||
<el-button @click="handleCancel" size="large">取消</el-button>
|
||||
<el-button type="primary" @click="capture" size="large" :disabled="!isCameraReady">拍照</el-button>
|
||||
<template v-if="!imgSrc">
|
||||
<el-button @click="handleCancel" size="large">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="capture"
|
||||
size="large"
|
||||
:disabled="!isCameraReady"
|
||||
>
|
||||
<el-icon class="el-icon--left"><Camera /></el-icon>
|
||||
拍照
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<el-button @click="retake" size="large" type="warning" plain>
|
||||
<el-icon class="el-icon--left"><RefreshLeft /></el-icon>
|
||||
重拍
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
@click="confirmUse"
|
||||
size="large"
|
||||
>
|
||||
<el-icon class="el-icon--left"><Check /></el-icon>
|
||||
确认使用
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -17,15 +58,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Camera, RefreshLeft, Check } from '@element-plus/icons-vue'
|
||||
|
||||
const props = defineProps<{
|
||||
visible?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
confirm: [file: File]
|
||||
cancel: []
|
||||
}>()
|
||||
const emit = defineEmits(['photo-submit', 'cancel'])
|
||||
|
||||
const videoRef = ref<HTMLVideoElement>()
|
||||
const canvasRef = ref<HTMLCanvasElement>()
|
||||
@ -33,24 +68,39 @@ const mediaStream = ref<MediaStream>()
|
||||
const error = ref('')
|
||||
const isCameraReady = ref(false)
|
||||
|
||||
const imgSrc = ref('')
|
||||
const currentFile = ref<File | null>(null)
|
||||
|
||||
const startCamera = async () => {
|
||||
stopCamera()
|
||||
error.value = ''
|
||||
imgSrc.value = ''
|
||||
currentFile.value = null
|
||||
|
||||
try {
|
||||
const constraints = {
|
||||
video: { facingMode: 'environment' }
|
||||
video: {
|
||||
facingMode: 'environment',
|
||||
width: { ideal: 1920 },
|
||||
height: { ideal: 1080 }
|
||||
}
|
||||
}
|
||||
const stream = await navigator.mediaDevices.getUserMedia(constraints)
|
||||
mediaStream.value = stream
|
||||
|
||||
if (videoRef.value) {
|
||||
videoRef.value.srcObject = stream
|
||||
await videoRef.value.play()
|
||||
isCameraReady.value = true
|
||||
}
|
||||
} catch (err: any) {
|
||||
error.value = '无法访问摄像头: ' + (err.message || '请检查权限或连接')
|
||||
console.error(err)
|
||||
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
|
||||
error.value = '无法访问摄像头: 浏览器限制非 HTTPS 环境调用摄像头,请使用 localhost 或 配置 HTTPS。'
|
||||
} else {
|
||||
error.value = '无法访问摄像头: ' + (err.message || '请检查权限')
|
||||
}
|
||||
ElMessage.error(error.value)
|
||||
emit('cancel')
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,6 +123,8 @@ const capture = () => {
|
||||
const width = video.videoWidth
|
||||
const height = video.videoHeight
|
||||
|
||||
if (width === 0 || height === 0) return
|
||||
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
|
||||
@ -82,13 +134,47 @@ const capture = () => {
|
||||
ctx.drawImage(video, 0, 0, width, height)
|
||||
|
||||
canvas.toBlob((blob) => {
|
||||
if (!blob) return
|
||||
const file = new File([blob], 'camera.jpg', { type: 'image/jpeg' })
|
||||
emit('confirm', file)
|
||||
stopCamera()
|
||||
if (!blob) {
|
||||
ElMessage.error('照片生成失败,请重试')
|
||||
return
|
||||
}
|
||||
|
||||
const timestamp = new Date().getTime()
|
||||
const filename = `photo_${timestamp}.jpg`
|
||||
const file = new File([blob], filename, { type: 'image/jpeg' })
|
||||
|
||||
currentFile.value = file
|
||||
imgSrc.value = URL.createObjectURL(blob)
|
||||
console.log('✅ 照片已捕获:', filename, (file.size / 1024).toFixed(2) + 'KB')
|
||||
}, 'image/jpeg', 0.95)
|
||||
}
|
||||
|
||||
const retake = () => {
|
||||
if (imgSrc.value) {
|
||||
URL.revokeObjectURL(imgSrc.value)
|
||||
}
|
||||
imgSrc.value = ''
|
||||
currentFile.value = null
|
||||
|
||||
if (!mediaStream.value || !mediaStream.value.active) {
|
||||
startCamera()
|
||||
}
|
||||
}
|
||||
|
||||
const confirmUse = () => {
|
||||
console.log('👆 点击了确认使用')
|
||||
if (currentFile.value) {
|
||||
try {
|
||||
console.log('📤 发送 photo-submit 事件', currentFile.value)
|
||||
emit('photo-submit', currentFile.value)
|
||||
} catch (err) {
|
||||
console.error('父组件处理事件失败:', err)
|
||||
}
|
||||
} else {
|
||||
ElMessage.warning('照片数据未就绪,请稍后或重拍')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
stopCamera()
|
||||
emit('cancel')
|
||||
@ -100,6 +186,9 @@ onMounted(() => {
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopCamera()
|
||||
if (imgSrc.value) {
|
||||
URL.revokeObjectURL(imgSrc.value)
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
@ -113,22 +202,39 @@ defineExpose({
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.video-preview {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
max-height: 360px;
|
||||
height: 100%;
|
||||
}
|
||||
.media-box {
|
||||
width: 100%;
|
||||
max-width: 640px;
|
||||
height: 400px;
|
||||
background: #000;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
.media-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
.error-message {
|
||||
color: #f56c6c;
|
||||
padding: 20px;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
background: #fef0f0;
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
.camera-actions {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
Reference in New Issue
Block a user