ImageTask.vue 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <template>
  2. <el-card class="dr-task" body-class="task-card" shadow="never">
  3. <template #header>绘画任务</template>
  4. <div class="task-image-list" ref="imageTaskRef">
  5. <ImageTaskCard
  6. v-for="image in imageList"
  7. :key="image"
  8. :image-detail="image"
  9. @on-btn-click="handleImageBtnClick"
  10. @on-mj-btn-click="handleImageMjBtnClick"
  11. />
  12. </div>
  13. <div class="task-image-pagination">
  14. <el-pagination
  15. background
  16. layout="prev, pager, next"
  17. :default-page-size="pageSize"
  18. :total="pageTotal"
  19. @change="handlePageChange"
  20. />
  21. </div>
  22. </el-card>
  23. <!-- 图片 detail 抽屉 -->
  24. <ImageDetailDrawer
  25. :show="isShowImageDetail"
  26. :id="showImageDetailId"
  27. @handle-drawer-close="handleDrawerClose"
  28. />
  29. </template>
  30. <script setup lang="ts">
  31. import { ImageApi, ImageVO, ImageMjActionVO, ImageMjButtonsVO } from '@/api/ai/image'
  32. import ImageDetailDrawer from './ImageDetailDrawer.vue'
  33. import ImageTaskCard from './ImageTaskCard.vue'
  34. import { ElLoading, LoadingOptionsResolved } from 'element-plus'
  35. import { AiImageStatusEnum } from '@/views/ai/utils/constants'
  36. import { downloadImage } from '@/utils/download'
  37. const message = useMessage() // 消息弹窗
  38. const imageList = ref<ImageVO[]>([]) // image 列表
  39. const inProgressImageMap = ref<{}>({}) // 监听的 image 映射,一般是生成中(需要轮询),key 为 image 编号,value 为 image
  40. const imageListInterval = ref<any>() // image 列表定时器,刷新列表
  41. const isShowImageDetail = ref<boolean>(false) // 是否显示 task 详情
  42. const showImageDetailId = ref<number>(0) // 是否显示 task 详情
  43. const imageTaskRef = ref<any>() // ref
  44. const imageTaskLoadingInstance = ref<any>() // loading
  45. const imageTaskLoading = ref<boolean>(false) // loading
  46. const pageNo = ref<number>(1) // page no
  47. const pageSize = ref<number>(10) // page size
  48. const pageTotal = ref<number>(0) // page size
  49. /** 抽屉 - close */
  50. const handleDrawerClose = async () => {
  51. isShowImageDetail.value = false
  52. }
  53. /** 任务 - detail */
  54. const handleDrawerOpen = async () => {
  55. isShowImageDetail.value = true
  56. }
  57. /**
  58. * 获取 - image 列表
  59. */
  60. const getImageList = async (apply: boolean = false) => {
  61. imageTaskLoading.value = true
  62. try {
  63. imageTaskLoadingInstance.value = ElLoading.service({
  64. target: imageTaskRef.value,
  65. text: '加载中...'
  66. } as LoadingOptionsResolved)
  67. const { list, total } = await ImageApi.getImagePageMy({
  68. pageNo: pageNo.value,
  69. pageSize: pageSize.value
  70. })
  71. if (apply) {
  72. imageList.value = [...imageList.value, ...list]
  73. } else {
  74. imageList.value = list
  75. }
  76. pageTotal.value = total
  77. // 需要 watch 的数据
  78. const newWatImages = {}
  79. imageList.value.forEach((item) => {
  80. if (item.status === AiImageStatusEnum.IN_PROGRESS) {
  81. newWatImages[item.id] = item
  82. }
  83. })
  84. inProgressImageMap.value = newWatImages
  85. } finally {
  86. if (imageTaskLoadingInstance.value) {
  87. imageTaskLoadingInstance.value.close()
  88. imageTaskLoadingInstance.value = null
  89. }
  90. }
  91. }
  92. /** 轮询生成中的 image 列表 */
  93. const refreshWatchImages = async () => {
  94. const imageIds = Object.keys(inProgressImageMap.value).map(Number)
  95. if (imageIds.length == 0) {
  96. return
  97. }
  98. const list = (await ImageApi.getImageListMyByIds(imageIds)) as ImageVO[]
  99. const newWatchImages = {}
  100. list.forEach((image) => {
  101. if (image.status === AiImageStatusEnum.IN_PROGRESS) {
  102. newWatchImages[image.id] = image
  103. } else {
  104. const index = imageList.value.findIndex((oldImage) => image.id === oldImage.id)
  105. if (index >= 0) {
  106. // 更新 imageList
  107. imageList.value[index] = image
  108. }
  109. }
  110. })
  111. inProgressImageMap.value = newWatchImages
  112. }
  113. /** 图片 - btn click */
  114. const handleImageBtnClick = async (type: string, imageDetail: ImageVO) => {
  115. // 获取 image detail id
  116. showImageDetailId.value = imageDetail.id
  117. // 处理不用 btn
  118. if (type === 'more') {
  119. await handleDrawerOpen()
  120. } else if (type === 'delete') {
  121. await message.confirm(`是否删除照片?`)
  122. await ImageApi.deleteImageMy(imageDetail.id)
  123. await getImageList()
  124. message.success('删除成功!')
  125. } else if (type === 'download') {
  126. await downloadImage(imageDetail.picUrl)
  127. } else if (type === 'regeneration') {
  128. // Midjourney 平台
  129. console.log('regeneration', imageDetail.id)
  130. await emits('onRegeneration', imageDetail)
  131. }
  132. }
  133. /** 图片 - mj btn click */
  134. const handleImageMjBtnClick = async (button: ImageMjButtonsVO, imageDetail: ImageVO) => {
  135. // 1、构建 params 参数
  136. const data = {
  137. id: imageDetail.id,
  138. customId: button.customId
  139. } as ImageMjActionVO
  140. // 2、发送 action
  141. await ImageApi.midjourneyAction(data)
  142. // 3、刷新列表
  143. await getImageList()
  144. }
  145. // page change
  146. const handlePageChange = async (page) => {
  147. pageNo.value = page
  148. await getImageList(false)
  149. }
  150. /** 暴露组件方法 */
  151. defineExpose({ getImageList })
  152. // emits
  153. const emits = defineEmits(['onRegeneration'])
  154. /** 组件挂在的时候 */
  155. onMounted(async () => {
  156. // 获取 image 列表
  157. await getImageList()
  158. // 自动刷新 image 列表
  159. imageListInterval.value = setInterval(async () => {
  160. await refreshWatchImages()
  161. }, 1000 * 3)
  162. })
  163. /** 组件取消挂在的时候 */
  164. onUnmounted(async () => {
  165. if (imageListInterval.value) {
  166. clearInterval(imageListInterval.value)
  167. }
  168. })
  169. </script>
  170. <style lang="scss">
  171. .task-card {
  172. margin: 0;
  173. padding: 0;
  174. height: 100%;
  175. position: relative;
  176. }
  177. .task-image-list {
  178. position: relative;
  179. display: flex;
  180. flex-direction: row;
  181. flex-wrap: wrap;
  182. align-content: flex-start;
  183. height: 100%;
  184. overflow: auto;
  185. padding: 20px;
  186. padding-bottom: 140px;
  187. box-sizing: border-box; /* 确保内边距不会增加高度 */
  188. > div {
  189. margin-right: 20px;
  190. margin-bottom: 20px;
  191. }
  192. > div:last-of-type {
  193. //margin-bottom: 100px;
  194. }
  195. }
  196. .task-image-pagination {
  197. position: absolute;
  198. bottom: 60px;
  199. height: 50px;
  200. line-height: 90px;
  201. width: 100%;
  202. z-index: 999;
  203. background-color: #ffffff;
  204. display: flex;
  205. flex-direction: row;
  206. justify-content: center;
  207. align-items: center;
  208. }
  209. </style>
  210. <style scoped lang="scss">
  211. .dr-task {
  212. width: 100%;
  213. height: 100%;
  214. }
  215. </style>