houzhongjian
2024-08-08 820397e43a0b64d35c6d31d2a55475061438593b
提交 | 用户 | 时间
820397 1 <template>
H 2   <el-card class="dr-task" body-class="task-card" shadow="never">
3     <template #header>
4       绘画任务
5       <!-- TODO @fan:看看,怎么优化下这个样子哈。 -->
6       <el-button @click="handleViewPublic">绘画作品</el-button>
7     </template>
8     <!-- 图片列表 -->
9     <div class="task-image-list" ref="imageListRef">
10       <ImageCard
11         v-for="image in imageList"
12         :key="image.id"
13         :detail="image"
14         @on-btn-click="handleImageButtonClick"
15         @on-mj-btn-click="handleImageMidjourneyButtonClick"
16       />
17     </div>
18     <div class="task-image-pagination">
19       <Pagination
20         :total="pageTotal"
21         v-model:page="queryParams.pageNo"
22         v-model:limit="queryParams.pageSize"
23         @pagination="getImageList"
24       />
25     </div>
26   </el-card>
27
28   <!-- 图片详情 -->
29   <ImageDetail
30     :show="isShowImageDetail"
31     :id="showImageDetailId"
32     @handle-drawer-close="handleDetailClose"
33   />
34 </template>
35 <script setup lang="ts">
36 import {
37   ImageApi,
38   ImageVO,
39   ImageMidjourneyActionVO,
40   ImageMidjourneyButtonsVO
41 } from '@/api/ai/image'
42 import ImageDetail from './ImageDetail.vue'
43 import ImageCard from './ImageCard.vue'
44 import { ElLoading, LoadingOptionsResolved } from 'element-plus'
45 import { AiImageStatusEnum } from '@/views/ai/utils/constants'
46 import download from '@/utils/download'
47
48 const message = useMessage() // 消息弹窗
49 const router = useRouter() // 路由
50
51 // 图片分页相关的参数
52 const queryParams = reactive({
53   pageNo: 1,
54   pageSize: 10
55 })
56 const pageTotal = ref<number>(0) // page size
57 const imageList = ref<ImageVO[]>([]) // image 列表
58 const imageListLoadingInstance = ref<any>() // image 列表是否正在加载中
59 const imageListRef = ref<any>() // ref
60 // 图片轮询相关的参数(正在生成中的)
61 const inProgressImageMap = ref<{}>({}) // 监听的 image 映射,一般是生成中(需要轮询),key 为 image 编号,value 为 image
62 const inProgressTimer = ref<any>() // 生成中的 image 定时器,轮询生成进展
63 // 图片详情相关的参数
64 const isShowImageDetail = ref<boolean>(false) // 图片详情是否展示
65 const showImageDetailId = ref<number>(0) // 图片详情的图片编号
66
67 /** 处理查看绘图作品 */
68 const handleViewPublic = () => {
69   router.push({
70     name: 'AiImageSquare'
71   })
72 }
73
74 /** 查看图片的详情  */
75 const handleDetailOpen = async () => {
76   isShowImageDetail.value = true
77 }
78
79 /** 关闭图片的详情  */
80 const handleDetailClose = async () => {
81   isShowImageDetail.value = false
82 }
83
84 /** 获得 image 图片列表 */
85 const getImageList = async () => {
86   try {
87     // 1. 加载图片列表
88     imageListLoadingInstance.value = ElLoading.service({
89       target: imageListRef.value,
90       text: '加载中...'
91     } as LoadingOptionsResolved)
92     const { list, total } = await ImageApi.getImagePageMy(queryParams)
93     imageList.value = list
94     pageTotal.value = total
95
96     // 2. 计算需要轮询的图片
97     const newWatImages = {}
98     imageList.value.forEach((item) => {
99       if (item.status === AiImageStatusEnum.IN_PROGRESS) {
100         newWatImages[item.id] = item
101       }
102     })
103     inProgressImageMap.value = newWatImages
104   } finally {
105     // 关闭正在“加载中”的 Loading
106     if (imageListLoadingInstance.value) {
107       imageListLoadingInstance.value.close()
108       imageListLoadingInstance.value = null
109     }
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 /** 图片的点击事件 */
136 const handleImageButtonClick = async (type: string, imageDetail: ImageVO) => {
137   // 详情
138   if (type === 'more') {
139     showImageDetailId.value = imageDetail.id
140     await handleDetailOpen()
141     return
142   }
143   // 删除
144   if (type === 'delete') {
145     await message.confirm(`是否删除照片?`)
146     await ImageApi.deleteImageMy(imageDetail.id)
147     await getImageList()
148     message.success('删除成功!')
149     return
150   }
151   // 下载
152   if (type === 'download') {
153     await download.image(imageDetail.picUrl)
154     return
155   }
156   // 重新生成
157   if (type === 'regeneration') {
158     await emits('onRegeneration', imageDetail)
159     return
160   }
161 }
162
163 /** 处理 Midjourney 按钮点击事件  */
164 const handleImageMidjourneyButtonClick = async (
165   button: ImageMidjourneyButtonsVO,
166   imageDetail: ImageVO
167 ) => {
168   // 1. 构建 params 参数
169   const data = {
170     id: imageDetail.id,
171     customId: button.customId
172   } as ImageMidjourneyActionVO
173   // 2. 发送 action
174   await ImageApi.midjourneyAction(data)
175   // 3. 刷新列表
176   await getImageList()
177 }
178
179 defineExpose({ getImageList }) // 暴露组件方法
180
181 const emits = defineEmits(['onRegeneration'])
182
183 /** 组件挂在的时候 */
184 onMounted(async () => {
185   // 获取 image 列表
186   await getImageList()
187   // 自动刷新 image 列表
188   inProgressTimer.value = setInterval(async () => {
189     await refreshWatchImages()
190   }, 1000 * 3)
191 })
192
193 /** 组件取消挂在的时候 */
194 onUnmounted(async () => {
195   if (inProgressTimer.value) {
196     clearInterval(inProgressTimer.value)
197   }
198 })
199 </script>
200 <style lang="scss">
201 .dr-task {
202   width: 100%;
203   height: 100%;
204 }
205 .task-card {
206   margin: 0;
207   padding: 0;
208   height: 100%;
209   position: relative;
210 }
211
212 .task-image-list {
213   position: relative;
214   display: flex;
215   flex-direction: row;
216   flex-wrap: wrap;
217   align-content: flex-start;
218   height: 100%;
219   overflow: auto;
220   padding: 20px 20px 140px;
221   box-sizing: border-box; /* 确保内边距不会增加高度 */
222
223   > div {
224     margin-right: 20px;
225     margin-bottom: 20px;
226   }
227   > div:last-of-type {
228     //margin-bottom: 100px;
229   }
230 }
231
232 .task-image-pagination {
233   position: absolute;
234   bottom: 60px;
235   height: 50px;
236   line-height: 90px;
237   width: 100%;
238   z-index: 999;
239   background-color: #ffffff;
240   display: flex;
241   flex-direction: row;
242   justify-content: center;
243   align-items: center;
244 }
245 </style>