ImageList.vue 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. <template>
  2. <el-card
  3. class="wh-full"
  4. :body-style="{ margin: 0, padding: 0, height: '100%', position: 'relative' }"
  5. shadow="never"
  6. >
  7. <template #header>
  8. 绘画任务
  9. <!-- TODO @fan:看看,怎么优化下这个样子哈。 -->
  10. <el-button @click="handleViewPublic">绘画作品</el-button>
  11. </template>
  12. <!-- 图片列表 -->
  13. <div
  14. class="relative flex flex-row flex-wrap content-start h-full overflow-auto p-5 pb-[140px] box-border [&>div]:mr-5 [&>div]:mb-5"
  15. ref="imageListRef"
  16. >
  17. <ImageCard
  18. v-for="image in imageList"
  19. :key="image.id"
  20. :detail="image"
  21. @on-btn-click="handleImageButtonClick"
  22. @on-mj-btn-click="handleImageMidjourneyButtonClick"
  23. />
  24. </div>
  25. <div
  26. class="absolute bottom-[60px] h-[50px] leading-[90px] w-full z-[999] bg-white flex flex-row justify-center items-center"
  27. >
  28. <Pagination
  29. :total="pageTotal"
  30. v-model:page="queryParams.pageNo"
  31. v-model:limit="queryParams.pageSize"
  32. @pagination="getImageList"
  33. />
  34. </div>
  35. </el-card>
  36. <!-- 图片详情 -->
  37. <ImageDetail
  38. :show="isShowImageDetail"
  39. :id="showImageDetailId"
  40. @handle-drawer-close="handleDetailClose"
  41. />
  42. </template>
  43. <script setup lang="ts">
  44. import {
  45. ImageApi,
  46. ImageVO,
  47. ImageMidjourneyActionVO,
  48. ImageMidjourneyButtonsVO
  49. } from '@/api/ai/image'
  50. import ImageDetail from './ImageDetail.vue'
  51. import ImageCard from './ImageCard.vue'
  52. import { ElLoading, LoadingOptionsResolved } from 'element-plus'
  53. import { AiImageStatusEnum } from '@/views/ai/utils/constants'
  54. import download from '@/utils/download'
  55. const message = useMessage() // 消息弹窗
  56. const router = useRouter() // 路由
  57. // 图片分页相关的参数
  58. const queryParams = reactive({
  59. pageNo: 1,
  60. pageSize: 10
  61. })
  62. const pageTotal = ref<number>(0) // page size
  63. const imageList = ref<ImageVO[]>([]) // image 列表
  64. const imageListLoadingInstance = ref<any>() // image 列表是否正在加载中
  65. const imageListRef = ref<any>() // ref
  66. // 图片轮询相关的参数(正在生成中的)
  67. const inProgressImageMap = ref<{}>({}) // 监听的 image 映射,一般是生成中(需要轮询),key 为 image 编号,value 为 image
  68. const inProgressTimer = ref<any>() // 生成中的 image 定时器,轮询生成进展
  69. // 图片详情相关的参数
  70. const isShowImageDetail = ref<boolean>(false) // 图片详情是否展示
  71. const showImageDetailId = ref<number>(0) // 图片详情的图片编号
  72. /** 处理查看绘图作品 */
  73. const handleViewPublic = () => {
  74. router.push({
  75. name: 'AiImageSquare'
  76. })
  77. }
  78. /** 查看图片的详情 */
  79. const handleDetailOpen = async () => {
  80. isShowImageDetail.value = true
  81. }
  82. /** 关闭图片的详情 */
  83. const handleDetailClose = async () => {
  84. isShowImageDetail.value = false
  85. }
  86. /** 获得 image 图片列表 */
  87. const getImageList = async () => {
  88. try {
  89. // 1. 加载图片列表
  90. imageListLoadingInstance.value = ElLoading.service({
  91. target: imageListRef.value,
  92. text: '加载中...'
  93. } as LoadingOptionsResolved)
  94. const { list, total } = await ImageApi.getImagePageMy(queryParams)
  95. imageList.value = list
  96. pageTotal.value = total
  97. // 2. 计算需要轮询的图片
  98. const newWatImages = {}
  99. imageList.value.forEach((item) => {
  100. if (item.status === AiImageStatusEnum.IN_PROGRESS) {
  101. newWatImages[item.id] = item
  102. }
  103. })
  104. inProgressImageMap.value = newWatImages
  105. } finally {
  106. // 关闭正在“加载中”的 Loading
  107. if (imageListLoadingInstance.value) {
  108. imageListLoadingInstance.value.close()
  109. imageListLoadingInstance.value = null
  110. }
  111. }
  112. }
  113. /** 轮询生成中的 image 列表 */
  114. const refreshWatchImages = async () => {
  115. const imageIds = Object.keys(inProgressImageMap.value).map(Number)
  116. if (imageIds.length == 0) {
  117. return
  118. }
  119. const list = (await ImageApi.getImageListMyByIds(imageIds)) as ImageVO[]
  120. const newWatchImages = {}
  121. list.forEach((image) => {
  122. if (image.status === AiImageStatusEnum.IN_PROGRESS) {
  123. newWatchImages[image.id] = image
  124. } else {
  125. const index = imageList.value.findIndex((oldImage) => image.id === oldImage.id)
  126. if (index >= 0) {
  127. // 更新 imageList
  128. imageList.value[index] = image
  129. }
  130. }
  131. })
  132. inProgressImageMap.value = newWatchImages
  133. }
  134. /** 图片的点击事件 */
  135. const handleImageButtonClick = async (type: string, imageDetail: ImageVO) => {
  136. // 详情
  137. if (type === 'more') {
  138. showImageDetailId.value = imageDetail.id
  139. await handleDetailOpen()
  140. return
  141. }
  142. // 删除
  143. if (type === 'delete') {
  144. await message.confirm(`是否删除照片?`)
  145. await ImageApi.deleteImageMy(imageDetail.id)
  146. await getImageList()
  147. message.success('删除成功!')
  148. return
  149. }
  150. // 下载
  151. if (type === 'download') {
  152. download.image({ url: imageDetail.picUrl })
  153. return
  154. }
  155. // 重新生成
  156. if (type === 'regeneration') {
  157. emits('onRegeneration', imageDetail)
  158. return
  159. }
  160. }
  161. /** 处理 Midjourney 按钮点击事件 */
  162. const handleImageMidjourneyButtonClick = async (
  163. button: ImageMidjourneyButtonsVO,
  164. imageDetail: ImageVO
  165. ) => {
  166. // 1. 构建 params 参数
  167. const data = {
  168. id: imageDetail.id,
  169. customId: button.customId
  170. } as ImageMidjourneyActionVO
  171. // 2. 发送 action
  172. await ImageApi.midjourneyAction(data)
  173. // 3. 刷新列表
  174. await getImageList()
  175. }
  176. defineExpose({ getImageList }) // 暴露组件方法
  177. const emits = defineEmits(['onRegeneration'])
  178. /** 组件挂在的时候 */
  179. onMounted(async () => {
  180. // 获取 image 列表
  181. await getImageList()
  182. // 自动刷新 image 列表
  183. inProgressTimer.value = setInterval(async () => {
  184. await refreshWatchImages()
  185. }, 1000 * 3)
  186. })
  187. /** 组件取消挂在的时候 */
  188. onUnmounted(async () => {
  189. if (inProgressTimer.value) {
  190. clearInterval(inProgressTimer.value)
  191. }
  192. })
  193. </script>