Kaynağa Gözat

feat: menu update

28968 7 ay önce
ebeveyn
işleme
c1ae2c8d90

+ 21 - 0
src/api/modules/demo/dataAugmentation.ts

@@ -16,6 +16,23 @@ export const getTaskDictData = () => {
 export const listDataAugmentationApi = (query: DataAugmentationQuery) => {
   return http.get<DataAugmentationVO[]>('/demo/dataAugmentation/list', query, { loading: false })
 }
+/**
+ * @name 查询图像拼接
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listDataAugmentationApi1 = (query: DataAugmentationQuery) => {
+  return http.get<DataAugmentationVO[]>('/demo/dataAugmentation/imageMosaicList', query, { loading: false })
+}
+
+/**
+ * @name 查询目标毁伤
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listDataAugmentationApi2 = (query: DataAugmentationQuery) => {
+  return http.get<DataAugmentationVO[]>('/demo/dataAugmentation/targetDamageAcessList', query, { loading: false })
+}
 
 /**
  * @name 查询视频去抖动详细
@@ -96,3 +113,7 @@ export const getCompareImageCountApi = (taskId: String | Number) => {
 export const getDialogApi = (id: String | Number) => {
   return http.get('/demo/dataAugmentation/log/' + id)
 }
+
+export const getMetricApi = (id: String | Number) => {
+  return http.get('/demo/dataAugmentation/metric/' + id)
+}

+ 52 - 14
src/views/demo/dataAugmentation/index.vue

@@ -27,6 +27,15 @@
         </el-popconfirm>
         <el-button type="primary" link icon="View" @click="compareDataAugmentation(scope.row)" v-if="scope.row.status == '2'"> 预览 </el-button>
         <el-button type="primary" link icon="View" @click="downloadFile(scope.row)" v-if="scope.row.status == '2'"> 导出 </el-button>
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          @click="openMetricDialog(scope.row.id)"
+          v-if="(scope.row.taskType == '图像增强' || scope.row.taskType == '图像逆光') && scope.row.status == '2'"
+        >
+          指标
+        </el-button>
         <el-button type="primary" link icon="View" v-auth="['demo:DataAugmentation:query']" @click="openDialog(3, '任务查看', scope.row)">
           查看
         </el-button>
@@ -74,6 +83,12 @@
     <el-dialog v-model="logDialogVisible" title="日志" width="80%">
       <el-text class="mx-1">{{ logDialog }}</el-text>
     </el-dialog>
+    <el-dialog v-model="metricDialogVisible" title="算法结果指标" width="500px" style="text-align: center">
+      <el-table :data="metricList" style="width: 100%; margin-left: 10%; text-align: left">
+        <el-table-column prop="name" label="name" width="200px" />
+        <el-table-column prop="value" label="value" width="200px" />
+      </el-table>
+    </el-dialog>
   </div>
 </template>
 
@@ -100,11 +115,23 @@ import {
   getCompareImageApi,
   getCompareImageCountApi,
   getDialogApi,
-  getTaskDictData
+  getTaskDictData,
+  getMetricApi
 } from '@/api/modules/demo/dataAugmentation'
 // import {  } from '@/api/modules/system/dictData'
 import { S } from 'vite/dist/node/types.d-aGj9QkWt'
 import { servicesVersion } from 'typescript'
+
+//打开指标窗口查看算法指标信息
+const metricDialogVisible = ref(false)
+const metricList = reactive([])
+const openMetricDialog = async (id: string | number) => {
+  const res: any = await getMetricApi(id)
+  console.log(res.data)
+  metricList.splice(0, metricList.length, ...res.data)
+  metricDialogVisible.value = true
+}
+
 const dialogVisible = ref(false)
 const taskId = ref('')
 const imageIdx = ref(0)
@@ -120,6 +147,7 @@ const dialogTitle = ref('')
 const fileCount = ref(0)
 const imageFps = ref(0)
 const intervalChangeFps: any = ref()
+
 const taskType = ref([])
 const taskTypeEnums: EnumProps[] = []
 
@@ -146,20 +174,30 @@ const openLogDialog = async (id: string | number) => {
 const getTaskType = async () => {
   const res: any = await getTaskDictData()
   if (res.data.length != 0) {
-    taskType.value = res.data.map(item => ({
-      label: item.taskType,
-      value: item.taskType
-    }))
-    res.data.forEach(item => {
-      taskTypeEnums.push({
-        label: item.taskType,
-        value: item.taskType,
-        disabled: false,
-        tagType: 'default'
+    taskType.value = res.data
+      .map(item => {
+        if (item.taskType === '图像逆光' || item.taskType === '图像增强') {
+          return {
+            label: item.taskType,
+            value: item.taskType
+          }
+        } else {
+          return null
+        }
       })
-      let obj = {}
-      obj[item.taskType] = item.hyperparameterConfiguration
-      hyperparameterConfiguration.push(obj)
+      .filter(item => item !== null)
+    res.data.forEach(item => {
+      if (item.taskType === '图像增强' || item.taskType === '图像逆光') {
+        taskTypeEnums.push({
+          label: item.taskType,
+          value: item.taskType,
+          disabled: false,
+          tagType: 'default'
+        })
+        let obj = {}
+        obj[item.taskType] = item.hyperparameterConfiguration
+        hyperparameterConfiguration.push(obj)
+      }
     })
   }
 }

+ 579 - 0
src/views/demo/imageMosaic/index.vue

@@ -0,0 +1,579 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listDataAugmentationApi1">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['demo:DataAugmentation:add']" icon="CirclePlus" @click="openDialog(1, '任务新增')"> 新增 </el-button>
+        <!-- <el-button type="primary" v-auth="['demo:DataAugmentation:import']" icon="Upload" plain @click="batchAdd"> 导入</el-button> -->
+        <!-- <el-button type="primary" v-auth="['demo:DataAugmentation:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button> -->
+        <el-button
+          type="danger"
+          v-auth="['demo:DataAugmentation:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button type="primary" link icon="View" @click="startDataAugmentation(scope.row)" v-if="scope.row.status == '0'"> 开始 </el-button>
+        <el-popconfirm title="确定终止此任务吗?" @confirm="stopDataAugmentation(scope.row)" v-if="scope.row.status == '1'">
+          <template #reference>
+            <el-button type="primary" link icon="View"> 终止 </el-button>
+          </template>
+        </el-popconfirm>
+        <el-button type="primary" link icon="View" @click="compareDataAugmentation(scope.row)" v-if="scope.row.status == '2'"> 预览 </el-button>
+        <el-button type="primary" link icon="View" @click="downloadFile(scope.row)" v-if="scope.row.status == '2'"> 导出 </el-button>
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          @click="openMetricDialog(scope.row.id)"
+          v-if="(scope.row.taskType == '图像增强' || scope.row.taskType == '图像逆光') && scope.row.status == '2'"
+        >
+          指标
+        </el-button>
+        <el-button type="primary" link icon="View" v-auth="['demo:DataAugmentation:query']" @click="openDialog(3, '任务查看', scope.row)">
+          查看
+        </el-button>
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          v-auth="['demo:DataAugmentation:query']"
+          @click="openLogDialog(scope.row.id)"
+          v-if="scope.row.status != '0'"
+        >
+          日志
+        </el-button>
+        <el-button type="primary" link icon="Delete" v-auth="['demo:DataAugmentation:remove']" @click="deleteDataAugmentation(scope.row)">
+          删除
+        </el-button>
+      </template>
+    </ProTable>
+    <DataAugmentationFormDialog ref="formDialogRef" />
+    <ImportExcel ref="dialogRef" />
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="80%">
+      <div class="image-dialog" v-if="imageIdx >= 0 && cacheImages[imageIdx]">
+        <div style="width: 50%">
+          <el-image v-for="(image, index) in cacheImages[imageIdx].origin" :key="index" :src="'data:image/png;base64,' + image"></el-image>
+          <!-- <el-tag>结果:</el-tag> -->
+        </div>
+        <div v-if="task_type_view == '目标毁伤评估'" style="width: 50%">
+          <ul v-infinite-scroll="load" class="list" :infinite-scroll-disabled="disabled">
+            <li v-for="i in count" :key="i" style="list-style-type: none">
+              <el-image :src="'data:image/png;base64,' + cacheImages[imageIdx].stable[i - 1]"></el-image>
+            </li>
+          </ul>
+          <p v-if="loading">Loading...</p>
+          <p v-if="noMore">No more</p>
+        </div>
+        <div v-else style="width: 50%">
+          <el-image v-for="(image, index) in cacheImages[imageIdx].stable" :key="index" :src="'data:image/png;base64,' + image"></el-image>
+        </div>
+      </div>
+      <div class="image-dialog-btn" v-if="imageFps == 0">
+        <el-button type="primary" @click="pre_picture" :disabled="imageIdx <= 0">上一个</el-button>
+        <el-button type="primary" @click="next_picture" :disabled="imageIdx >= fileCount - 1">下一个</el-button>
+      </div>
+    </el-dialog>
+    <el-dialog v-model="logDialogVisible" title="日志" width="80%">
+      <el-text class="mx-1">{{ logDialog }}</el-text>
+    </el-dialog>
+    <el-dialog v-model="metricDialogVisible" title="算法结果指标" width="40%;" style="text-align: center">
+      <el-table :data="metricList" style="width: 100%; margin-left: 10%; text-align: left">
+        <el-table-column prop="name" label="name" width="200px" />
+        <el-table-column prop="value" label="value" width="200px" />
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="tsx" name="DataAugmentation">
+import { ref, reactive, onMounted, computed, onUnmounted, onBeforeMount } from 'vue'
+import { useHandleData } from '@/hooks/useHandleData'
+import { useDownload } from '@/hooks/useDownload'
+import { ElMessageBox, ElMessage } from 'element-plus'
+import ProTable from '@/components/ProTable/index.vue'
+import ImportExcel from '@/components/ImportExcel/index.vue'
+import DataAugmentationFormDialog from '@/components/DataAugmentationFormDialog/index.vue'
+import { ProTableInstance, ColumnProps, EnumProps } from '@/components/ProTable/interface'
+import {
+  listDataAugmentationApi1,
+  delDataAugmentationApi,
+  addDataAugmentationApi,
+  updateDataAugmentationApi,
+  importTemplateApi,
+  importDataAugmentationDataApi,
+  exportDataAugmentationApi,
+  getDataAugmentationApi,
+  startDataAugmentationApi,
+  stopDataAugmentationApi,
+  getCompareImageApi,
+  getCompareImageCountApi,
+  getDialogApi,
+  getTaskDictData,
+  getMetricApi
+} from '@/api/modules/demo/dataAugmentation'
+// import {  } from '@/api/modules/system/dictData'
+import { S } from 'vite/dist/node/types.d-aGj9QkWt'
+import { servicesVersion } from 'typescript'
+
+//打开指标窗口查看算法指标信息
+const metricDialogVisible = ref(false)
+const metricList = reactive([])
+const openMetricDialog = async (id: string | number) => {
+  const res: any = await getMetricApi(id)
+  console.log(res.data)
+  metricList.splice(0, metricList.length, ...res.data)
+  metricDialogVisible.value = true
+}
+
+const dialogVisible = ref(false)
+const taskId = ref('')
+const imageIdx = ref(0)
+const imageBase64List = ref({
+  origin: '',
+  stable: ''
+})
+const inFileCount = ref(0)
+const outFileCount = ref(0)
+// 直接缓存所有图片
+const cacheImages = ref({})
+const dialogTitle = ref('')
+const fileCount = ref(0)
+const imageFps = ref(0)
+const intervalChangeFps: any = ref()
+
+const taskType = ref([])
+const taskTypeEnums: EnumProps[] = []
+
+const hyperparameterConfiguration = []
+
+const logDialogVisible = ref(false)
+const logDialog = ref('')
+const count = ref(4)
+const loading = ref(false)
+const noMore = computed(() => count.value >= cacheImages.value[imageIdx.value].stable.length)
+const disabled = computed(() => loading.value || noMore.value)
+const load = () => {
+  loading.value = true
+  setTimeout(() => {
+    count.value += 1
+    loading.value = false
+  }, 2000)
+}
+const openLogDialog = async (id: string | number) => {
+  const res: any = await getDialogApi(id)
+  logDialog.value = res.data
+  logDialogVisible.value = true
+}
+const getTaskType = async () => {
+  const res: any = await getTaskDictData()
+  if (res.data.length != 0) {
+    taskType.value = res.data
+      .map(item => {
+        if (item.taskType === '侦察图像拼接算法_coordinate' || item.taskType === '侦察图像拼接算法_sift') {
+          return {
+            label: item.taskType,
+            value: item.taskType
+          }
+        } else {
+          return null
+        }
+      })
+      .filter(item => item !== null)
+    res.data.forEach(item => {
+      if (item.taskType === '侦察图像拼接算法_coordinate' || item.taskType === '侦察图像拼接算法_sift') {
+        taskTypeEnums.push({
+          label: item.taskType,
+          value: item.taskType,
+          disabled: false,
+          tagType: 'default'
+        })
+        let obj = {}
+        obj[item.taskType] = item.hyperparameterConfiguration
+        hyperparameterConfiguration.push(obj)
+      }
+    })
+  }
+}
+let intervalId: NodeJS.Timeout | null = null
+
+onUnmounted(() => {
+  // 组件卸载时清除定时器
+  if (intervalId !== null) {
+    clearInterval(intervalId)
+  }
+})
+
+onMounted(() => {
+  intervalId = setInterval(async () => {
+    proTable.value?.getTableList()
+  }, 5000)
+  getTaskType()
+})
+
+const startDataAugmentation = async (params: any) => {
+  const res = await startDataAugmentationApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('任务已经开始,请等待')
+  } else {
+    ElMessage.error('任务开始失败,请检查!')
+  }
+  proTable.value?.getTableList()
+}
+
+const stopDataAugmentation = async (params: any) => {
+  const res = await stopDataAugmentationApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('任务终止成功')
+  } else {
+    ElMessage.error('任务终止失败!')
+  }
+  proTable.value?.getTableList()
+}
+
+const loadImageData = async (taskId: string, imageIdx: number) => {
+  const res: any = await getCompareImageApi(taskId, imageIdx)
+  // imageBase64List.value.origin = res.origin
+  // imageBase64List.value.stable = res.stable
+  cacheImages.value[imageIdx].origin = res.origin
+  cacheImages.value[imageIdx].stable = res.stable
+}
+const task_type_view = ref('')
+const compareDataAugmentation = async (params: any) => {
+  if (taskId.value !== '' && taskId.value !== null && taskId.value !== undefined && taskId.value == params.id) {
+    dialogVisible.value = true
+    return
+  }
+  task_type_view.value = params.taskType
+  taskId.value = params.id
+  imageIdx.value = 0
+  cacheImages.value = {}
+  const resCount: any = await getCompareImageCountApi(params.id)
+  if (resCount.code === 200) {
+    inFileCount.value = resCount.data.inFileCount
+    outFileCount.value = resCount.data.outFileCount
+  } else {
+    ElMessage.error('获取图片对比数量失败')
+    return
+  }
+  dialogVisible.value = true
+
+  dialogTitle.value = '缓存图片中'
+  fileCount.value = Math.min(inFileCount.value, outFileCount.value)
+  for (let idx = 1; idx <= fileCount.value; idx++) {
+    dialogTitle.value = '缓存图片中: 第' + idx + '个样本 共' + fileCount.value + '个样本'
+    if (cacheImages.value[idx - 1]) {
+      continue
+    }
+    const res: any = await getCompareImageApi(taskId.value, idx - 1)
+    // console.log(res)
+
+    cacheImages.value[idx - 1] = {
+      origin: res.origin,
+      stable: res.stable
+    }
+  }
+  // console.log(cacheImages.value[0])
+  if (fileCount.value > 0) {
+    dialogTitle.value = '预览: 第1个样本 共' + fileCount.value + '个样本'
+  } else {
+    dialogTitle.value = '无结果预览'
+  }
+
+  // next_picture()
+}
+const next_picture = async () => {
+  if (imageIdx.value < fileCount.value - 1) {
+    if (!cacheImages.value[imageIdx.value + 1]) {
+      await loadImageData(taskId.value, imageIdx.value + 1)
+    }
+    imageIdx.value = imageIdx.value + 1
+  }
+  dialogTitle.value = '预览: 第' + (imageIdx.value + 1) + '个样本 共' + fileCount.value + '个样本'
+}
+const pre_picture = async () => {
+  if (imageIdx.value > 0) {
+    if (!cacheImages.value[imageIdx.value - 1]) {
+      await loadImageData(taskId.value, imageIdx.value - 1)
+    }
+    imageIdx.value = imageIdx.value - 1
+  }
+  dialogTitle.value = '预览: 第' + (imageIdx.value + 1) + '个样本 共' + fileCount.value + '个样本'
+}
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除视频去抖动信息
+const deleteDataAugmentation = async (params: any) => {
+  await useHandleData(delDataAugmentationApi, params.id, '删除任务【' + params.name + '】?')
+  proTable.value?.getTableList()
+}
+
+// 批量删除视频去抖动信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delDataAugmentationApi, ids, '删除所选任务?')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出视频去抖动列表
+const downloadFile = async task => {
+  ElMessageBox.confirm('确认导出任务数据?', '温馨提示', { type: 'warning' }).then(() =>
+    useDownload(exportDataAugmentationApi, 'export', task.outputPath)
+  )
+}
+
+// 批量添加视频去抖动
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+
+const formDialogRef = ref<InstanceType<typeof DataAugmentationFormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  // hyperparameter.value = ''
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getDataAugmentationApi(row?.id || null)
+    // hyperparameter.value = res.data?.hyperparameterConfiguration
+  }
+  // console.log(itemsOptions[1].compOptions?.value)
+  // 重置表单
+  // setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? model : res.data,
+    api: type == 1 ? addDataAugmentationApi : updateDataAugmentationApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+const statusEnums: EnumProps[] = [
+  {
+    label: '未开始',
+    value: '0',
+    disabled: false,
+    tagType: 'default'
+  },
+  {
+    label: '进行中',
+    value: '1',
+    disabled: false,
+    tagType: 'primary'
+  },
+  {
+    label: '完成',
+    value: '2',
+    disabled: false,
+    tagType: 'success'
+  },
+  {
+    label: '失败',
+    value: '3',
+    disabled: false,
+    tagType: 'danger'
+  },
+  {
+    label: '已中断',
+    value: '4',
+    disabled: false,
+    tagType: 'default'
+  }
+]
+
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  { type: 'selection', fixed: 'left', width: 70 },
+  { prop: 'id', label: '主键ID', width: 180 },
+  {
+    prop: 'name',
+    label: '任务名称',
+    search: {
+      el: 'input'
+    },
+    width: 150
+  },
+  {
+    prop: 'taskType',
+    label: '任务类型',
+    search: {
+      el: 'select'
+    },
+    width: 100,
+    tag: true,
+    enum: taskTypeEnums
+  },
+  {
+    prop: 'status',
+    label: '任务状态',
+    search: {
+      el: 'select'
+    },
+    width: 100,
+    tag: true,
+    enum: statusEnums
+  },
+  {
+    prop: 'startTime',
+    label: '开始时间',
+    width: 180
+  },
+  {
+    prop: 'endTime',
+    label: '结束时间',
+    width: 180
+  },
+  {
+    prop: 'costSecond',
+    label: '耗时',
+    search: {
+      el: 'input'
+    },
+    width: 80
+  },
+  {
+    prop: 'log',
+    label: '日志',
+    width: 120
+  },
+
+  {
+    prop: 'algorithmPath',
+    label: '日志路径',
+    width: 120
+  },
+  {
+    prop: 'hyperparameterConfiguration',
+    label: '超参配置',
+    width: 120
+  },
+  {
+    prop: 'remarks',
+    label: '备注',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'operation',
+    label: '操作',
+    width: 230,
+    fixed: 'right'
+  }
+])
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = [
+  {
+    label: '任务名称',
+    prop: 'name',
+    rules: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
+    compOptions: {
+      placeholder: '请输入任务名称'
+    }
+  },
+  {
+    label: '任务类型',
+    prop: 'taskType',
+    rules: [{ required: true, message: '任务类型不能为空', trigger: 'change' }],
+    compOptions: {
+      elTagName: 'select', // 指定使用 el-select 组件
+      placeholder: '请选择任务类型',
+      enum: taskType,
+      onChange: (value: string) => {
+        model.value['taskType'] = value
+        hyperparameterConfiguration.forEach(obj => {
+          if (value in obj) {
+            // console.log(obj[value])
+            addParams(obj[value])
+            openDialog(1, '任务新增')
+          }
+        })
+      }
+    }
+  },
+  {
+    label: '图片集压缩包',
+    prop: 'inputOssId',
+    rules: [{ required: true, message: '数据压缩包不能为空', trigger: 'change' }],
+    compOptions: {
+      elTagName: 'file-upload',
+      fileSize: 4096,
+      fileType: ['zip'],
+      placeholder: '请上传图片集压缩包'
+    }
+  },
+  {
+    label: '备注',
+    prop: 'remarks',
+    rules: [
+      {
+        required: false,
+        trigger: 'blur'
+      }
+    ],
+    compOptions: {
+      placeholder: '请输入备注'
+    }
+  }
+]
+const model = ref({})
+const setItemsOptions = () => {
+  if (itemsOptions.length > 4) {
+    itemsOptions.splice(4) // 如果里面有新增参数,删除,重新添加
+  }
+}
+
+const addParams = params => {
+  setItemsOptions()
+  if (params == 'null') {
+    return
+  }
+  let validJsonString = params.replace(/'/g, '"')
+  try {
+    const obj: { [key: string]: number } = JSON.parse(validJsonString)
+    Object.keys(obj).forEach(key => {
+      model.value[key] = obj[key]
+
+      itemsOptions.push({
+        label: key,
+        prop: key,
+        rules: [{ trigger: 'blur' }],
+        compOptions: {
+          type: 'input',
+          clearable: true,
+          placeholder: '默认值为' + obj[key]
+        }
+      })
+    })
+  } catch (error) {
+    console.error('解析 JSON 字符串时出错:', error)
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.image-dialog {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  .el-image {
+    margin-right: 20px;
+    margin-bottom: 20px;
+  }
+}
+.image-dialog-btn {
+  display: flex;
+  justify-content: center;
+  margin-top: 20px;
+}
+</style>

+ 579 - 0
src/views/demo/targetDamageAcess/index.vue

@@ -0,0 +1,579 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listDataAugmentationApi2">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['demo:DataAugmentation:add']" icon="CirclePlus" @click="openDialog(1, '任务新增')"> 新增 </el-button>
+        <!-- <el-button type="primary" v-auth="['demo:DataAugmentation:import']" icon="Upload" plain @click="batchAdd"> 导入</el-button> -->
+        <!-- <el-button type="primary" v-auth="['demo:DataAugmentation:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button> -->
+        <el-button
+          type="danger"
+          v-auth="['demo:DataAugmentation:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button type="primary" link icon="View" @click="startDataAugmentation(scope.row)" v-if="scope.row.status == '0'"> 开始 </el-button>
+        <el-popconfirm title="确定终止此任务吗?" @confirm="stopDataAugmentation(scope.row)" v-if="scope.row.status == '1'">
+          <template #reference>
+            <el-button type="primary" link icon="View"> 终止 </el-button>
+          </template>
+        </el-popconfirm>
+        <el-button type="primary" link icon="View" @click="compareDataAugmentation(scope.row)" v-if="scope.row.status == '2'"> 预览 </el-button>
+        <el-button type="primary" link icon="View" @click="downloadFile(scope.row)" v-if="scope.row.status == '2'"> 导出 </el-button>
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          @click="openMetricDialog(scope.row.id)"
+          v-if="(scope.row.taskType == '图像增强' || scope.row.taskType == '图像逆光') && scope.row.status == '2'"
+        >
+          指标
+        </el-button>
+        <el-button type="primary" link icon="View" v-auth="['demo:DataAugmentation:query']" @click="openDialog(3, '任务查看', scope.row)">
+          查看
+        </el-button>
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          v-auth="['demo:DataAugmentation:query']"
+          @click="openLogDialog(scope.row.id)"
+          v-if="scope.row.status != '0'"
+        >
+          日志
+        </el-button>
+        <el-button type="primary" link icon="Delete" v-auth="['demo:DataAugmentation:remove']" @click="deleteDataAugmentation(scope.row)">
+          删除
+        </el-button>
+      </template>
+    </ProTable>
+    <DataAugmentationFormDialog ref="formDialogRef" />
+    <ImportExcel ref="dialogRef" />
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="80%">
+      <div class="image-dialog" v-if="imageIdx >= 0 && cacheImages[imageIdx]">
+        <div style="width: 50%">
+          <el-image v-for="(image, index) in cacheImages[imageIdx].origin" :key="index" :src="'data:image/png;base64,' + image"></el-image>
+          <!-- <el-tag>结果:</el-tag> -->
+        </div>
+        <div v-if="task_type_view == '目标毁伤评估'" style="width: 50%">
+          <ul v-infinite-scroll="load" class="list" :infinite-scroll-disabled="disabled">
+            <li v-for="i in count" :key="i" style="list-style-type: none">
+              <el-image :src="'data:image/png;base64,' + cacheImages[imageIdx].stable[i - 1]"></el-image>
+            </li>
+          </ul>
+          <p v-if="loading">Loading...</p>
+          <p v-if="noMore">No more</p>
+        </div>
+        <div v-else style="width: 50%">
+          <el-image v-for="(image, index) in cacheImages[imageIdx].stable" :key="index" :src="'data:image/png;base64,' + image"></el-image>
+        </div>
+      </div>
+      <div class="image-dialog-btn" v-if="imageFps == 0">
+        <el-button type="primary" @click="pre_picture" :disabled="imageIdx <= 0">上一个</el-button>
+        <el-button type="primary" @click="next_picture" :disabled="imageIdx >= fileCount - 1">下一个</el-button>
+      </div>
+    </el-dialog>
+    <el-dialog v-model="logDialogVisible" title="日志" width="80%">
+      <el-text class="mx-1">{{ logDialog }}</el-text>
+    </el-dialog>
+    <el-dialog v-model="metricDialogVisible" title="算法结果指标" width="40%;" style="text-align: center">
+      <el-table :data="metricList" style="width: 100%; margin-left: 10%; text-align: left">
+        <el-table-column prop="name" label="name" width="200px" />
+        <el-table-column prop="value" label="value" width="200px" />
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="tsx" name="DataAugmentation">
+import { ref, reactive, onMounted, computed, onUnmounted, onBeforeMount } from 'vue'
+import { useHandleData } from '@/hooks/useHandleData'
+import { useDownload } from '@/hooks/useDownload'
+import { ElMessageBox, ElMessage } from 'element-plus'
+import ProTable from '@/components/ProTable/index.vue'
+import ImportExcel from '@/components/ImportExcel/index.vue'
+import DataAugmentationFormDialog from '@/components/DataAugmentationFormDialog/index.vue'
+import { ProTableInstance, ColumnProps, EnumProps } from '@/components/ProTable/interface'
+import {
+  listDataAugmentationApi2,
+  delDataAugmentationApi,
+  addDataAugmentationApi,
+  updateDataAugmentationApi,
+  importTemplateApi,
+  importDataAugmentationDataApi,
+  exportDataAugmentationApi,
+  getDataAugmentationApi,
+  startDataAugmentationApi,
+  stopDataAugmentationApi,
+  getCompareImageApi,
+  getCompareImageCountApi,
+  getDialogApi,
+  getTaskDictData,
+  getMetricApi
+} from '@/api/modules/demo/dataAugmentation'
+// import {  } from '@/api/modules/system/dictData'
+import { S } from 'vite/dist/node/types.d-aGj9QkWt'
+import { servicesVersion } from 'typescript'
+
+//打开指标窗口查看算法指标信息
+const metricDialogVisible = ref(false)
+const metricList = reactive([])
+const openMetricDialog = async (id: string | number) => {
+  const res: any = await getMetricApi(id)
+  console.log(res.data)
+  metricList.splice(0, metricList.length, ...res.data)
+  metricDialogVisible.value = true
+}
+
+const dialogVisible = ref(false)
+const taskId = ref('')
+const imageIdx = ref(0)
+const imageBase64List = ref({
+  origin: '',
+  stable: ''
+})
+const inFileCount = ref(0)
+const outFileCount = ref(0)
+// 直接缓存所有图片
+const cacheImages = ref({})
+const dialogTitle = ref('')
+const fileCount = ref(0)
+const imageFps = ref(0)
+const intervalChangeFps: any = ref()
+
+const taskType = ref([])
+const taskTypeEnums: EnumProps[] = []
+
+const hyperparameterConfiguration = []
+
+const logDialogVisible = ref(false)
+const logDialog = ref('')
+const count = ref(4)
+const loading = ref(false)
+const noMore = computed(() => count.value >= cacheImages.value[imageIdx.value].stable.length)
+const disabled = computed(() => loading.value || noMore.value)
+const load = () => {
+  loading.value = true
+  setTimeout(() => {
+    count.value += 1
+    loading.value = false
+  }, 2000)
+}
+const openLogDialog = async (id: string | number) => {
+  const res: any = await getDialogApi(id)
+  logDialog.value = res.data
+  logDialogVisible.value = true
+}
+const getTaskType = async () => {
+  const res: any = await getTaskDictData()
+  if (res.data.length != 0) {
+    taskType.value = res.data
+      .map(item => {
+        if (item.taskType === '目标毁伤评估') {
+          return {
+            label: item.taskType,
+            value: item.taskType
+          }
+        } else {
+          return null
+        }
+      })
+      .filter(item => item !== null)
+    res.data.forEach(item => {
+      if (item.taskType === '目标毁伤评估') {
+        taskTypeEnums.push({
+          label: item.taskType,
+          value: item.taskType,
+          disabled: false,
+          tagType: 'default'
+        })
+        let obj = {}
+        obj[item.taskType] = item.hyperparameterConfiguration
+        hyperparameterConfiguration.push(obj)
+      }
+    })
+  }
+}
+let intervalId: NodeJS.Timeout | null = null
+
+onUnmounted(() => {
+  // 组件卸载时清除定时器
+  if (intervalId !== null) {
+    clearInterval(intervalId)
+  }
+})
+
+onMounted(() => {
+  intervalId = setInterval(async () => {
+    proTable.value?.getTableList()
+  }, 5000)
+  getTaskType()
+})
+
+const startDataAugmentation = async (params: any) => {
+  const res = await startDataAugmentationApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('任务已经开始,请等待')
+  } else {
+    ElMessage.error('任务开始失败,请检查!')
+  }
+  proTable.value?.getTableList()
+}
+
+const stopDataAugmentation = async (params: any) => {
+  const res = await stopDataAugmentationApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('任务终止成功')
+  } else {
+    ElMessage.error('任务终止失败!')
+  }
+  proTable.value?.getTableList()
+}
+
+const loadImageData = async (taskId: string, imageIdx: number) => {
+  const res: any = await getCompareImageApi(taskId, imageIdx)
+  // imageBase64List.value.origin = res.origin
+  // imageBase64List.value.stable = res.stable
+  cacheImages.value[imageIdx].origin = res.origin
+  cacheImages.value[imageIdx].stable = res.stable
+}
+const task_type_view = ref('')
+const compareDataAugmentation = async (params: any) => {
+  if (taskId.value !== '' && taskId.value !== null && taskId.value !== undefined && taskId.value == params.id) {
+    dialogVisible.value = true
+    return
+  }
+  task_type_view.value = params.taskType
+  taskId.value = params.id
+  imageIdx.value = 0
+  cacheImages.value = {}
+  const resCount: any = await getCompareImageCountApi(params.id)
+  if (resCount.code === 200) {
+    inFileCount.value = resCount.data.inFileCount
+    outFileCount.value = resCount.data.outFileCount
+  } else {
+    ElMessage.error('获取图片对比数量失败')
+    return
+  }
+  dialogVisible.value = true
+
+  dialogTitle.value = '缓存图片中'
+  fileCount.value = Math.min(inFileCount.value, outFileCount.value)
+  for (let idx = 1; idx <= fileCount.value; idx++) {
+    dialogTitle.value = '缓存图片中: 第' + idx + '个样本 共' + fileCount.value + '个样本'
+    if (cacheImages.value[idx - 1]) {
+      continue
+    }
+    const res: any = await getCompareImageApi(taskId.value, idx - 1)
+    // console.log(res)
+
+    cacheImages.value[idx - 1] = {
+      origin: res.origin,
+      stable: res.stable
+    }
+  }
+  // console.log(cacheImages.value[0])
+  if (fileCount.value > 0) {
+    dialogTitle.value = '预览: 第1个样本 共' + fileCount.value + '个样本'
+  } else {
+    dialogTitle.value = '无结果预览'
+  }
+
+  // next_picture()
+}
+const next_picture = async () => {
+  if (imageIdx.value < fileCount.value - 1) {
+    if (!cacheImages.value[imageIdx.value + 1]) {
+      await loadImageData(taskId.value, imageIdx.value + 1)
+    }
+    imageIdx.value = imageIdx.value + 1
+  }
+  dialogTitle.value = '预览: 第' + (imageIdx.value + 1) + '个样本 共' + fileCount.value + '个样本'
+}
+const pre_picture = async () => {
+  if (imageIdx.value > 0) {
+    if (!cacheImages.value[imageIdx.value - 1]) {
+      await loadImageData(taskId.value, imageIdx.value - 1)
+    }
+    imageIdx.value = imageIdx.value - 1
+  }
+  dialogTitle.value = '预览: 第' + (imageIdx.value + 1) + '个样本 共' + fileCount.value + '个样本'
+}
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除视频去抖动信息
+const deleteDataAugmentation = async (params: any) => {
+  await useHandleData(delDataAugmentationApi, params.id, '删除任务【' + params.name + '】?')
+  proTable.value?.getTableList()
+}
+
+// 批量删除视频去抖动信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delDataAugmentationApi, ids, '删除所选任务?')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出视频去抖动列表
+const downloadFile = async task => {
+  ElMessageBox.confirm('确认导出任务数据?', '温馨提示', { type: 'warning' }).then(() =>
+    useDownload(exportDataAugmentationApi, 'export', task.outputPath)
+  )
+}
+
+// 批量添加视频去抖动
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+
+const formDialogRef = ref<InstanceType<typeof DataAugmentationFormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  // hyperparameter.value = ''
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getDataAugmentationApi(row?.id || null)
+    // hyperparameter.value = res.data?.hyperparameterConfiguration
+  }
+  // console.log(itemsOptions[1].compOptions?.value)
+  // 重置表单
+  // setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? model : res.data,
+    api: type == 1 ? addDataAugmentationApi : updateDataAugmentationApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+const statusEnums: EnumProps[] = [
+  {
+    label: '未开始',
+    value: '0',
+    disabled: false,
+    tagType: 'default'
+  },
+  {
+    label: '进行中',
+    value: '1',
+    disabled: false,
+    tagType: 'primary'
+  },
+  {
+    label: '完成',
+    value: '2',
+    disabled: false,
+    tagType: 'success'
+  },
+  {
+    label: '失败',
+    value: '3',
+    disabled: false,
+    tagType: 'danger'
+  },
+  {
+    label: '已中断',
+    value: '4',
+    disabled: false,
+    tagType: 'default'
+  }
+]
+
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  { type: 'selection', fixed: 'left', width: 70 },
+  { prop: 'id', label: '主键ID', width: 180 },
+  {
+    prop: 'name',
+    label: '任务名称',
+    search: {
+      el: 'input'
+    },
+    width: 150
+  },
+  {
+    prop: 'taskType',
+    label: '任务类型',
+    search: {
+      el: 'select'
+    },
+    width: 100,
+    tag: true,
+    enum: taskTypeEnums
+  },
+  {
+    prop: 'status',
+    label: '任务状态',
+    search: {
+      el: 'select'
+    },
+    width: 100,
+    tag: true,
+    enum: statusEnums
+  },
+  {
+    prop: 'startTime',
+    label: '开始时间',
+    width: 180
+  },
+  {
+    prop: 'endTime',
+    label: '结束时间',
+    width: 180
+  },
+  {
+    prop: 'costSecond',
+    label: '耗时',
+    search: {
+      el: 'input'
+    },
+    width: 80
+  },
+  {
+    prop: 'log',
+    label: '日志',
+    width: 120
+  },
+
+  {
+    prop: 'algorithmPath',
+    label: '日志路径',
+    width: 120
+  },
+  {
+    prop: 'hyperparameterConfiguration',
+    label: '超参配置',
+    width: 120
+  },
+  {
+    prop: 'remarks',
+    label: '备注',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'operation',
+    label: '操作',
+    width: 230,
+    fixed: 'right'
+  }
+])
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = [
+  {
+    label: '任务名称',
+    prop: 'name',
+    rules: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
+    compOptions: {
+      placeholder: '请输入任务名称'
+    }
+  },
+  {
+    label: '任务类型',
+    prop: 'taskType',
+    rules: [{ required: true, message: '任务类型不能为空', trigger: 'change' }],
+    compOptions: {
+      elTagName: 'select', // 指定使用 el-select 组件
+      placeholder: '请选择任务类型',
+      enum: taskType,
+      onChange: (value: string) => {
+        model.value['taskType'] = value
+        hyperparameterConfiguration.forEach(obj => {
+          if (value in obj) {
+            // console.log(obj[value])
+            addParams(obj[value])
+            openDialog(1, '任务新增')
+          }
+        })
+      }
+    }
+  },
+  {
+    label: '图片集压缩包',
+    prop: 'inputOssId',
+    rules: [{ required: true, message: '数据压缩包不能为空', trigger: 'change' }],
+    compOptions: {
+      elTagName: 'file-upload',
+      fileSize: 4096,
+      fileType: ['zip'],
+      placeholder: '请上传图片集压缩包'
+    }
+  },
+  {
+    label: '备注',
+    prop: 'remarks',
+    rules: [
+      {
+        required: false,
+        trigger: 'blur'
+      }
+    ],
+    compOptions: {
+      placeholder: '请输入备注'
+    }
+  }
+]
+const model = ref({})
+const setItemsOptions = () => {
+  if (itemsOptions.length > 4) {
+    itemsOptions.splice(4) // 如果里面有新增参数,删除,重新添加
+  }
+}
+
+const addParams = params => {
+  setItemsOptions()
+  if (params == 'null') {
+    return
+  }
+  let validJsonString = params.replace(/'/g, '"')
+  try {
+    const obj: { [key: string]: number } = JSON.parse(validJsonString)
+    Object.keys(obj).forEach(key => {
+      model.value[key] = obj[key]
+
+      itemsOptions.push({
+        label: key,
+        prop: key,
+        rules: [{ trigger: 'blur' }],
+        compOptions: {
+          type: 'input',
+          clearable: true,
+          placeholder: '默认值为' + obj[key]
+        }
+      })
+    })
+  } catch (error) {
+    console.error('解析 JSON 字符串时出错:', error)
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.image-dialog {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  .el-image {
+    margin-right: 20px;
+    margin-bottom: 20px;
+  }
+}
+.image-dialog-btn {
+  display: flex;
+  justify-content: center;
+  margin-top: 20px;
+}
+</style>