ImageTask.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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" @scroll="handleTabsScroll">
  5. <ImageTaskCard
  6. v-for="image in imageList"
  7. :key="image"
  8. :image-detail="image"
  9. @on-btn-click="handlerImageBtnClick"
  10. @on-mj-btn-click="handlerImageMjBtnClick"/>
  11. </div>
  12. </el-card>
  13. <!-- 图片 detail 抽屉 -->
  14. <ImageDetailDrawer
  15. :show="isShowImageDetail"
  16. :id="showImageDetailId"
  17. @handler-drawer-close="handlerDrawerClose"
  18. />
  19. </template>
  20. <script setup lang="ts">
  21. import {ImageApi, ImageDetailVO, ImageMjActionVO, ImageMjButtonsVO} from '@/api/ai/image';
  22. import ImageDetailDrawer from './ImageDetailDrawer.vue'
  23. import ImageTaskCard from './ImageTaskCard.vue'
  24. import {ElLoading} from "element-plus";
  25. const message = useMessage() // 消息弹窗
  26. const imageList = ref<ImageDetailVO[]>([]) // image 列表
  27. const imageListInterval = ref<any>() // image 列表定时器,刷新列表
  28. const isShowImageDetail = ref<boolean>(false) // 是否显示 task 详情
  29. const showImageDetailId = ref<number>(0) // 是否显示 task 详情
  30. const imageTaskRef = ref<any>() // ref
  31. const imageTaskLoadingInstance = ref<any>() // loading
  32. const imageTaskLoading = ref<boolean>(false) // loading
  33. const pageNo = ref<number>(1) // page no
  34. const pageSize = ref<number>(20) // page size
  35. /** 抽屉 - close */
  36. const handlerDrawerClose = async () => {
  37. isShowImageDetail.value = false
  38. }
  39. /** 任务 - detail */
  40. const handlerDrawerOpen = async () => {
  41. isShowImageDetail.value = true
  42. }
  43. /**
  44. * 获取 - image 列表
  45. */
  46. const getImageList = async () => {
  47. imageTaskLoading.value = true
  48. try {
  49. imageTaskLoadingInstance.value = ElLoading.service({
  50. target: imageTaskRef.value,
  51. text: '加载中...'
  52. })
  53. const { list } = await ImageApi.getImageList({pageNo: pageNo.value, pageSize: pageSize.value})
  54. // imageList.value.push.apply(imageList.value, list)
  55. imageList.value = list
  56. } finally {
  57. if (imageTaskLoadingInstance.value) {
  58. imageTaskLoadingInstance.value.close();
  59. imageTaskLoadingInstance.value = null;
  60. }
  61. }
  62. }
  63. /** 图片 - btn click */
  64. const handlerImageBtnClick = async (type, imageDetail: ImageDetailVO) => {
  65. // 获取 image detail id
  66. showImageDetailId.value = imageDetail.id
  67. console.log('type', imageDetail.id)
  68. // 处理不用 btn
  69. if (type === 'more') {
  70. await handlerDrawerOpen()
  71. } else if (type === 'delete') {
  72. await message.confirm(`是否删除照片?`)
  73. await ImageApi.deleteImage(imageDetail.id)
  74. await getImageList()
  75. await message.success("删除成功!")
  76. } else if (type === 'download') {
  77. await downloadImage(imageDetail.picUrl)
  78. }
  79. }
  80. /** 图片 - mj btn click */
  81. const handlerImageMjBtnClick = async (button: ImageMjButtonsVO, imageDetail: ImageDetailVO) => {
  82. // 1、构建 params 参数
  83. const params = {
  84. id: imageDetail.id,
  85. customId: button.customId,
  86. } as ImageMjActionVO
  87. // 2、发送 action
  88. await ImageApi.midjourneyAction(params)
  89. // 3、刷新列表
  90. await getImageList()
  91. }
  92. /** 下载 - image */
  93. // TODO @fan:貌似可以考虑抽到 download 里面,作为一个方法
  94. const downloadImage = async (imageUrl) => {
  95. const image = new Image()
  96. image.setAttribute('crossOrigin', 'anonymous')
  97. image.src = imageUrl
  98. image.onload = () => {
  99. const canvas = document.createElement('canvas')
  100. canvas.width = image.width
  101. canvas.height = image.height
  102. const ctx = canvas.getContext('2d') as CanvasDrawImage
  103. ctx.drawImage(image, 0, 0, image.width, image.height)
  104. const url = canvas.toDataURL('image/png')
  105. const a = document.createElement('a')
  106. a.href = url
  107. a.download = 'image.png'
  108. a.click()
  109. }
  110. }
  111. const handleTabsScroll = async () => {
  112. // todo 先不分页,只显示 top 前多少
  113. // if (imageTaskRef.value) {
  114. // const { scrollTop, scrollHeight, clientHeight } = imageTaskRef.value;
  115. // if (scrollTop + clientHeight >= scrollHeight - 20 && !imageTaskLoading.value) {
  116. // console.log('分页')
  117. // pageNo.value = pageNo.value + 1
  118. // await getImageList();
  119. // }
  120. // }
  121. }
  122. /** 暴露组件方法 */
  123. defineExpose({getImageList})
  124. /** 组件挂在的时候 */
  125. onMounted(async () => {
  126. // 获取 image 列表
  127. await getImageList()
  128. // 自动刷新 image 列表
  129. imageListInterval.value = setInterval(async () => {
  130. await getImageList()
  131. }, 1000 * 20)
  132. })
  133. /** 组件取消挂在的时候 */
  134. onUnmounted(async () => {
  135. if (imageListInterval.value) {
  136. clearInterval(imageListInterval.value)
  137. }
  138. })
  139. </script>
  140. <style lang="scss">
  141. .task-card {
  142. margin: 0;
  143. padding: 0;
  144. height: 100%;
  145. position: relative;
  146. }
  147. .task-image-list {
  148. position: relative;
  149. display: flex;
  150. flex-direction: row;
  151. flex-wrap: wrap;
  152. align-content: flex-start;
  153. height: 100%;
  154. overflow: auto;
  155. padding: 20px;
  156. padding-bottom: 100px;
  157. box-sizing: border-box; /* 确保内边距不会增加高度 */
  158. >div {
  159. margin-right: 20px;
  160. margin-bottom: 20px;
  161. }
  162. >div:last-of-type {
  163. //margin-bottom: 100px;
  164. }
  165. }
  166. </style>
  167. <style scoped lang="scss">
  168. .dr-task {
  169. width: 100%;
  170. height: 100%;
  171. }
  172. </style>