修改拍照上传逻辑,避免平板不可以调用照相机

This commit is contained in:
dxc
2026-02-10 09:27:52 +08:00
parent d4b23790a1
commit a0ed92319c
5 changed files with 267 additions and 150 deletions

View File

@ -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>