【代码优化】AI:绘图 index.vue 代码梳理 30%(ImageList)
parent
80d87b8e99
commit
82b53b9b03
|
@ -14,16 +14,11 @@ export interface ImageVO {
|
|||
errorMessage: string // 错误信息
|
||||
options: object // 配置 Map<string, string>
|
||||
taskId: number // 任务编号
|
||||
buttons: ImageMjButtonsVO[] // mj 操作按钮
|
||||
buttons: ImageMidjourneyButtonsVO[] // mj 操作按钮
|
||||
createTime: string // 创建时间
|
||||
finishTime: string // 完成时间
|
||||
}
|
||||
|
||||
export interface ImagePageReqVO {
|
||||
pageNo: number // 分页编号
|
||||
pageSize: number // 分页大小
|
||||
}
|
||||
|
||||
export interface ImageDrawReqVO {
|
||||
platform: string // 平台
|
||||
prompt: string // 提示词
|
||||
|
@ -43,22 +38,22 @@ export interface ImageMidjourneyImagineReqVO {
|
|||
version: string // 版本
|
||||
}
|
||||
|
||||
export interface ImageMjActionVO {
|
||||
export interface ImageMidjourneyActionVO {
|
||||
id: number // 图片编号
|
||||
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 动作标识
|
||||
emoji: string // 图标 emoji
|
||||
label: string // Make Variations 文本
|
||||
style: number // 样式: 2(Primary)、3(Green)
|
||||
}
|
||||
|
||||
// AI API 密钥 API
|
||||
// AI 图片 API
|
||||
export const ImageApi = {
|
||||
// 获取【我的】绘图分页
|
||||
getImagePageMy: async (params: ImagePageReqVO) => {
|
||||
getImagePageMy: async (params: PageParam) => {
|
||||
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 })
|
||||
},
|
||||
// 【Midjourney】Action 操作(二次生成图片)
|
||||
midjourneyAction: async (data: ImageMjActionVO) => {
|
||||
midjourneyAction: async (data: ImageMidjourneyActionVO) => {
|
||||
return await request.post({ url: `/ai/image/midjourney/action`, data })
|
||||
},
|
||||
|
||||
|
|
|
@ -32,26 +32,25 @@ const download = {
|
|||
// 下载 Markdown 方法
|
||||
markdown: (data: Blob, fileName: string) => {
|
||||
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 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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,12 +30,7 @@
|
|||
:icon="RefreshRight"
|
||||
@click="handleBtnClick('regeneration', imageDetail)"
|
||||
/>
|
||||
<el-button
|
||||
class="btn"
|
||||
text
|
||||
:icon="Delete"
|
||||
@click="handleBtnClick('delete', imageDetail)"
|
||||
/>
|
||||
<el-button class="btn" text :icon="Delete" @click="handleBtnClick('delete', imageDetail)" />
|
||||
<el-button class="btn" text :icon="More" @click="handleBtnClick('more', imageDetail)" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -61,10 +56,10 @@
|
|||
</el-card>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {Delete, Download, More, RefreshRight} from '@element-plus/icons-vue'
|
||||
import { ImageVO, ImageMjButtonsVO } from '@/api/ai/image'
|
||||
import { Delete, Download, More, RefreshRight } from '@element-plus/icons-vue'
|
||||
import { ImageVO, ImageMidjourneyButtonsVO } from '@/api/ai/image'
|
||||
import { PropType } from 'vue'
|
||||
import {ElLoading, LoadingOptionsResolved} from 'element-plus'
|
||||
import { ElLoading, LoadingOptionsResolved } from 'element-plus'
|
||||
import { AiImageStatusEnum } from '@/views/ai/utils/constants'
|
||||
|
||||
const cardImageRef = ref<any>() // 卡片 image ref
|
||||
|
@ -98,7 +93,7 @@ const handleLoading = async (status: number) => {
|
|||
}
|
||||
|
||||
/** mj 按钮 click */
|
||||
const handleMjBtnClick = async (button: ImageMjButtonsVO) => {
|
||||
const handleMjBtnClick = async (button: ImageMidjourneyButtonsVO) => {
|
||||
// 确认窗体
|
||||
await message.confirm(`确认操作 "${button.label} ${button.emoji}" ?`)
|
||||
emits('onMjBtnClick', button, props.imageDetail)
|
||||
|
|
|
@ -2,22 +2,21 @@
|
|||
<el-card class="dr-task" body-class="task-card" shadow="never">
|
||||
<template #header>绘画任务</template>
|
||||
<!-- 图片列表 -->
|
||||
<div class="task-image-list" ref="imageTaskRef">
|
||||
<div class="task-image-list" ref="imageListRef">
|
||||
<ImageCard
|
||||
v-for="image in imageList"
|
||||
:key="image.id"
|
||||
:image-detail="image"
|
||||
@on-btn-click="handleImageBtnClick"
|
||||
@on-mj-btn-click="handleImageMjBtnClick"
|
||||
@on-btn-click="handleImageButtonClick"
|
||||
@on-mj-btn-click="handleImageMidjourneyButtonClick"
|
||||
/>
|
||||
</div>
|
||||
<div class="task-image-pagination">
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
:default-page-size="pageSize"
|
||||
<Pagination
|
||||
:total="pageTotal"
|
||||
@change="handlePageChange"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getImageList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
@ -26,62 +25,63 @@
|
|||
<ImageDetail
|
||||
:show="isShowImageDetail"
|
||||
:id="showImageDetailId"
|
||||
@handle-drawer-close="handleDrawerClose"
|
||||
@handle-drawer-close="handleDetailClose"
|
||||
/>
|
||||
</template>
|
||||
<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 ImageCard from './ImageCard.vue'
|
||||
import { ElLoading, LoadingOptionsResolved } from 'element-plus'
|
||||
import { AiImageStatusEnum } from '@/views/ai/utils/constants'
|
||||
import { downloadImage } from '@/utils/download'
|
||||
import download from '@/utils/download'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const imageList = ref<ImageVO[]>([]) // image 列表
|
||||
const inProgressImageMap = ref<{}>({}) // 监听的 image 映射,一般是生成中(需要轮询),key 为 image 编号,value 为 image
|
||||
const imageListInterval = ref<any>() // image 列表定时器,刷新列表
|
||||
const isShowImageDetail = ref<boolean>(false) // 是否显示 task 详情
|
||||
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 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) // 图片详情的图片编号
|
||||
|
||||
/** 抽屉 - close */
|
||||
const handleDrawerClose = async () => {
|
||||
isShowImageDetail.value = false
|
||||
}
|
||||
|
||||
/** 任务 - detail */
|
||||
const handleDrawerOpen = async () => {
|
||||
/** 查看图片的详情 */
|
||||
const handleDetailOpen = async () => {
|
||||
isShowImageDetail.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 - image 列表
|
||||
*/
|
||||
const getImageList = async (apply: boolean = false) => {
|
||||
imageTaskLoading.value = true
|
||||
/** 关闭图片的详情 */
|
||||
const handleDetailClose = async () => {
|
||||
isShowImageDetail.value = false
|
||||
}
|
||||
|
||||
/** 获得 image 图片列表 */
|
||||
const getImageList = async () => {
|
||||
try {
|
||||
imageTaskLoadingInstance.value = ElLoading.service({
|
||||
target: imageTaskRef.value,
|
||||
// 1. 加载图片列表
|
||||
imageListLoadingInstance.value = ElLoading.service({
|
||||
target: imageListRef.value,
|
||||
text: '加载中...'
|
||||
} as LoadingOptionsResolved)
|
||||
const { list, total } = await ImageApi.getImagePageMy({
|
||||
pageNo: pageNo.value,
|
||||
pageSize: pageSize.value
|
||||
})
|
||||
if (apply) {
|
||||
imageList.value = [...imageList.value, ...list]
|
||||
} else {
|
||||
imageList.value = list
|
||||
}
|
||||
const { list, total } = await ImageApi.getImagePageMy(queryParams)
|
||||
imageList.value = list
|
||||
pageTotal.value = total
|
||||
// 需要 watch 的数据
|
||||
|
||||
// 2. 计算需要轮询的图片
|
||||
const newWatImages = {}
|
||||
imageList.value.forEach((item) => {
|
||||
if (item.status === AiImageStatusEnum.IN_PROGRESS) {
|
||||
|
@ -90,9 +90,10 @@ const getImageList = async (apply: boolean = false) => {
|
|||
})
|
||||
inProgressImageMap.value = newWatImages
|
||||
} finally {
|
||||
if (imageTaskLoadingInstance.value) {
|
||||
imageTaskLoadingInstance.value.close()
|
||||
imageTaskLoadingInstance.value = null
|
||||
// 关闭正在“加载中”的 Loading
|
||||
if (imageListLoadingInstance.value) {
|
||||
imageListLoadingInstance.value.close()
|
||||
imageListLoadingInstance.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,50 +120,52 @@ const refreshWatchImages = async () => {
|
|||
inProgressImageMap.value = newWatchImages
|
||||
}
|
||||
|
||||
/** 图片 - btn click */
|
||||
const handleImageBtnClick = async (type: string, imageDetail: ImageVO) => {
|
||||
// 获取 image detail id
|
||||
showImageDetailId.value = imageDetail.id
|
||||
// 处理不用 btn
|
||||
/** 图片的点击事件 */
|
||||
const handleImageButtonClick = async (type: string, imageDetail: ImageVO) => {
|
||||
// 详情
|
||||
if (type === 'more') {
|
||||
await handleDrawerOpen()
|
||||
} else if (type === 'delete') {
|
||||
showImageDetailId.value = imageDetail.id
|
||||
await handleDetailOpen()
|
||||
return
|
||||
}
|
||||
// 删除
|
||||
if (type === 'delete') {
|
||||
await message.confirm(`是否删除照片?`)
|
||||
await ImageApi.deleteImageMy(imageDetail.id)
|
||||
await getImageList()
|
||||
message.success('删除成功!')
|
||||
} else if (type === 'download') {
|
||||
await downloadImage(imageDetail.picUrl)
|
||||
} else if (type === 'regeneration') {
|
||||
// Midjourney 平台
|
||||
console.log('regeneration', imageDetail.id)
|
||||
return
|
||||
}
|
||||
// 下载
|
||||
if (type === 'download') {
|
||||
await download.image(imageDetail.picUrl)
|
||||
return
|
||||
}
|
||||
// 重新生成
|
||||
if (type === 'regeneration') {
|
||||
await emits('onRegeneration', imageDetail)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/** 图片 - mj btn click */
|
||||
const handleImageMjBtnClick = async (button: ImageMjButtonsVO, imageDetail: ImageVO) => {
|
||||
// 1、构建 params 参数
|
||||
/** 处理 Midjourney 按钮点击事件 */
|
||||
const handleImageMidjourneyButtonClick = async (
|
||||
button: ImageMidjourneyButtonsVO,
|
||||
imageDetail: ImageVO
|
||||
) => {
|
||||
// 1. 构建 params 参数
|
||||
const data = {
|
||||
id: imageDetail.id,
|
||||
customId: button.customId
|
||||
} as ImageMjActionVO
|
||||
// 2、发送 action
|
||||
} as ImageMidjourneyActionVO
|
||||
// 2. 发送 action
|
||||
await ImageApi.midjourneyAction(data)
|
||||
// 3、刷新列表
|
||||
// 3. 刷新列表
|
||||
await getImageList()
|
||||
}
|
||||
|
||||
// page change
|
||||
const handlePageChange = async (page) => {
|
||||
pageNo.value = page
|
||||
await getImageList(false)
|
||||
}
|
||||
defineExpose({ getImageList }) // 暴露组件方法
|
||||
|
||||
/** 暴露组件方法 */
|
||||
defineExpose({ getImageList })
|
||||
|
||||
// emits
|
||||
const emits = defineEmits(['onRegeneration'])
|
||||
|
||||
/** 组件挂在的时候 */
|
||||
|
@ -170,15 +173,15 @@ onMounted(async () => {
|
|||
// 获取 image 列表
|
||||
await getImageList()
|
||||
// 自动刷新 image 列表
|
||||
imageListInterval.value = setInterval(async () => {
|
||||
inProgressTimer.value = setInterval(async () => {
|
||||
await refreshWatchImages()
|
||||
}, 1000 * 3)
|
||||
})
|
||||
|
||||
/** 组件取消挂在的时候 */
|
||||
onUnmounted(async () => {
|
||||
if (imageListInterval.value) {
|
||||
clearInterval(imageListInterval.value)
|
||||
if (inProgressTimer.value) {
|
||||
clearInterval(inProgressTimer.value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -126,7 +126,6 @@
|
|||
<script setup lang="ts">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||
import CategoryForm from './CategoryForm.vue'
|
||||
|
||||
|
|
Loading…
Reference in New Issue