6 Commits f5136c4f31 ... 7c172b36db

Autor SHA1 Mensagem Data
  lushixing 7c172b36db update há 1 mês atrás
  lushixing a3b232f55a update há 1 mês atrás
  lushixing a16f522b3b 图生图批量操作&裁图功能 há 1 mês atrás
  lushixing 74f225886a 图生图批量操作&裁图功能 há 1 mês atrás
  lushixing ed47999dda 图生图批量操作&裁图功能 há 2 meses atrás
  lushixing a997836774 图生图新增批量裁图功能 há 2 meses atrás
74 ficheiros alterados com 2621 adições e 50 exclusões
  1. 3 0
      package.json
  2. 0 0
      src/icons/svg/shopping.svg
  3. 0 0
      src/icons/svg/sidebar-icon1.svg
  4. 0 0
      src/icons/svg/sidebar-icon10.svg
  5. 1 0
      src/icons/svg/sidebar-icon11.svg
  6. 0 0
      src/icons/svg/sidebar-icon12.svg
  7. 1 0
      src/icons/svg/sidebar-icon13.svg
  8. 1 0
      src/icons/svg/sidebar-icon14.svg
  9. 0 0
      src/icons/svg/sidebar-icon15.svg
  10. 0 0
      src/icons/svg/sidebar-icon16.svg
  11. 0 0
      src/icons/svg/sidebar-icon17.svg
  12. 0 0
      src/icons/svg/sidebar-icon18.svg
  13. 0 0
      src/icons/svg/sidebar-icon19.svg
  14. 0 0
      src/icons/svg/sidebar-icon2.svg
  15. 0 0
      src/icons/svg/sidebar-icon20.svg
  16. 0 0
      src/icons/svg/sidebar-icon21.svg
  17. 0 0
      src/icons/svg/sidebar-icon22.svg
  18. 0 0
      src/icons/svg/sidebar-icon23.svg
  19. 0 0
      src/icons/svg/sidebar-icon24.svg
  20. 0 0
      src/icons/svg/sidebar-icon25.svg
  21. 0 0
      src/icons/svg/sidebar-icon26.svg
  22. 0 0
      src/icons/svg/sidebar-icon27.svg
  23. 0 0
      src/icons/svg/sidebar-icon28.svg
  24. 0 0
      src/icons/svg/sidebar-icon29.svg
  25. 1 0
      src/icons/svg/sidebar-icon3.svg
  26. 0 0
      src/icons/svg/sidebar-icon30.svg
  27. 0 0
      src/icons/svg/sidebar-icon31.svg
  28. 0 0
      src/icons/svg/sidebar-icon32.svg
  29. 0 0
      src/icons/svg/sidebar-icon33.svg
  30. 0 0
      src/icons/svg/sidebar-icon34.svg
  31. 0 0
      src/icons/svg/sidebar-icon35.svg
  32. 0 0
      src/icons/svg/sidebar-icon36.svg
  33. 0 0
      src/icons/svg/sidebar-icon37.svg
  34. 0 0
      src/icons/svg/sidebar-icon38.svg
  35. 0 0
      src/icons/svg/sidebar-icon39.svg
  36. 0 0
      src/icons/svg/sidebar-icon4.svg
  37. 0 0
      src/icons/svg/sidebar-icon40.svg
  38. 0 0
      src/icons/svg/sidebar-icon41.svg
  39. 0 0
      src/icons/svg/sidebar-icon42.svg
  40. 0 0
      src/icons/svg/sidebar-icon43.svg
  41. 0 0
      src/icons/svg/sidebar-icon44.svg
  42. 0 0
      src/icons/svg/sidebar-icon45.svg
  43. 0 0
      src/icons/svg/sidebar-icon46.svg
  44. 0 0
      src/icons/svg/sidebar-icon5.svg
  45. 0 0
      src/icons/svg/sidebar-icon6.svg
  46. 0 0
      src/icons/svg/sidebar-icon7.svg
  47. 1 0
      src/icons/svg/sidebar-icon8.svg
  48. 0 0
      src/icons/svg/sidebar-icon9.svg
  49. 2 2
      src/layout/components/AppMain.vue
  50. 3 1
      src/permission.js
  51. 2 0
      src/router/index.js
  52. 1 1
      src/router/modules/AIAssetSearch.js
  53. 1 1
      src/router/modules/catwalkVideo.js
  54. 19 0
      src/router/modules/cropList.js
  55. 1 1
      src/router/modules/designAgentManager.js
  56. 1 1
      src/router/modules/faceSwapVideo.js
  57. 1 1
      src/router/modules/musicLibraryManager.js
  58. 1 1
      src/router/modules/oralVideo.js
  59. 1 1
      src/router/modules/productRepo.js
  60. 1 1
      src/router/modules/trendingTermList.js
  61. 1 1
      src/router/modules/userAuth.js
  62. 1 1
      src/router/modules/videoCoverManager.js
  63. 4 0
      src/styles/layout.scss
  64. 10 0
      src/styles/sidebar.scss
  65. 6 2
      src/utils/request.js
  66. 2 0
      src/views/common/swPage.vue
  67. 190 0
      src/views/crop-list/components/CropSizeModal.vue
  68. 1090 0
      src/views/crop-list/components/ImagesGroupModal.vue
  69. 171 0
      src/views/crop-list/components/ImagesItem.vue
  70. 572 0
      src/views/crop-list/index.vue
  71. 5 3
      src/views/design-agent/components/productRepoModal.vue
  72. 30 3
      src/views/design-agent/design-agent.scss
  73. 496 28
      src/views/design-agent/feature-mod-index.vue
  74. 1 1
      src/views/product-repo/index.vue

+ 3 - 0
package.json

@@ -33,10 +33,12 @@
     "babel-polyfill": "^6.26.0",
     "clipboard": "2.0.4",
     "element-ui": "^2.15.0",
+    "file-saver": "^2.0.5",
     "font-awesome": "^4.7.0",
     "highlight.js": "^11.3.1",
     "jquery": "^3.4.1",
     "js-cookie": "2.2.0",
+    "jszip": "^3.10.1",
     "normalize.css": "7.0.0",
     "nprogress": "0.2.0",
     "sm-crypto": "^0.3.13",
@@ -45,6 +47,7 @@
     "swiper": "^4.5.1",
     "vue": "2.6.10",
     "vue-router": "3.0.2",
+    "vuedraggable": "^2.24.3",
     "vuex": "3.1.0",
     "xlsx": "0.14.1"
   },

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/shopping.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon1.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon10.svg


+ 1 - 0
src/icons/svg/sidebar-icon11.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><defs><clipPath id="master_svg0_26_07095"><rect x="0" y="0" width="16" height="16" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_26_07095)"><g><path d="M5.5,2C5.5,2,5.5,14,5.5,14C5.5,14,4.5,14,4.5,14C4.5,14,4.5,2,4.5,2C4.5,2,3,2,3,2C3,2,3,14,3,14C3,14,13,14,13,14C13,14,13,2,13,2C13,2,5.5,2,5.5,2C5.5,2,5.5,2,5.5,2ZM2.5,1C2.5,1,13.5,1,13.5,1C13.646,1,13.7658,1.046833299,13.8595,1.14049999C13.9532,1.23416699,14,1.354,14,1.5C14,1.5,14,14.5,14,14.5C14,14.646,13.9532,14.7658,13.8595,14.8595C13.7658,14.9532,13.646,15,13.5,15C13.5,15,2.5,15,2.5,15C2.354,15,2.23416699,14.9532,2.14049999,14.8595C2.0468334,14.7658,1.9999999999999998,14.646,2,14.5C2,14.5,2,1.5,2,1.5C2,1.354,2.0468334,1.23416699,2.14049999,1.14049999C2.23416699,1.046833299,2.354,1.0000000000000002,2.5,1C2.5,1,2.5,1,2.5,1ZM7.5,4C7.5,4,10.5,4,10.5,4C10.8333302,4,11,4.1666701,11,4.5C11,4.8333299,10.8333302,5,10.5,5C10.5,5,7.5,5,7.5,5C7.1666698,5,7,4.8333299,7,4.5C7,4.1666701,7.1666698,4,7.5,4C7.5,4,7.5,4,7.5,4ZM7.5,7C7.5,7,10.5,7,10.5,7C10.8333302,7,11,7.1666698,11,7.5C11,7.8333302,10.8333302,8,10.5,8C10.5,8,7.5,8,7.5,8C7.1666698,8,7,7.8333302,7,7.5C7,7.1666698,7.1666698,7,7.5,7C7.5,7,7.5,7,7.5,7ZM7.5,10C7.5,10,10.5,10,10.5,10C10.8333302,10,11,10.1666698,11,10.5C11,10.8333302,10.8333302,11,10.5,11C10.5,11,7.5,11,7.5,11C7.1666698,11,7,10.8333302,7,10.5C7,10.1666698,7.1666698,10,7.5,10C7.5,10,7.5,10,7.5,10Z" fill="#333333" fill-opacity="1"/></g></g></svg>

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon12.svg


+ 1 - 0
src/icons/svg/sidebar-icon13.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><defs><clipPath id="master_svg0_26_07059"><rect x="0" y="0" width="16" height="16" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_26_07059)"><g><path d="M4.5,6C4.5,6,11.5,6,11.5,6C11.5,6,11.5,7,11.5,7C11.5,7,4.5,7,4.5,7C4.5,7,4.5,6,4.5,6C4.5,6,4.5,6,4.5,6ZM6,4C6,4,10,4,10,4C10,4,10,5,10,5C10,5,6,5,6,5C6,5,6,4,6,4C6,4,6,4,6,4ZM2.0470001,8C2.0470001,8,6,8,6,8C6,8,6,10,6,10C6,10,10,10,10,10C10,10,10,8,10,8C10,8,13.953,8,13.953,8C13.953,8,11.281,3,11.281,3C11.281,3,4.718499899999999,3,4.718499899999999,3C4.718499899999999,3,2.0470001,8,2.0470001,8C2.0470001,8,2.0470001,8,2.0470001,8ZM14,9C14,9,11,9,11,9C11,9,11,11,11,11C11,11,5,11,5,11C5,11,5,9,5,9C5,9,2,9,2,9C2,9,2,13,2,13C2,13,14,13,14,13C14,13,14,9,14,9C14,9,14,9,14,9ZM4.3125,2C4.3125,2,11.6875,2,11.6875,2C11.8958,2.0103333,12.0468,2.104167,12.1405,2.2815000100000002C12.1405,2.2815000100000002,14.953,7.8909998,14.953,7.8909998C14.9843,7.9640002,15,8.042169999999999,15,8.125500200000001C15,8.125500200000001,15,13.5005,15,13.5005C15,13.6465,14.9532,13.7663,14.8595,13.86C14.7658,13.9537,14.646,14.0005,14.5,14.0005C14.5,14.0005,1.5,14.0005,1.5,14.0005C1.354,14.0005,1.23416699,13.9537,1.14049999,13.86C1.046833299,13.7663,1.0000000000000002,13.6465,1,13.5005C1,13.5005,1,8.125500200000001,1,8.125500200000001C1,8.042169999999999,1.015666701,7.9640002,1.047000099,7.8909998C1.047000099,7.8909998,3.8594999,2.2815000100000002,3.8594999,2.2815000100000002C3.9531701,2.1045,4.1041701,2.0106667,4.3125,2C4.3125,2,4.3125,2,4.3125,2C4.3125,2,4.3125,2,4.3125,2Z" fill="#333333" fill-opacity="1"/></g></g></svg>

+ 1 - 0
src/icons/svg/sidebar-icon14.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><defs><clipPath id="master_svg0_26_07067"><rect x="0" y="0" width="16" height="16" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_26_07067)"><g><path d="M8,2C8,2,8,3,8,3C8,3,4,3,4,3C3.71867,3.01033,3.4843301,3.1093299,3.2970001,3.2969999C3.10967,3.4846699,3.0106699,3.7190001,3,4C3,4,3,12,3,12C3.0103299999999997,12.2813,3.1093301,12.5157,3.2970001,12.703C3.48467,12.8903,3.7190000000000003,12.9893,4,13C4,13,12,13,12,13C12.2813,12.9897,12.5157,12.8907,12.703,12.703C12.8903,12.5153,12.9893,12.281,13,12C13,12,13,8,13,8C13,8,14,8,14,8C14,8,14,12,14,12C13.9897,12.5627,13.7943,13.034,13.414,13.414C13.0337,13.794,12.5623,13.9893,12,14C12,14,4,14,4,14C3.43733,13.9897,2.96600002,13.7943,2.58600003,13.414C2.206,13.0337,2.0106667,12.5623,2,12C2,12,2,4,2,4C2.0103333,3.43733,2.205667,2.966,2.58600003,2.5860000000000003C2.96633297,2.206,3.43767,2.0106699,4,2C4,2,8,2,8,2C8,2,8,2,8,2C8,2,8,2,8,2ZM12,6C12.5627,5.9896698,13.034,5.7943301,13.414,5.414C13.794,5.0336699,13.9893,4.56233,14,4C13.9897,3.43733,13.7943,2.966,13.414,2.5860000000000003C13.0337,2.206,12.5623,2.0106699,12,2C11.4373302,2.0103299999999997,10.9659996,2.20567,10.5860004,2.5860000000000003C10.2060003,2.9663301,10.0106697,3.43767,10,4C10.0103302,4.56267,10.2056704,5.0339999,10.5860004,5.414C10.9663296,5.7940001,11.4376698,5.9893298,12,6C12,6,12,6,12,6ZM12,7C11.1456699,6.9790001,10.4373302,6.6873298,9.875,6.125C9.3126702,5.5626702,9.0209999,4.8543301,9,4C9.0209999,3.1456699,9.3126702,2.43733,9.875,1.875C10.4373302,1.31266701,11.1456699,1.021,12,1C12.8543,1.021,13.5627,1.31266701,14.125,1.875C14.6873,2.43733,14.979,3.1456699,15,4C14.979,4.8543301,14.6873,5.5626702,14.125,6.125C13.5627,6.6873298,12.8543,6.9790001,12,7C12,7,12,7,12,7Z" fill="#333333" fill-opacity="1"/></g></g></svg>

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon15.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon16.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon17.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon18.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon19.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon2.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon20.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon21.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon22.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon23.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon24.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon25.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon26.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon27.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon28.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon29.svg


+ 1 - 0
src/icons/svg/sidebar-icon3.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><defs><clipPath id="master_svg0_26_07081"><rect x="0" y="0" width="16" height="16" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_26_07081)"><g><path d="M4,2C4,2,4,12.922,4,12.922C4,12.922,7.0625,10.4689999,7.0625,10.4689999C7.3438301,10.25033,7.6563301,10.1409998,8,10.1409998C8.3436699,10.1409998,8.6561699,10.25033,8.9375,10.4689999C8.9375,10.4689999,12,12.922,12,12.922C12,12.922,12,2,12,2C12,2,4,2,4,2C4,2,4,2,4,2ZM3.5,1C3.5,1,12.5,1,12.5,1C12.6459999,1,12.76583,1.046833299,12.8594999,1.14049999C12.9531698,1.23416699,13,1.354,13,1.5C13,1.5,13,13.953,13,13.953C12.9896698,14.1613,12.8933296,14.3098,12.7110004,14.3985C12.5286703,14.4872,12.3541698,14.469,12.1875,14.344C12.1875,14.344,8.3125,11.25,8.3125,11.25C8.2188301,11.177,8.1146698,11.1405,8,11.1405C7.8853302,11.1405,7.7811699,11.177,7.6875,11.25C7.6875,11.25,3.8125,14.344,3.8125,14.344C3.64583302,14.469,3.471333,14.4872,3.289,14.3985C3.106667,14.3098,3.0103333,14.1613,3,13.953C3,13.953,3,1.5,3,1.5C3,1.354,3.0468334,1.23416699,3.14049999,1.14049999C3.23416699,1.046833299,3.354,1.0000000000000002,3.5,1C3.5,1,3.5,1,3.5,1Z" fill="#333333" fill-opacity="1"/></g></g></svg>

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon30.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon31.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon32.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon33.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon34.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon35.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon36.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon37.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon38.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon39.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon4.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon40.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon41.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon42.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon43.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon44.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon45.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon46.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon5.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon6.svg


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon7.svg


+ 1 - 0
src/icons/svg/sidebar-icon8.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="16" height="16" viewBox="0 0 16 16"><defs><clipPath id="master_svg0_26_07073"><rect x="0" y="0" width="16" height="16" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_26_07073)"><g><path d="M2,6C2,6,2,13,2,13C2,13,14,13,14,13C14,13,14,6,14,6C14,6,2,6,2,6C2,6,2,6,2,6ZM1.5,5C1.5,5,14.5,5,14.5,5C14.646,5,14.7658,5.0468302,14.8595,5.1405001C14.9532,5.23417,15,5.3540001,15,5.5C15,5.5,15,13.5,15,13.5C15,13.646,14.9532,13.7658,14.8595,13.8595C14.7658,13.9532,14.646,14,14.5,14C14.5,14,1.5,14,1.5,14C1.354,14,1.23416699,13.9532,1.14049999,13.8595C1.046833299,13.7658,1.0000000000000002,13.646,1,13.5C1,13.5,1,5.5,1,5.5C1,5.3540001,1.046833299,5.23417,1.14049999,5.1405001C1.23416699,5.0468302,1.354,5,1.5,5C1.5,5,1.5,5,1.5,5ZM2.5,3C2.5,3,13.5,3,13.5,3C13.5,3,13.5,4,13.5,4C13.5,4,2.5,4,2.5,4C2.5,4,2.5,3,2.5,3C2.5,3,2.5,3,2.5,3ZM4,1C4,1,12,1,12,1C12,1,12,2,12,2C12,2,4,2,4,2C4,2,4,1,4,1C4,1,4,1,4,1Z" fill="#333333" fill-opacity="1"/></g></g></svg>

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
src/icons/svg/sidebar-icon9.svg


+ 2 - 2
src/layout/components/AppMain.vue

@@ -34,7 +34,7 @@ export default {
   height: calc(100vh - 50px);
   width: 100%;
   position: relative;
-  overflow-y: scroll;
+  overflow-y: hidden;
   /*display: flex;*/
 }
 
@@ -78,7 +78,7 @@ export default {
   .app-main {
     /* 84 = navbar + tags-view = 50 + 34 */
     height: calc(100vh - 110px);
-    overflow-y: scroll;
+    overflow-y: hidden;
     margin-top: 10px;
     border-radius: 5px;
     box-shadow: 0 0 5px rgba(100,100,100,.2);

+ 3 - 1
src/permission.js

@@ -78,7 +78,9 @@ router.beforeEach(async (to, from, next) => {
             { routerPath: "/trendingTermList" },
             { routerPath: "/trendingTermList/trendingTermListIndex" },
             { routerPath: "/productRepo" },
-            { routerPath: "/productRepo/productRepoIndex" }]
+            { routerPath: "/productRepo/productRepoIndex" },
+            { routerPath: "/cropList" },
+            { routerPath: "/cropList/cropListIndex" }]
          
           const accessRoutes = await store.dispatch('permission/generateRoutes', data);
           router.addRoutes(accessRoutes);

+ 2 - 0
src/router/index.js

@@ -43,11 +43,13 @@ import faceSwapVideoRouter from "./modules/faceSwapVideo";
 import AIAssetSearchRouter from "./modules/AIAssetSearch";
 import trendingTermListRouter from "./modules/trendingTermList";
 import productRepoRouter from "./modules/productRepo";
+import cropListRouter from "./modules/cropList";
 export const asyncRoutes = [
   oralVideoRouter,
   catwalkVideoRouter,
   faceSwapVideoRouter,
   designAgentManager,
+  cropListRouter,
   videoCoverManagerRouter,
   musicLibraryManager,
   AIAssetSearchRouter,

+ 1 - 1
src/router/modules/AIAssetSearch.js

@@ -6,7 +6,7 @@ const AIAssetSearchRouter = {
   path: "/AIAssetSearch",
   component: Layout,
   redirect: "/AIAssetSearch/AIAssetSearchIndex",
-  meta: { title: "AI素材搜索管理", icon: "el-icon-s-shop" },
+  meta: { title: "AI素材搜索管理", icon: "sidebar-icon1" },
   children: [
     {
       path: "AIAssetSearchIndex",

+ 1 - 1
src/router/modules/catwalkVideo.js

@@ -6,7 +6,7 @@ const catwalkVideoRouter = {
   path: "/catwalkVideo",
   component: Layout,
   redirect: "/catwalkVideo/catwalkVideoIndex",
-  meta: { title: "走秀视频管理", icon: "el-icon-camera-solid" },
+  meta: { title: "走秀视频管理", icon: "sidebar-icon2" },
   children: [
     {
       path: "catwalkVideoIndex",

+ 19 - 0
src/router/modules/cropList.js

@@ -0,0 +1,19 @@
+/** When your routing table is too long, you can split it into small modules **/
+
+import Layout from "@/layout";
+
+const cropListRouter = {
+  path: "/cropList",
+  component: Layout,
+  redirect: "/cropList/cropListIndex",
+  meta: { title: "裁图", icon: "sidebar-icon3" },
+  children: [
+    {
+      path: "cropListIndex",
+      component: () => import("@/views/crop-list/index"),
+      name: "cropListIndex",
+      meta: { title: "裁图列表" }
+    }
+  ]
+};
+export default cropListRouter;

+ 1 - 1
src/router/modules/designAgentManager.js

@@ -6,7 +6,7 @@ const designAgentManagerRouter = {
   path: "/designAgent",
   component: Layout,
   redirect: "/designAgentManager/designAgentIndex",
-  meta: { title: "设计智能体", icon: "el-icon-picture" },
+  meta: { title: "设计智能体", icon: "sidebar-icon4" },
   children: [
     {
       path: "designAgentIndex",

+ 1 - 1
src/router/modules/faceSwapVideo.js

@@ -6,7 +6,7 @@ const faceSwapVideoRouter = {
   path: "/faceSwapVideo",
   component: Layout,
   redirect: "/face-swap-video/faceSwapVideoIndex",
-  meta: { title: "视频换脸管理", icon: "el-icon-s-cooperation" },
+  meta: { title: "视频换脸管理", icon: "sidebar-icon5" },
   children: [
     {
       path: "faceSwapVideoIndex",

+ 1 - 1
src/router/modules/musicLibraryManager.js

@@ -6,7 +6,7 @@ const musicLibraryManagerRouter = {
   path: "/musicLibraryManager",
   component: Layout,
   redirect: "/musicLibraryManager/musicLibraryIndex",
-  meta: { title: "音乐库管理", icon: "el-icon-s-claim" },
+  meta: { title: "音乐库管理", icon: "sidebar-icon6" },
   children: [
     {
       path: "musicCategoryIndex",

+ 1 - 1
src/router/modules/oralVideo.js

@@ -6,7 +6,7 @@ const oralVideoRouter = {
   path: "/oralVideo",
   component: Layout,
   redirect: "/oralVideo/oralVideoIndex",
-  meta: { title: "口播视频管理", icon: "el-icon-video-camera-solid" },
+  meta: { title: "口播视频管理", icon: "sidebar-icon7" },
   children: [
     {
       path: "oralVideoIndex",

+ 1 - 1
src/router/modules/productRepo.js

@@ -6,7 +6,7 @@ const productRepoRouter = {
   path: "/productRepo",
   component: Layout,
   redirect: "/productRepo/productRepoIndex",
-  meta: { title: "产品库", icon: "el-icon-s-shop" },
+  meta: { title: "产品库", icon: "sidebar-icon8" },
   children: [
     {
       path: "productRepoIndex",

+ 1 - 1
src/router/modules/trendingTermList.js

@@ -6,7 +6,7 @@ const trendingTermListRouter = {
   path: "/trendingTermList",
   component: Layout,
   redirect: "/trendingTermList/trendingTermListIndex",
-  meta: { title: "热词管理", icon: "el-icon-s-shop" },
+  meta: { title: "热词管理", icon: "sidebar-icon9" },
   children: [
     {
       path: "trendingTermListIndex",

+ 1 - 1
src/router/modules/userAuth.js

@@ -6,7 +6,7 @@ const userAuthRouter = {
   path: "/auth",
   component: Layout,
   redirect: "/auth/user",
-  meta: { title: "权限管理", icon: "el-icon-user-solid" },
+  meta: { title: "权限管理", icon: "sidebar-icon10" },
   children: [
     {
       path: "user",

+ 1 - 1
src/router/modules/videoCoverManager.js

@@ -6,7 +6,7 @@ const videoCoverManagerRouter = {
   path: "/videoCoverManager",
   component: Layout,
   redirect: "/videoCoverManager/videoCoverIndex",
-  meta: { title: "店铺信息管理", icon: "el-icon-s-shop" },
+  meta: { title: "店铺信息管理", icon: "sidebar-icon11" },
   children: [
     {
       path: "videoCoverIndex",

+ 4 - 0
src/styles/layout.scss

@@ -20,4 +20,8 @@
 }
 .app-container{
   height: 100%;
+  display: flex;
+  flex-direction: column;
+  padding: 10px;
+  padding-bottom: 0;
 }

+ 10 - 0
src/styles/sidebar.scss

@@ -37,6 +37,16 @@
         height: calc(100% - 50px);
       }
     }
+    .el-submenu__title,.el-menu-item {
+      display: flex;
+      align-items: center;
+
+      &.is-active {
+        svg {
+          stroke: #ffffff;
+        }
+      }
+    }
     .is-horizontal {
       display: none;
     }

+ 6 - 2
src/utils/request.js

@@ -7,7 +7,7 @@ import { resetRouter } from '@/router'
 // 创建axios实例
 const service = axios.create({
   baseURL: process.env.VUE_APP_AI_API, // api 的 base_url
-  timeout: 50000 // 请求超时时间
+  timeout: 300000 // 请求超时时间
 });
 
 // request拦截器
@@ -55,7 +55,11 @@ service.interceptors.response.use(
           location.reload()
         })
       } else {
-        return Promise.reject('error');
+        if (res.code === 500) {
+          return response.data
+        } else {
+          return Promise.reject('error');
+        }
       }
     } else {
       return response.data

+ 2 - 0
src/views/common/swPage.vue

@@ -62,6 +62,8 @@ export default {
 		margin-top: 10px;
 		margin-bottom: 10px;
 		float: right;
+		display: flex;
+    justify-content: flex-end;
 	}
 
 

+ 190 - 0
src/views/crop-list/components/CropSizeModal.vue

@@ -0,0 +1,190 @@
+<template>
+  <el-dialog title="裁图尺寸设置" width="800px" :visible.sync="dialogVisible">
+    <el-form ref="CropSize" v-loading="loading">
+      <el-form-item class="crop-size-form" v-for="(acc, index) in form" :key="index">
+        <div class="crop-size-left">
+          <el-input v-model="acc.positionName" size="small" disabled></el-input>
+          <span>:</span>
+          <el-input-number size="small" v-model="acc.width" :controls="false"></el-input-number>
+          <span>x</span>
+          <el-input-number size="small" v-model="acc.height" :controls="false"></el-input-number>
+          <span>px</span>
+        </div>
+        <!-- <span class="crop-size-right" @click="del(acc, index)" v-if="index > 0">删除</span> -->
+      </el-form-item>
+    </el-form>
+    <!-- <el-button
+      size="small"
+      @click="addCropSize()"
+    >
+      添加新模板
+    </el-button> -->
+
+    <span slot="footer" class="dialog-footer">
+      <el-button @click="dialogVisible = false">取消</el-button>
+      <el-button type="primary" :loading="comfirmLoading" @click="handleConfirm">确定</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+import request from '@/utils/request'
+export default {
+  name: "CropSizeModal",
+  props: {
+    list: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    }
+  },
+  data() {
+    return {
+      loading: false,
+      form: [
+        {
+          id: '',
+          positionName: '',
+          width: null,
+          height: null
+        }
+      ],
+      // 后期可能需要用到,勿删
+      sizes: [
+        {label: '1600*1600 主图', value: 'main'},
+        {label: '1800*2400 主图', value: 'main'},
+        {label: '1600*2400 竖图', value: 'list'},
+        {label: '1800*2400 展示图', value: 'list'},
+        {label: '1600*1600 模特图', value: 'model'}
+      ],
+      dialogVisible: false,
+      comfirmLoading: false
+    };
+  },
+  computed: {
+  },
+  watch: {
+    dialogVisible(val) {
+      if (!val) {
+        this.comfirmLoading = false;
+      }
+    }
+  },
+  methods: {
+    show() {
+      this.dialogVisible = true;
+      this.$nextTick(() => {
+        this.form = this.list.map(acc => {
+          return {
+            id: acc.id,
+            positionName: acc.positionName,
+            width: acc.width,
+            height: acc.height,
+            sort: acc.sort
+          }
+        })
+      })
+    },
+    handleConfirm() {
+      this.comfirmLoading = true;
+      request({
+        url: '/sizeTemplate/update',
+        method: 'post',
+        data: this.form
+      }).then(res => {
+        if (res.code == 200) {
+          this.dialogVisible = false;
+          this.$emit('success');
+        }
+      }).finally(() => {
+        this.comfirmLoading = false;
+      })
+    },
+    isOptionDisabled(value, currentIndex) {
+      return this.form.some(
+        (item, index) =>
+          index !== currentIndex && item.selectValue === value
+      )
+    },
+    addCropSize() {
+      this.form.push({
+        id: '',
+        positionName: '',
+        width: null,
+        height: null
+      })
+    },
+    del(row, itemIndex) {
+      //删除
+      this.$confirm("确定要删除吗?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.loading = true;
+        request({
+          url: `/sizeTemplate/delete?id=${row.id}`,
+          method: 'post'
+        }).then(res => {
+          if (res.code == 200) {
+            this.form.splice(itemIndex, 1); 
+            this.$notify({
+              title: "成功",
+              message: "删除成功",
+              type: "success",
+              duration: 3000
+            });
+          }
+        }).finally(() => {
+          this.loading = false;
+        })
+        
+      });
+    },
+    changeSelect(val, index) {
+      const match = val.match(/(\d+)\*(\d+)/);
+
+      const width = Number(match[1]);
+      const height = Number(match[2]);
+      if (!this.form[index].width) {
+        this.form[index].width = width;
+      }
+      if (!this.form[index].height) {
+        this.form[index].height = height;
+      }
+    }
+  }
+};
+</script>
+<style lang="scss">
+  .crop-size-form {
+    .el-form-item__content {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      font-size: 12px;
+      line-height: 32px;
+
+      &::before,&::after {
+        display: none;
+      }
+
+      .crop-size-left {
+        display: flex;
+        gap: 10px;
+
+        >.el-input {
+          width: 200px;
+        }
+      }
+      .crop-size-right {
+        padding: 0 10px;
+        cursor: pointer;
+        color: #ac0917;
+      }
+    }
+  }
+</style>
+
+

+ 1090 - 0
src/views/crop-list/components/ImagesGroupModal.vue

@@ -0,0 +1,1090 @@
+<template>
+  <el-dialog title="结果预定与审核" width="80%" custom-class="reservation-approval-dialog" :visible.sync="dialogVisible" :close-on-click-modal="false">
+    <div class="reservation-approval-block">
+      <div class="reservation-approval-header">
+        <div class="crop-type-block" v-if="pages == 'crop' && statusPages == 4">
+          <span class="label">裁切类型:</span>
+          <el-select v-model="form.cropType" clearable placeholder="请选择" size="small">
+            <el-option
+              v-for="item in cropTypes"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value">
+            </el-option>
+          </el-select>
+        </div>
+        <div class="crop-colorCode-block" style="display: flex;align-items: center;" v-if="pages == 'crop' && statusPages == 4">
+          <span class="label" style="white-space: nowrap;">颜色代码:</span>
+          <el-input v-model="form.colorCode" size="small"></el-input>
+        </div>
+        <div class="crop-colorCode-block" style="display: flex;align-items: center;" v-if="cropPreview && statusPages == 4">
+          <span class="label" style="white-space: nowrap;">SKU:</span>
+          <el-input v-model="form.sku" size="small"></el-input>
+        </div>
+        <div class="reservation-approval__status" v-if="!cropPreview">
+          <ul v-if="pages == 'review'">
+            <li :class="{'active': statusPages == 2}">待审核图</li>
+            <li :class="{'active': statusPages == 3}">待复核</li>
+            <li :class="{'active': statusPages == 4}">审核完成</li>
+          </ul>
+          <ul v-if="pages == 'crop'">
+            <li :class="{'active': statusPages == 4}">待裁图</li>
+            <li :class="{'active': statusPages == 5}">待复核</li>
+            <li :class="{'active': statusPages >= 6}">已完成</li>
+          </ul>
+        </div>
+        <div class="reservation-approval__save" v-if="!cropPreview && (pages == 'review' || (pages == 'crop' && statusPages >= 4))">
+          <el-button
+            size="small"
+            :disabled="(pages == 'review' && statusPages == 4) || (pages == 'crop' && statusPages >= 6) || !imagesList.length"
+            :loading="saveLoading"
+            @click="saveHandle"
+          >
+            保存修改
+          </el-button>
+        </div>
+        <div class="reservation-approval__confirm">
+          <el-button
+            type="primary"
+            size="small"
+            :disabled="(pages == 'review' && statusPages == 4) || (pages == 'crop' && statusPages >= 6) || !imagesList.length"
+            :loading="reviewLoading"
+            @click="handleConfirm"
+          >
+            {{ pages == 'crop' && statusPages == 4 ? '确认裁图' : '审核通过' }}
+          </el-button>
+        </div>
+        <!-- <div class="reservation-approval__submit" v-if="statusPages == 8">
+          <el-button
+            type="primary"
+            size="small"
+            :loading="submitLoading"
+            @click="uploadGalleryList"
+          >
+            上传图库
+          </el-button>
+        </div> -->
+      </div>
+      <div class="reservation-approval-body" :class="{'full': !form.regenerateImage}">
+        <el-row>
+          <el-col :span="24">
+            <div class="images-list-wapper" v-if="statusPages >= 5">
+              <div class="images-list__one">
+                <h2>主图1:1</h2>
+                <draggable
+                  tag="div"
+                  class="images-list-block"
+                  v-model="imagesList"
+                  @start="onDragStart"
+                  @end="onDragEnd"
+                  :options="{ animation: 150 }">
+                  <div class="images-items" v-for="(acc, index) in imagesList" v-if="acc.position === 'main' && acc.width === acc.height" :key="index">
+                    <images-item :images-list="cropPreview ? imagesList : originalImagesList" :pages="pages" :item="acc" :index="index" :status-pages="statusPages" @delImage="delImage" @needRegenerate="needRegenerate"></images-item>
+                  </div>
+                </draggable>
+              </div>
+              <div class="images-list__one">
+                <h2>主图3:4</h2>
+                <draggable
+                  tag="div"
+                  class="images-list-block"
+                  v-model="imagesList"
+                  @start="onDragStart"
+                  @end="onDragEnd"
+                  :options="{ animation: 150 }">
+                  <div class="images-items" v-for="(acc, index) in imagesList" v-if="acc.position === 'main' && acc.width !== acc.height" :key="index">
+                    <images-item :images-list="cropPreview ? imagesList : originalImagesList" :pages="pages" :item="acc" :index="index" :status-pages="statusPages" @delImage="delImage" @needRegenerate="needRegenerate"></images-item>
+                  </div>
+                </draggable>
+              </div>
+              <div class="images-list__one">
+                <h2>竖图2:3</h2>
+                <draggable
+                  tag="div"
+                  class="images-list-block"
+                  v-model="imagesList"
+                  @start="onDragStart"
+                  @end="onDragEnd"
+                  :options="{ animation: 150 }">
+                  <div class="images-items" v-for="(acc, index) in imagesList" v-if="acc.position === 'list'" :key="index">
+                    <images-item :images-list="cropPreview ? imagesList : originalImagesList" :pages="pages" :item="acc" :index="index" :status-pages="statusPages" @delImage="delImage" @needRegenerate="needRegenerate"></images-item>
+                  </div>
+                </draggable>
+              </div>
+              <div class="images-list__one">
+                <h2>颜色图1:1</h2>
+                <draggable
+                  tag="div"
+                  class="images-list-block"
+                  v-model="imagesList"
+                  @start="onDragStart"
+                  @end="onDragEnd"
+                  :options="{ animation: 150 }">
+                  <div class="images-items" v-for="(acc, index) in imagesList" v-if="acc.position === 'color'" :key="index">
+                    <images-item :images-list="cropPreview ? imagesList : originalImagesList" :pages="pages" :item="acc" :index="index" :status-pages="statusPages" @delImage="delImage" @needRegenerate="needRegenerate"></images-item>
+                  </div>
+                </draggable>
+              </div>
+            </div>
+            <div class="images-list-wapper" v-else>
+              <draggable
+                tag="div"
+                class="images-list-block"
+                v-model="imagesList"
+                @start="onDragStart"
+                @end="onDragEnd"
+                :move="checkMove"
+                :options="{ animation: 150 }">
+                <div class="images-items" v-for="(acc, index) in imagesList" :key="index">
+                  <images-item :images-list="cropPreview ? imagesList : originalImagesList" :crop-preview="cropPreview" :pages="pages" :item="acc" :index="index" :status-pages="statusPages" @delImage="delImage" @needRegenerate="needRegenerate"></images-item>
+                </div>
+                <div class="images-items fixed-item" v-if="cropPreview">
+                  <div class="images-items__img">
+                    <el-upload
+                      ref="uploadRefCrop"
+                      class="upload-demo"
+                      drag
+                      :action="action" 
+                      :show-file-list="false" 
+                      :headers="{
+                        'Authorization': 'Bearer ' + token
+                      }"
+                      multiple 
+                      :on-success="handleVideoSuccess" 
+                      :before-upload="beforeUploadVideo">
+                      <div v-loading="uploading">
+                        <div class="el-upload__info">
+                          <i class="el-icon-upload"></i>
+                          <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+                          <div class="el-upload__tip" slot="tip">图片要求:支持JPG、PNG和WEBP,最大为20M</div>
+                        </div>
+                      </div>
+                    </el-upload>
+                  </div>
+                </div>
+              </draggable>
+            </div>
+          </el-col>
+        </el-row>
+        <div class="regenerate-block" v-if="form.regenerateImage && statusPages < 6">
+          <el-form ref="regenerate-form" :model="form" label-width="100px" label-position="top">
+            <el-form-item>
+              <img class="regenerate-image" :src="form.regenerateImage" alt="">
+            </el-form-item>
+            <el-form-item label="商品信息">
+              <el-input v-model="form.sku" size="small" disabled></el-input>
+            </el-form-item>
+            <el-form-item label="内容" v-if="pages == 'review'" class="upload-form-item">
+              <el-input v-model="form.prompt" :rows="5" type="textarea"></el-input>
+            </el-form-item>
+            <el-form-item label="裁图图片" v-if="pages == 'crop'" class="upload-form-item">
+              <el-upload
+                ref="uploadRef"
+                class="upload-demo"
+                drag
+                :action="action" 
+                :show-file-list="false" 
+                :headers="{
+                  'Authorization': 'Bearer ' + token
+                }" 
+                :on-success="handleVideoSuccess" 
+                :before-upload="beforeUploadVideo">
+                <div v-loading="uploading">
+                  <div v-if="form.imageUrl" class="avatar-black">
+                    <img :src="form.imageUrl" class="avatar">
+                    <div class="custom-file__btns">
+                      <el-button 
+                        class="replace-btn" 
+                        @click.stop="replaceFile()">
+                        <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.stop="imgUploadDel()">
+                        <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 class="el-upload__info" v-else>
+                    <i class="el-icon-upload"></i>
+                    <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+                    <div class="el-upload__tip" slot="tip">图片要求:支持JPG、PNG和WEBP,最大为20M</div>
+                  </div>
+                </div>
+              </el-upload>
+            </el-form-item>
+            <span class="tips">重生成之后,需要保存!</span>
+          </el-form>
+          <span class="regenerate-footer">
+            <el-button size="small" @click="colseRegenerateImage">取消</el-button>
+            <el-button type="primary" size="small" :loading="regenerateLoading" @click="handleRegenerate">重生成</el-button>
+          </span>
+        </div>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { api } from "@/api/api";
+import { getToken } from '@/utils/auth'
+import draggable from "vuedraggable"; 
+import ImagesItem from "./ImagesItem"; 
+import request from '@/utils/request'
+import { genCidHex16, fetchStreamText } from '@/utils/index'
+export default {
+  name: "CropSizeModal",
+  props: {
+    sizeList: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    }
+  },
+  components: {
+    draggable,
+    ImagesItem
+  },
+  data() {
+    return {
+      action: api.fileUrl,
+      uploading: false,
+      cropPreview: false,
+      pages: '',
+      statusPages: '',
+      paramsId: '',
+      taskId: '',
+      form: {
+        imageId: '',
+        regenerateImage: null,
+        sku: null,
+        prompt: '',
+        imageUrl: '',
+        cropType: null,
+        colorCode: ''
+      },
+      rules: {
+        cropType: [
+          { required: true, message: '请选择裁图类型', trigger: 'change' }
+        ]
+      },
+      cropTypes: [
+        {label: '上衣/短外套', value: 1},
+        {label: '下装(裤子、半身裙)', value: 2},
+        {label: '连衣裙/套装/中长外套', value: 3},
+        {label: '膝盖以上的连身装(连衣裙、外套)', value: 4}
+      ],
+      imagesList: [],
+      originalOrders: [],
+      originalImagesList: [],
+      dialogVisible: false,
+      saveLoading: false,
+      submitLoading: false,
+      reviewLoading: false,
+      regenerateLoading: false,
+      isCropDone: false
+    };
+  },
+  computed: {
+    token() {
+      return getToken();
+    }
+  },
+  watch: {
+    dialogVisible(val) {
+      if (!val) {
+        this.regenerateLoading = false;
+        if (this.reviewLoading) {
+          // 说明裁图还没有执行完毕
+          this.isCropDone = true;
+        } else {
+          this.isCropDone = false;
+        }
+        this.resetForm();
+      }
+    }
+  },
+  methods: {
+    show(row, type) {
+      this.dialogVisible = true;
+      this.$nextTick(() => {
+        // if (localStorage.getItem('cropInfo')) {
+        //   try {
+        //     const cropInfo = JSON.parse(localStorage.getItem('cropInfo'));
+        //     this.form.sku = cropInfo.sku || null;
+        //     this.form.cropType = cropInfo.cropType || null;
+        //     this.form.colorCode = cropInfo.colorCode || '';
+        //   } catch (e) {}
+        // }
+        this.cropPreview = false;
+        this.saveLoading = false;
+        this.pages = type;
+        this.paramsId = row.id;
+        if (row.type == 1) {
+          this.originalImagesList = [...row.referenceImagesList, ...row.originalImagesList];
+        } else {
+          this.originalImagesList = row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1).map(acc => {
+            return acc.imageUrl
+          });
+        }
+        // this.originalImagesList = [...row.referenceImagesList, ...row.originalImagesList];
+        this.taskId = row.aiGeneratedImageVOList && row.aiGeneratedImageVOList[0].taskId;
+        this.statusPages = row.status * 1;
+        this.form.sku = row.sku;
+        const images = this.statusPages < 5 ? row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1) : row.aiGeneratedImageVOList.filter(acc => acc.imageType == 2);
+        this.imagesList = images.map(acc => {
+          return {
+            id: acc.id,
+            imageUrl: acc.imageUrl,
+            imageOrder: acc.imageOrder,
+            position: acc.position,
+            width: acc.width,
+            height: acc.height
+          }
+        })
+        console.log(this.imagesList, 444)
+      })
+    },
+    init() {
+      this.dialogVisible = true;
+      this.$nextTick(() => {
+        this.saveLoading = false;
+        this.reviewLoading = false;
+        this.taskId = '';
+        this.cropPreview = true;
+        this.pages = 'crop';
+        this.statusPages = 4;
+        this.originalImagesList = []
+      })
+    },
+    uploadGalleryList() {
+      this.submitLoading = true;
+      const params = {
+        taskId: this.taskId
+      }
+      request({
+        url: '/imageTask/uploadGallery',
+        method: 'post',
+        data: params
+      }).then(res => {
+        if (res.code == 200) {
+          this.$message.success(res.msg || '操作成功!');
+        }
+      }).finally(() => {
+        this.submitLoading = false
+      })
+    },
+    saveHandle() {
+      this.saveLoading = true;
+      request({
+        url: '/image/update',
+        method: 'post',
+        data: this.imagesList.map(acc => {
+          return {
+            id: acc.id,
+            imageUrl: acc.imageUrl,
+            imageOrder: acc.imageOrder,
+          }
+        })
+      }).then(res => {
+        if (res.code == 200) {
+          this.$emit('update-success');
+          this.$message.success(res.msg || '保存成功!');
+        }
+      }).finally(() => {
+        this.saveLoading = false;
+      })
+    },
+    async handleCrop() {
+      // 参数校验
+      if (!this.validateCropParams()) return;
+
+      try {
+        this.reviewLoading = true;
+
+        // 如果是裁图预览模式,先直传图片
+        if (this.cropPreview && !this.taskId) {
+          const res = await this.directSubmitImages();
+          if (res.code === 200) {
+            this.isCropDone = false;
+            this.dialogVisible = false
+            this.$emit('update-success');
+            this.$message.success('裁图已提交');
+          }
+        } else {
+          // 执行裁图
+          const res = await this.cropImagesHandle();
+          if (res.code === 200 && this.taskId && !this.isCropDone) {
+            this.isCropDone = false;
+            this.afterCropSuccess(res);
+          }
+          if (res.code === 500) {
+            this.isCropDone = false;
+            this.$emit('update-success');
+          }
+        }
+      } catch (err) {
+        console.error(err);
+      } finally {
+        this.reviewLoading = false;
+      }
+    },
+
+    // ======================
+    // 参数校验
+    // ======================
+    validateCropParams() {
+      if (this.cropPreview && !this.form.sku) {
+        this.$message.error('请填写SKU!');
+        return false;
+      }
+
+      if (!this.form.cropType || !this.form.colorCode) {
+        this.$message.error('请选择裁切类型或填写颜色代码!');
+        return false;
+      }
+
+      return true;
+    },
+
+    // ======================
+    // 直传图片(directSubmit)
+    // ======================
+    directSubmitImages() {
+      return request({
+        url: '/imageTask/directSubmit',
+        method: 'post',
+        data: {
+          sessionId: `${Date.now() + 10}_${genCidHex16()}`,
+          applicationId: 6,
+          sizes: this.sizeList.map(acc => ({
+            position: acc.positionCode,
+            height: acc.height,
+            width: acc.width,
+            sort: acc.sort
+          })),
+          tasks: [
+            {
+              sku: this.form.sku,
+              type: this.form.cropType,
+              colorCode: this.form.colorCode,
+              images: this.imagesList.map((acc, index) => ({
+                imageUrl: acc.imageUrl,
+                imageOrder: index
+              }))
+            }
+          ]
+        }
+      });
+    },
+
+    // ======================
+    // 裁图接口(crop)
+    // ======================
+    cropImagesHandle() {
+      return request({
+        url: '/imageTask/crop',
+        method: 'post',
+        data: {
+          sessionId: `${Date.now() + 10}_${genCidHex16()}`,
+          applicationId: 6,
+          sku: this.form.sku,
+          type: this.form.cropType,
+          taskId: this.taskId,
+          colorCode: this.form.colorCode,
+          sizes: this.sizeList.map(acc => ({
+            position: acc.positionCode,
+            height: acc.height,
+            width: acc.width,
+            sort: acc.sort
+          }))
+        }
+      });
+    },
+
+    // ======================
+    // 裁图成功后的统一处理
+    // ======================
+    afterCropSuccess(res) {
+      this.statusPages += 1;
+
+      this.$emit('update-success');
+      this.$emit('update-status', this.paramsId, this.statusPages);
+      
+      this.imagesList = res.data.map(acc => ({
+        id: acc.id,
+        imageUrl: acc.imageUrl,
+        imageOrder: acc.imageOrder,
+        position: acc.position,
+        width: acc.width,
+        height: acc.height
+      }));
+
+      this.$message.success(res.msg || '操作成功!');
+    },
+    handleConfirm() {
+      if (this.pages == 'crop' && this.statusPages == 4) {
+        this.handleCrop();
+      } else {
+        this.reviewLoading = true;
+        request({
+          url: `/imageTask/audit/${this.paramsId}`,
+          method: 'post',
+          data: {
+            id: this.paramsId,
+            aiGeneratedImageUpdateParams: this.imagesList.map(acc => {
+              return {
+                id: acc.id,
+                imageUrl: acc.imageUrl,
+                imageOrder: acc.imageOrder,
+              }
+            })
+          }
+        }).then(res => {
+          if (res.code == 200) {
+            this.statusPages += 1; 
+            if (this.statusPages >= 6) {
+              this.form.regenerateImage = null;
+            }
+            this.$emit('update-status', this.paramsId, this.statusPages);
+            this.$message.success(res.msg || '操作成功!');
+          }
+        }).finally(() => {
+          this.reviewLoading = false;
+        })
+      }
+      
+    },
+    // 拖动开始:记录原始 imageOrder 顺序
+    onDragStart() {
+      this.originalOrders = this.imagesList.map(item => item.imageOrder)
+    },
+    checkMove(evt) {
+      // 禁止 fixed-item 被拖动
+      if (evt.dragged.classList.contains('fixed-item')) {
+        return false
+      }
+      return true
+    },
+    // 拖动结束:按当前位置重新赋值 imageOrder
+    onDragEnd() {
+      this.imagesList = this.imagesList.map((item, index) => {
+        return {
+          ...item,
+          imageOrder: this.originalOrders[index]
+        }
+      })
+    },
+    delImage(row, itemIndex) {
+      //删除
+      this.$confirm("确定要删除吗?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        if (this.cropPreview) {
+          this.imagesList.splice(itemIndex, 1);
+          this.$notify({
+            title: "成功",
+            message: "删除成功",
+            type: "success",
+            duration: 3000
+          });
+        } else {
+          request({
+            url: '/image/delete',
+            method: 'post',
+            data: [row.id]
+          }).then(res => {
+            if (res.code == 200) {
+              this.imagesList.forEach((acc, itemIndex) => {
+                if (acc.id == row.id) {
+                  this.imagesList.splice(itemIndex, 1);
+                }
+              })
+              this.$emit('update-images', this.paramsId, row.id);
+              if (row.id == this.form.imageId) {
+                this.form.regenerateImage = null;
+
+              }
+              this.$notify({
+                title: "成功",
+                message: "删除成功",
+                type: "success",
+                duration: 3000
+              });
+            }
+          }).finally(() => {
+            
+          })
+        }
+        
+      });
+    },
+    colseRegenerateImage() {
+      this.form.regenerateImage = null;
+      this.form.prompt = '';
+      this.form.imageUrl = '';
+    },
+    async handleRegenerate() {
+      if (this.pages == 'review' && !this.form.prompt) {
+        this.$message.error('请填写内容!');
+        return;
+      }
+      if (this.pages == 'crop' && !this.form.imageUrl) {
+        this.$message.error('请上传裁图图片!');
+        return;
+      }
+      if (this.pages == 'review') {
+        this.regenerateLoading = true;
+        try {
+          const payload = {
+            sessionId: `${Date.now() + 10}_${genCidHex16()}`,
+            applicationId: 2,
+            images: this.pages == 'review' ? [this.form.regenerateImage] : [this.form.imageUrl]
+          }
+          if (this.pages == 'review') {
+            payload.prompt = this.form.prompt;
+          }
+          const result = await fetchStreamText(`/app/ai/send/imageMessage`, payload, {
+            onChunk: (chunk) => {
+              console.log('实时分片:', chunk)
+              // this.replyText += chunk // Vue 可实时更新聊天内容
+            }
+          })
+          try {
+            this.regenerateLoading = false;
+            if (result && typeof result == 'string') {
+              console.log('完整文本:', result)
+              const data = JSON.parse(result)
+              if (result.code == 200) {
+                this.imagesList.forEach(acc => {
+                  if (acc.id == this.form.imageId) {
+                    acc.imageUrl = data.image
+                  }
+                })
+              } else {
+                if (result.code == 401) {
+                  this.$router.push(`/login`);
+                } else {
+                  this.$message.error(result.msg || result.errorContent || '请求出错,请联系管理员!');
+                }
+              }
+            } else {
+              console.log('JSON 对象12', result)
+              if (result.code == 200) {
+                this.imagesList.forEach(acc => {
+                  if (acc.id == this.form.imageId) {
+                    acc.imageUrl = result.image
+                  }
+                })
+              } else {
+                if (result.code == 401) {
+                  this.$router.push(`/login`);
+                } else {
+                  this.$message.error(result.msg || result.errorContent || '请求出错,请联系管理员!');
+                }
+                
+              }
+            }
+          } catch (e) {
+            console.error('报错:', e)
+          }
+        } catch (e) {
+          console.error('请求错误:', e)
+        } finally {
+          this.regenerateLoading = false;
+        }
+      } else {
+        this.imagesList.forEach(acc => {
+          if (acc.id == this.form.imageId) {
+            acc.imageUrl = this.form.imageUrl;
+          }
+        })
+      }
+    },
+    needRegenerate(item) {
+      this.form.imageUrl = '';
+      this.form.imageId = this.imagesList.filter(acc => acc.id == item.id)[0].id;
+      this.form.regenerateImage = this.imagesList.filter(acc => acc.id == item.id)[0].imageUrl;
+    },
+    replaceFile() {
+      if (this.$refs.uploadRef) {
+        // 清空上传队列
+        this.$refs.uploadRef.uploadFiles = []
+
+        // 手动触发上传框选择文件
+        const input = this.$refs.uploadRef.$el.querySelector('input[type=file]')
+        input.click()
+      }
+      // 在 handleSuccess 中会替换对应文件
+    },
+    beforeUploadVideo(file) {
+      this.uploading = true;
+      const isLt10M = file.size / 1024 / 1024 < 200;
+      const imagesTypes = ['image/png','image/jpeg','image/webp']
+      if (!imagesTypes.includes(file.type)) {
+        this.uploading = false;
+        this.$message.error('图片只能是jpg、png和webp格式!');
+        return false;
+      }
+      if (!isLt10M) {
+        this.$message.error('上传文件大小不能超过20MB哦!');
+        return false;
+      }
+      return true
+    },
+    handleVideoSuccess(res) {
+      this.uploading = false
+      if (res.code == 200) {
+        if (this.cropPreview) {
+          this.imagesList.push({
+            imageUrl: res.data.url
+          })
+        } else {
+          this.form.imageUrl = res.data.url;
+        }
+        
+      } else {
+        this.$message.error('上传失败,请重新上传!');
+      }
+    },
+    imgUploadDel() {
+      //删除
+      this.$confirm("确定要删除吗?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.form.imageUrl = '';
+        this.$notify({
+          title: "成功",
+          message: "删除成功",
+          type: "success",
+          duration: 3000
+        });
+      });
+    },
+    resetForm() {
+      this.cropPreview = false;
+      this.pages = '';
+      this.statusPages = '';
+      this.paramsId = '';
+      this.taskId = '';
+      this.imagesList = [];
+      this.originalOrders = [];
+      const data = { sku: this.form.sku, cropType: this.form.cropType, colorCode: this.form.colorCode };
+      localStorage.setItem('cropInfo', JSON.stringify(data));
+      this.form = {
+        imageId: '',
+        regenerateImage: null,
+        sku: null,
+        prompt: '',
+        imageUrl: '',
+        cropType: null,
+        colorCode: ''
+      }
+    }
+  }
+};
+</script>
+<style lang="scss">
+  .reservation-approval-dialog {
+    margin-top: 5vh !important;
+    max-height: 90vh;
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    .el-dialog__body {
+      padding-top: 0;
+      flex: 1;
+      overflow: hidden;
+      min-height: 0;
+      padding-bottom: 20px;
+    }
+  }
+  .reservation-approval-block {
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    .reservation-approval-header {
+      display: flex;
+      justify-content: flex-end;
+      gap: 20px;
+      margin-bottom: 20px;
+    }
+    .reservation-approval__status ul{
+      display: flex;
+      margin: 0;
+      padding: 0;
+      height: 32px;
+      border-radius: 200px;
+      background: #f2f2f2;
+      align-items: center;
+      gap: 15px;
+
+      li {
+        list-style: none;
+        margin: 0;
+        height: 100%;
+        display: flex;
+        align-items: center;
+        padding: 0 10px;
+        border-radius: 200px;
+        font-size: 12px;
+        line-height: 32px;
+        white-space: nowrap;
+
+        &.active {
+          background: #ae8877;
+          color: #fff;
+        }
+      }
+    }
+    .reservation-approval-body {
+      position: relative;
+      display: flex;
+      height: 100%;
+      flex: 1;
+      min-height: 0;
+      overflow: hidden;
+      overflow-y: auto;
+      .el-row {
+        width: 75%;
+      }
+
+      .tips {
+        font-size: 12px;
+        color: red;
+      }
+
+      &.full .el-row {
+        width: 100%;
+      }
+      .images-list__one {
+        margin-bottom: 20px;
+
+        &:last-child {
+          margin-bottom: 0;
+        }
+        h2 {
+          font-size: 16px;
+          color: #333;
+          margin: 0;
+          padding: 0;
+          margin-bottom: 10px;
+        }
+      }
+      .images-list-block {
+        display: flex;
+        flex-wrap: wrap;
+        margin-left: -15px;
+        .images-items {
+          width: 20%;
+          padding-left: 15px;
+          margin-bottom: 15px;
+        }
+        .images-items__img {
+          position: relative;
+          width: 100%;
+          aspect-ratio: 3 / 4;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          border-radius: 4px;
+          border: 1px solid #ccc;
+          overflow: hidden;
+          cursor: pointer;
+
+          &:hover {
+            .btns-group {
+              display: block;
+            }
+          }
+
+          .size {
+            position: absolute;
+            top: 5px;
+            left: 5px;
+            padding: 0px 7px;
+            background: linear-gradient(to right,#b8857b,#e5c0ac);
+            color: #fff;
+            font-size: 12px;
+            height: 20px;
+            line-height: 20px;
+            display: block;
+            z-index: 10;
+          }
+
+          .upload-demo {
+            width: 100%;
+            height: 100%;
+            display: flex;
+            align-items: center;
+          }
+
+          .el-upload-dragger {
+            width: 100%;
+            height: 100%;
+            border: none;
+            padding: 0 20px;
+
+            .el-icon-upload {
+              margin: 0;
+              margin-bottom: 30px;
+            }
+          }
+        }
+        .btns-group {
+          position: absolute;
+          bottom: 10px;
+          z-index: 10;
+          font-size: 12px;
+          display: none;
+
+          .el-button {
+            border: none;
+            background: rgba(0,0,0,0.5);
+            color: #fff;
+            padding: 7px 15px;
+          }
+        }
+        img {
+          display: block;
+          max-height: 100%;
+        }
+      }
+    }
+  }
+  .regenerate-block {
+    position: sticky;
+    top: 0;
+    right: 0;
+    width: 25%;
+    padding: 0 20px 10px;
+    max-height: 100%;
+    overflow-y: auto;
+
+    .el-form-item {
+      margin-bottom: 10px;
+    }
+
+    .regenerate-image {
+      max-width: 200px;
+      width: 100%;
+      margin: 0 auto;
+      display: block;
+    }
+    .el-form-item__label {
+      line-height: 20px;
+      padding: 0;
+    }
+
+    .upload-form-item .el-form-item__label{
+      padding-bottom: 4px;
+    }
+    .el-select {
+      width: 100%;
+    }
+    .el-upload {
+      width: 100%;
+      height: 100%;
+    }
+    .el-upload-dragger {
+      width: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      .el-icon-upload {
+        margin: 0;
+      }
+
+      .el-upload__text,.el-upload__tip {
+        line-height: 20px;
+      }
+    }
+    .avatar-black {
+      position: relative;
+      width: 100%;
+      height: 100%;
+      margin: 0 auto;
+
+      img {
+        display: block;
+        object-fit: cover;
+        width: 100%;
+        height: 100%;
+        max-width: 50%;
+        margin: 0 auto;
+      }
+      .custom-file__btns {
+        position: absolute;
+        top: 0;
+        left: 0;
+        z-index: 10;
+        width: 100%;
+        height: 100%;
+        background: rgba(0, 0, 0, 0.5);
+        border-radius: 6px;
+        display: none;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        gap: 10px;
+        transition: all .5s;
+
+        .replace-btn {
+          font-size: 12px;
+          padding: 4px 10px;
+          background: rgba(0, 0, 0, 0.8);
+          border-color: rgba(0, 0, 0, 0.8);
+          color: #fff;
+          margin: 0;
+
+          /deep/ span {
+            display: flex;
+            align-items: center;
+            gap: 4px;
+          }
+
+          svg {
+            width: 14px;
+            height: 14px;
+          }
+
+          &:hover {
+            background: #ae8878;
+            border-color: #ae8878;
+          }
+        }
+      }
+      &:hover .custom-file__btns{
+        display: flex;
+      }
+    }
+    .regenerate-footer {
+      display: flex;
+      justify-content: flex-end;
+      margin-top: 20px;
+    }
+  }
+  @media screen and (max-width: 1366px) {
+    .reservation-approval-block .reservation-approval-body .images-list-block .images-items {
+      width: 25%;
+    }
+  }
+  @media screen and (max-width: 1540px) {
+    .reservation-approval-block .reservation-approval-header {
+      gap: 10px;
+    }
+    .reservation-approval-dialog {
+      width: 98% !important;
+    }
+  }
+</style>
+
+

+ 171 - 0
src/views/crop-list/components/ImagesItem.vue

@@ -0,0 +1,171 @@
+<template>
+  <div>
+    <div class="images-items__img">
+      <span class="size">序号:{{ item.imageOrder || index }}</span>
+      <span class="size" style="top: 30px;" v-if="statusPages > 4">{{ item.width }} x {{ item.height }}</span>
+      <img class="items-img" :src="item.imageUrl" @click="imagePreview" alt="">
+      <div class="btns-group" v-if="(pages == 'review' && [2,3].includes(statusPages*1)) || (pages == 'crop' && [4,5].includes(statusPages*1))">
+        <el-button size="small" @click="delImage(item, index)">删除</el-button>
+        <el-button size="small" @click="handleDownload(item.imageUrl)">下载</el-button>
+        <el-button type="primary" size="small" v-if="!cropPreview" @click="needRegenerate(item)">重生成</el-button>
+      </div>
+    </div>
+    
+    <el-dialog
+      title=""
+      custom-class="preview-images"
+      :visible.sync="dialogVisible"
+      append-to-body
+      width="100%">
+      <div class="images-dialog">
+        <div class="images-dialog__left">
+          <el-carousel trigger="click" :autoplay="false" arrow="always">
+            <el-carousel-item v-for="(acc, i) in imagesList" :key="i">
+              <img :src="acc" alt="">
+            </el-carousel-item>
+          </el-carousel>
+        </div>
+        <div class="images-dialog__right">
+          <p><img :src="item.imageUrl" alt=""></img></p>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import downloadUtil from "@/utils/downloadUtil";
+export default {
+  name: "ImagesItem",
+  props: {
+    item: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    },
+    index: {
+      type: Number
+    },
+    statusPages: {
+      type: [String, Number],
+      default: 1
+    },
+    pages: {
+      type: String,
+      default: 'review'
+    },
+    cropPreview: {
+      type: Boolean,
+      default: false
+    },
+    imagesList: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    }
+  },
+  data() {
+    return {
+      dialogVisible: false
+    };
+  },
+  methods: {
+    delImage(item, index) {
+      this.$emit('delImage', item, index);
+    },
+    needRegenerate(item) {
+      this.$emit('needRegenerate', item);
+    },
+    async handleDownload(image) {
+      const match = image.match(/\/([^\/?#]+)$/);
+      if (match) {
+        const fileName = match[1]
+        try {
+          const res = await downloadUtil.fileDownload(image, fileName);
+          if (res) {
+            this.$message.success("下载成功");
+          }
+        } catch (error) {
+          this.$message.error(error.message);
+        } finally {
+          
+        }
+      }
+    },
+    imagePreview() {
+      this.dialogVisible = true;
+    }
+  }
+};
+</script>
+<style lang="scss">
+  .preview-images {
+    margin-top: 5vh !important;
+    margin-bottom: 5vh;
+    max-height: 90vh;
+    box-sizing: border-box;
+    background: transparent;
+    .el-dialog__header {
+      padding: 0;
+
+      .el-dialog__headerbtn {
+        position: fixed;
+        top: 20px;
+        right: 20px;
+        .el-icon-close {
+          color: #fff;
+          font-size: 30px;
+          font-weight: normal;
+        }
+      }
+    }
+    .el-dialog__body {
+      padding: 0;
+    }
+    .images-dialog {
+      overflow: hidden;
+      width: 80%;
+      margin: 0 auto;
+    }
+    .images-dialog__left,.images-dialog__right {
+      float: left;
+      width: 50%;
+      display: flex;
+      justify-content: center;
+    }
+    .el-carousel {
+      width: 80%;
+      aspect-ratio: 3/4;
+      .el-carousel__container {
+        height: 100%;
+      }
+      .el-carousel__item {
+        display: flex;
+        align-items: center;
+      }
+      img {
+        display: block;
+        width: 100%;
+      }
+    }
+    .images-dialog__right {
+      p {
+        width: 80%;
+        aspect-ratio: 3/4;
+        background: #fff;
+        display: flex;
+        align-items: center;
+        overflow: hidden;
+        margin: 0;
+      }
+      img {
+        display: block;
+        width: 100%;
+      }
+    }
+  }
+</style>
+
+

+ 572 - 0
src/views/crop-list/index.vue

@@ -0,0 +1,572 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <div class="filter-container-lt">
+        <el-form :inline="true" label-position="left" :model="listQuery">
+          <el-form-item>
+            <el-input clearable v-model="listQuery.sku" placeholder="搜索SKU"
+            ></el-input>
+          </el-form-item>
+          <el-form-item>
+            <el-select clearable v-model="listQuery.status" placeholder="请选择状态">
+              <el-option
+                v-for="item in statusSets"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value">
+              </el-option>
+            </el-select>
+          </el-form-item>
+          <el-button type="primary" @click="getList()">搜索</el-button >
+          <el-button type="primary" @click="showCropSizeModal()">裁图尺寸</el-button >
+          <el-button type="primary" @click="aiCropPreview()" :loading="uploadLoading">AI裁图</el-button>
+        </el-form>
+      </div>
+    </div>
+    <div class="table-container">
+      <el-table  style="width: 100%" v-loading="listLoading" :key="tableKey" :data="list" row-key="id"
+        stripe border fit highlight-current-row @selection-change="handleSelectionChange">
+        <!-- <el-table-column type="selection" width="55" :selectable="selectable"></el-table-column> -->
+        <el-table-column label="SKU名称" align="center" min-width="100">
+          <template slot-scope="scope">
+            <el-popover
+              placement="bottom"
+              width="260"
+              trigger="manual"
+              v-model="scope.row._popoverVisible"
+            >
+              <div class="input-block">
+                <span style="display: block;">SKU:</span>
+                <el-input
+                  v-model="editValue"
+                  type="textarea"
+                  :rows="2"
+                />
+              </div>
+              <div class="input-block" style="margin-top: 10px;" v-if="scope.row.status >= 5">
+                <span style="display: block;">颜色代码:</span>
+                <el-input
+                  v-model="editValueColor"
+                  type="textarea"
+                  :rows="2"
+                />
+              </div>
+
+              <div class="popover-footer">
+                <el-button size="mini" @click="cancelEdit(scope.row)">取消</el-button>
+                <el-button size="mini" :loading="skuLoading" type="primary" @click="confirmEdit(scope.row)">确认</el-button>
+              </div>
+
+              <span slot="reference" class="click-text" @click="openEdit(scope.row)">
+                {{ scope.row.sku }} 
+                <template v-if="scope.row.colorCode">
+                  <br>
+                  {{ scope.row.colorCode }}
+                </template>
+              </span>
+            </el-popover>
+          </template>
+        </el-table-column>
+        <el-table-column label="原图" min-width="180" align="center">
+          <template slot-scope="scope">
+            <p v-if="scope.row.type == 1 && scope.row.referenceImagesList && scope.row.referenceImagesList.length" @click="openGallery(scope.row, 'default')">
+              <img style="max-width: 100px;"  :src="scope.row.referenceImagesList[0]"  alt=""></img>
+            </p>
+            <p style="margin: 0;cursor: pointer;" v-if="scope.row.type == 2 && scope.row.aiGeneratedImageVOList && scope.row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1).length" @click="openGallery(scope.row, 'default')">
+              <img style="max-width: 100px;"  :src="scope.row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1)[0].imageUrl"  alt="">
+            </p>
+          </template>
+        </el-table-column>
+        <el-table-column label="AI生成图" min-width="180" align="center">
+          <template slot-scope="scope">
+            <p style="margin: 0;cursor: pointer;" v-if="scope.row.aiGeneratedImageVOList && scope.row.aiGeneratedImageVOList.length" @click="openGallery(scope.row)">
+              <img style="max-width: 100px;"  :src="scope.row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1)[0].imageUrl"  alt="">
+            </p>
+          </template>
+        </el-table-column>
+        <el-table-column label="预计完成时间" min-width="150" align="center" prop="expectedCompletionTime" />
+        <el-table-column label="状态" min-width="120" align="center">
+          <template slot-scope="scope">
+            <el-tag v-if="scope.row.status == 0">提交中</el-tag>
+            <el-tag v-if="scope.row.status == 1">AI生成中</el-tag>
+            <el-tag type="warning" v-if="scope.row.status == 2">待审核</el-tag>
+            <el-tag type="warning" v-if="scope.row.status == 3">待复核</el-tag>
+            <el-tag type="success" v-if="scope.row.status == 4">待裁图</el-tag>
+            <el-tag type="warning" v-if="scope.row.status == 5">裁图待复核</el-tag>
+            <el-tag type="success" v-if="scope.row.status == 6">已完成</el-tag>
+            <el-tag type="danger" v-if="scope.row.status == 7">生图失败</el-tag>
+            <el-tag v-if="scope.row.status == 8">待推送</el-tag>
+            <el-tag v-if="scope.row.status == 9">裁图中</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="处理人" min-width="100" align="center" prop="creator" />
+        <el-table-column label="处理时间" min-width="100" align="center" prop="updateTime" />
+        <el-table-column label="操作" align="center" width="250" fixed="right">
+          <template slot-scope="scope">
+            <!-- scope.row.status == 2 || scope.row.status == 3 -->
+            <el-button size="mini" v-if="scope.row.status == 2 || scope.row.status == 3" @click="showImagesGroupModal(scope.row, 'review')">审核</el-button>
+            <el-button type="success" size="mini" v-if="scope.row.status * 1 >= 4 && scope.row.status != 7 && scope.row.status != 9 && scope.row.status != 6" @click="showImagesGroupModal(scope.row, 'crop')">裁图</el-button>
+            <el-button type="primary" v-if="scope.row.status * 1 > 1 && scope.row.status != 7" size="mini" @click="handleDownload(scope.row)">下载</el-button>
+            <!-- 自动生成 1 手动生成 2 -->
+            <el-button type="info" size="mini" v-if="scope.row.type != 2" @click="handleRegenerate(scope.row)">重生成</el-button>
+
+            <!-- <el-dropdown v-if="scope.row.status == 8" @command="uploadGalleryList($event, scope.row)">
+              <el-button type="warning" size="mini">
+                上传图库<i class="el-icon-arrow-down el-icon--right"></i>
+              </el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="1">覆盖模式</el-dropdown-item>
+                <el-dropdown-item command="2">追加模式</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown> -->
+            <el-popover
+              placement="left"
+              width="320"
+              trigger="click"
+            >
+              <!-- 日志内容 -->
+              <div v-if="scope.row.operationLogs && scope.row.operationLogs.length" style="max-height: 500px;overflow-y: auto;">
+                <div
+                  v-for="(log, index) in scope.row.operationLogs"
+                  :key="index"
+                  class="log-item"
+                >
+                  <div class="log-user">{{ log.userName }}</div>
+                  <div class="log-action">{{ log.msg }}</div>
+                  <div class="log-time">{{ log.updateTime }}</div>
+                </div>
+              </div>
+
+              <div v-else class="empty-log">
+                暂无操作日志
+              </div>
+
+              <!-- 触发按钮 -->
+              <el-button
+                slot="reference"
+                size="mini"
+                type="danger"
+              >
+                操作日志
+              </el-button>
+            </el-popover>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+    <!-- 分页 -->
+    <swPage v-if="total > 0" key="2" :listQuery="listQuery" :total="total" pos="btmRight" @retPage="retPage" />
+    <crop-size-modal ref="CropSizeModal" :list="sizeList" @success="getSizeTemplate()"></crop-size-modal>
+    <images-group-modal ref="ImagesGroupModal" :sizeList="sizeList" @update-status="updateStatus" @update-images="updateImages" @update-success="handleFilter()"></images-group-modal>
+  </div>
+</template>
+
+<script>
+import waves from "@/directive/waves";
+import swPage from "@/views/common/swPage";
+import CropSizeModal from "./components/CropSizeModal";
+import ImagesGroupModal from "./components/ImagesGroupModal";
+import { auditStatus } from "@/constants/index";
+import { getValueByKey } from "@/utils/index";
+import permission from "@/directive/permission";
+import { genCidHex16 } from '@/utils/index'
+import { Fancybox } from "@fancyapps/ui";
+import request from '@/utils/request'
+import "@fancyapps/ui/dist/fancybox.css";
+import JSZip from 'jszip'
+import { saveAs } from 'file-saver'
+
+export default {
+  name: "cropList",
+  directives: {
+    waves,
+    permission
+  },
+  components: {
+    swPage,
+    CropSizeModal,
+    ImagesGroupModal
+  },
+  computed: {
+  },
+  data() {
+    return {
+      auditStatus,
+      tableKey: 0,
+      list: [],
+      total: 0,
+      skuLoading: false,
+      editValue: '',
+      editValueColor: '',
+      listLoading: false,
+      uploadLoading: false,
+      listQuery: {
+        currentPage: 1,
+        pageSize: 10,
+        sku: null,
+        status: ''
+      },
+      sizeList: [],
+      multipleSelection: []
+    };
+  },
+  //页面创建的时候执行
+  created() {
+    this.getSizeTemplate();
+    this.getList();
+  },
+  computed: {
+    statusSets() {
+      return [
+        {label: '提交中', value: '0'},
+        {label: 'AI生成中', value: '1'},
+        {label: '待审核', value: '2'},
+        {label: '待复核', value: '3'},
+        {label: '待裁图', value: '4'},
+        {label: '裁图待复核', value: '5'},
+        {label: '已完成', value: '6'},
+        {label: '生图失败', value: '7'},
+        {label: '待推送', value: '8'},
+        {label: '裁图中', value: '9'}
+      ]
+    }
+  },
+  mounted() {
+    document.addEventListener('click', this.handleClickOutside)
+  },
+  beforeDestroy() {
+    document.removeEventListener('click', this.handleClickOutside)
+  },
+  methods: {
+    aiCropPreview() {
+      this.$refs.ImagesGroupModal.init();
+    },
+    selectable(row, index) {
+      // 返回 false → 该行不可选
+      // 返回 true  → 该行可选
+      return row.status == 6 || row.status == 8;
+    },
+    handleRegenerate(row) {
+      this.listLoading = true;
+      request({
+        url: '/imageTask/regenerate',
+        method: 'post',
+        data: {
+          sessionId: `${Date.now() + 10}_${genCidHex16()}`,
+          applicationId: 5,
+          id: row.id,
+        }
+      }).then(res => {
+        if (res.code == 200) {
+          this.getList();
+          this.$message.success("操作成功");
+        }
+      }).finally(() => {
+        this.listLoading = false;
+      })
+    },
+    getSizeTemplate() {
+      request({
+        url: '/sizeTemplate/list',
+        method: 'get'
+      }).then(res => {
+        if (res.code == 200) {
+          this.sizeList = res.data;
+        }
+      }).finally(() => {
+        
+      })
+    },
+    updateImages(id, imageId){
+      this.list.forEach(item => {
+        if (item.id == id) {
+          item.aiGeneratedImageVOList.map((acc, index) => {
+            if (acc.id == imageId) {
+              item.aiGeneratedImageVOList.splice(index, 1); 
+            }
+          })
+        }
+      })
+    },
+    updateStatus(id, status) {
+      this.list.forEach(item => {
+        if (item.id == id) {
+          item.status = status;
+        }
+      })
+    },
+    handleClickOutside(e) {
+      // 如果点击发生在 popover 或 reference 内,不处理
+      const popovers = document.querySelectorAll('.el-popover')
+      const references = document.querySelectorAll('.click-text')
+
+      for (let el of [...popovers, ...references]) {
+        if (el.contains(e.target)) return
+      }
+
+      // 否则关闭所有 popover
+      this.list.forEach(item => {
+        item._popoverVisible = false
+      })
+    },
+    openEdit(row) {
+      // 先关闭所有 popover
+      this.list.forEach(item => {
+        item._popoverVisible = false;
+      })
+      row._popoverVisible = true;
+      this.editValue = row.sku;
+      this.editValueColor = row.colorCode;
+    },
+    cancelEdit(row) {
+      row._popoverVisible = false
+    },
+    confirmEdit(row) {
+      // 👉 这里可直接调用保存接口
+      const params = {
+        sessionId: `${Date.now() + 10}_${genCidHex16()}`,
+        applicationId: 5,
+        id: row.id,
+        sku: this.editValue
+      }
+      if (row.status >= 5) {
+        params['colorCode'] = this.editValueColor;
+      }
+      this.skuLoading = true;
+      request({
+        url: '/imageTask/update',
+        method: 'post',
+        data: params
+      }).then(res => {
+        if (res.code == 200) {
+          row._popoverVisible = false;
+          row.sku = this.editValue;
+          if (row.status >= 5) {
+            row.colorCode = this.editValueColor;
+          }
+          this.$message.success("操作成功");
+        }
+      }).finally(() => {
+        this.skuLoading = false;
+      })
+    },
+    getValueByKey,
+    getList() {
+      this.listLoading = true;
+      const params = {}
+      for (const key in this.listQuery) {
+        const value = this.listQuery[key]
+        if (value !== null && value !== '' && value !== undefined) {
+          params[key] = value
+        }
+      }
+      request({
+        url: '/imageTask/page',
+        method: 'get',
+        params: params
+      }).then(res => {
+        if (res.code == 200) {
+          this.total = res.data.total;
+          this.list = res.data.rows;
+        }
+      }).finally(() => {
+        this.listLoading = false
+      })
+    },
+    retPage() {
+      //分页
+      this.getList();
+    },
+    handleFilter() {
+      this.listQuery.page = 1;
+      this.getList();
+    },
+    // 上传图库
+    uploadGalleryList(command, row) {
+      this.listLoading = true;
+      const params = {
+        taskId: row.id,
+        uploadType: command || '2'
+      }
+      request({
+        url: '/imageTask/uploadGallery',
+        method: 'post',
+        data: params
+      }).then(res => {
+        if (res.code == 200) {
+          this.$message.success(res.msg || '操作成功!');
+        }
+      }).finally(() => {
+        this.listLoading = false
+      })
+    },
+    openGallery(row, type) {
+      let images = [];
+      if (type == 'default') {
+        if (row.type == 1) {
+          images = [...row.referenceImagesList, ...row.originalImagesList].slice(0, 10);
+        } else {
+          images = row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1).slice(0, 10).map(acc => {
+            return acc.imageUrl
+          });
+        }
+      } else {
+        if (row.type == 1) {
+          row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1).slice(0, 10).forEach(acc => {
+            images.push(acc.imageUrl)
+          })
+        } else {
+          row.aiGeneratedImageVOList.filter(acc => acc.imageType == 2).slice(0, 10).forEach(acc => {
+            images.push(acc.imageUrl)
+          })
+        }
+        if (!images.length) {
+          row.aiGeneratedImageVOList.slice(0, 10).forEach(acc => {
+            images.push(acc.imageUrl)
+          })
+        }
+      }
+
+      Fancybox.show(
+        images.map((src) => ({
+          src,
+          type: "image",
+        })),
+        {
+          startIndex: 0,
+        }
+      );
+    },
+    handleSelectionChange(val) {
+      this.multipleSelection = val;
+    },
+    async runWithConcurrencyLimit(tasks, limit = 5) {
+      const executing = []
+      const results = []
+
+      for (const task of tasks) {
+        const p = Promise.resolve().then(task)
+        results.push(p)
+        executing.push(p)
+
+        p.finally(() => {
+          const index = executing.indexOf(p)
+          if (index > -1) executing.splice(index, 1)
+        })
+
+        if (executing.length >= limit) {
+          await Promise.race(executing)
+        }
+      }
+
+      return Promise.allSettled(results)
+    },
+    async handleDownload(row) {
+      const imagesList = row.aiGeneratedImageVOList
+      if (!imagesList || !imagesList.length) return
+
+      this.listLoading = true
+
+      const zip = new JSZip()
+      const folder = zip.folder('images')
+
+      const tasks = imagesList.map((item, index) => async () => {
+        try {
+          const res = await fetch(item.imageUrl)
+          const blob = await res.blob()
+
+          // 更安全的文件名解析
+          const urlObj = new URL(item.imageUrl)
+          const fileName = urlObj.pathname.split('/').pop()
+
+          folder.file(fileName || `image_${index + 1}.jpg`, blob)
+        } catch (err) {
+          console.error('下载失败:', item.imageUrl, err)
+        }
+      })
+
+      // 并发限制(5 个)
+      await this.runWithConcurrencyLimit(tasks, 5)
+
+      const zipBlob = await zip.generateAsync({ type: 'blob' })
+      saveAs(zipBlob, `${row.sku || 'images'}.zip`)
+
+      this.listLoading = false
+    },
+    showCropSizeModal() {
+      this.$refs.CropSizeModal.show();
+    },
+    showImagesGroupModal(row, type) {
+      this.$refs.ImagesGroupModal.show(row, type);
+    }
+  }
+};
+</script>
+
+<style rel="stylesheet/scss" lang="scss" scoped>
+@import "@/styles/layout.scss";
+
+.table-container {
+  .el-button+.el-button {
+    margin-left: 7px;
+    margin-bottom: 10px;
+  }
+  .el-button+span,
+  .el-button+.el-dropdown {
+    margin-left: 7px;
+  }
+}
+.button {
+  .el-button {
+    margin-bottom: 10px;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+}
+.log-item {
+  padding: 6px 0;
+  border-bottom: 1px dashed #eee;
+}
+
+.log-item:last-child {
+  border-bottom: none;
+}
+
+.log-user {
+  font-weight: 500;
+  color: #ae8877;
+}
+
+.log-action {
+  font-size: 13px;
+  margin: 2px 0;
+}
+
+.log-time {
+  font-size: 12px;
+  color: #999;
+}
+
+.empty-log {
+  text-align: center;
+  color: #aaa;
+  padding: 20px 0;
+}
+.click-text {
+  cursor: pointer;
+  color: #409eff;
+}
+
+.click-text:hover {
+  text-decoration: underline;
+}
+
+.popover-footer {
+  margin-top: 10px;
+  text-align: right;
+}
+
+
+</style>

+ 5 - 3
src/views/design-agent/components/productRepoModal.vue

@@ -96,7 +96,8 @@ export default {
         currentPage: 1,
         pageSize: 10,
         productCode: null
-      }
+      },
+      imagesType: null
     };
   },
   computed: {
@@ -110,9 +111,10 @@ export default {
     }
   },
   methods: {
-    show(row) {
+    show(row, type) {
       this.dialogVisible = true;
       this.delArr = [];
+      this.imagesType = type;
       this.chooseImageList = JSON.parse(JSON.stringify(row));
       this.getList();
     },
@@ -159,7 +161,7 @@ export default {
     },
     changeImageList(checked, index) {
       if (checked) {
-        if (this.chooseImageList.length >= 5) {
+        if (this.chooseImageList.length >= 5 && this.imagesType === null) {
           this.$set(this.currentImageList[index], 'checked', false)
           this.$message.error("最多只能添加5张图片哦!");
         } else {

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

@@ -51,6 +51,7 @@
 }
 .design-agent-block {
   height: 100%;
+  overflow-y: auto;
   * {
     margin: 0;
     list-style: none;
@@ -699,7 +700,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 {
@@ -769,6 +770,7 @@
         }
         .picture-card-wrapper {
           position: relative;
+          width: 100%;
 
           &:hover .custom-file__btns{
             display: flex;
@@ -808,7 +810,7 @@
         }
         .upload-demo {
           width: 100%;
-          height: 240px;
+          height: 200px;
 
           &.upload-demo__data {
             width: 100px;
@@ -836,7 +838,7 @@
 
           .el-upload-dragger {
             width: 100%;
-            height: 240px;
+            height: 200px;
             border: 1px solid #ae8878;
 
             .el-upload__info {
@@ -849,11 +851,28 @@
               .el-upload__text,.el-upload__tip {
                 color: #ae8877;
                 font-size: 14px;
+                line-height: 20px;
               }
             }
           }
         }
       }
+      .tips {
+        font-size: 12px;
+        color: #b8180c;
+        display: block;
+        line-height: 14px;
+        margin-top: 5px;
+      }
+      .tools-sku {
+        display: flex;
+        gap: 10px;
+        margin-top: 20px;
+
+        /deep/ .el-input {
+          max-width: 80%;
+        }
+      }
       .avatar-black {
         position: relative;
         width: 100%;
@@ -912,6 +931,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 {

+ 496 - 28
src/views/design-agent/feature-mod-index.vue

@@ -48,17 +48,134 @@
             <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" @change="handleBatch"></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}`">
+                              <draggable
+                                tag="div"
+                                class="swiper-wrapper"
+                                :list="item.content"
+                                :options="{ animation: 150 }"
+                              >
+                                <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="imgUploadDelGroup(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>
+                              </draggable>
+                              <!-- 左右箭头 -->
+                              <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">点击或将图片拖拽到这里上传</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>
+                        <span class="tips" v-if="item.content.length">拖动图片改变图片位置</span>
+                        <div class="tools-sku">
+                          SKU <el-input v-model="item.sku" size="small" placeholder="请输入sku"></el-input>
+                        </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">
                       <!-- 自定义显示每个图片的重新上传按钮 -->
                       <div class="swiper-container-block" v-if="ruleForm.imageList.length">
                         <div class="swiper-container my-swiper" ref="swiperContainer">
-                          <div class="swiper-wrapper">
+                          <draggable
+                            tag="div"
+                            class="swiper-wrapper"
+                            :list="ruleForm.imageList"
+                            :options="{ animation: 150 }"
+                          >
                             <div class="swiper-slide" v-for="(group, index) in ruleForm.imageList" :key="index">
                               <div class="img-group">
                                 <div class="custom-file-list" >
@@ -82,7 +199,7 @@
                                 </div>
                               </div>
                             </div>
-                          </div>
+                          </draggable>
                           <!-- 左右箭头 -->
                           <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>
@@ -122,6 +239,7 @@
                         </div>
                       </el-upload>
                     </div>
+                    <span class="tips" v-if="ruleForm.imageList.length">拖动图片改变图片位置</span>
                   </el-form-item>
                   <el-form-item prop="imageUrl" required v-if="pagesData.applicationId == 3">
                     <template #label>
@@ -172,7 +290,7 @@
                     <template #label>
                       <div class="label-title">
                         <span>
-                          <em class="no">{{pagesData.applicationId == 1 ? '1' : '2'}}</em> 画面描述{{ pagesData.applicationId == 3 ? '(选填)' : '' }}
+                          <em class="no">{{pagesData.applicationId == 1 ? '1' : (ruleForm.isBatch ? '3' : '2')}}</em> 画面描述{{ pagesData.applicationId == 3 ? '(选填)' : '' }}
                         </span>
                         <el-tooltip 
                           class="item" 
@@ -182,19 +300,40 @@
                         >
                           <i class="el-icon-info" style="color: #ccc;cursor: pointer;" />
                         </el-tooltip>
+
+                        <div class="clothing-type__block" style="margin-left: 20px;" v-if="ruleForm.isBatch">
+                          <el-select v-model="ruleForm.clothingType" size="small" placeholder="衣服类型">
+                            <el-option
+                              v-for="item in clothingTypes"
+                              :key="item.value"
+                              :label="item.label"
+                              :value="item.value">
+                            </el-option>
+                          </el-select>
+                        </div>
                       </div>
                     </template>
                     <el-input
                       type="textarea"
                       :rows="5"
-                      :placeholder="pagesData.placeholder"
+                      :placeholder="ruleForm.isBatch ? '例如:把图2女士身上衣服替换到图1模特身上' : pagesData.placeholder"
                       :maxlength="1000"
                       show-word-limit
                       v-model="ruleForm.screenDescription">
                     </el-input>
                     <span class="clear-block" v-if="ruleForm.screenDescription"  @click.stop="clearDescription()">清除</span>
                   </el-form-item>
-                  <template v-if="pagesData.applicationId == 2">
+                  <el-form-item v-if="pagesData.applicationId == 2 && ruleForm.isBatch">
+                    <template #label>
+                      <div class="label-title">
+                        <span><em class="no">4</em> 批量生产</span>
+                      </div>
+                    </template>
+                    <div>
+                      <el-input-number size="small" :min="1" :step="1" step-strictly v-model="ruleForm.batchCount" :controls="false"></el-input-number> 张
+                    </div>
+                  </el-form-item>
+                  <template v-if="pagesData.applicationId == 2 && !ruleForm.isBatch">
                     <el-form-item>
                       <template #label>
                         <div class="label-title">
@@ -377,12 +516,14 @@ import downloadUtil from "@/utils/downloadUtil";
 import { genCidHex16, fetchStreamText } from '@/utils/index'
 import request from '@/utils/request'
 import productRepoModal from './components/productRepoModal'
+import draggable from "vuedraggable"; 
 // 引入 Swiper 及样式
 import Swiper from 'swiper'
 import 'swiper/dist/css/swiper.min.css'
 export default {
   components: {
-    productRepoModal
+    productRepoModal,
+    draggable
   },
   data() {
     return {
@@ -394,17 +535,38 @@ 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: false,
+        editableTabsValue: '1',
+        editableTabs: [{
+          title: '图片组 1',
+          name: '1',
+          sku: '',
+          content: []
+        }],
+        tabIndex: 1,
+        batchCount: 1,
+        clothingType: ''
       },
+      swiperList: {},
       ratioList: ['3:2', '16:9', '1:1', '2:3', '3:4', '9:16'],
+      clothingTypes: [
+        {label: '所有上装', value: 1},
+        {label: '所有下装', value: 2},
+        {label: '连衣裙', value: 3},
+        {label: '整套衣服', value: 4}
+      ],
       rules: {
         imageUrl: [
           { validator: (rule, value, callback) => this.checkImage(rule, value, callback), trigger: "change" }
@@ -412,6 +574,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) => {
@@ -424,7 +589,7 @@ export default {
                 callback()
               }
             },
-            trigger: ['blur']
+            trigger: ['blur', 'change']
           }
         ]
       },
@@ -501,9 +666,128 @@ 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: {
+    handleBatch() {
+      this.$nextTick(() => {
+        // this.$refs.ruleForm.clearValidate()
+      })
+    },
+    addTab(targetName) {
+      this.tabParentIndex = targetName
+      let newTabName = ++this.ruleForm.tabIndex + '';
+      this.ruleForm.editableTabs.push({
+        title: `图片组 ${targetName}`,
+        name: newTabName,
+        sku: '',
+        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) {
@@ -523,12 +807,44 @@ export default {
         slidesPerGroup: 2,
         spaceBetween: 10,
         loop: false,
+        allowTouchMove: false,
         navigation: {
           nextEl: '.swiper-button-next',
           prevEl: '.swiper-button-prev'
         }
       })
     },
+    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,
+          allowTouchMove: 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 +865,72 @@ export default {
         callback();
       }
     },
+    checkImageGroup(rule, value, callback) {
+      const imgs = this.ruleForm.editableTabs.filter(acc => acc.content.length)
+      const skus = this.ruleForm.editableTabs.filter(acc => acc.sku)
+      if (!imgs.length || !skus.length) {
+        if (!skus.length) {
+          callback(new Error('SKU为必填项!'));
+        } else {
+          callback(new Error('请至少上传一组上衣图片!'));
+        }
+      } else {
+        callback();
+      }
+    },
     submitForm() {
       this.$refs['ruleForm'].validate(async (valid) => {
         if (valid) {
+          if (this.pagesData.applicationId == 2 && this.ruleForm.isBatch) {
+            let keyword_1 = '整套服装';
+            let keyword_2 = '那套服装';
+            let defaultDescription = `任务:进行精确的虚拟服装替换。
+            参考图像分析:
+            请仔细查看前几张图像([图片1], [图片2]...[倒数第二张])。识别这些图像中模特所穿戴的{{s%}},包括其款式、材质、颜色、图案、纹理以及在直播间灯光下的所有细节。
+            目标图像定义:
+            请查看最后一张图像([最后一张图片])。这张图片定义了目标人物的身份、面部特征、发型、当前的姿势以及所处的背景环境。
+            执行操作:
+            保持最后一张图像中的人物(脸、发型、身体形态)和背景完全不变。将前几张参考图像中的{{ss%}},自然、真实地“穿”在最后一张图像的人物身上。
+            细节要求:
+            1. 新的服装必须完美贴合目标人物的当前姿势。
+            2. 必须根据最后一张图像中的环境光线,在衣服上生成极其真实的褶皱、阴影和高光。
+            3. 确保服装的材质感(如棉麻、丝绸、牛仔等)得到完美还原。`;
+            if (this.ruleForm.clothingType) {
+              const keywords = this.clothingTypes.filter(acc => acc.value == this.ruleForm.clothingType);
+              if (keywords.length) {
+                keyword_1 = keywords[0].label;
+                keyword_2 = keywords[0].label;
+              }
+            }
+            defaultDescription = defaultDescription.replace(/{{s%}}/g, keyword_1).replace(/{{ss%}}/g, keyword_2);
+            this.comfirmLoading = true;
+            let payload = {
+              sessionId: `${Date.now() + 10}_${genCidHex16()}`,
+              applicationId: 5,
+              prompt: this.ruleForm.screenDescription || defaultDescription,
+              poseImages: this.ruleForm.imageList,
+              count: this.ruleForm.batchCount,
+              clothesItems: this.ruleForm.editableTabs.map(acc => {
+                return {
+                  sku: acc.sku,
+                  clothesImages: acc.content
+                }
+              })
+            }
+            request({
+              url: '/imageTask/submit',
+              method: 'post',
+              data: payload
+            }).then(res => {
+              if (res.code == 200) {
+                this.$router.push(`/cropList/cropListIndex`);
+                this.$message.success(res.msg || '操作成功!');
+              }
+            }).finally(() => {
+              this.comfirmLoading = false;
+            })
+            return;
+          }
           this.comfirmLoading = true;
           this.workId = null;
           this.isSave = false;
@@ -769,6 +1148,76 @@ export default {
         });
       });
     },
+    replaceFileGroup(itemIndex, parentIndex) {
+      this.replaceTabIndex = itemIndex
+
+      if (this.$refs[`uploadRef-${parentIndex}`]) {
+        // 清空上传队列
+        this.$refs[`uploadRef-${parentIndex}`][0].uploadFiles = []
+
+        // 手动触发上传框选择文件
+        const input = this.$refs[`uploadRef-${parentIndex}`][0].$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].content.splice(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 +1284,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, type);
+        } else {
+          this.hasTabs = null;
+          this.$refs.productRepoModal.show(this.ruleForm.imageList, this.ruleForm.isBatch ? 'tabs' : null);
+        }
+      }
     }
   }
 };

+ 1 - 1
src/views/product-repo/index.vue

@@ -47,7 +47,7 @@ import "@fancyapps/ui/dist/fancybox.css";
 
 
 export default {
-  name: "TrendingTermList",
+  name: "productRepo",
   directives: {
     waves,
     permission

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff