【代码优化】AI:绘图 index.vue 代码梳理 30%(ImageList)

pull/474/MERGE
YunaiV 2024-07-09 00:00:53 +08:00
parent 80d87b8e99
commit 82b53b9b03
5 changed files with 110 additions and 119 deletions

View File

@ -14,16 +14,11 @@ export interface ImageVO {
errorMessage: string // 错误信息 errorMessage: string // 错误信息
options: object // 配置 Map<string, string> options: object // 配置 Map<string, string>
taskId: number // 任务编号 taskId: number // 任务编号
buttons: ImageMjButtonsVO[] // mj 操作按钮 buttons: ImageMidjourneyButtonsVO[] // mj 操作按钮
createTime: string // 创建时间 createTime: string // 创建时间
finishTime: string // 完成时间 finishTime: string // 完成时间
} }
export interface ImagePageReqVO {
pageNo: number // 分页编号
pageSize: number // 分页大小
}
export interface ImageDrawReqVO { export interface ImageDrawReqVO {
platform: string // 平台 platform: string // 平台
prompt: string // 提示词 prompt: string // 提示词
@ -43,22 +38,22 @@ export interface ImageMidjourneyImagineReqVO {
version: string // 版本 version: string // 版本
} }
export interface ImageMjActionVO { export interface ImageMidjourneyActionVO {
id: number // 图片编号 id: number // 图片编号
customId: string // MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识 customId: string // MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识
} }
export interface ImageMjButtonsVO { export interface ImageMidjourneyButtonsVO {
customId: string // MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识 customId: string // MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识
emoji: string // 图标 emoji emoji: string // 图标 emoji
label: string // Make Variations 文本 label: string // Make Variations 文本
style: number // 样式: 2Primary、3Green style: number // 样式: 2Primary、3Green
} }
// AI API 密钥 API // AI 图片 API
export const ImageApi = { export const ImageApi = {
// 获取【我的】绘图分页 // 获取【我的】绘图分页
getImagePageMy: async (params: ImagePageReqVO) => { getImagePageMy: async (params: PageParam) => {
return await request.get({ url: `/ai/image/my-page`, params }) return await request.get({ url: `/ai/image/my-page`, params })
}, },
// 获取【我的】绘图记录 // 获取【我的】绘图记录
@ -85,7 +80,7 @@ export const ImageApi = {
return await request.post({ url: `/ai/image/midjourney/imagine`, data }) return await request.post({ url: `/ai/image/midjourney/imagine`, data })
}, },
// 【Midjourney】Action 操作(二次生成图片) // 【Midjourney】Action 操作(二次生成图片)
midjourneyAction: async (data: ImageMjActionVO) => { midjourneyAction: async (data: ImageMidjourneyActionVO) => {
return await request.post({ url: `/ai/image/midjourney/action`, data }) return await request.post({ url: `/ai/image/midjourney/action`, data })
}, },

View File

@ -32,26 +32,25 @@ const download = {
// 下载 Markdown 方法 // 下载 Markdown 方法
markdown: (data: Blob, fileName: string) => { markdown: (data: Blob, fileName: string) => {
download0(data, fileName, 'text/markdown') download0(data, fileName, 'text/markdown')
},
// 下载图片(允许跨域)
image: (url: string) => {
const image = new Image()
image.setAttribute('crossOrigin', 'anonymous')
image.src = url
image.onload = () => {
const canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
const ctx = canvas.getContext('2d') as CanvasDrawImage
ctx.drawImage(image, 0, 0, image.width, image.height)
const url = canvas.toDataURL('image/png')
const a = document.createElement('a')
a.href = url
a.download = 'image.png'
a.click()
}
} }
} }
export default download export default download
/** 图片下载(通过浏览器图片下载) */
export const downloadImage = async (imageUrl) => {
const image = new Image()
image.setAttribute('crossOrigin', 'anonymous')
image.src = imageUrl
image.onload = () => {
const canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
const ctx = canvas.getContext('2d') as CanvasDrawImage
ctx.drawImage(image, 0, 0, image.width, image.height)
const url = canvas.toDataURL('image/png')
const a = document.createElement('a')
a.href = url
a.download = 'image.png'
a.click()
}
}

View File

@ -30,12 +30,7 @@
:icon="RefreshRight" :icon="RefreshRight"
@click="handleBtnClick('regeneration', imageDetail)" @click="handleBtnClick('regeneration', imageDetail)"
/> />
<el-button <el-button class="btn" text :icon="Delete" @click="handleBtnClick('delete', imageDetail)" />
class="btn"
text
:icon="Delete"
@click="handleBtnClick('delete', imageDetail)"
/>
<el-button class="btn" text :icon="More" @click="handleBtnClick('more', imageDetail)" /> <el-button class="btn" text :icon="More" @click="handleBtnClick('more', imageDetail)" />
</div> </div>
</div> </div>
@ -61,10 +56,10 @@
</el-card> </el-card>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {Delete, Download, More, RefreshRight} from '@element-plus/icons-vue' import { Delete, Download, More, RefreshRight } from '@element-plus/icons-vue'
import { ImageVO, ImageMjButtonsVO } from '@/api/ai/image' import { ImageVO, ImageMidjourneyButtonsVO } from '@/api/ai/image'
import { PropType } from 'vue' import { PropType } from 'vue'
import {ElLoading, LoadingOptionsResolved} from 'element-plus' import { ElLoading, LoadingOptionsResolved } from 'element-plus'
import { AiImageStatusEnum } from '@/views/ai/utils/constants' import { AiImageStatusEnum } from '@/views/ai/utils/constants'
const cardImageRef = ref<any>() // image ref const cardImageRef = ref<any>() // image ref
@ -98,7 +93,7 @@ const handleLoading = async (status: number) => {
} }
/** mj 按钮 click */ /** mj 按钮 click */
const handleMjBtnClick = async (button: ImageMjButtonsVO) => { const handleMjBtnClick = async (button: ImageMidjourneyButtonsVO) => {
// //
await message.confirm(`确认操作 "${button.label} ${button.emoji}" ?`) await message.confirm(`确认操作 "${button.label} ${button.emoji}" ?`)
emits('onMjBtnClick', button, props.imageDetail) emits('onMjBtnClick', button, props.imageDetail)

View File

@ -2,22 +2,21 @@
<el-card class="dr-task" body-class="task-card" shadow="never"> <el-card class="dr-task" body-class="task-card" shadow="never">
<template #header>绘画任务</template> <template #header>绘画任务</template>
<!-- 图片列表 --> <!-- 图片列表 -->
<div class="task-image-list" ref="imageTaskRef"> <div class="task-image-list" ref="imageListRef">
<ImageCard <ImageCard
v-for="image in imageList" v-for="image in imageList"
:key="image.id" :key="image.id"
:image-detail="image" :image-detail="image"
@on-btn-click="handleImageBtnClick" @on-btn-click="handleImageButtonClick"
@on-mj-btn-click="handleImageMjBtnClick" @on-mj-btn-click="handleImageMidjourneyButtonClick"
/> />
</div> </div>
<div class="task-image-pagination"> <div class="task-image-pagination">
<el-pagination <Pagination
background
layout="prev, pager, next"
:default-page-size="pageSize"
:total="pageTotal" :total="pageTotal"
@change="handlePageChange" v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getImageList"
/> />
</div> </div>
</el-card> </el-card>
@ -26,62 +25,63 @@
<ImageDetail <ImageDetail
:show="isShowImageDetail" :show="isShowImageDetail"
:id="showImageDetailId" :id="showImageDetailId"
@handle-drawer-close="handleDrawerClose" @handle-drawer-close="handleDetailClose"
/> />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ImageApi, ImageVO, ImageMjActionVO, ImageMjButtonsVO } from '@/api/ai/image' import {
ImageApi,
ImageVO,
ImageMidjourneyActionVO,
ImageMidjourneyButtonsVO
} from '@/api/ai/image'
import ImageDetail from './ImageDetail.vue' import ImageDetail from './ImageDetail.vue'
import ImageCard from './ImageCard.vue' import ImageCard from './ImageCard.vue'
import { ElLoading, LoadingOptionsResolved } from 'element-plus' import { ElLoading, LoadingOptionsResolved } from 'element-plus'
import { AiImageStatusEnum } from '@/views/ai/utils/constants' import { AiImageStatusEnum } from '@/views/ai/utils/constants'
import { downloadImage } from '@/utils/download' import download from '@/utils/download'
const message = useMessage() // const message = useMessage() //
const imageList = ref<ImageVO[]>([]) // image //
const inProgressImageMap = ref<{}>({}) // image key image value image const queryParams = reactive({
const imageListInterval = ref<any>() // image pageNo: 1,
const isShowImageDetail = ref<boolean>(false) // task pageSize: 10
const showImageDetailId = ref<number>(0) // task })
const imageTaskRef = ref<any>() // ref
const imageTaskLoadingInstance = ref<any>() // loading
const imageTaskLoading = ref<boolean>(false) // loading
const pageNo = ref<number>(1) // page no
const pageSize = ref<number>(10) // page size
const pageTotal = ref<number>(0) // page size 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) //
/** 抽屉 - close */ /** 查看图片的详情 */
const handleDrawerClose = async () => { const handleDetailOpen = async () => {
isShowImageDetail.value = false
}
/** 任务 - detail */
const handleDrawerOpen = async () => {
isShowImageDetail.value = true isShowImageDetail.value = true
} }
/** /** 关闭图片的详情 */
* 获取 - image 列表 const handleDetailClose = async () => {
*/ isShowImageDetail.value = false
const getImageList = async (apply: boolean = false) => { }
imageTaskLoading.value = true
/** 获得 image 图片列表 */
const getImageList = async () => {
try { try {
imageTaskLoadingInstance.value = ElLoading.service({ // 1.
target: imageTaskRef.value, imageListLoadingInstance.value = ElLoading.service({
target: imageListRef.value,
text: '加载中...' text: '加载中...'
} as LoadingOptionsResolved) } as LoadingOptionsResolved)
const { list, total } = await ImageApi.getImagePageMy({ const { list, total } = await ImageApi.getImagePageMy(queryParams)
pageNo: pageNo.value, imageList.value = list
pageSize: pageSize.value
})
if (apply) {
imageList.value = [...imageList.value, ...list]
} else {
imageList.value = list
}
pageTotal.value = total pageTotal.value = total
// watch
// 2.
const newWatImages = {} const newWatImages = {}
imageList.value.forEach((item) => { imageList.value.forEach((item) => {
if (item.status === AiImageStatusEnum.IN_PROGRESS) { if (item.status === AiImageStatusEnum.IN_PROGRESS) {
@ -90,9 +90,10 @@ const getImageList = async (apply: boolean = false) => {
}) })
inProgressImageMap.value = newWatImages inProgressImageMap.value = newWatImages
} finally { } finally {
if (imageTaskLoadingInstance.value) { // Loading
imageTaskLoadingInstance.value.close() if (imageListLoadingInstance.value) {
imageTaskLoadingInstance.value = null imageListLoadingInstance.value.close()
imageListLoadingInstance.value = null
} }
} }
} }
@ -119,50 +120,52 @@ const refreshWatchImages = async () => {
inProgressImageMap.value = newWatchImages inProgressImageMap.value = newWatchImages
} }
/** 图片 - btn click */ /** 图片的点击事件 */
const handleImageBtnClick = async (type: string, imageDetail: ImageVO) => { const handleImageButtonClick = async (type: string, imageDetail: ImageVO) => {
// image detail id //
showImageDetailId.value = imageDetail.id
// btn
if (type === 'more') { if (type === 'more') {
await handleDrawerOpen() showImageDetailId.value = imageDetail.id
} else if (type === 'delete') { await handleDetailOpen()
return
}
//
if (type === 'delete') {
await message.confirm(`是否删除照片?`) await message.confirm(`是否删除照片?`)
await ImageApi.deleteImageMy(imageDetail.id) await ImageApi.deleteImageMy(imageDetail.id)
await getImageList() await getImageList()
message.success('删除成功!') message.success('删除成功!')
} else if (type === 'download') { return
await downloadImage(imageDetail.picUrl) }
} else if (type === 'regeneration') { //
// Midjourney if (type === 'download') {
console.log('regeneration', imageDetail.id) await download.image(imageDetail.picUrl)
return
}
//
if (type === 'regeneration') {
await emits('onRegeneration', imageDetail) await emits('onRegeneration', imageDetail)
return
} }
} }
/** 图片 - mj btn click */ /** 处理 Midjourney 按钮点击事件 */
const handleImageMjBtnClick = async (button: ImageMjButtonsVO, imageDetail: ImageVO) => { const handleImageMidjourneyButtonClick = async (
// 1 params button: ImageMidjourneyButtonsVO,
imageDetail: ImageVO
) => {
// 1. params
const data = { const data = {
id: imageDetail.id, id: imageDetail.id,
customId: button.customId customId: button.customId
} as ImageMjActionVO } as ImageMidjourneyActionVO
// 2 action // 2. action
await ImageApi.midjourneyAction(data) await ImageApi.midjourneyAction(data)
// 3 // 3.
await getImageList() await getImageList()
} }
// page change defineExpose({ getImageList }) //
const handlePageChange = async (page) => {
pageNo.value = page
await getImageList(false)
}
/** 暴露组件方法 */
defineExpose({ getImageList })
// emits
const emits = defineEmits(['onRegeneration']) const emits = defineEmits(['onRegeneration'])
/** 组件挂在的时候 */ /** 组件挂在的时候 */
@ -170,15 +173,15 @@ onMounted(async () => {
// image // image
await getImageList() await getImageList()
// image // image
imageListInterval.value = setInterval(async () => { inProgressTimer.value = setInterval(async () => {
await refreshWatchImages() await refreshWatchImages()
}, 1000 * 3) }, 1000 * 3)
}) })
/** 组件取消挂在的时候 */ /** 组件取消挂在的时候 */
onUnmounted(async () => { onUnmounted(async () => {
if (imageListInterval.value) { if (inProgressTimer.value) {
clearInterval(imageListInterval.value) clearInterval(inProgressTimer.value)
} }
}) })
</script> </script>

View File

@ -126,7 +126,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict' import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { CategoryApi, CategoryVO } from '@/api/bpm/category' import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import CategoryForm from './CategoryForm.vue' import CategoryForm from './CategoryForm.vue'