liuhairui il y a 3 jours
Parent
commit
ecfdad9862

+ 243 - 0
src/view/Product/ProductImageGeneration.vue

@@ -0,0 +1,243 @@
+<template>
+  <div class="product-image-generation">
+    <div class="two-column-layout">
+      <div class="left-column">
+        <el-card class="config-card" shadow="hover">
+          <template #header><span class="card-header"><el-icon><Setting /></el-icon>生成配置</span></template>
+
+          <div class="config-body">
+          <el-form :model="formData" label-position="top" size="small" class="config-form">
+            <!-- 产品图+参考图 并排正方形 -->
+            <el-form-item class="upload-row">
+              <div class="upload-row-inner">
+                <div v-for="item in uploadConfig" :key="item.key" class="upload-cell">
+                  <span class="upload-label">{{ item.label }}<span v-if="item.required" class="required"> *</span></span>
+                  <el-upload
+                    class="image-uploader square"
+                    :action="`${uploadPath}/fileUploadAndDownload/upload`"
+                    :headers="{ 'x-token': userStore.token }"
+                    :show-file-list="false"
+                    :on-success="(r) => handleUploadSuccess(item.key, r)"
+                    :before-upload="beforeImageUpload"
+                    accept="image/jpeg,image/png,image/webp"
+                  >
+                    <div v-if="formData[item.key]" class="uploaded-preview">
+                      <el-image :src="formatImageUrl(formData[item.key])" fit="contain" class="preview-image">
+                        <template #error><div class="image-error"><el-icon><Picture /></el-icon></div></template>
+                      </el-image>
+                      <div class="upload-mask"><el-icon><ZoomIn /></el-icon><span>更换</span></div>
+                    </div>
+                    <div v-else class="upload-placeholder">
+                      <el-icon :size="20"><Plus /></el-icon>
+                      <span>{{ item.placeholder }}</span>
+                    </div>
+                  </el-upload>
+                </div>
+              </div>
+            </el-form-item>
+
+            <el-form-item label="提示词" required>
+              <el-input v-model="formData.prompt" type="textarea" :rows="5" placeholder="请输入对效果图的描述" maxlength="500" />
+            </el-form-item>
+
+            <!-- 尺寸+模型 紧凑同一行(红框区域) -->
+            <el-form-item class="config-row-compact">
+              <div class="config-row-inner">
+                <div class="config-item">
+                  <span class="config-label">尺寸</span>
+                  <el-radio-group v-model="formData.size" size="small" class="size-radio-group">
+                    <el-radio v-for="item in sizeOptions" :key="item.value" :label="item.value" border>{{ item.label }}</el-radio>
+                  </el-radio-group>
+                </div>
+                <div class="config-item model-item">
+                  <span class="config-label">模型</span>
+                  <el-select v-model="formData.model" placeholder="请选择模型" size="small" class="model-select" filterable>
+                    <el-option v-for="item in modelList" :key="item.id" :label="item.name" :value="item.name" />
+                  </el-select>
+                </div>
+              </div>
+            </el-form-item>
+          </el-form>
+
+          <div class="action-buttons-sticky">
+            <el-button type="success" :loading="isOptimizing" @click="optimizePrompt">
+              <el-icon><MagicStick /></el-icon>{{ isOptimizing ? '优化中...' : '优化提示词' }}
+            </el-button>
+            <el-button type="primary" :loading="isGenerating" @click="generateImage">
+              <el-icon><PictureFilled /></el-icon>{{ isGenerating ? '生成中...' : '生成效果图' }}
+            </el-button>
+          </div>
+          </div>
+        </el-card>
+      </div>
+
+      <div class="right-column">
+        <el-card class="result-card" shadow="hover">
+          <template #header><span class="card-header"><el-icon><Picture /></el-icon>生成效果图</span></template>
+          <div class="result-area">
+            <div v-if="generatedImageUrl" class="result-preview">
+              <el-image :src="formatImageUrl(generatedImageUrl)" fit="contain" class="result-image" :preview-src-list="[formatImageUrl(generatedImageUrl)]" preview-teleported>
+                <template #error><div class="image-error-large"><el-icon :size="48"><Picture /></el-icon><span>图片加载失败</span></div></template>
+              </el-image>
+              <el-button type="primary" size="small" @click="downloadImage"><el-icon><Download /></el-icon>下载</el-button>
+            </div>
+            <div v-else class="result-empty">
+              <el-icon :size="60" class="empty-icon"><Picture /></el-icon>
+              <p>效果图将在此处显示</p>
+              <span>上传产品图、填写提示词后点击「生成效果图」</span>
+            </div>
+          </div>
+        </el-card>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue'
+import { ElMessage } from 'element-plus'
+import { useUserStore } from '@/pinia/modules/user'
+import { Setting, Picture, Plus, ZoomIn, MagicStick, PictureFilled, Download } from '@element-plus/icons-vue'
+
+defineOptions({ name: 'ProductImageGeneration' })
+
+const userStore = useUserStore()
+const uploadPath = ref(import.meta.env.VITE_BASE_API)
+const uploadConfig = [
+  { key: 'productImageUrl', label: '产品图', placeholder: '上传产品图', required: true },
+  { key: 'referenceImageUrl', label: '参考图(可选)', placeholder: '上传参考图', required: false },
+]
+
+const formData = reactive({ productImageUrl: '', referenceImageUrl: '', prompt: '', size: '1:1', model: '' })
+const sizeOptions = [
+  { label: '1:1', value: '1:1' }, { label: '4:3', value: '4:3' }, { label: '3:2', value: '3:2' },
+  { label: '2:3', value: '2:3' }, { label: '16:9', value: '16:9' }, { label: '9:16', value: '9:16' },
+]
+const modelList = ref([{ id: 1, name: '默认模型' }])
+const isGenerating = ref(false)
+const isOptimizing = ref(false)
+const generatedImageUrl = ref('')
+
+const formatImageUrl = (path) => {
+  if (!path || typeof path !== 'string') return ''
+  if (path.startsWith('http://') || path.startsWith('https://')) return path
+  const base = import.meta.env.VITE_BASE_API
+  return `${base.endsWith('/') ? base : base + '/'}${path.startsWith('/') ? path.slice(1) : path}`
+}
+
+const beforeImageUpload = (file) => {
+  const ok = ['image/jpeg', 'image/png', 'image/webp'].includes(file.type)
+  if (!ok) { ElMessage.error('仅支持 jpg、png、webp 格式'); return false }
+  if (file.size / 1024 / 1024 >= 5) { ElMessage.error('图片大小不能超过 5MB'); return false }
+  return true
+}
+
+const handleUploadSuccess = (key, res) => {
+  if (res?.data?.file?.url) { formData[key] = res.data.file.url; ElMessage.success('上传成功') }
+}
+
+const optimizePrompt = async () => {
+  if (!formData.prompt) { ElMessage.warning('请先输入提示词'); return }
+  isOptimizing.value = true
+  try {
+    await new Promise(r => setTimeout(r, 1000))
+    ElMessage.success('提示词优化完成(演示)')
+  } catch { ElMessage.error('优化失败') }
+  finally { isOptimizing.value = false }
+}
+
+const generateImage = async () => {
+  if (!formData.productImageUrl) { ElMessage.warning('请先上传产品图'); return }
+  if (!formData.prompt) { ElMessage.warning('请填写提示词'); return }
+  isGenerating.value = true
+  generatedImageUrl.value = ''
+  try {
+    await new Promise(r => setTimeout(r, 2000))
+    generatedImageUrl.value = formData.productImageUrl
+    ElMessage.success('效果图生成成功')
+  } catch { ElMessage.error('生成失败') }
+  finally { isGenerating.value = false }
+}
+
+const downloadImage = () => {
+  if (!generatedImageUrl.value) return
+  const link = document.createElement('a')
+  link.href = formatImageUrl(generatedImageUrl.value)
+  link.download = `效果图_${Date.now()}.png`
+  link.target = '_blank'
+  document.body.appendChild(link)
+  link.click()
+  document.body.removeChild(link)
+  ElMessage.success('下载中')
+}
+</script>
+
+<style lang="scss" scoped>
+.product-image-generation { padding: 6px 10px; height: calc(100vh - 200px); max-height: calc(100vh - 200px); min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
+.two-column-layout { display: flex; gap: 10px; flex: 1; min-height: 0; overflow: hidden; }
+.left-column { flex: 0 0 380px; min-width: 280px; display: flex; flex-direction: column; overflow: hidden; }
+.right-column { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
+
+.config-card, .result-card {
+  flex: 1; display: flex; flex-direction: column; overflow: hidden; min-height: 0;
+  :deep(.el-card__header) { padding: 5px 10px; flex-shrink: 0; }
+  :deep(.el-card__body) { padding: 0; flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
+}
+.result-card :deep(.el-card__body) { padding: 8px; }
+.card-header { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; }
+
+.config-body { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
+.config-form { flex: 1; min-height: 0; padding: 6px 10px; overflow: hidden; :deep(.el-form-item) { margin-bottom: 4px; } }
+.action-buttons-sticky { flex-shrink: 0; display: flex; gap: 8px; padding: 6px 10px; background: #fff; border-top: 1px solid #ebeef5; }
+
+/* 产品图+参考图 并排正方形,尺寸加大 */
+.upload-row-inner { display: flex; gap: 10px; width: 100%; }
+.upload-cell { flex: 1; min-width: 0; max-width: 140px; display: flex; flex-direction: column; gap: 4px; }
+.upload-label { font-size: 12px; color: #606266; .required { color: #f56c6c; } }
+.image-uploader.square {
+  width: 100%;
+  :deep(.el-upload) { width: 100%; display: block; }
+  .upload-placeholder, .uploaded-preview {
+    width: 100%; aspect-ratio: 1; height: auto !important; min-height: 80px; max-height: 130px; max-width: 130px; margin: 0 auto;
+    border: 1px dashed #d9d9d9; border-radius: 6px; display: flex; flex-direction: column; align-items: center; justify-content: center;
+    cursor: pointer; transition: all 0.2s; background: #fafafa; overflow: hidden;
+    &:hover { border-color: #409eff; background: #f0f9ff; }
+  }
+  .upload-placeholder { color: #8c939d; span { font-size: 11px; margin-top: 2px; } }
+  .uploaded-preview {
+    position: relative; .preview-image { width: 100%; height: 100%; }
+    .upload-mask { position: absolute; inset: 0; background: rgba(0,0,0,0.4); display: flex; flex-direction: column; align-items: center; justify-content: center; color: #fff; opacity: 0; transition: opacity 0.2s; span { font-size: 11px; margin-top: 2px; } }
+    &:hover .upload-mask { opacity: 1; }
+  }
+}
+.image-error { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: #f5f7fa; color: #909399; }
+
+/* 尺寸+模型 紧凑同一行(红框) */
+.config-row-compact { margin-bottom: 0 !important; }
+.config-row-inner { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
+.config-item { display: flex; flex-direction: column; gap: 4px; }
+.config-label { font-size: 12px; color: #606266; }
+.model-item { flex: 0 0 100px; }
+.model-select { width: 100%; min-width: 100px; }
+.size-radio-group { display: flex; flex-wrap: wrap; gap: 4px; }
+
+.result-area { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; background: #f9fafb; border-radius: 6px; padding: 6px; }
+.result-preview { width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; }
+.result-image { max-width: 100%; max-height: 100%; flex: 1; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
+.result-empty { text-align: center; color: #909399; .empty-icon { color: #dcdfe6; margin-bottom: 6px; } p { margin: 0 0 4px; font-size: 13px; } span { font-size: 11px; color: #c0c4cc; } }
+.image-error-large { width: 100%; flex: 1; min-height: 120px; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #f5f7fa; color: #909399; gap: 8px; }
+
+@media (max-width: 900px) {
+  .two-column-layout { flex-direction: column; }
+  .left-column { flex: 1; min-width: 100%; min-height: 0; }
+  .right-column { flex: 1; min-height: 0; }
+}
+@media (max-width: 600px) {
+  .product-image-generation { padding: 4px 8px; height: calc(100vh - 200px); max-height: calc(100vh - 200px); }
+  .left-column { max-height: 55vh; }
+  .upload-row-inner { flex-direction: column; align-items: flex-start; }
+  .upload-cell { flex: 0 0 auto; width: 110px; }
+  .upload-cell { max-width: 110px; }
+  .image-uploader.square .upload-placeholder, .image-uploader.square .uploaded-preview { min-height: 70px; max-height: 100px; max-width: 100px; }
+}
+</style>

+ 205 - 37
src/view/Product/ProductTemplateReplace.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
   <div>
     <layout>
       <layout-header>
@@ -129,17 +129,17 @@
       <el-icon><Close /></el-icon>关闭
     </el-button>
   </div>
-  <div class="image-edit-container">
+  <div class="image-edit-container" style="display: flex; width: 100%; height: 100%; overflow: hidden;">
     <!-- 左侧:原图新图 + 输入框 -->
-    <div class="left-column">
+    <div class="left-column" style="flex: 1.5; min-width: 350px; display: flex; flex-direction: column; overflow-y: auto; padding: 10px; border-right: 1px solid #e4e7ed;">
       <!-- 标题 -->
-      <div style="font-size: 18px; font-weight: bold; color: white; margin-bottom: 10px; background-color: #404040; padding: 10px; border-radius: 4px; text-align: center;">②出图</div>
+      <div style="font-size: 18px; font-weight: bold; color: white; margin-bottom: 15px; background-color: #404040; padding: 10px; border-radius: 4px; text-align: center; display: flex; justify-content: space-between; align-items: center;">②出图</div>
         <!-- 图片对比区域 -->
-      <div class="image-comparison-section">
+      <div class="image-comparison-section" style="flex: 1; min-height: 200px; margin-bottom: 15px;">
         <!-- 原图区域 -->
-        <div class="image-preview" style="flex: 1; min-width: 120px; display: flex; flex-direction: column; align-items: center;">
+        <div class="image-preview" style="min-width: 120px; display: flex; flex-direction: column; align-items: center;">
           <h3 style="margin-top: 0; margin-bottom: 8px; font-size: 14px; font-weight: bold; color: #303133; align-self: flex-start;">上传商品白底图</h3>
-          <div v-if="editFormData.original_image_url" class="upload-image-box" style="cursor: pointer;" @click="handleImageZoom(formatImageUrl(editFormData.original_image_url))">
+          <div v-if="editFormData.original_image_url" class="upload-image-box" style="cursor: pointer; width: 100%; height: 200px;" @click="handleImageZoom(formatImageUrl(editFormData.original_image_url))">
             <el-image
               :src="formatImageUrl(editFormData.original_image_url)"
               style="width: 100%; height: 100%;"
@@ -150,7 +150,7 @@
               </template>
             </el-image>
           </div>
-          <div v-else class="image-placeholder image-placeholder-small">
+          <div v-else class="image-placeholder image-placeholder-small" style="width: 100%; height: 200px; display: flex; flex-direction: column; align-items: center; justify-content: center;">
             <el-icon :size="30"><Picture /></el-icon>
             <span style="margin-top: 5px; font-size: 10px;">暂无原图</span>
           </div>
@@ -178,12 +178,12 @@
       </div>
           
     <!-- 输入框区域 -->
-    <div class="edit-section" style="border: 1px solid #e4e7ed; border-radius: 4px; display: flex; flex-direction: column; position: relative;">
+    <div class="edit-section" style="flex: 1; min-height: 300px; border: 1px solid #e4e7ed; border-radius: 4px; display: flex; flex-direction: column; position: relative;">
         <el-form :model="editFormData" label-width="80px" style="flex: 1; display: flex; flex-direction: column; min-height: 0;">
             <el-input
               type="textarea"
               v-model="editFormData.chinese_description"
-              :rows="14"
+              :rows="8"
               placeholder="点击右侧模板图片可自动填充描述内容"
               show-word-limit
               maxlength="500"
@@ -197,8 +197,8 @@
             <span style="margin-left: 10px; color: #409eff;">正在生成中...</span>
         </div>
         
-        <!-- 按钮放在表单下方 -->
-        <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #e4e7ed;">
+        <!-- 按钮放在表单下方(flex-shrink:0 确保不被压缩,始终可见) -->
+        <div class="edit-actions-area">
         <!-- 尺寸选择区域 -->
         <div style="margin-bottom: 15px;">
           <div style="display: flex; gap: 15px; flex-wrap: wrap; align-items: center;">
@@ -243,7 +243,7 @@
         </div>
 
         <!-- 操作按钮 -->
-        <div style="display: flex; justify-content: flex-end; gap: 12px;">
+        <div class="action-buttons-row">
           <!-- <el-button size="medium" @click="clearInput">清空</el-button> -->
           <el-button size="medium" type="success" @click="optimizeContent" :loading="loadingStatus || pollStatus === 'optimizing'">
             {{ loadingStatus || pollStatus === 'optimizing' ? '正在生成中' : '扩写提示词' }} <span style="margin-left: 8px;">⚡ 25 <span style="font-size: 12px;"></span></span>
@@ -257,7 +257,7 @@
     </div>
     
    <!-- 中间:产品信息和历史记录(保证两侧边框可见,不与其他列重叠) -->
-<div class="middle-column">
+<div class="middle-column" style="flex: 1.5; min-width: 400px; max-width: 500px; overflow-y: auto; padding: 10px; border-right: 1px solid #e4e7ed; display: flex; flex-direction: column;">
   <!-- 历史记录覆盖层 -->
   <div v-if="showHistoryPanel" style="position: absolute; top: 0; left: 0; right: 0; height: 100%; background: #ffffff; border-radius: 0; z-index: 1000; display: flex; justify-content: center; align-items: flex-start; box-shadow: 4px 0 20px rgba(0,0,0,0.15); border-right: 1px solid #e4e7ed;">
     <div style="width: 95%; height: 100%; overflow-y: auto;">
@@ -324,11 +324,9 @@
   </div>
   
   <!-- 主内容区域 -->
-  <div style="display: flex; height: calc(100% - 90px);">
-    <!-- 左侧:历史记录面板 (已移至覆盖层) -->
-    
+  <div style="display: flex; flex-direction: column; flex: 1; overflow: hidden;">
     <!-- 右侧:原始内容 -->
-    <div class="original-content" style="flex: 1; overflow-y: auto; min-width: 0;">
+    <div class="original-content" style="flex: 1; overflow-y: auto; min-width: 0; display: flex; flex-direction: column;">
       <!-- 商品图例覆盖层 -->
       <div v-if="productExampleVisible" style="position: absolute; top: 0; left: 0; right: 0; height: 100%; background: #ffffff; border-radius: 0; z-index: 1000; display: flex; justify-content: center; align-items: flex-start; box-shadow: 4px 0 20px rgba(0,0,0,0.15); border-right: 1px solid #e4e7ed;">
         <div style="width: 95%; height: 100%; overflow-y: auto;">
@@ -706,13 +704,13 @@
         </div>
       </div>
       
-      <div style="height: 100%; display: flex; flex-direction: column; width: 100%; min-width: 0; align-items: center;">
+      <div style="height: 100%; display: flex; flex-direction: column; width: 100%; min-width: 0; align-items: center; flex: 1;">
         <!-- 上:留出的空白区域 -->
         <div style="width: 100%; height: 90px; background-image: url('/src/assets/top-bg.png'); background-size: cover; background-position: center; border-top-left-radius: 8px; border-top-right-radius: 8px;"></div>
-        <!-- 上中:图片显示区域 -->
-        <div style="display: flex; flex-direction: column; align-items: center; width: 100%;">
+        <!-- 上中:图片显示区域 - 更长 -->
+        <div style="display: flex; flex-direction: column; align-items: center; width: 100%; flex: 4; min-height: 300px;">
           <!-- 产品图片显示(默认) -->
-          <div style="width: 100%; height: 290px; max-width: 430px; display: flex; justify-content: center; align-items: center; background-color: white; border-radius: 4px;">
+          <div style="width: 100%; height: 100%; max-width: 430px; display: flex; justify-content: center; align-items: center; background-color: white; border-radius: 4px;">
             <el-carousel v-if="newImages.length > 0" indicator-position="outside" style="width: 100%; height: 100%;">
               <el-carousel-item v-for="(image, index) in newImages" :key="index">
                 <div style="width: 100%; height: 100%; cursor: pointer;" @click="handleImageZoom(formatImageUrl(image.url))">
@@ -739,14 +737,14 @@
                 </template>
               </el-image>
             </div>
-            <div v-else-if="!showProductImage || !editFormData.new_image_url" class="image-placeholder">
+            <div v-else-if="!showProductImage || !editFormData.new_image_url" class="image-placeholder" style="width: 100%; height: 100%;">
               <el-icon :size="60"><Picture /></el-icon>
               <span style="margin-top: 10px; display: block;">暂无效果图</span>
             </div>
           </div>
         </div>
-        <!-- 中:商品信息表单(宽度 100% 含边框,避免右侧边框被裁) -->
-        <div class="product-info-container" style="width: 100%; max-width: 430px; height: 320px; border: 12px solid #f6E0dd; border-radius: 4px; padding: 15px; box-sizing: border-box;">
+        <!-- 中:商品信息表单(宽度 100% 含边框,避免右侧边框被裁) - 更短 -->
+        <div class="product-info-container" style="width: 100%; max-width: 430px; flex: 1; border: 12px solid #f6E0dd; border-radius: 4px; padding: 15px; box-sizing: border-box; min-height: 180px;">
           <div class="product-info-item">
             <span class="product-info-label">商品条码:</span>
             <span class="product-info-value">{{ editFormData.barcode || '-' }}</span>
@@ -773,17 +771,18 @@
           </div>
         </div>
         <!-- 下:空出来的部分 -->
-        <div style="width: 100%; max-width: 430px; height: 61px;"></div>
+        <div style="width: 100%; max-width: 430px; height: 20px;"></div>
       </div>
     </div>
   </div>
 </div>
     
     <!-- 右侧:模版列表(与中间列左右留白一致) -->
-    <div class="right-column">
-      <div class="right-template" style="height: 100%; display: flex; flex-direction: column;">
+    <div class="right-column" style="flex: 1.2; min-width: 300px; display: flex; flex-direction: column; flex-shrink: 1;">
+      <!-- 固定的标题和搜索框 -->
+      <div style="padding: 10px; border-bottom: 1px solid #e4e7ed;">
         <!-- 标题 -->
-        <div style="font-size: 18px; font-weight: bold; color: white; margin-bottom: 15px; background-color: #404040; padding: 10px; border-radius: 4px; text-align: center;margin-top:15px">①模版</div>
+        <div style="font-size: 18px; font-weight: bold; color: white; margin-bottom: 15px; background-color: #404040; padding: 10px; border-radius: 4px; text-align: center; display: flex; justify-content: space-between; align-items: center;">①模版</div>
         
         <!-- 搜索框 -->
         <div class="template-search" style="margin-bottom: 15px;">
@@ -796,14 +795,16 @@
             </template>
           </el-input>
         </div>
-
+      </div>
+      
+      <!-- 可滚动的模板列表 -->
+      <div style="flex: 1; overflow-y: auto; padding: 10px;">
         <!-- 关键词搜索次数最多的 -->
         
-
         <!-- 模板列表 -->
-        <div style="flex: 1; min-height: 0;">
+        <div style="height: 100%;">
           <h4 style="margin-bottom: 10px;margin: 0px 0px 20px 0px;padding: 0px;">模版分类 ({{ templateList.length }})</h4>
-          <el-scrollbar style="height: 100%;">
+          <el-scrollbar style="height: calc(100% - 30px);">
             <div class="template-list">
               <!-- 搜索无结果提示 -->
               <div v-if="!searchLoading && searchKeyword && templateList.length === 0" class="empty-search">
@@ -1174,6 +1175,67 @@ const currentImageUrl = ref('')
 const width = ref('')
 const height = ref('')
 const isLoading = ref(false)
+
+// 响应式布局变量
+const windowWidth = ref(window.innerWidth)
+const windowHeight = ref(window.innerHeight)
+
+// 计算属性:根据窗口宽度调整布局
+const isMobile = computed(() => windowWidth.value < 768)
+const isTablet = computed(() => windowWidth.value >= 768 && windowWidth.value < 1024)
+const isDesktop = computed(() => windowWidth.value >= 1024)
+
+// 搜索输入框宽度
+const searchInputWidth = computed(() => {
+  if (isMobile.value) return '200px'
+  if (isTablet.value) return '250px'
+  return '300px'
+})
+
+// 侧边栏宽度
+const sidebarWidth = computed(() => {
+  if (isMobile.value) return 200
+  if (isTablet.value) return 250
+  return 290
+})
+
+// 表格高度
+const tableHeight = computed(() => {
+  if (isMobile.value) return '50vh'
+  if (isTablet.value) return '55vh'
+  return '62vh'
+})
+
+// 产品名称列宽度
+const productNameWidth = computed(() => {
+  if (isMobile.value) return 180
+  if (isTablet.value) return 220
+  return 260
+})
+
+// 图片列宽度和图片大小
+const imageColumnWidth = computed(() => {
+  if (isMobile.value) return 100
+  return 120
+})
+
+const imageSize = computed(() => {
+  if (isMobile.value) return '70px'
+  return '90px'
+})
+
+// 监听窗口大小变化
+const handleResize = () => {
+  windowWidth.value = window.innerWidth
+  windowHeight.value = window.innerHeight
+}
+
+// 模板列表列数
+const templateColumns = computed(() => {
+  if (isMobile.value) return 2
+  if (isTablet.value) return 3
+  return 4
+})
 const txttotxt_selectedOption = ref('gemini-2.0-flash')
 const selectedOption = ref('dall-e-3')
 const num = ref(1)
@@ -2495,10 +2557,13 @@ defineExpose({
     font-weight: 600;
   }
   
-  /* 编辑弹窗三列布局:自适应,小屏横向滚动防跑偏 */
+  /* 编辑弹窗三列布局:始终保持横向,窄屏可横向滚动 */
   .image-edit-container {
     display: flex;
+    flex-direction: row;
+    flex-wrap: nowrap;
     height: 94vh;
+    min-height: 400px;
     padding-top: 20px;
     gap: 0;
     width: 100%;
@@ -2532,6 +2597,17 @@ defineExpose({
     background: #fff;
     overflow-y: auto;
     overflow-x: hidden;
+    display: flex;
+    flex-direction: column;
+  }
+  
+  .middle-column > div:first-child {
+    flex-shrink: 0;
+  }
+  
+  .middle-column > div:last-child {
+    flex: 1;
+    min-height: 0;
   }
 
   .right-column {
@@ -2544,7 +2620,82 @@ defineExpose({
     flex-direction: column;
     overflow: hidden;
   }
-  
+
+  /* 响应式布局 */
+  @media (max-width: 1200px) {
+    .middle-column {
+      width: 380px;
+      min-width: 320px;
+    }
+    
+    .template-item {
+      width: calc(50% - 5px);
+    }
+  }
+
+  @media (max-width: 992px) {
+    .image-edit-container {
+      flex-direction: column;
+      height: auto;
+      min-height: 800px;
+    }
+    
+    .left-column {
+      max-width: 100%;
+      border-right: none;
+      border-bottom: 1px solid #e4e7ed;
+    }
+    
+    .middle-column {
+      width: 100%;
+      min-width: 100%;
+      border-left: none;
+      border-right: none;
+      border-bottom: 1px solid #e4e7ed;
+    }
+    
+    .right-column {
+      max-width: 100%;
+      padding: 15px;
+    }
+    
+    .template-item {
+      width: calc(33.33% - 7px);
+    }
+  }
+
+  @media (max-width: 768px) {
+    .template-item {
+      width: calc(50% - 5px);
+    }
+    
+    .image-comparison-section {
+      flex-direction: column;
+      align-items: center;
+    }
+    
+    .case-links-column {
+      flex-direction: row;
+      height: auto;
+      gap: 20px;
+      margin-top: 10px;
+    }
+  }
+
+  @media (max-width: 480px) {
+    .template-item {
+      width: 100%;
+    }
+    
+    .action-buttons-row {
+      flex-direction: column;
+    }
+    
+    .action-buttons-row .el-button {
+      width: 100%;
+    }
+  }
+
   .image-comparison-section {
     flex: none;
     display: flex;
@@ -2648,9 +2799,26 @@ defineExpose({
   }
   
   .edit-section {
-    overflow: hidden; /* 防止溢出 */
+    min-height: 0;
+    overflow-y: auto; /* 内容过多时可滚动,确保操作按钮可到达 */
   }
-  
+
+  /* 底部操作区:不被压缩,始终可见 */
+  .edit-actions-area {
+    flex-shrink: 0;
+    margin-top: 15px;
+    padding-top: 15px;
+    border-top: 1px solid #e4e7ed;
+  }
+
+  /* 操作按钮行:小屏时换行,避免被截断 */
+  .action-buttons-row {
+    display: flex;
+    justify-content: flex-end;
+    gap: 12px;
+    flex-wrap: wrap;
+  }
+
   .edit-form {
     height: 100%;
   }