Bläddra i källkod

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

YunaiV 11 månader sedan
förälder
incheckning
82b53b9b03

+ 6 - 11
src/api/ai/image/index.ts

@@ -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 })
   },
 

+ 18 - 19
src/utils/download.ts

@@ -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()
-  }
-}

+ 5 - 10
src/views/ai/image/index/components/ImageCard.vue

@@ -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)

+ 79 - 76
src/views/ai/image/index/components/ImageList.vue

@@ -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 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 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 pageTotal = ref<number>(0) // page size
+const inProgressTimer = ref<any>() // 生成中的 image 定时器,轮询生成进展
+// 图片详情相关的参数
+const isShowImageDetail = ref<boolean>(false) // 图片详情是否展示
+const showImageDetailId = ref<number>(0) // 图片详情的图片编号
 
-/**  抽屉 - close  */
-const handleDrawerClose = async () => {
-  isShowImageDetail.value = false
+/** 查看图片的详情  */
+const handleDetailOpen = async () => {
+  isShowImageDetail.value = true
 }
 
-/**  任务 - detail  */
-const handleDrawerOpen = async () => {
-  isShowImageDetail.value = true
+/** 关闭图片的详情  */
+const handleDetailClose = async () => {
+  isShowImageDetail.value = false
 }
 
-/**
- * 获取 - image 列表
- */
-const getImageList = async (apply: boolean = false) => {
-  imageTaskLoading.value = true
+/** 获得 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>

+ 0 - 1
src/views/bpm/category/index.vue

@@ -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'