【代码优化】AI:绘图 index.vue 代码梳理 30%(ImageList)
parent
80d87b8e99
commit
82b53b9b03
|
@ -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 // 样式: 2(Primary)、3(Green)
|
style: number // 样式: 2(Primary)、3(Green)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue