Просмотр исходного кода

调整相机拍照适应手机尺寸、默认调取后摄不翻转镜面,无后摄调取前摄

liyanbo 5 месяцев назад
Родитель
Сommit
8ecea5f8dd
1 измененных файлов с 150 добавлено и 45 удалено
  1. 150 45
      src/components/ImageUpload/index.vue

+ 150 - 45
src/components/ImageUpload/index.vue

@@ -1,13 +1,13 @@
 <template>
   <div class="image-upload-container">
     <input
-      type="file"
-      ref="fileInput"
-      accept="image/*"
-      style="display: none"
-      @change="handleFileSelect"
+        type="file"
+        ref="fileInput"
+        accept="image/*"
+        style="display: none"
+        @change="handleFileSelect"
     />
-    
+
     <div class="combined-upload-btn">
       <span class="upload-part" @click="triggerFileSelect" :title="'上传参考图'">
         上传参考图
@@ -25,22 +25,22 @@
         <button class="delete-btn" @click="removeImage">×</button>
         <!-- 使用el-image组件实现预览功能 -->
         <el-image
-          :src="previewImage"
-          :preview-src-list="[previewImage]"
-          fit="cover"
-          show-progress
-          class="preview-image"
+            :src="previewImage"
+            :preview-src-list="[previewImage]"
+            fit="cover"
+            show-progress
+            class="preview-image"
         >
           <template
-            #toolbar="{ actions,  reset, activeIndex }"
+              #toolbar="{ actions,  reset, activeIndex }"
           >
             <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
             <el-icon
-              @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })">
+                @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })">
               <ZoomIn />
             </el-icon>
             <el-icon
-              @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })">
+                @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })">
               <RefreshRight />
             </el-icon>
             <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
@@ -53,10 +53,12 @@
 
     <!-- 相机弹窗 -->
     <el-dialog
-      v-model="cameraVisible"
-      title="相机拍照"
-      width="700px"
-      @close="closeCamera"
+        v-model="cameraVisible"
+        title="相机拍照"
+        :width="isSmallScreen ? '100%' : '90vw'"
+        :fullscreen="isSmallScreen"
+        @close="closeCamera"
+        class="camera-dialog"
     >
       <div class="camera-container">
         <div v-if="!isCapturing" class="camera-view">
@@ -79,7 +81,7 @@
 </template>
 
 <script setup>
-import { ref, watch, nextTick } from 'vue';
+import { ref, watch, nextTick, onMounted, onUnmounted } from 'vue';
 import { Picture, ZoomOut, ZoomIn, RefreshRight, RefreshLeft, Refresh, Download, Camera } from '@element-plus/icons-vue';
 import { Message } from '@/utils/message/Message.js';
 // 导入axios用于文件上传
@@ -109,6 +111,21 @@ const stream = ref(null); // 媒体流
 const capturedImage = ref(''); // 拍摄的照片
 const isCapturing = ref(false); // 是否正在拍摄中
 const videoRef = ref(null); // 视频元素引用
+const isSmallScreen = ref(false); // 是否为小屏设备
+
+// 监听窗口大小变化
+const handleResize = () => {
+  isSmallScreen.value = window.innerWidth < 768;
+};
+
+onMounted(() => {
+  handleResize();
+  window.addEventListener('resize', handleResize);
+});
+
+onUnmounted(() => {
+  window.removeEventListener('resize', handleResize);
+});
 
 // 监听modelValue变化,更新预览图
 watch(() => props.modelValue, (newValue) => {
@@ -150,10 +167,10 @@ const uploadFile = async (file) => {
     // 创建FormData对象
     const formData = new FormData();
     formData.append('file', file);
-    
+
     // 构建上传地址
     const uploadUrl = import.meta.env.VITE_BASE_URL + '/infra/file/upload';
-    
+
     // 发送文件上传请求
     const response = await axios.post(uploadUrl, formData, {
       headers: {
@@ -162,13 +179,13 @@ const uploadFile = async (file) => {
       },
       timeout: 60000
     });
-    
+
     let imageUrl = '';
     if (response.data?.data) {
       // 去除反引号和可能的空格
       imageUrl = response.data.data.replace(/[`\s]/g, '');
     }
-    
+
     // 更新预览图
     previewImage.value = imageUrl;
     // 触发更新事件
@@ -219,10 +236,38 @@ const download = (index) => {
 // 打开相机弹窗
 const openCamera = async () => {
   try {
-    // 获取用户媒体设备权限
-    stream.value = await navigator.mediaDevices.getUserMedia({
-      video: true
-    });
+    // 获取相机容器的尺寸
+    const containerWidth = window.innerWidth * (isSmallScreen.value ? 0.9 : 0.8);
+    const containerHeight = window.innerHeight * (isSmallScreen.value ? 0.7 : 0.6);
+    
+    // 定义相机视频的最大分辨率
+    const videoConstraints = {
+      width: { ideal: containerWidth, max: containerWidth * 1.2 },
+      height: { ideal: containerHeight, max: containerHeight * 1.2 },
+      frameRate: { ideal: 30, max: 60 }
+    };
+    
+    // 尝试优先使用后摄像头(不带exact,提高兼容性)
+    try {
+      stream.value = await navigator.mediaDevices.getUserMedia({
+        video: { ...videoConstraints, facingMode: 'environment' }
+      });
+    } catch (backCameraError) {
+      console.log('后摄像头不可用,尝试使用前摄像头:', backCameraError);
+      // 后摄像头不可用,尝试使用前摄像头
+      try {
+        stream.value = await navigator.mediaDevices.getUserMedia({
+          video: { ...videoConstraints, facingMode: 'user' }
+        });
+      } catch (frontCameraError) {
+        console.log('前摄像头也不可用,尝试默认设置:', frontCameraError);
+        // 前摄像头也不可用,使用默认设置
+        stream.value = await navigator.mediaDevices.getUserMedia({ 
+          video: videoConstraints 
+        });
+      }
+    }
+
     // 先显示弹窗,让DOM渲染完成
     cameraVisible.value = true;
     isCapturing.value = false;
@@ -257,14 +302,20 @@ const takePhoto = () => {
   canvas.width = video.videoWidth;
   canvas.height = video.videoHeight;
   const context = canvas.getContext('2d');
-  
-  // 应用镜像效果
-  context.save();
-  context.translate(canvas.width, 0); // 平移到右侧
-  context.scale(-1, 1); // 水平翻转
-  context.drawImage(video, 0, 0, canvas.width, canvas.height);
-  context.restore();
-  
+
+  // 应用镜像效果(仅对前摄像头)
+  const isFrontCamera = stream.value?.getVideoTracks()[0]?.getSettings().facingMode === 'user';
+
+  if (isFrontCamera) {
+    context.save();
+    context.translate(canvas.width, 0); // 平移到右侧
+    context.scale(-1, 1); // 水平翻转
+    context.drawImage(video, 0, 0, canvas.width, canvas.height);
+    context.restore();
+  } else {
+    context.drawImage(video, 0, 0, canvas.width, canvas.height);
+  }
+
   capturedImage.value = canvas.toDataURL('image/jpeg');
   isCapturing.value = true;
 };
@@ -273,16 +324,16 @@ const takePhoto = () => {
 const retakePhoto = async () => {
   capturedImage.value = '';
   isCapturing.value = false;
-  
+
   // 确保视频元素已经渲染且媒体流存在
   await nextTick();
-  
+
   if (videoRef.value && stream.value) {
     // 确保视频元素的srcObject正确设置为媒体流
     if (videoRef.value.srcObject !== stream.value) {
       videoRef.value.srcObject = stream.value;
     }
-    
+
     // 尝试重新播放视频(在某些浏览器中可能需要)
     try {
       await videoRef.value.play();
@@ -303,7 +354,7 @@ const confirmAndUpload = async () => {
 
       // 调用共用的上传函数
       await uploadFile(file);
-      
+
       // 关闭相机弹窗
       closeCamera();
     } catch (error) {
@@ -399,7 +450,7 @@ const confirmAndUpload = async () => {
 
 .preview-image {
   width: rpx(90);
-   max-height: rpx(150);
+  max-height: rpx(150);
   object-fit: contain;
   border-radius: rpx(3);
 }
@@ -438,26 +489,80 @@ const confirmAndUpload = async () => {
 }
 
 // 相机弹窗样式
+.camera-dialog .el-dialog__body {
+  padding: 0;
+  overflow: hidden;
+}
+
 .camera-container {
   display: flex;
   justify-content: center;
   align-items: center;
-  padding: 20px 0;
+  padding: 0;
+  max-width: 100%;
+  max-height: 100%;
+  height: calc(100vh - 150px);
+  overflow: hidden;
+}
+
+.camera-view {
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
 
 .camera-view video {
   max-width: 100%;
-  height: auto;
+  max-height: 100%;
+  object-fit: contain;
   border: 1px solid #ddd;
   border-radius: 5px;
-  transform: scaleX(-1);
+}
+
+.captured-image-view {
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
 
 .captured-image-view img {
   max-width: 100%;
-  height: auto;
+  max-height: 100%;
+  object-fit: contain;
   border: 1px solid #ddd;
   border-radius: 5px;
-  transform: scaleX(-1);
+}
+
+// 小屏设备样式
+@media (max-width: 768px) {
+  .camera-container {
+    padding: 10px 0;
+    height: calc(100vh - 150px);
+    overflow: hidden;
+  }
+
+  .camera-view video {
+    width: 100%;
+    height: 100%;
+    max-width: none;
+    max-height: none;
+    object-fit: contain;
+    border-radius: 0;
+    border: none;
+  }
+
+  .captured-image-view img {
+    max-width: 100%;
+    max-height: 100%;
+    object-fit: contain;
+    border-radius: 0;
+    border: none;
+  }
 }
 </style>