Переглянути джерело

图生图批量操作&裁图功能

lushixing 2 місяців тому
батько
коміт
ed47999dda
71 змінених файлів з 1733 додано та 55 видалено
  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. 3 1
      src/permission.js
  50. 2 0
      src/router/index.js
  51. 1 1
      src/router/modules/AIAssetSearch.js
  52. 1 1
      src/router/modules/catwalkVideo.js
  53. 19 0
      src/router/modules/cropList.js
  54. 1 1
      src/router/modules/designAgentManager.js
  55. 1 1
      src/router/modules/faceSwapVideo.js
  56. 1 1
      src/router/modules/musicLibraryManager.js
  57. 1 1
      src/router/modules/oralVideo.js
  58. 1 1
      src/router/modules/productRepo.js
  59. 1 1
      src/router/modules/trendingTermList.js
  60. 1 1
      src/router/modules/userAuth.js
  61. 1 1
      src/router/modules/videoCoverManager.js
  62. 10 0
      src/styles/sidebar.scss
  63. 1 1
      src/utils/request.js
  64. 190 0
      src/views/crop-list/components/CropSizeModal.vue
  65. 806 0
      src/views/crop-list/components/ImagesGroupModal.vue
  66. 55 0
      src/views/crop-list/components/ImagesItem.vue
  67. 504 0
      src/views/crop-list/index.vue
  68. 5 3
      src/views/design-agent/components/productRepoModal.vue
  69. 17 0
      src/views/design-agent/design-agent.scss
  70. 102 39
      src/views/design-agent/feature-mod-index.vue
  71. 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"
   },

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/shopping.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon1.svg


Різницю між файлами не показано, бо вона завелика
+ 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>

Різницю між файлами не показано, бо вона завелика
+ 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>

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon15.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon16.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon17.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon18.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon19.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon2.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon20.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon21.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon22.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon23.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon24.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon25.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon26.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon27.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon28.svg


Різницю між файлами не показано, бо вона завелика
+ 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>

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon30.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon31.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon32.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon33.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon34.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon35.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon36.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon37.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon38.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon39.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon4.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon40.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon41.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon42.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon43.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon44.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon45.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon46.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon5.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon6.svg


Різницю між файлами не показано, бо вона завелика
+ 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>

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/icons/svg/sidebar-icon9.svg


+ 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-icon4" },
   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",

+ 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;
     }

+ 1 - 1
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拦截器

+ 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>
+
+

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

@@ -0,0 +1,806 @@
+<template>
+  <el-dialog title="结果预定与审核" width="80%" custom-class="reservation-approval-dialog" :visible.sync="dialogVisible">
+    <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="reservation-approval__status">
+          <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="pages == 'review' || (pages == 'crop' && statusPages != 4)">
+          <el-button
+            size="small"
+            :disabled="(pages == 'review' && statusPages == 4) || (pages == 'crop' && statusPages >= 6)"
+            :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)"
+            :loading="reviewLoading"
+            @click="handleConfirm"
+          >
+            {{ pages == 'crop' && statusPages == 4 ? '确认裁图' : '审核通过' }}
+          </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" v-for="(item, count) in cropImages" :key="count">
+                <h2>{{ item.title }}</h2>
+                <draggable
+                  tag="div"
+                  class="images-list-block"
+                  v-model="item.images"
+                  @start="onDragStart"
+                  @end="onDragEnd"
+                  :options="{ animation: 150 }">
+                  <div class="images-items" v-for="(acc, index) in item.images" :key="acc.id">
+                    <images-item :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"
+                :options="{ animation: 150 }">
+                <div class="images-items" v-for="(acc, index) in imagesList" :key="acc.id">
+                  <images-item :pages="pages" :item="acc" :index="index" :status-pages="statusPages" @delImage="delImage" @needRegenerate="needRegenerate"></images-item>
+                </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
+                }"
+                multiple 
+                :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>
+          </el-form>
+          <span class="regenerate-footer">
+            <el-button size="small" @click="resetForm">取消</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,
+      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: [],
+      dialogVisible: false,
+      saveLoading: false,
+      reviewLoading: false,
+      regenerateLoading: false,
+      comfirmLoading: false
+    };
+  },
+  computed: {
+    token() {
+      return getToken();
+    },
+    cropImages() {
+      if (this.statusPages >= 5) {
+        return [
+          {
+            title: '主图1:1',
+            images: this.imagesList.filter(acc => acc.position == 'main' && acc.width == acc.height)
+          },
+          {
+            title: '主图3:4',
+            images: this.imagesList.filter(acc => acc.position == 'main' && acc.width != acc.height)
+          },
+          {
+            title: '竖图2:3',
+            images: this.imagesList.filter(acc => acc.position == 'list')
+          }
+        ]
+      } else {
+        return []
+      }
+    }
+  },
+  watch: {
+    dialogVisible(val) {
+      if (!val) {
+        this.comfirmLoading = false;
+        this.regenerateLoading = false;
+        this.resetForm();
+      }
+    }
+  },
+  methods: {
+    show(row, type) {
+      this.dialogVisible = true;
+      this.$nextTick(() => {
+        this.pages = type;
+        this.paramsId = row.id;
+        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)
+      })
+    },
+    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;
+      })
+    },
+    handleConfirm() {
+      if (this.pages == 'crop' && this.statusPages == 4) {
+        // 裁图
+        if (!this.form.cropType || !this.form.colorCode) {
+          this.$message.error('请选择裁切类型或填写颜色代码!');
+          return;
+        }
+        this.reviewLoading = true;
+        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 => {
+              return {
+                position: acc.positionCode,
+                height: acc.height,
+                width: acc.width,
+                sort: acc.sort
+              }
+            })
+          }
+        }).then(res => {
+          if (res.code == 200) {
+            this.statusPages += 1; 
+            this.$emit('update-success');
+            this.$emit('update-status', this.paramsId, this.statusPages);
+            // 更新imagesList
+            this.imagesList = res.data.map(acc => {
+              return {
+                id: acc.id,
+                imageUrl: acc.imageUrl,
+                imageOrder: acc.imageOrder,
+                width: acc.width,
+                height: acc.height
+              }
+            })
+            this.$message.success(res.msg || '操作成功!');
+          }
+        }).finally(() => {
+          this.reviewLoading = false;
+        })
+        return;
+      }
+      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)
+    },
+
+    // 拖动结束:按当前位置重新赋值 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(() => {
+        request({
+          url: '/image/delete',
+          method: 'post',
+          data: [row.id]
+        }).then(res => {
+          if (res.code == 200) {
+            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(() => {
+          
+        })
+      });
+    },
+    async handleRegenerate() {
+      if (this.pages == 'review' && !this.form.prompt) {
+        this.$message.error('请填写内容!');
+        return;
+      }
+      if (this.pages == 'crop' && !this.form.imageUrl) {
+        this.$message.error('请上传裁图图片!');
+        return;
+      }
+      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;
+      }
+    },
+    needRegenerate(index) {
+      this.form.imageId = this.imagesList[index].id;
+      this.form.regenerateImage = this.imagesList[index].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) {
+        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.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;
+
+        &.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%;
+      }
+
+      &.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;
+          }
+        }
+        .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%;
+    }
+  }
+</style>
+
+

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

@@ -0,0 +1,55 @@
+<template>
+  <div>
+    <div class="images-items__img">
+      <span class="size">序号:{{ item.imageOrder }}</span>
+      <span class="size" style="top: 30px;" v-if="statusPages > 4">{{ item.width }} x {{ item.height }}</span>
+      <el-image 
+        style="max-height: 100%;"
+        :src="item.imageUrl" 
+        :preview-src-list="[item.imageUrl]">
+      </el-image>
+      <div class="btns-group" v-if="(pages == 'review' && [2,3].includes(statusPages*1)) || (pages == 'crop' && [5].includes(statusPages*1))">
+        <el-button size="small" @click="delImage(item, index)">删除</el-button>
+        <el-button type="primary" size="small" @click="needRegenerate(index)">重生成</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "ImagesItem",
+  props: {
+    item: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    },
+    index: {
+      type: Number
+    },
+    statusPages: {
+      type: [String, Number],
+      default: 1
+    },
+    pages: {
+      type: String,
+      default: 'review'
+    }
+  },
+  data() {
+    return {};
+  },
+  methods: {
+    delImage(item, index) {
+      this.$emit('delImage', item, index);
+    },
+    needRegenerate(index) {
+      this.$emit('needRegenerate', index);
+    }
+  }
+};
+</script>
+
+

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

@@ -0,0 +1,504 @@
+<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="productsSync()" icon="el-icon-upload" :loading="uploadLoading">上传图库</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">
+          <template slot-scope="scope">
+            <el-popover
+              placement="bottom"
+              width="260"
+              trigger="manual"
+              v-model="scope.row._popoverVisible"
+            >
+              <el-input
+                v-model="editValue"
+                type="textarea"
+                :rows="3"
+              />
+
+              <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 }}
+              </span>
+            </el-popover>
+          </template>
+        </el-table-column>
+        <el-table-column label="原图" min-width="100" align="center">
+          <template slot-scope="scope">
+            <p style="margin: 0;cursor: pointer;" v-if="scope.row.referenceImagesList && scope.row.referenceImagesList.length" @click="openGallery(scope.row, 'default')">
+              <img style="max-width: 100px;"  :src="scope.row.referenceImagesList[0]"  alt="">
+            </p>
+          </template>
+        </el-table-column>
+        <el-table-column label="AI生成图" min-width="100" 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="100" align="center" prop="expectedCompletionTime" />
+        <el-table-column label="状态" min-width="100" 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>
+          </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="160" fixed="right">
+          <template slot-scope="scope">
+            <!-- scope.row.status == 2 || scope.row.status == 3 -->
+            <el-button type="text" v-if="scope.row.status == 2 || scope.row.status == 3" size="mini" @click="showImagesGroupModal(scope.row, 'review')">审核</el-button>
+            <el-button type="text" v-if="scope.row.status * 1 >= 4 && scope.row.status != 7" size="mini" @click="showImagesGroupModal(scope.row, 'crop')">裁图</el-button>
+            <el-button v-if="scope.row.status * 1 > 1 && scope.row.status != 7" type="text" @click="handleDownload(scope.row)" size="mini">下载</el-button>
+            <el-button type="text" @click="handleRegenerate(scope.row)" size="mini">重生成</el-button>
+            <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.creator }}</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"
+                type="text"
+                size="small"
+              >
+                操作日志
+              </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="getList()"></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: '',
+      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'}
+      ]
+    }
+  },
+  mounted() {
+    document.addEventListener('click', this.handleClickOutside)
+  },
+  beforeDestroy() {
+    document.removeEventListener('click', this.handleClickOutside)
+  },
+  methods: {
+    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
+    },
+    cancelEdit(row) {
+      row._popoverVisible = false
+    },
+    confirmEdit(row) {
+      // 👉 这里可直接调用保存接口
+      this.skuLoading = true;
+      request({
+        url: '/imageTask/update',
+        method: 'post',
+        data: {
+          sessionId: `${Date.now() + 10}_${genCidHex16()}`,
+          applicationId: 5,
+          id: row.id,
+          sku: this.editValue
+        }
+      }).then(res => {
+        if (res.code == 200) {
+          row._popoverVisible = false;
+          row.sku = this.editValue;
+          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();
+    },
+    productsSync() {
+      const params = this.listQuery ? { productCode: this.listQuery.productCode } : {}
+      if (!this.listQuery.productCode) {
+        this.$message.error("产品款号不能为空!");
+        return;
+      }
+      this.productsSyncLoading = true;
+      pullGalleryProduct(params).then(res => {
+        if (200 == res.code) {
+          this.$message.success(res.msg || "操作成功");
+        }
+        this.productsSyncLoading = false;
+      }).finally(() => {
+        this.productsSyncLoading = false;
+      })
+    },
+    openGallery(row, type) {
+      let images = [];
+      if (type == 'default') {
+        images = row.referenceImagesList.slice(0, 10);
+      } else {
+        row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1).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: 0;
+  }
+}
+.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 {

+ 17 - 0
src/views/design-agent/design-agent.scss

@@ -769,6 +769,7 @@
         }
         .picture-card-wrapper {
           position: relative;
+          width: 100%;
 
           &:hover .custom-file__btns{
             display: flex;
@@ -855,6 +856,22 @@
           }
         }
       }
+      .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%;

+ 102 - 39
src/views/design-agent/feature-mod-index.vue

@@ -54,7 +54,7 @@
                       <div class="label-title">
                         <span>批量</span>
                       </div>
-                      <el-switch v-model="ruleForm.isBatch"></el-switch>
+                      <el-switch v-model="ruleForm.isBatch" @change="handleBatch"></el-switch>
                     </template>
                   </el-form-item>
 
@@ -83,7 +83,12 @@
                           <!-- 自定义显示每个图片的重新上传按钮 -->
                           <div class="swiper-container-block" v-if="item.content.length">
                             <div class="swiper-container my-swiper" :ref="`swiperContainer-${parentIndex}`">
-                              <div class="swiper-wrapper">
+                              <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" >
@@ -98,7 +103,7 @@
                                           </el-button>
                                           <el-button 
                                             class="replace-btn" 
-                                            @click="imgUploadDeGroup(index, parentIndex)">
+                                            @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>
@@ -107,7 +112,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>
@@ -138,7 +143,7 @@
                               </template>
                               <div class="el-upload__info" v-else>
                                 <i class="el-icon-upload" style="margin: 0;"></i>
-                                <div class="el-upload__text">点击或将图片拖拽到这里上传,最多上传 5 张图片</div>
+                                <div class="el-upload__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>
@@ -147,6 +152,10 @@
                             </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>
@@ -161,7 +170,12 @@
                       <!-- 自定义显示每个图片的重新上传按钮 -->
                       <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" >
@@ -185,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>
@@ -225,16 +239,7 @@
                         </div>
                       </el-upload>
                     </div>
-                  </el-form-item>
-                  <el-form-item v-if="pagesData.applicationId == 2 && ruleForm.isBatch">
-                    <template #label>
-                      <div class="label-title">
-                        <span><em class="no">3</em> 批量生产</span>
-                      </div>
-                    </template>
-                    <div>
-                      <el-input-number v-model="ruleForm.batchCount" :controls="false"></el-input-number> 张
-                    </div>
+                    <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>
@@ -281,11 +286,11 @@
                       </el-upload>
                     </div>
                   </el-form-item>
-                  <el-form-item prop="screenDescription" class="tool-direct-left__submit" v-if="!ruleForm.isBatch">
+                  <el-form-item prop="screenDescription" class="tool-direct-left__submit">
                     <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" 
@@ -300,13 +305,23 @@
                     <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>
+                  <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" 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>
@@ -490,12 +505,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 {
@@ -519,11 +536,12 @@ export default {
         imageRatioList: '',
         imageSizeSelect: '',
         imageSize: {},
-        isBatch: true,
+        isBatch: false,
         editableTabsValue: '1',
         editableTabs: [{
           title: '图片组 1',
           name: '1',
+          sku: '',
           content: []
         }],
         tabIndex: 1,
@@ -553,7 +571,7 @@ export default {
                 callback()
               }
             },
-            trigger: ['blur']
+            trigger: ['blur', 'change']
           }
         ]
       },
@@ -669,12 +687,18 @@ export default {
     }
   },
   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;
@@ -765,6 +789,7 @@ export default {
         slidesPerGroup: 2,
         spaceBetween: 10,
         loop: false,
+        allowTouchMove: false,
         navigation: {
           nextEl: '.swiper-button-next',
           prevEl: '.swiper-button-prev'
@@ -788,6 +813,7 @@ export default {
           slidesPerGroup: 2,
           spaceBetween: 10,
           loop: false,
+          allowTouchMove: false,
           navigation: {
             nextEl: '.swiper-button-next',
             prevEl: '.swiper-button-prev'
@@ -823,9 +849,13 @@ export default {
     },
     checkImageGroup(rule, value, callback) {
       const imgs = this.ruleForm.editableTabs.filter(acc => acc.content.length)
-      console.log(imgs, 1111)
-      if (!imgs.length) {
-        callback(new Error('请至少上传一组上衣图片!'));
+      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();
       }
@@ -833,8 +863,41 @@ export default {
     submitForm() {
       this.$refs['ruleForm'].validate(async (valid) => {
         if (valid) {
-          console.log(this.ruleForm.editableTabs, 232323232323)
-          if (this.ruleForm.isBatch) {
+          if (this.pagesData.applicationId == 2 && this.ruleForm.isBatch) {
+            if (!this.ruleForm.screenDescription) {
+              this.$message.error('画面描述为必填项!');
+              return;
+            }
+            if (this.ruleForm.screenDescription.trim() && this.ruleForm.screenDescription.trim().length < 3) {
+              this.$message.error('画面描述至少需要 3 个字符!');
+              return;
+            }
+            this.comfirmLoading = true;
+            let payload = {
+              sessionId: `${Date.now() + 10}_${genCidHex16()}`,
+              applicationId: 5,
+              prompt: this.ruleForm.screenDescription,
+              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;
@@ -1054,15 +1117,15 @@ export default {
         });
       });
     },
-    replaceFileGroup(itemIndex) {
+    replaceFileGroup(itemIndex, parentIndex) {
       this.replaceTabIndex = itemIndex
 
-      if (this.$refs[`uploadRef-${this.tabParentIndex}`]) {
+      if (this.$refs[`uploadRef-${parentIndex}`]) {
         // 清空上传队列
-        this.$refs[`uploadRef-${this.tabParentIndex}`].uploadFiles = []
+        this.$refs[`uploadRef-${parentIndex}`][0].uploadFiles = []
 
         // 手动触发上传框选择文件
-        const input = this.$refs[`uploadRef-${this.tabParentIndex}`].$el.querySelector('input[type=file]')
+        const input = this.$refs[`uploadRef-${parentIndex}`][0].$el.querySelector('input[type=file]')
         input.click()
       }
       // 在 handleSuccess 中会替换对应文件
@@ -1071,10 +1134,10 @@ export default {
       this.uploading = false
       this.$refs.ruleForm.clearValidate(['editableTabs']);
 
-      if (this.ruleForm.editableTabs[this.tabParentIndex].content.length >= 5) {
-        this.$message.error('最多只能上传5张图片!');
-        return;
-      }
+      // if (this.ruleForm.editableTabs[this.tabParentIndex].content.length >= 5) {
+      //   this.$message.error('最多只能上传5张图片!');
+      //   return;
+      // }
       if (this.replaceTabIndex !== null) {
         // 用新图片替换原有 URL
         if (res.code == 200) {
@@ -1107,7 +1170,7 @@ export default {
         cancelButtonText: "取消",
         type: "warning"
       }).then(() => {
-        this.ruleForm.editableTabs[this.tabParentIndex].contentsplice(itemIndex, 1);
+        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]) {
@@ -1231,10 +1294,10 @@ export default {
       if (this.$refs.productRepoModal) {
         if (type === 'tabs') {
           this.hasTabs = type;
-          this.$refs.productRepoModal.show(this.ruleForm.editableTabs[this.tabParentIndex].content);
+          this.$refs.productRepoModal.show(this.ruleForm.editableTabs[this.tabParentIndex].content, type);
         } else {
           this.hasTabs = null;
-          this.$refs.productRepoModal.show(this.ruleForm.imageList);
+          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

Деякі файли не було показано, через те що забагато файлів було змінено