Jelajahi Sumber

增加生成视频的上传图片尺寸限制

liyanbo 3 minggu lalu
induk
melakukan
d8d59adb72
2 mengubah file dengan 132 tambahan dan 28 penghapusan
  1. 114 18
      src/components/ImageUpload/index.vue
  2. 18 10
      src/components/ai/video/ImageToVideo.vue

+ 114 - 18
src/components/ImageUpload/index.vue

@@ -8,7 +8,7 @@
         @change="handleFileSelect"
     />
 
-    <div class="combined-upload-btn">
+    <div class="combined-upload-btn" :title="resolutionHint || ''">
       <span class="upload-part" @click="triggerFileSelect" :title="'上传参考图'">
         上传参考图
         <el-icon><Picture /></el-icon>
@@ -17,6 +17,7 @@
       <span class="camera-part" @click="openCamera" :title="'使用相机拍照'">
         <el-icon><Camera /></el-icon>
       </span>
+      <div v-if="resolutionHint" class="resolution-hint">{{ resolutionHint }}</div>
     </div>
 
     <!-- 预览图显示区域 - 使用el-image组件 -->
@@ -94,11 +95,36 @@ import { Message } from '@/utils/message/Message.js';
 // 导入axios用于文件上传
 import axios from 'axios';
 
-// 定义props(保留,但在内部写死默认值)
+// 定义props
 const props = defineProps({
   modelValue: {
     type: String,
     default: ''
+  },
+  // 最小宽度(像素),0表示不限制
+  minWidth: {
+    type: Number,
+    default: 0
+  },
+  // 最小高度(像素),0表示不限制
+  minHeight: {
+    type: Number,
+    default: 0
+  },
+  // 最大宽度(像素),0表示不限制
+  maxWidth: {
+    type: Number,
+    default: 0
+  },
+  // 最大高度(像素),0表示不限制
+  maxHeight: {
+    type: Number,
+    default: 0
+  },
+  // 分辨率提示文字
+  resolutionHint: {
+    type: String,
+    default: ''
   }
 });
 
@@ -147,24 +173,64 @@ const triggerFileSelect = () => {
   fileInput.value?.click();
 };
 
+// 验证图片分辨率
+const validateImageResolution = (file) => {
+  return new Promise((resolve) => {
+    if (!props.minWidth && !props.minHeight && !props.maxWidth && !props.maxHeight) {
+      resolve(true);
+      return;
+    }
+    const img = new Image();
+    const url = URL.createObjectURL(file);
+    img.onload = () => {
+      URL.revokeObjectURL(url);
+      const { width, height } = img;
+      if (props.minWidth && width < props.minWidth) {
+        Message().error(`图片宽度不能小于 ${props.minWidth}px(当前 ${width}px)`, true);
+        resolve(false); return;
+      }
+      if (props.minHeight && height < props.minHeight) {
+        Message().error(`图片高度不能小于 ${props.minHeight}px(当前 ${height}px)`, true);
+        resolve(false); return;
+      }
+      if (props.maxWidth && width > props.maxWidth) {
+        Message().error(`图片宽度不能大于 ${props.maxWidth}px(当前 ${width}px)`, true);
+        resolve(false); return;
+      }
+      if (props.maxHeight && height > props.maxHeight) {
+        Message().error(`图片高度不能大于 ${props.maxHeight}px(当前 ${height}px)`, true);
+        resolve(false); return;
+      }
+      resolve(true);
+    };
+    img.onerror = () => {
+      URL.revokeObjectURL(url);
+      Message().error('无法读取图片信息,请检查文件是否损坏', true);
+      resolve(false);
+    };
+    img.src = url;
+  });
+};
+
 // 处理文件选择
 const handleFileSelect = async (event) => {
   const file = event.target.files[0];
   if (!file) return;
 
-  // 验证文件类型
   if (!file.type.match('image.*')) {
     Message().error('请选择有效的图片文件!', true);
     return;
   }
 
-  // 验证文件大小
   const maxSize = 50 * 1024 * 1024;
   if (file.size > maxSize) {
     Message().error('图片大小不能超过50MB!', true);
     return;
   }
 
+  const valid = await validateImageResolution(file);
+  if (!valid) return;
+
   await uploadFile(file);
 };
 
@@ -247,14 +313,14 @@ const getCameraStream = async (facingMode) => {
     // 获取相机容器的尺寸
     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 }
     };
-    
+
     // 尝试获取指定方向的摄像头
     return await navigator.mediaDevices.getUserMedia({
       video: { ...videoConstraints, facingMode }
@@ -283,15 +349,15 @@ const openCamera = async () => {
         // 前摄像头也不可用,使用默认设置
         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 }
         };
-        
-        stream.value = await navigator.mediaDevices.getUserMedia({ 
-          video: videoConstraints 
+
+        stream.value = await navigator.mediaDevices.getUserMedia({
+          video: videoConstraints
         });
         currentCamera.value = 'default';
       }
@@ -324,12 +390,12 @@ const switchCamera = async () => {
 
     // 切换摄像头方向
     const newCamera = currentCamera.value === 'environment' ? 'user' : 'environment';
-    
+
     try {
       // 尝试获取新的摄像头流
       stream.value = await getCameraStream(newCamera);
       currentCamera.value = newCamera;
-      
+
       // 更新视频源
       if (videoRef.value) {
         videoRef.value.srcObject = stream.value;
@@ -338,7 +404,7 @@ const switchCamera = async () => {
       // 如果获取新摄像头失败,尝试切换回原来的摄像头
       console.error(`切换到${newCamera === 'environment' ? '后' : '前'}摄像头失败:`, error);
       Message().error(`无法切换到${newCamera === 'environment' ? '后' : '前'}摄像头,请检查设备是否支持!`, true);
-      
+
       // 尝试恢复原来的摄像头
       try {
         stream.value = await getCameraStream(currentCamera.value);
@@ -420,15 +486,14 @@ const retakePhoto = async () => {
 const confirmAndUpload = async () => {
   if (capturedImage.value) {
     try {
-      // 创建Blob对象
       const response = await fetch(capturedImage.value);
       const blob = await response.blob();
       const file = new File([blob], `camera_photo_${Date.now()}.jpg`, { type: 'image/jpeg' });
 
-      // 调用共用的上传函数
-      await uploadFile(file);
+      const valid = await validateImageResolution(file);
+      if (!valid) return;
 
-      // 关闭相机弹窗
+      await uploadFile(file);
       closeCamera();
     } catch (error) {
       console.error('相机拍摄图片上传失败:', error);
@@ -450,6 +515,37 @@ const confirmAndUpload = async () => {
   display: inline-block;
 }
 
+.resolution-hint {
+  position: absolute;
+  bottom: calc(100% + 6px);
+  left: 50%;
+  transform: translateX(-50%);
+  background: rgba(49, 37, 12, 0.92);
+  color: #fff7d8;
+  font-size: 11px;
+  padding: 3px 8px;
+  border-radius: 4px;
+  white-space: nowrap;
+  pointer-events: none;
+  z-index: 20;
+  opacity: 0;
+  transition: opacity 0.16s ease;
+}
+
+.combined-upload-btn:hover .resolution-hint {
+  opacity: 1;
+}
+
+.resolution-hint::after {
+  content: '';
+  position: absolute;
+  top: 100%;
+  left: 50%;
+  transform: translateX(-50%);
+  border: 4px solid transparent;
+  border-top-color: rgba(49, 37, 12, 0.92);
+}
+
 // 合并按钮样式
 .combined-upload-btn {
   display: inline-flex;
@@ -671,4 +767,4 @@ const confirmAndUpload = async () => {
     border: none;
   }
 }
-</style>
+</style>

+ 18 - 10
src/components/ai/video/ImageToVideo.vue

@@ -58,7 +58,7 @@
                     </video>
                   </div>
               </div>
-                
+
             </div>
             </div>
           </div>
@@ -72,7 +72,15 @@
               @keyup.enter="sendMessage"
               style="flex: 1; margin-right: 8px;"
             />
-             <ImageUpload v-model="uploadedImage" ref="imageUploadRef"/>
+             <ImageUpload
+              v-model="uploadedImage"
+              ref="imageUploadRef"
+              :min-width="720"
+              :min-height="720"
+              :max-width="6000"
+              :max-height="6000"
+              resolution-hint="图片宽高需在 720px ~ 6000px 之间"
+            />
             <!-- 语音输入按钮 -->
                 <VoiceInput
                 inputSelector="input[type='text']"
@@ -285,7 +293,7 @@ const sendMessage = async() => {
       type: 'user',
       content: content,
     };
-    
+
     // 如果有上传的图片,添加到用户消息中
     if (uploadedImage.value) {
       userMessage.imageUrl = uploadedImage.value;
@@ -315,7 +323,7 @@ const sendMessage = async() => {
         "modelId": modelId.value,
         "prompt":content,
         "duration":4,
-        "resolution":"1080P",
+        "resolution":"480P",
         "promptImage":[uploadedImage.value]
       }).then(res=>{
         console.log("生成视频",res)
@@ -368,7 +376,7 @@ const refreshWatchImages = async () => {
     return
   }
   const list = await VideoGetMys(imageIds)
-  
+
       console.log('AI生成的视频地址:', list,imageIds);
   const newWatchImages = {}
   list?.data.forEach((image) => {
@@ -571,7 +579,7 @@ const inProgressTimerFun = () => {
   align-content: center;
   flex-wrap: nowrap;
   min-height: rpx(30);
-  
+
   > * {
     display: flex;
     align-items: center;
@@ -579,7 +587,7 @@ const inProgressTimerFun = () => {
     height: rpx(20);
     box-sizing: border-box;
   }
-  
+
   .speech-btn {
     padding: rpx(5) rpx(10);
     background: #fff;
@@ -600,7 +608,7 @@ const inProgressTimerFun = () => {
       color: #666;
     }
   }
-  
+
   // 终止按钮样式
   .stop-btn {
     cursor: pointer;
@@ -610,14 +618,14 @@ const inProgressTimerFun = () => {
       height: rpx(20);
     }
   }
-  
+
   // 确保图片上传组件垂直居中
   :deep(.el-upload) {
     display: flex;
     align-items: center;
     justify-content: center;
   }
-  
+
   // 确保语音输入组件垂直居中
   :deep(.voice-input-container) {
     display: flex;