Browse Source

图生图新增批量裁图功能

lushixing 2 months ago
parent
commit
a997836774
2 changed files with 409 additions and 26 deletions
  1. 12 3
      src/views/design-agent/design-agent.scss
  2. 397 23
      src/views/design-agent/feature-mod-index.vue

+ 12 - 3
src/views/design-agent/design-agent.scss

@@ -699,7 +699,7 @@
           margin-bottom: 5px;
         }
       }
-      /deep/ .el-form-item + .el-form-item {
+      /deep/ .el-form-item + .el-form-item:not(.images-group-form) {
         margin-top: 30px;
       }
       /deep/ .el-form-item__label {
@@ -808,7 +808,7 @@
         }
         .upload-demo {
           width: 100%;
-          height: 240px;
+          height: 200px;
 
           &.upload-demo__data {
             width: 100px;
@@ -836,7 +836,7 @@
 
           .el-upload-dragger {
             width: 100%;
-            height: 240px;
+            height: 200px;
             border: 1px solid #ae8878;
 
             .el-upload__info {
@@ -849,6 +849,7 @@
               .el-upload__text,.el-upload__tip {
                 color: #ae8877;
                 font-size: 14px;
+                line-height: 20px;
               }
             }
           }
@@ -912,6 +913,14 @@
           display: flex;
         }
       }
+      .batch-form {
+        display: flex;
+        justify-content: flex-end;
+        /deep/ .el-form-item__label {
+          display: flex;
+          gap: 10px;
+        }
+      }
       .tool-direct-left__submit {
         position: relative;
         /deep/ .el-form-item__label {

+ 397 - 23
src/views/design-agent/feature-mod-index.vue

@@ -48,10 +48,113 @@
             <el-form :model="ruleForm" :rules="rules" ref="ruleForm" class="demo-ruleForm" label-position="top">
               <div class="ruleFormContent">
                 <el-scrollbar>
+                  <!-- 图生图 -->
+                  <el-form-item class="batch-form" v-if="pagesData.applicationId == 2">
+                    <template #label>
+                      <div class="label-title">
+                        <span>批量</span>
+                      </div>
+                      <el-switch v-model="ruleForm.isBatch"></el-switch>
+                    </template>
+                  </el-form-item>
+
+                  <el-form-item class="images-group-form"  prop="editableTabs" required v-if="ruleForm.isBatch">
+                    <template #label>
+                      <div class="label-title">
+                        <span><em class="no">1</em> 上衣图片</span>
+                      </div>
+                    </template>
+                    <div style="margin-bottom: 10px;display:flex;justify-content: flex-end;">
+                      <el-button
+                        size="small"
+                        @click="addTab(ruleForm.editableTabsValue)"
+                      >
+                        添加上衣图片项
+                      </el-button>
+                    </div>
+                    <el-tabs v-model="ruleForm.editableTabsValue" type="card" closable @tab-remove="removeTab" @tab-click="clickTabs">
+                      <el-tab-pane
+                        v-for="(item, parentIndex) in ruleForm.editableTabs"
+                        :key="item.name"
+                        :label="`图片组 ${parentIndex+1}`"
+                        :name="item.name"
+                      >
+                        <div class="tools-upload">
+                          <!-- 自定义显示每个图片的重新上传按钮 -->
+                          <div class="swiper-container-block" v-if="item.content.length">
+                            <div class="swiper-container my-swiper" :ref="`swiperContainer-${parentIndex}`">
+                              <div class="swiper-wrapper">
+                                <div class="swiper-slide" v-for="(group, index) in item.content" :key="index">
+                                  <div class="img-group">
+                                    <div class="custom-file-list" >
+                                      <div class="picture-card-wrapper">
+                                        <img :src="group" class="picture-card-image" />
+                                        <div class="custom-file__btns">
+                                          <el-button 
+                                            class="replace-btn" 
+                                            @click="replaceFileGroup(index, parentIndex)">
+                                            <svg t="1760412251487" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6466" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M902.314667 206.741333a21.333333 21.333333 0 0 1 0 30.122667l-46.933334 47.146667-122.581333 123.178666a21.333333 21.333333 0 0 1-30.229333 0l-31.829334-31.957333a21.333333 21.333333 0 0 1 0-30.122667l42.666667-42.816c13.376-13.44 3.84-36.394667-15.146667-36.394666H375.189333c-46.933333 0-91.157333 18.389333-124.373333 51.733333a175.914667 175.914667 0 0 0-51.52 124.885333v91.712a21.333333 21.333333 0 0 1-21.333333 21.333334H132.970667a21.333333 21.333333 0 0 1-21.333334-21.333334v-91.818666c0-146.090667 118.037333-264.618667 263.530667-264.618667h323.008c18.986667 0 28.522667-22.933333 15.125333-36.373333l-42.666666-42.837334a21.333333 21.333333 0 0 1 0-30.101333l31.829333-31.978667a21.333333 21.333333 0 0 1 30.250667 0l122.581333 123.093334 47.018667 47.146666z m-97.941334 353.706667c0 47.146667-18.325333 91.52-51.541333 124.885333a174.464 174.464 0 0 1-124.373333 51.733334H325.653333c-18.986667 0-28.522667-22.954667-15.125333-36.394667l42.752-42.922667a21.333333 21.333333 0 0 0 0-30.122666l-31.829333-31.957334a21.333333 21.333333 0 0 0-30.229334 0l-122.602666 123.008-46.933334 47.146667a21.333333 21.333333 0 0 0 0 30.08l46.933334 47.146667 122.602666 123.093333a21.333333 21.333333 0 0 0 30.229334 0l31.829333-31.957333a21.333333 21.333333 0 0 0 0-30.101334l-42.453333-42.624c-13.397333-13.44-3.861333-36.394667 15.104-36.394666H628.48c145.493333 0 263.530667-118.528 263.530667-264.618667v-90.538667a21.333333 21.333333 0 0 0-21.333334-21.333333h-44.970666a21.333333 21.333333 0 0 0-21.333334 21.333333v90.538667z" p-id="6467" fill="#ffffff"></path></svg>
+                                            重新上传
+                                          </el-button>
+                                          <el-button 
+                                            class="replace-btn" 
+                                            @click="imgUploadDeGroup(index, parentIndex)">
+                                            <svg t="1760412304801" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7509" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M853.333333 256H170.666667V853.333333a85.333333 85.333333 0 0 0 85.333333 85.333334h512A85.333333 85.333333 0 0 0 853.333333 853.333333V256zM85.333333 170.666667h170.666667V85.333333A85.333333 85.333333 0 0 1 341.333333 0h341.333334a85.333333 85.333333 0 0 1 85.333333 85.333333V170.666667h213.333333a42.666667 42.666667 0 0 1 0 85.333333H938.666667V853.333333a170.666667 170.666667 0 0 1-170.666667 170.666667h-512a170.666667 170.666667 0 0 1-170.666667-170.666667V256H42.666667a42.666667 42.666667 0 1 1 0-85.333333H85.333333zM341.333333 170.666667h341.333334V85.333333H341.333333V170.666667z m42.666667 256c23.552 0 42.666667 19.114667 42.666667 42.666666v256a42.666667 42.666667 0 0 1-85.333334 0v-256c0-23.552 19.114667-42.666667 42.666667-42.666666z m256 0c23.552 0 42.666667 19.114667 42.666667 42.666666v256a42.666667 42.666667 0 0 1-85.333334 0v-256c0-23.552 19.114667-42.666667 42.666667-42.666666z" fill="#ffffff" p-id="7510"></path></svg>
+                                            删除图片
+                                          </el-button>
+                                        </div>
+                                      </div>
+                                    </div>
+                                  </div>
+                                </div>
+                              </div>
+                              <!-- 左右箭头 -->
+                              <div class="swiper-button-prev swiper-button">
+                                <svg t="1760505375902" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10647" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M307.6 104.6c-14.2 14.2-14.2 37.2 0 51.4L655 503.4c2.8 2.9 2.8 7.5 0 10.3L307.6 861.2c-14.2 14.2-14.2 37.2 0 51.4 14.2 14.2 37.2 14.2 51.4 0l347.4-347.4c15.6-15.6 23.4-36 23.4-56.5s-7.8-41-23.4-56.5L359 104.6c-14.2-14.2-37.2-14.2-51.4 0z" p-id="10648"></path></svg>
+                              </div>
+                              <div class="swiper-button-next swiper-button">
+                                <svg t="1760505375902" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10647" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M307.6 104.6c-14.2 14.2-14.2 37.2 0 51.4L655 503.4c2.8 2.9 2.8 7.5 0 10.3L307.6 861.2c-14.2 14.2-14.2 37.2 0 51.4 14.2 14.2 37.2 14.2 51.4 0l347.4-347.4c15.6-15.6 23.4-36 23.4-56.5s-7.8-41-23.4-56.5L359 104.6c-14.2-14.2-37.2-14.2-51.4 0z" p-id="10648"></path></svg>
+                              </div>
+                            </div>
+                          </div>
+                          <el-upload
+                            :ref="`uploadRef-${parentIndex}`"
+                            class="upload-demo"
+                            :class="{'upload-demo__data': item.content.length}"
+                            drag
+                            :action="action"
+                            :show-file-list="false" 
+                            :headers="{
+                              'Authorization': 'Bearer ' + token
+                            }"
+                            multiple
+                            :on-success="handleVideoSuccessGroup" 
+                            :before-upload="beforeUploadVideo"
+                          >
+                            <div v-loading="uploading">
+                              <template v-if="item.content.length">
+                                <i class="el-icon-plus"></i>
+                                <el-button type="primary" size="mini" @click.stop="handleProductRepo('tabs')">产品库</el-button>
+                              </template>
+                              <div class="el-upload__info" v-else>
+                                <i class="el-icon-upload" style="margin: 0;"></i>
+                                <div class="el-upload__text">点击或将图片拖拽到这里上传,最多上传 5 张图片</div>
+                                <div class="el-upload__tip" slot="tip">图片要求:支持JPG、PNG和WEBP,最大为20M</div>
+                                <div class="el-upload__btn">
+                                  <el-button type="primary" size="small" @click.stop="handleProductRepo('tabs')">从产品库选择</el-button>
+                                </div>
+                              </div>
+                            </div>
+                          </el-upload>
+                        </div>
+                      </el-tab-pane>
+                    </el-tabs>
+                  </el-form-item>
+                  
                   <el-form-item prop="imageList" required v-if="pagesData.applicationId == 2">
                     <template #label>
                       <div class="label-title">
-                        <span><em class="no">1</em> 上传图片</span>
+                        <span><em class="no">{{ ruleForm.isBatch ? 2 : 1 }}</em> {{ ruleForm.isBatch ? '上传姿势图' : '上传图片' }}</span>
                       </div>
                     </template>
                     <div class="tools-upload">
@@ -123,6 +226,16 @@
                       </el-upload>
                     </div>
                   </el-form-item>
+                  <el-form-item v-if="pagesData.applicationId == 2 && ruleForm.isBatch">
+                    <template #label>
+                      <div class="label-title">
+                        <span><em class="no">3</em> 批量生产</span>
+                      </div>
+                    </template>
+                    <div>
+                      <el-input-number v-model="ruleForm.batchCount" :controls="false"></el-input-number> 张
+                    </div>
+                  </el-form-item>
                   <el-form-item prop="imageUrl" required v-if="pagesData.applicationId == 3">
                     <template #label>
                       <div class="label-title">
@@ -168,7 +281,7 @@
                       </el-upload>
                     </div>
                   </el-form-item>
-                  <el-form-item prop="screenDescription" class="tool-direct-left__submit">
+                  <el-form-item prop="screenDescription" class="tool-direct-left__submit" v-if="!ruleForm.isBatch">
                     <template #label>
                       <div class="label-title">
                         <span>
@@ -194,7 +307,7 @@
                     </el-input>
                     <span class="clear-block" v-if="ruleForm.screenDescription"  @click.stop="clearDescription()">清除</span>
                   </el-form-item>
-                  <template v-if="pagesData.applicationId == 2">
+                  <template v-if="pagesData.applicationId == 2 && !ruleForm.isBatch">
                     <el-form-item>
                       <template #label>
                         <div class="label-title">
@@ -394,16 +507,29 @@ export default {
       pagesData: {},
       pagesId: null,
       replaceIndex: null,
+      tabParentIndex: 0,
+      replaceTabIndex: null,
       isSave: false,
       workId: null,
+      hasTabs: null,
       ruleForm: {
         imageUrl: '',
         imageList: [],
         screenDescription: '',
         imageRatioList: '',
         imageSizeSelect: '',
-        imageSize: {}
+        imageSize: {},
+        isBatch: true,
+        editableTabsValue: '1',
+        editableTabs: [{
+          title: '图片组 1',
+          name: '1',
+          content: []
+        }],
+        tabIndex: 1,
+        batchCount: null
       },
+      swiperList: {},
       ratioList: ['3:2', '16:9', '1:1', '2:3', '3:4', '9:16'],
       rules: {
         imageUrl: [
@@ -412,6 +538,9 @@ export default {
         imageList: [
           { validator: (rule, value, callback) => this.checkImageList(rule, value, callback), trigger: "change" }
         ],
+        editableTabs: [
+          { validator: (rule, value, callback) => this.checkImageGroup(rule, value, callback), trigger: "change" }
+        ],
         screenDescription: [
           { 
             validator: (rule, value, callback) => {
@@ -501,9 +630,122 @@ export default {
           }
         })
       }
+    },
+    'ruleForm.editableTabs': {
+      deep: true,
+      immediate: true, // 页面初次挂载时也会执行一次
+      handler(newVal) {
+        // 没有图片就不初始化
+        if (!newVal[this.tabParentIndex] || !newVal[this.tabParentIndex].content || !newVal[this.tabParentIndex].content.length) return
+        // 确保 DOM 已经渲染完毕
+        this.$nextTick(() => {
+          if (!this.swiperList[this.tabParentIndex]) {
+            this.initSwiperGroup(this.tabParentIndex) // 首次初始化
+          }
+        })
+      }
+    },
+    'ruleForm.isBatch'(val) {
+      this.$nextTick(() => {
+        setTimeout(() => {
+          const index = this.tabParentIndex;
+          const swiper = this.swiperList[index];
+
+          if (swiper) {
+            swiper.destroy(true, true);
+            this.$set(this.swiperList, index, null);
+          }
+          if (this.swiper) {
+            this.swiper.destroy(true, true);
+            this.swiper = null;
+          }
+          this.initSwiper();
+
+          if (val) {
+            this.initSwiperGroup(index);
+          }
+        }, 0) // 🔥 给 el-switch / el-tabs 动画时间
+      })
     }
   },
   methods: {
+    addTab(targetName) {
+      this.tabParentIndex = targetName
+      let newTabName = ++this.ruleForm.tabIndex + '';
+      this.ruleForm.editableTabs.push({
+        title: `图片组 ${targetName}`,
+        name: newTabName,
+        content: []
+      });
+      this.ruleForm.editableTabsValue = newTabName;
+    },
+    removeTab(targetName) {
+      const tabs = this.ruleForm.editableTabs
+
+      if (tabs.length <= 1) {
+        this.$message.error('至少存在一个图片组!')
+        return
+      }
+
+      const tabIndex = tabs.findIndex(tab => tab.name === targetName)
+      if (tabIndex === -1) return
+
+      const hasContent = tabs[tabIndex].content.length > 0
+
+      const doRemove = () => {
+        this.handleActiveTabChange(targetName)
+        this.destroySwiperByIndex(tabIndex)
+
+        this.ruleForm.editableTabs = tabs.filter(tab => tab.name !== targetName)
+
+        this.$notify({
+          title: '成功',
+          message: '删除成功',
+          type: 'success',
+          duration: 3000
+        })
+      }
+
+      if (hasContent) {
+        this.$confirm('该组有图片数据,确定要删除吗?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(doRemove)
+      } else {
+        doRemove()
+      }
+    },
+    handleActiveTabChange(removedName) {
+      let activeName = this.ruleForm.editableTabsValue
+
+      if (activeName !== removedName) return
+
+      const tabs = this.ruleForm.editableTabs
+      const index = tabs.findIndex(tab => tab.name === removedName)
+
+      const nextTab = tabs[index + 1] || tabs[index - 1]
+      this.ruleForm.editableTabsValue = nextTab ? nextTab.name : ''
+    },
+    destroySwiperByIndex(index) {
+      const swiper = this.swiperList[index]
+      if (!swiper) return
+
+      swiper.destroy(true, true)
+
+      // Vue 2 响应式安全删除
+      this.$delete(this.swiperList, index)
+    },
+
+    clickTabs(tab) {
+      const parentIndex = this.ruleForm.editableTabs.findIndex(
+        item => item.name === tab.name
+      )
+      if (parentIndex !== -1) {
+        this.tabParentIndex = parentIndex
+        this.initSwiperGroup(parentIndex)
+      }
+    },
     toggle(item) {
       // 点击已选中 → 取消
       if (this.ruleForm.imageRatioList === item) {
@@ -529,6 +771,36 @@ export default {
         }
       })
     },
+    initSwiperGroup(parentIndex) {
+      this.$nextTick(() => {
+        const refName = `swiperContainer-${parentIndex}`
+        const container = this.$refs[refName]
+
+        if (!container) return
+
+        // 防止重复初始化
+        if (this.swiperList && this.swiperList[parentIndex]) {
+          this.swiperList[parentIndex].destroy(true, true)
+        }
+
+        const swiper = new Swiper(container, {
+          slidesPerView: 2.5,
+          slidesPerGroup: 2,
+          spaceBetween: 10,
+          loop: false,
+          navigation: {
+            nextEl: '.swiper-button-next',
+            prevEl: '.swiper-button-prev'
+          }
+        })
+
+        // 统一管理 swiper 实例
+        if (!this.swiperList) {
+          this.swiperList = {}
+        }
+        this.swiperList[parentIndex] = swiper
+      })
+    },
     handleCustom() {
       this.hasCustom = !this.hasCustom
       if (!this.hasCustom) {
@@ -549,9 +821,22 @@ export default {
         callback();
       }
     },
+    checkImageGroup(rule, value, callback) {
+      const imgs = this.ruleForm.editableTabs.filter(acc => acc.content.length)
+      console.log(imgs, 1111)
+      if (!imgs.length) {
+        callback(new Error('请至少上传一组上衣图片!'));
+      } else {
+        callback();
+      }
+    },
     submitForm() {
       this.$refs['ruleForm'].validate(async (valid) => {
         if (valid) {
+          console.log(this.ruleForm.editableTabs, 232323232323)
+          if (this.ruleForm.isBatch) {
+            return;
+          }
           this.comfirmLoading = true;
           this.workId = null;
           this.isSave = false;
@@ -769,6 +1054,76 @@ export default {
         });
       });
     },
+    replaceFileGroup(itemIndex) {
+      this.replaceTabIndex = itemIndex
+
+      if (this.$refs[`uploadRef-${this.tabParentIndex}`]) {
+        // 清空上传队列
+        this.$refs[`uploadRef-${this.tabParentIndex}`].uploadFiles = []
+
+        // 手动触发上传框选择文件
+        const input = this.$refs[`uploadRef-${this.tabParentIndex}`].$el.querySelector('input[type=file]')
+        input.click()
+      }
+      // 在 handleSuccess 中会替换对应文件
+    },
+    handleVideoSuccessGroup(res, file) {
+      this.uploading = false
+      this.$refs.ruleForm.clearValidate(['editableTabs']);
+
+      if (this.ruleForm.editableTabs[this.tabParentIndex].content.length >= 5) {
+        this.$message.error('最多只能上传5张图片!');
+        return;
+      }
+      if (this.replaceTabIndex !== null) {
+        // 用新图片替换原有 URL
+        if (res.code == 200) {
+          this.$set(this.ruleForm.editableTabs[this.tabParentIndex].content, this.replaceTabIndex, res.data.url);
+        }
+        this.tabParentIndex = null
+      } else {
+        // 新增上传
+        if (res.code == 200) {
+          this.ruleForm.editableTabs[this.tabParentIndex].content.push(res.data.url)
+        } else {
+          this.$message.error('上传失败,请重新上传!');
+        }
+        this.$nextTick(() => {
+          if (this.swiperList[this.tabParentIndex]) {
+            this.swiperList[this.tabParentIndex].update()
+            const lastIndex = this.ruleForm.editableTabs[this.tabParentIndex].content.length - 1
+            this.swiperList[this.tabParentIndex].slideTo(lastIndex, 300) // 平滑滚动到最后
+          } else {
+            this.initSwiper()
+          }
+        })
+      }
+      
+    },
+    imgUploadDelGroup(itemIndex) {
+      //删除
+      this.$confirm("确定要删除吗?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.ruleForm.editableTabs[this.tabParentIndex].contentsplice(itemIndex, 1);
+        this.swiperList[this.tabParentIndex].update()
+        if (this.ruleForm.editableTabs[this.tabParentIndex].content.length == 0) {
+          if (this.swiperList[this.tabParentIndex]) {
+            this.swiperList[this.tabParentIndex].destroy(true, true)
+            delete this.swiperList[this.tabParentIndex]
+          }
+        }
+        
+        this.$notify({
+          title: "成功",
+          message: "删除成功",
+          type: "success",
+          duration: 3000
+        });
+      });
+    },
     clearDescription() {
       this.$refs.ruleForm.clearValidate(['screenDescription']);
       this.ruleForm.screenDescription = ''
@@ -835,34 +1190,53 @@ export default {
         this.viewLoading = false
       })
     },
-    setImageList(row, dels) {
-      row.forEach(acc => {
-        if (!this.ruleForm.imageList.includes(acc)) {
-          this.ruleForm.imageList.push(acc);
+    setImageList(row = [], dels = []) {
+      const isTabs = this.hasTabs === 'tabs';
+      const parentIndex = this.tabParentIndex;
+
+      // 1️⃣ 取目标图片数组
+      const targetList = isTabs
+        ? this.ruleForm.editableTabs[parentIndex].content
+        : this.ruleForm.imageList;
+
+      // 2️⃣ 新增图片(去重)
+      row.forEach(item => {
+        if (!targetList.includes(item)) {
+          targetList.push(item);
         }
       })
-      if (dels && dels.length) {
-        var diff = dels.filter(function(item){
-          return row.indexOf(item) === -1;
-        });
-        if (diff.length) {
-          this.ruleForm.imageList.forEach((acc, index) => {
-            if (diff.includes(acc)) {
-              this.ruleForm.imageList.splice(index, 1);
-            }
-          })
+
+      // 3️⃣ 删除图片(安全方式)
+      if (Array.isArray(dels) && dels.length) {
+        const keepSet = new Set(row);
+        for (let i = targetList.length - 1; i >= 0; i--) {
+          if (!keepSet.has(targetList[i])) {
+            targetList.splice(i, 1);
+          }
         }
       }
+
+      // 4️⃣ Swiper 更新
       this.$nextTick(() => {
-        if (this.swiper) {
-          this.swiper.update()
+        if (isTabs) {
+          const swiper = this.swiperList[parentIndex];
+          swiper ? swiper.update() : this.initSwiperGroup(parentIndex);
         } else {
-          this.initSwiper()
+          this.swiper ? this.swiper.update() : this.initSwiper();
         }
       })
     },
-    handleProductRepo() {
-      this.$refs.productRepoModal && this.$refs.productRepoModal.show(this.ruleForm.imageList);
+
+    handleProductRepo(type) {
+      if (this.$refs.productRepoModal) {
+        if (type === 'tabs') {
+          this.hasTabs = type;
+          this.$refs.productRepoModal.show(this.ruleForm.editableTabs[this.tabParentIndex].content);
+        } else {
+          this.hasTabs = null;
+          this.$refs.productRepoModal.show(this.ruleForm.imageList);
+        }
+      }
     }
   }
 };