ImagesGroupModal.vue 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090
  1. <template>
  2. <el-dialog title="结果预定与审核" width="80%" custom-class="reservation-approval-dialog" :visible.sync="dialogVisible" :close-on-click-modal="false">
  3. <div class="reservation-approval-block">
  4. <div class="reservation-approval-header">
  5. <div class="crop-type-block" v-if="pages == 'crop' && statusPages == 4">
  6. <span class="label">裁切类型:</span>
  7. <el-select v-model="form.cropType" clearable placeholder="请选择" size="small">
  8. <el-option
  9. v-for="item in cropTypes"
  10. :key="item.value"
  11. :label="item.label"
  12. :value="item.value">
  13. </el-option>
  14. </el-select>
  15. </div>
  16. <div class="crop-colorCode-block" style="display: flex;align-items: center;" v-if="pages == 'crop' && statusPages == 4">
  17. <span class="label" style="white-space: nowrap;">颜色代码:</span>
  18. <el-input v-model="form.colorCode" size="small"></el-input>
  19. </div>
  20. <div class="crop-colorCode-block" style="display: flex;align-items: center;" v-if="cropPreview && statusPages == 4">
  21. <span class="label" style="white-space: nowrap;">SKU:</span>
  22. <el-input v-model="form.sku" size="small"></el-input>
  23. </div>
  24. <div class="reservation-approval__status" v-if="!cropPreview">
  25. <ul v-if="pages == 'review'">
  26. <li :class="{'active': statusPages == 2}">待审核图</li>
  27. <li :class="{'active': statusPages == 3}">待复核</li>
  28. <li :class="{'active': statusPages == 4}">审核完成</li>
  29. </ul>
  30. <ul v-if="pages == 'crop'">
  31. <li :class="{'active': statusPages == 4}">待裁图</li>
  32. <li :class="{'active': statusPages == 5}">待复核</li>
  33. <li :class="{'active': statusPages >= 6}">已完成</li>
  34. </ul>
  35. </div>
  36. <div class="reservation-approval__save" v-if="!cropPreview && (pages == 'review' || (pages == 'crop' && statusPages >= 4))">
  37. <el-button
  38. size="small"
  39. :disabled="(pages == 'review' && statusPages == 4) || (pages == 'crop' && statusPages >= 6) || !imagesList.length"
  40. :loading="saveLoading"
  41. @click="saveHandle"
  42. >
  43. 保存修改
  44. </el-button>
  45. </div>
  46. <div class="reservation-approval__confirm">
  47. <el-button
  48. type="primary"
  49. size="small"
  50. :disabled="(pages == 'review' && statusPages == 4) || (pages == 'crop' && statusPages >= 6) || !imagesList.length"
  51. :loading="reviewLoading"
  52. @click="handleConfirm"
  53. >
  54. {{ pages == 'crop' && statusPages == 4 ? '确认裁图' : '审核通过' }}
  55. </el-button>
  56. </div>
  57. <div class="reservation-approval__submit" v-if="statusPages == 8">
  58. <el-button
  59. type="primary"
  60. size="small"
  61. :loading="submitLoading"
  62. @click="uploadGalleryList"
  63. >
  64. 上传图库
  65. </el-button>
  66. </div>
  67. </div>
  68. <div class="reservation-approval-body" :class="{'full': !form.regenerateImage}">
  69. <el-row>
  70. <el-col :span="24">
  71. <div class="images-list-wapper" v-if="statusPages >= 5">
  72. <div class="images-list__one">
  73. <h2>主图1:1</h2>
  74. <draggable
  75. tag="div"
  76. class="images-list-block"
  77. v-model="imagesList"
  78. @start="onDragStart"
  79. @end="onDragEnd"
  80. :options="{ animation: 150 }">
  81. <div class="images-items" v-for="(acc, index) in imagesList" v-if="acc.position === 'main' && acc.width === acc.height" :key="index">
  82. <images-item :images-list="cropPreview ? imagesList : originalImagesList" :pages="pages" :item="acc" :index="index" :status-pages="statusPages" @delImage="delImage" @needRegenerate="needRegenerate"></images-item>
  83. </div>
  84. </draggable>
  85. </div>
  86. <div class="images-list__one">
  87. <h2>主图3:4</h2>
  88. <draggable
  89. tag="div"
  90. class="images-list-block"
  91. v-model="imagesList"
  92. @start="onDragStart"
  93. @end="onDragEnd"
  94. :options="{ animation: 150 }">
  95. <div class="images-items" v-for="(acc, index) in imagesList" v-if="acc.position === 'main' && acc.width !== acc.height" :key="index">
  96. <images-item :images-list="cropPreview ? imagesList : originalImagesList" :pages="pages" :item="acc" :index="index" :status-pages="statusPages" @delImage="delImage" @needRegenerate="needRegenerate"></images-item>
  97. </div>
  98. </draggable>
  99. </div>
  100. <div class="images-list__one">
  101. <h2>竖图2:3</h2>
  102. <draggable
  103. tag="div"
  104. class="images-list-block"
  105. v-model="imagesList"
  106. @start="onDragStart"
  107. @end="onDragEnd"
  108. :options="{ animation: 150 }">
  109. <div class="images-items" v-for="(acc, index) in imagesList" v-if="acc.position === 'list'" :key="index">
  110. <images-item :images-list="cropPreview ? imagesList : originalImagesList" :pages="pages" :item="acc" :index="index" :status-pages="statusPages" @delImage="delImage" @needRegenerate="needRegenerate"></images-item>
  111. </div>
  112. </draggable>
  113. </div>
  114. <div class="images-list__one">
  115. <h2>颜色图1:1</h2>
  116. <draggable
  117. tag="div"
  118. class="images-list-block"
  119. v-model="imagesList"
  120. @start="onDragStart"
  121. @end="onDragEnd"
  122. :options="{ animation: 150 }">
  123. <div class="images-items" v-for="(acc, index) in imagesList" v-if="acc.position === 'color'" :key="index">
  124. <images-item :images-list="cropPreview ? imagesList : originalImagesList" :pages="pages" :item="acc" :index="index" :status-pages="statusPages" @delImage="delImage" @needRegenerate="needRegenerate"></images-item>
  125. </div>
  126. </draggable>
  127. </div>
  128. </div>
  129. <div class="images-list-wapper" v-else>
  130. <draggable
  131. tag="div"
  132. class="images-list-block"
  133. v-model="imagesList"
  134. @start="onDragStart"
  135. @end="onDragEnd"
  136. :move="checkMove"
  137. :options="{ animation: 150 }">
  138. <div class="images-items" v-for="(acc, index) in imagesList" :key="index">
  139. <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>
  140. </div>
  141. <div class="images-items fixed-item" v-if="cropPreview">
  142. <div class="images-items__img">
  143. <el-upload
  144. ref="uploadRefCrop"
  145. class="upload-demo"
  146. drag
  147. :action="action"
  148. :show-file-list="false"
  149. :headers="{
  150. 'Authorization': 'Bearer ' + token
  151. }"
  152. multiple
  153. :on-success="handleVideoSuccess"
  154. :before-upload="beforeUploadVideo">
  155. <div v-loading="uploading">
  156. <div class="el-upload__info">
  157. <i class="el-icon-upload"></i>
  158. <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
  159. <div class="el-upload__tip" slot="tip">图片要求:支持JPG、PNG和WEBP,最大为20M</div>
  160. </div>
  161. </div>
  162. </el-upload>
  163. </div>
  164. </div>
  165. </draggable>
  166. </div>
  167. </el-col>
  168. </el-row>
  169. <div class="regenerate-block" v-if="form.regenerateImage && statusPages < 6">
  170. <el-form ref="regenerate-form" :model="form" label-width="100px" label-position="top">
  171. <el-form-item>
  172. <img class="regenerate-image" :src="form.regenerateImage" alt="">
  173. </el-form-item>
  174. <el-form-item label="商品信息">
  175. <el-input v-model="form.sku" size="small" disabled></el-input>
  176. </el-form-item>
  177. <el-form-item label="内容" v-if="pages == 'review'" class="upload-form-item">
  178. <el-input v-model="form.prompt" :rows="5" type="textarea"></el-input>
  179. </el-form-item>
  180. <el-form-item label="裁图图片" v-if="pages == 'crop'" class="upload-form-item">
  181. <el-upload
  182. ref="uploadRef"
  183. class="upload-demo"
  184. drag
  185. :action="action"
  186. :show-file-list="false"
  187. :headers="{
  188. 'Authorization': 'Bearer ' + token
  189. }"
  190. :on-success="handleVideoSuccess"
  191. :before-upload="beforeUploadVideo">
  192. <div v-loading="uploading">
  193. <div v-if="form.imageUrl" class="avatar-black">
  194. <img :src="form.imageUrl" class="avatar">
  195. <div class="custom-file__btns">
  196. <el-button
  197. class="replace-btn"
  198. @click.stop="replaceFile()">
  199. <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>
  200. 重新上传
  201. </el-button>
  202. <el-button
  203. class="replace-btn"
  204. @click.stop="imgUploadDel()">
  205. <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>
  206. 删除图片
  207. </el-button>
  208. </div>
  209. </div>
  210. <div class="el-upload__info" v-else>
  211. <i class="el-icon-upload"></i>
  212. <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
  213. <div class="el-upload__tip" slot="tip">图片要求:支持JPG、PNG和WEBP,最大为20M</div>
  214. </div>
  215. </div>
  216. </el-upload>
  217. </el-form-item>
  218. <span class="tips">重生成之后,需要保存!</span>
  219. </el-form>
  220. <span class="regenerate-footer">
  221. <el-button size="small" @click="colseRegenerateImage">取消</el-button>
  222. <el-button type="primary" size="small" :loading="regenerateLoading" @click="handleRegenerate">重生成</el-button>
  223. </span>
  224. </div>
  225. </div>
  226. </div>
  227. </el-dialog>
  228. </template>
  229. <script>
  230. import { api } from "@/api/api";
  231. import { getToken } from '@/utils/auth'
  232. import draggable from "vuedraggable";
  233. import ImagesItem from "./ImagesItem";
  234. import request from '@/utils/request'
  235. import { genCidHex16, fetchStreamText } from '@/utils/index'
  236. export default {
  237. name: "CropSizeModal",
  238. props: {
  239. sizeList: {
  240. type: Array,
  241. default: () => {
  242. return []
  243. }
  244. }
  245. },
  246. components: {
  247. draggable,
  248. ImagesItem
  249. },
  250. data() {
  251. return {
  252. action: api.fileUrl,
  253. uploading: false,
  254. cropPreview: false,
  255. pages: '',
  256. statusPages: '',
  257. paramsId: '',
  258. taskId: '',
  259. form: {
  260. imageId: '',
  261. regenerateImage: null,
  262. sku: null,
  263. prompt: '',
  264. imageUrl: '',
  265. cropType: null,
  266. colorCode: ''
  267. },
  268. rules: {
  269. cropType: [
  270. { required: true, message: '请选择裁图类型', trigger: 'change' }
  271. ]
  272. },
  273. cropTypes: [
  274. {label: '上衣/短外套', value: 1},
  275. {label: '下装(裤子、半身裙)', value: 2},
  276. {label: '连衣裙/套装/中长外套', value: 3},
  277. {label: '膝盖以上的连身装(连衣裙、外套)', value: 4}
  278. ],
  279. imagesList: [],
  280. originalOrders: [],
  281. originalImagesList: [],
  282. dialogVisible: false,
  283. saveLoading: false,
  284. submitLoading: false,
  285. reviewLoading: false,
  286. regenerateLoading: false,
  287. isCropDone: false
  288. };
  289. },
  290. computed: {
  291. token() {
  292. return getToken();
  293. }
  294. },
  295. watch: {
  296. dialogVisible(val) {
  297. if (!val) {
  298. this.regenerateLoading = false;
  299. if (this.reviewLoading) {
  300. // 说明裁图还没有执行完毕
  301. this.isCropDone = true;
  302. } else {
  303. this.isCropDone = false;
  304. }
  305. this.resetForm();
  306. }
  307. }
  308. },
  309. methods: {
  310. show(row, type) {
  311. this.dialogVisible = true;
  312. this.$nextTick(() => {
  313. // if (localStorage.getItem('cropInfo')) {
  314. // try {
  315. // const cropInfo = JSON.parse(localStorage.getItem('cropInfo'));
  316. // this.form.sku = cropInfo.sku || null;
  317. // this.form.cropType = cropInfo.cropType || null;
  318. // this.form.colorCode = cropInfo.colorCode || '';
  319. // } catch (e) {}
  320. // }
  321. this.cropPreview = false;
  322. this.saveLoading = false;
  323. this.pages = type;
  324. this.paramsId = row.id;
  325. if (row.type == 1) {
  326. this.originalImagesList = [...row.referenceImagesList, ...row.originalImagesList];
  327. } else {
  328. this.originalImagesList = row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1).map(acc => {
  329. return acc.imageUrl
  330. });
  331. }
  332. // this.originalImagesList = [...row.referenceImagesList, ...row.originalImagesList];
  333. this.taskId = row.aiGeneratedImageVOList && row.aiGeneratedImageVOList[0].taskId;
  334. this.statusPages = row.status * 1;
  335. this.form.sku = row.sku;
  336. const images = this.statusPages < 5 ? row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1) : row.aiGeneratedImageVOList.filter(acc => acc.imageType == 2);
  337. this.imagesList = images.map(acc => {
  338. return {
  339. id: acc.id,
  340. imageUrl: acc.imageUrl,
  341. imageOrder: acc.imageOrder,
  342. position: acc.position,
  343. width: acc.width,
  344. height: acc.height
  345. }
  346. })
  347. console.log(this.imagesList, 444)
  348. })
  349. },
  350. init() {
  351. this.dialogVisible = true;
  352. this.$nextTick(() => {
  353. this.saveLoading = false;
  354. this.reviewLoading = false;
  355. this.taskId = '';
  356. this.cropPreview = true;
  357. this.pages = 'crop';
  358. this.statusPages = 4;
  359. this.originalImagesList = []
  360. })
  361. },
  362. uploadGalleryList() {
  363. this.submitLoading = true;
  364. const params = {
  365. taskId: this.taskId
  366. }
  367. request({
  368. url: '/imageTask/uploadGallery',
  369. method: 'post',
  370. data: params
  371. }).then(res => {
  372. if (res.code == 200) {
  373. this.$message.success(res.msg || '操作成功!');
  374. }
  375. }).finally(() => {
  376. this.submitLoading = false
  377. })
  378. },
  379. saveHandle() {
  380. this.saveLoading = true;
  381. request({
  382. url: '/image/update',
  383. method: 'post',
  384. data: this.imagesList.map(acc => {
  385. return {
  386. id: acc.id,
  387. imageUrl: acc.imageUrl,
  388. imageOrder: acc.imageOrder,
  389. }
  390. })
  391. }).then(res => {
  392. if (res.code == 200) {
  393. this.$emit('update-success');
  394. this.$message.success(res.msg || '保存成功!');
  395. }
  396. }).finally(() => {
  397. this.saveLoading = false;
  398. })
  399. },
  400. async handleCrop() {
  401. // 参数校验
  402. if (!this.validateCropParams()) return;
  403. try {
  404. this.reviewLoading = true;
  405. // 如果是裁图预览模式,先直传图片
  406. if (this.cropPreview && !this.taskId) {
  407. const res = await this.directSubmitImages();
  408. if (res.code === 200) {
  409. this.isCropDone = false;
  410. this.dialogVisible = false
  411. this.$emit('update-success');
  412. this.$message.success('裁图已提交');
  413. }
  414. } else {
  415. // 执行裁图
  416. const res = await this.cropImagesHandle();
  417. if (res.code === 200 && this.taskId && !this.isCropDone) {
  418. this.isCropDone = false;
  419. this.afterCropSuccess(res);
  420. }
  421. if (res.code === 500) {
  422. this.isCropDone = false;
  423. this.$emit('update-success');
  424. }
  425. }
  426. } catch (err) {
  427. console.error(err);
  428. } finally {
  429. this.reviewLoading = false;
  430. }
  431. },
  432. // ======================
  433. // 参数校验
  434. // ======================
  435. validateCropParams() {
  436. if (this.cropPreview && !this.form.sku) {
  437. this.$message.error('请填写SKU!');
  438. return false;
  439. }
  440. if (!this.form.cropType || !this.form.colorCode) {
  441. this.$message.error('请选择裁切类型或填写颜色代码!');
  442. return false;
  443. }
  444. return true;
  445. },
  446. // ======================
  447. // 直传图片(directSubmit)
  448. // ======================
  449. directSubmitImages() {
  450. return request({
  451. url: '/imageTask/directSubmit',
  452. method: 'post',
  453. data: {
  454. sessionId: `${Date.now() + 10}_${genCidHex16()}`,
  455. applicationId: 6,
  456. sizes: this.sizeList.map(acc => ({
  457. position: acc.positionCode,
  458. height: acc.height,
  459. width: acc.width,
  460. sort: acc.sort
  461. })),
  462. tasks: [
  463. {
  464. sku: this.form.sku,
  465. type: this.form.cropType,
  466. colorCode: this.form.colorCode,
  467. images: this.imagesList.map((acc, index) => ({
  468. imageUrl: acc.imageUrl,
  469. imageOrder: index
  470. }))
  471. }
  472. ]
  473. }
  474. });
  475. },
  476. // ======================
  477. // 裁图接口(crop)
  478. // ======================
  479. cropImagesHandle() {
  480. return request({
  481. url: '/imageTask/crop',
  482. method: 'post',
  483. data: {
  484. sessionId: `${Date.now() + 10}_${genCidHex16()}`,
  485. applicationId: 6,
  486. sku: this.form.sku,
  487. type: this.form.cropType,
  488. taskId: this.taskId,
  489. colorCode: this.form.colorCode,
  490. sizes: this.sizeList.map(acc => ({
  491. position: acc.positionCode,
  492. height: acc.height,
  493. width: acc.width,
  494. sort: acc.sort
  495. }))
  496. }
  497. });
  498. },
  499. // ======================
  500. // 裁图成功后的统一处理
  501. // ======================
  502. afterCropSuccess(res) {
  503. this.statusPages += 1;
  504. this.$emit('update-success');
  505. this.$emit('update-status', this.paramsId, this.statusPages);
  506. this.imagesList = res.data.map(acc => ({
  507. id: acc.id,
  508. imageUrl: acc.imageUrl,
  509. imageOrder: acc.imageOrder,
  510. position: acc.position,
  511. width: acc.width,
  512. height: acc.height
  513. }));
  514. this.$message.success(res.msg || '操作成功!');
  515. },
  516. handleConfirm() {
  517. if (this.pages == 'crop' && this.statusPages == 4) {
  518. this.handleCrop();
  519. } else {
  520. this.reviewLoading = true;
  521. request({
  522. url: `/imageTask/audit/${this.paramsId}`,
  523. method: 'post',
  524. data: {
  525. id: this.paramsId,
  526. aiGeneratedImageUpdateParams: this.imagesList.map(acc => {
  527. return {
  528. id: acc.id,
  529. imageUrl: acc.imageUrl,
  530. imageOrder: acc.imageOrder,
  531. }
  532. })
  533. }
  534. }).then(res => {
  535. if (res.code == 200) {
  536. this.statusPages += 1;
  537. if (this.statusPages >= 6) {
  538. this.form.regenerateImage = null;
  539. }
  540. this.$emit('update-status', this.paramsId, this.statusPages);
  541. this.$message.success(res.msg || '操作成功!');
  542. }
  543. }).finally(() => {
  544. this.reviewLoading = false;
  545. })
  546. }
  547. },
  548. // 拖动开始:记录原始 imageOrder 顺序
  549. onDragStart() {
  550. this.originalOrders = this.imagesList.map(item => item.imageOrder)
  551. },
  552. checkMove(evt) {
  553. // 禁止 fixed-item 被拖动
  554. if (evt.dragged.classList.contains('fixed-item')) {
  555. return false
  556. }
  557. return true
  558. },
  559. // 拖动结束:按当前位置重新赋值 imageOrder
  560. onDragEnd() {
  561. this.imagesList = this.imagesList.map((item, index) => {
  562. return {
  563. ...item,
  564. imageOrder: this.originalOrders[index]
  565. }
  566. })
  567. },
  568. delImage(row, itemIndex) {
  569. //删除
  570. this.$confirm("确定要删除吗?", "提示", {
  571. confirmButtonText: "确定",
  572. cancelButtonText: "取消",
  573. type: "warning"
  574. }).then(() => {
  575. if (this.cropPreview) {
  576. this.imagesList.splice(itemIndex, 1);
  577. this.$notify({
  578. title: "成功",
  579. message: "删除成功",
  580. type: "success",
  581. duration: 3000
  582. });
  583. } else {
  584. request({
  585. url: '/image/delete',
  586. method: 'post',
  587. data: [row.id]
  588. }).then(res => {
  589. if (res.code == 200) {
  590. this.imagesList.forEach((acc, itemIndex) => {
  591. if (acc.id == row.id) {
  592. this.imagesList.splice(itemIndex, 1);
  593. }
  594. })
  595. this.$emit('update-images', this.paramsId, row.id);
  596. if (row.id == this.form.imageId) {
  597. this.form.regenerateImage = null;
  598. }
  599. this.$notify({
  600. title: "成功",
  601. message: "删除成功",
  602. type: "success",
  603. duration: 3000
  604. });
  605. }
  606. }).finally(() => {
  607. })
  608. }
  609. });
  610. },
  611. colseRegenerateImage() {
  612. this.form.regenerateImage = null;
  613. this.form.prompt = '';
  614. this.form.imageUrl = '';
  615. },
  616. async handleRegenerate() {
  617. if (this.pages == 'review' && !this.form.prompt) {
  618. this.$message.error('请填写内容!');
  619. return;
  620. }
  621. if (this.pages == 'crop' && !this.form.imageUrl) {
  622. this.$message.error('请上传裁图图片!');
  623. return;
  624. }
  625. if (this.pages == 'review') {
  626. this.regenerateLoading = true;
  627. try {
  628. const payload = {
  629. sessionId: `${Date.now() + 10}_${genCidHex16()}`,
  630. applicationId: 2,
  631. images: this.pages == 'review' ? [this.form.regenerateImage] : [this.form.imageUrl]
  632. }
  633. if (this.pages == 'review') {
  634. payload.prompt = this.form.prompt;
  635. }
  636. const result = await fetchStreamText(`/app/ai/send/imageMessage`, payload, {
  637. onChunk: (chunk) => {
  638. console.log('实时分片:', chunk)
  639. // this.replyText += chunk // Vue 可实时更新聊天内容
  640. }
  641. })
  642. try {
  643. this.regenerateLoading = false;
  644. if (result && typeof result == 'string') {
  645. console.log('完整文本:', result)
  646. const data = JSON.parse(result)
  647. if (result.code == 200) {
  648. this.imagesList.forEach(acc => {
  649. if (acc.id == this.form.imageId) {
  650. acc.imageUrl = data.image
  651. }
  652. })
  653. } else {
  654. if (result.code == 401) {
  655. this.$router.push(`/login`);
  656. } else {
  657. this.$message.error(result.msg || result.errorContent || '请求出错,请联系管理员!');
  658. }
  659. }
  660. } else {
  661. console.log('JSON 对象12', result)
  662. if (result.code == 200) {
  663. this.imagesList.forEach(acc => {
  664. if (acc.id == this.form.imageId) {
  665. acc.imageUrl = result.image
  666. }
  667. })
  668. } else {
  669. if (result.code == 401) {
  670. this.$router.push(`/login`);
  671. } else {
  672. this.$message.error(result.msg || result.errorContent || '请求出错,请联系管理员!');
  673. }
  674. }
  675. }
  676. } catch (e) {
  677. console.error('报错:', e)
  678. }
  679. } catch (e) {
  680. console.error('请求错误:', e)
  681. } finally {
  682. this.regenerateLoading = false;
  683. }
  684. } else {
  685. this.imagesList.forEach(acc => {
  686. if (acc.id == this.form.imageId) {
  687. acc.imageUrl = this.form.imageUrl;
  688. }
  689. })
  690. }
  691. },
  692. needRegenerate(item) {
  693. this.form.imageUrl = '';
  694. this.form.imageId = this.imagesList.filter(acc => acc.id == item.id)[0].id;
  695. this.form.regenerateImage = this.imagesList.filter(acc => acc.id == item.id)[0].imageUrl;
  696. },
  697. replaceFile() {
  698. if (this.$refs.uploadRef) {
  699. // 清空上传队列
  700. this.$refs.uploadRef.uploadFiles = []
  701. // 手动触发上传框选择文件
  702. const input = this.$refs.uploadRef.$el.querySelector('input[type=file]')
  703. input.click()
  704. }
  705. // 在 handleSuccess 中会替换对应文件
  706. },
  707. beforeUploadVideo(file) {
  708. this.uploading = true;
  709. const isLt10M = file.size / 1024 / 1024 < 200;
  710. const imagesTypes = ['image/png','image/jpeg','image/webp']
  711. if (!imagesTypes.includes(file.type)) {
  712. this.uploading = false;
  713. this.$message.error('图片只能是jpg、png和webp格式!');
  714. return false;
  715. }
  716. if (!isLt10M) {
  717. this.$message.error('上传文件大小不能超过20MB哦!');
  718. return false;
  719. }
  720. return true
  721. },
  722. handleVideoSuccess(res) {
  723. this.uploading = false
  724. if (res.code == 200) {
  725. if (this.cropPreview) {
  726. this.imagesList.push({
  727. imageUrl: res.data.url
  728. })
  729. } else {
  730. this.form.imageUrl = res.data.url;
  731. }
  732. } else {
  733. this.$message.error('上传失败,请重新上传!');
  734. }
  735. },
  736. imgUploadDel() {
  737. //删除
  738. this.$confirm("确定要删除吗?", "提示", {
  739. confirmButtonText: "确定",
  740. cancelButtonText: "取消",
  741. type: "warning"
  742. }).then(() => {
  743. this.form.imageUrl = '';
  744. this.$notify({
  745. title: "成功",
  746. message: "删除成功",
  747. type: "success",
  748. duration: 3000
  749. });
  750. });
  751. },
  752. resetForm() {
  753. this.cropPreview = false;
  754. this.pages = '';
  755. this.statusPages = '';
  756. this.paramsId = '';
  757. this.taskId = '';
  758. this.imagesList = [];
  759. this.originalOrders = [];
  760. const data = { sku: this.form.sku, cropType: this.form.cropType, colorCode: this.form.colorCode };
  761. localStorage.setItem('cropInfo', JSON.stringify(data));
  762. this.form = {
  763. imageId: '',
  764. regenerateImage: null,
  765. sku: null,
  766. prompt: '',
  767. imageUrl: '',
  768. cropType: null,
  769. colorCode: ''
  770. }
  771. }
  772. }
  773. };
  774. </script>
  775. <style lang="scss">
  776. .reservation-approval-dialog {
  777. margin-top: 5vh !important;
  778. max-height: 90vh;
  779. display: flex;
  780. flex-direction: column;
  781. height: 100%;
  782. .el-dialog__body {
  783. padding-top: 0;
  784. flex: 1;
  785. overflow: hidden;
  786. min-height: 0;
  787. padding-bottom: 20px;
  788. }
  789. }
  790. .reservation-approval-block {
  791. height: 100%;
  792. display: flex;
  793. flex-direction: column;
  794. .reservation-approval-header {
  795. display: flex;
  796. justify-content: flex-end;
  797. gap: 20px;
  798. margin-bottom: 20px;
  799. }
  800. .reservation-approval__status ul{
  801. display: flex;
  802. margin: 0;
  803. padding: 0;
  804. height: 32px;
  805. border-radius: 200px;
  806. background: #f2f2f2;
  807. align-items: center;
  808. gap: 15px;
  809. li {
  810. list-style: none;
  811. margin: 0;
  812. height: 100%;
  813. display: flex;
  814. align-items: center;
  815. padding: 0 10px;
  816. border-radius: 200px;
  817. font-size: 12px;
  818. line-height: 32px;
  819. white-space: nowrap;
  820. &.active {
  821. background: #ae8877;
  822. color: #fff;
  823. }
  824. }
  825. }
  826. .reservation-approval-body {
  827. position: relative;
  828. display: flex;
  829. height: 100%;
  830. flex: 1;
  831. min-height: 0;
  832. overflow: hidden;
  833. overflow-y: auto;
  834. .el-row {
  835. width: 75%;
  836. }
  837. .tips {
  838. font-size: 12px;
  839. color: red;
  840. }
  841. &.full .el-row {
  842. width: 100%;
  843. }
  844. .images-list__one {
  845. margin-bottom: 20px;
  846. &:last-child {
  847. margin-bottom: 0;
  848. }
  849. h2 {
  850. font-size: 16px;
  851. color: #333;
  852. margin: 0;
  853. padding: 0;
  854. margin-bottom: 10px;
  855. }
  856. }
  857. .images-list-block {
  858. display: flex;
  859. flex-wrap: wrap;
  860. margin-left: -15px;
  861. .images-items {
  862. width: 20%;
  863. padding-left: 15px;
  864. margin-bottom: 15px;
  865. }
  866. .images-items__img {
  867. position: relative;
  868. width: 100%;
  869. aspect-ratio: 3 / 4;
  870. display: flex;
  871. justify-content: center;
  872. align-items: center;
  873. border-radius: 4px;
  874. border: 1px solid #ccc;
  875. overflow: hidden;
  876. cursor: pointer;
  877. &:hover {
  878. .btns-group {
  879. display: block;
  880. }
  881. }
  882. .size {
  883. position: absolute;
  884. top: 5px;
  885. left: 5px;
  886. padding: 0px 7px;
  887. background: linear-gradient(to right,#b8857b,#e5c0ac);
  888. color: #fff;
  889. font-size: 12px;
  890. height: 20px;
  891. line-height: 20px;
  892. display: block;
  893. z-index: 10;
  894. }
  895. .upload-demo {
  896. width: 100%;
  897. height: 100%;
  898. display: flex;
  899. align-items: center;
  900. }
  901. .el-upload-dragger {
  902. width: 100%;
  903. height: 100%;
  904. border: none;
  905. padding: 0 20px;
  906. .el-icon-upload {
  907. margin: 0;
  908. margin-bottom: 30px;
  909. }
  910. }
  911. }
  912. .btns-group {
  913. position: absolute;
  914. bottom: 10px;
  915. z-index: 10;
  916. font-size: 12px;
  917. display: none;
  918. .el-button {
  919. border: none;
  920. background: rgba(0,0,0,0.5);
  921. color: #fff;
  922. padding: 7px 15px;
  923. }
  924. }
  925. img {
  926. display: block;
  927. max-height: 100%;
  928. }
  929. }
  930. }
  931. }
  932. .regenerate-block {
  933. position: sticky;
  934. top: 0;
  935. right: 0;
  936. width: 25%;
  937. padding: 0 20px 10px;
  938. max-height: 100%;
  939. overflow-y: auto;
  940. .el-form-item {
  941. margin-bottom: 10px;
  942. }
  943. .regenerate-image {
  944. max-width: 200px;
  945. width: 100%;
  946. margin: 0 auto;
  947. display: block;
  948. }
  949. .el-form-item__label {
  950. line-height: 20px;
  951. padding: 0;
  952. }
  953. .upload-form-item .el-form-item__label{
  954. padding-bottom: 4px;
  955. }
  956. .el-select {
  957. width: 100%;
  958. }
  959. .el-upload {
  960. width: 100%;
  961. height: 100%;
  962. }
  963. .el-upload-dragger {
  964. width: 100%;
  965. display: flex;
  966. align-items: center;
  967. justify-content: center;
  968. .el-icon-upload {
  969. margin: 0;
  970. }
  971. .el-upload__text,.el-upload__tip {
  972. line-height: 20px;
  973. }
  974. }
  975. .avatar-black {
  976. position: relative;
  977. width: 100%;
  978. height: 100%;
  979. margin: 0 auto;
  980. img {
  981. display: block;
  982. object-fit: cover;
  983. width: 100%;
  984. height: 100%;
  985. max-width: 50%;
  986. margin: 0 auto;
  987. }
  988. .custom-file__btns {
  989. position: absolute;
  990. top: 0;
  991. left: 0;
  992. z-index: 10;
  993. width: 100%;
  994. height: 100%;
  995. background: rgba(0, 0, 0, 0.5);
  996. border-radius: 6px;
  997. display: none;
  998. flex-direction: column;
  999. align-items: center;
  1000. justify-content: center;
  1001. gap: 10px;
  1002. transition: all .5s;
  1003. .replace-btn {
  1004. font-size: 12px;
  1005. padding: 4px 10px;
  1006. background: rgba(0, 0, 0, 0.8);
  1007. border-color: rgba(0, 0, 0, 0.8);
  1008. color: #fff;
  1009. margin: 0;
  1010. /deep/ span {
  1011. display: flex;
  1012. align-items: center;
  1013. gap: 4px;
  1014. }
  1015. svg {
  1016. width: 14px;
  1017. height: 14px;
  1018. }
  1019. &:hover {
  1020. background: #ae8878;
  1021. border-color: #ae8878;
  1022. }
  1023. }
  1024. }
  1025. &:hover .custom-file__btns{
  1026. display: flex;
  1027. }
  1028. }
  1029. .regenerate-footer {
  1030. display: flex;
  1031. justify-content: flex-end;
  1032. margin-top: 20px;
  1033. }
  1034. }
  1035. @media screen and (max-width: 1366px) {
  1036. .reservation-approval-block .reservation-approval-body .images-list-block .images-items {
  1037. width: 25%;
  1038. }
  1039. }
  1040. @media screen and (max-width: 1540px) {
  1041. .reservation-approval-block .reservation-approval-header {
  1042. gap: 10px;
  1043. }
  1044. .reservation-approval-dialog {
  1045. width: 98% !important;
  1046. }
  1047. }
  1048. </style>