对比新文件 |
| | |
| | | <template> |
| | | <el-card class="dr-task" body-class="task-card" shadow="never"> |
| | | <template #header> |
| | | 绘画任务 |
| | | <!-- TODO @fan:看看,怎么优化下这个样子哈。 --> |
| | | <el-button @click="handleViewPublic">绘画作品</el-button> |
| | | </template> |
| | | <!-- 图片列表 --> |
| | | <div class="task-image-list" ref="imageListRef"> |
| | | <ImageCard |
| | | v-for="image in imageList" |
| | | :key="image.id" |
| | | :detail="image" |
| | | @on-btn-click="handleImageButtonClick" |
| | | @on-mj-btn-click="handleImageMidjourneyButtonClick" |
| | | /> |
| | | </div> |
| | | <div class="task-image-pagination"> |
| | | <Pagination |
| | | :total="pageTotal" |
| | | v-model:page="queryParams.pageNo" |
| | | v-model:limit="queryParams.pageSize" |
| | | @pagination="getImageList" |
| | | /> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- 图片详情 --> |
| | | <ImageDetail |
| | | :show="isShowImageDetail" |
| | | :id="showImageDetailId" |
| | | @handle-drawer-close="handleDetailClose" |
| | | /> |
| | | </template> |
| | | <script setup lang="ts"> |
| | | import { |
| | | ImageApi, |
| | | ImageVO, |
| | | ImageMidjourneyActionVO, |
| | | ImageMidjourneyButtonsVO |
| | | } from '@/api/ai/image' |
| | | import ImageDetail from './ImageDetail.vue' |
| | | import ImageCard from './ImageCard.vue' |
| | | import { ElLoading, LoadingOptionsResolved } from 'element-plus' |
| | | import { AiImageStatusEnum } from '@/views/ai/utils/constants' |
| | | import download from '@/utils/download' |
| | | |
| | | const message = useMessage() // 消息弹窗 |
| | | const router = useRouter() // 路由 |
| | | |
| | | // 图片分页相关的参数 |
| | | const queryParams = reactive({ |
| | | pageNo: 1, |
| | | pageSize: 10 |
| | | }) |
| | | const pageTotal = ref<number>(0) // page size |
| | | const imageList = ref<ImageVO[]>([]) // image 列表 |
| | | const imageListLoadingInstance = ref<any>() // image 列表是否正在加载中 |
| | | const imageListRef = ref<any>() // ref |
| | | // 图片轮询相关的参数(正在生成中的) |
| | | const inProgressImageMap = ref<{}>({}) // 监听的 image 映射,一般是生成中(需要轮询),key 为 image 编号,value 为 image |
| | | const inProgressTimer = ref<any>() // 生成中的 image 定时器,轮询生成进展 |
| | | // 图片详情相关的参数 |
| | | const isShowImageDetail = ref<boolean>(false) // 图片详情是否展示 |
| | | const showImageDetailId = ref<number>(0) // 图片详情的图片编号 |
| | | |
| | | /** 处理查看绘图作品 */ |
| | | const handleViewPublic = () => { |
| | | router.push({ |
| | | name: 'AiImageSquare' |
| | | }) |
| | | } |
| | | |
| | | /** 查看图片的详情 */ |
| | | const handleDetailOpen = async () => { |
| | | isShowImageDetail.value = true |
| | | } |
| | | |
| | | /** 关闭图片的详情 */ |
| | | const handleDetailClose = async () => { |
| | | isShowImageDetail.value = false |
| | | } |
| | | |
| | | /** 获得 image 图片列表 */ |
| | | const getImageList = async () => { |
| | | try { |
| | | // 1. 加载图片列表 |
| | | imageListLoadingInstance.value = ElLoading.service({ |
| | | target: imageListRef.value, |
| | | text: '加载中...' |
| | | } as LoadingOptionsResolved) |
| | | const { list, total } = await ImageApi.getImagePageMy(queryParams) |
| | | imageList.value = list |
| | | pageTotal.value = total |
| | | |
| | | // 2. 计算需要轮询的图片 |
| | | const newWatImages = {} |
| | | imageList.value.forEach((item) => { |
| | | if (item.status === AiImageStatusEnum.IN_PROGRESS) { |
| | | newWatImages[item.id] = item |
| | | } |
| | | }) |
| | | inProgressImageMap.value = newWatImages |
| | | } finally { |
| | | // 关闭正在“加载中”的 Loading |
| | | if (imageListLoadingInstance.value) { |
| | | imageListLoadingInstance.value.close() |
| | | imageListLoadingInstance.value = null |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** 轮询生成中的 image 列表 */ |
| | | const refreshWatchImages = async () => { |
| | | const imageIds = Object.keys(inProgressImageMap.value).map(Number) |
| | | if (imageIds.length == 0) { |
| | | return |
| | | } |
| | | const list = (await ImageApi.getImageListMyByIds(imageIds)) as ImageVO[] |
| | | const newWatchImages = {} |
| | | list.forEach((image) => { |
| | | if (image.status === AiImageStatusEnum.IN_PROGRESS) { |
| | | newWatchImages[image.id] = image |
| | | } else { |
| | | const index = imageList.value.findIndex((oldImage) => image.id === oldImage.id) |
| | | if (index >= 0) { |
| | | // 更新 imageList |
| | | imageList.value[index] = image |
| | | } |
| | | } |
| | | }) |
| | | inProgressImageMap.value = newWatchImages |
| | | } |
| | | |
| | | /** 图片的点击事件 */ |
| | | const handleImageButtonClick = async (type: string, imageDetail: ImageVO) => { |
| | | // 详情 |
| | | if (type === 'more') { |
| | | showImageDetailId.value = imageDetail.id |
| | | await handleDetailOpen() |
| | | return |
| | | } |
| | | // 删除 |
| | | if (type === 'delete') { |
| | | await message.confirm(`是否删除照片?`) |
| | | await ImageApi.deleteImageMy(imageDetail.id) |
| | | await getImageList() |
| | | message.success('删除成功!') |
| | | return |
| | | } |
| | | // 下载 |
| | | if (type === 'download') { |
| | | await download.image({ url: imageDetail.picUrl }) |
| | | return |
| | | } |
| | | // 重新生成 |
| | | if (type === 'regeneration') { |
| | | await emits('onRegeneration', imageDetail) |
| | | return |
| | | } |
| | | } |
| | | |
| | | /** 处理 Midjourney 按钮点击事件 */ |
| | | const handleImageMidjourneyButtonClick = async ( |
| | | button: ImageMidjourneyButtonsVO, |
| | | imageDetail: ImageVO |
| | | ) => { |
| | | // 1. 构建 params 参数 |
| | | const data = { |
| | | id: imageDetail.id, |
| | | customId: button.customId |
| | | } as ImageMidjourneyActionVO |
| | | // 2. 发送 action |
| | | await ImageApi.midjourneyAction(data) |
| | | // 3. 刷新列表 |
| | | await getImageList() |
| | | } |
| | | |
| | | defineExpose({ getImageList }) // 暴露组件方法 |
| | | |
| | | const emits = defineEmits(['onRegeneration']) |
| | | |
| | | /** 组件挂在的时候 */ |
| | | onMounted(async () => { |
| | | // 获取 image 列表 |
| | | await getImageList() |
| | | // 自动刷新 image 列表 |
| | | inProgressTimer.value = setInterval(async () => { |
| | | await refreshWatchImages() |
| | | }, 1000 * 3) |
| | | }) |
| | | |
| | | /** 组件取消挂在的时候 */ |
| | | onUnmounted(async () => { |
| | | if (inProgressTimer.value) { |
| | | clearInterval(inProgressTimer.value) |
| | | } |
| | | }) |
| | | </script> |
| | | <style lang="scss"> |
| | | .dr-task { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | .task-card { |
| | | margin: 0; |
| | | padding: 0; |
| | | height: 100%; |
| | | position: relative; |
| | | } |
| | | |
| | | .task-image-list { |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: row; |
| | | flex-wrap: wrap; |
| | | align-content: flex-start; |
| | | height: 100%; |
| | | overflow: auto; |
| | | padding: 20px 20px 140px; |
| | | box-sizing: border-box; /* 确保内边距不会增加高度 */ |
| | | |
| | | > div { |
| | | margin-right: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | > div:last-of-type { |
| | | //margin-bottom: 100px; |
| | | } |
| | | } |
| | | |
| | | .task-image-pagination { |
| | | position: absolute; |
| | | bottom: 60px; |
| | | height: 50px; |
| | | line-height: 90px; |
| | | width: 100%; |
| | | z-index: 999; |
| | | background-color: #ffffff; |
| | | display: flex; |
| | | flex-direction: row; |
| | | justify-content: center; |
| | | align-items: center; |
| | | } |
| | | </style> |