Преглед изворни кода

Merge branch 'develop' of http://47.108.150.237:10000/www/taais-web into develop_async_excel

wanggaokun пре 8 месеци
родитељ
комит
023eb3a980
100 измењених фајлова са 14377 додато и 1743 уклоњено
  1. 1 1
      .editorconfig
  2. 1 1
      .env
  3. 1 0
      .env.development
  4. 2 2
      .env.production
  5. 13 13
      .eslintrc.cjs
  6. 1 1
      index.html
  7. 222 302
      package-lock.json
  8. 4 2
      package.json
  9. 0 0
      public/logo.svg
  10. 148 0
      src/api/interface/demo/AlgorithmConfigTrack.ts
  11. 133 0
      src/api/interface/demo/AlgorithmModelTrack.ts
  12. 221 0
      src/api/interface/demo/DataSeq.ts
  13. 239 0
      src/api/interface/demo/TargetDetection.ts
  14. 206 0
      src/api/interface/demo/toInfrared.ts
  15. 161 0
      src/api/interface/demo/traceMerge.ts
  16. 221 0
      src/api/interface/demo/trackSequence.ts
  17. 171 0
      src/api/interface/demo/video2image.ts
  18. 161 0
      src/api/interface/demo/videoStable.ts
  19. 1 1
      src/api/interface/index.ts
  20. 1 1
      src/api/interface/login.ts
  21. 181 0
      src/api/interface/task/bizProcess.ts
  22. 171 0
      src/api/interface/task/dataProcess.ts
  23. 151 0
      src/api/interface/task/dataSet.ts
  24. 33 9
      src/api/interface/task/subtask.ts
  25. 141 0
      src/api/interface/task/taskConfiguration.ts
  26. 8 0
      src/api/modules/ag/model.ts
  27. 74 0
      src/api/modules/demo/AlgorithmConfigTrack.ts
  28. 74 0
      src/api/modules/demo/AlgorithmModelTrack.ts
  29. 86 0
      src/api/modules/demo/DataSeq.ts
  30. 87 0
      src/api/modules/demo/TargetDetection.ts
  31. 24 4
      src/api/modules/demo/data.ts
  32. 86 0
      src/api/modules/demo/toInfrared.ts
  33. 78 0
      src/api/modules/demo/traceMerge.ts
  34. 86 0
      src/api/modules/demo/trackSequence.ts
  35. 102 0
      src/api/modules/demo/video2image.ts
  36. 91 0
      src/api/modules/demo/videoStable.ts
  37. 36 0
      src/api/modules/taais/task.ts
  38. 94 0
      src/api/modules/task/bizProcess.ts
  39. 97 0
      src/api/modules/task/bizProcessNew.ts
  40. 70 0
      src/api/modules/task/dataProcess.ts
  41. 70 0
      src/api/modules/task/dataSet.ts
  42. 1 1
      src/api/modules/task/subtask.ts
  43. 69 0
      src/api/modules/task/subtaskDetailNew.ts
  44. 69 0
      src/api/modules/task/subtaskNew.ts
  45. 31 0
      src/api/modules/task/task.ts
  46. 70 0
      src/api/modules/task/taskConfiguration.ts
  47. 6 0
      src/api/modules/upload.ts
  48. 0 1
      src/assets/images/logo.svg
  49. 73 7
      src/components/FormDialog/index.vue
  50. 151 160
      src/components/ImportPicDataset/index.vue
  51. 16 3
      src/components/ProForm/index.vue
  52. 0 1
      src/components/ProTable/index.vue
  53. 1 1
      src/components/ProTable/interface/index.ts
  54. 19 5
      src/components/Upload/File.vue
  55. 0 1
      src/components/Upload/FileS3.vue
  56. 30 8
      src/components/Upload/Img.vue
  57. 1 1
      src/hooks/useDownload.ts
  58. 8 8
      src/layouts/components/Header/ToolBarRight.vue
  59. 49 4
      src/routers/modules/routerData.json
  60. 1 1
      src/stores/modules/auth.ts
  61. 2 2
      src/stores/modules/global.ts
  62. 5 0
      src/typings/ProForm.d.ts
  63. 18 0
      src/typings/canvas.d.ts
  64. 106 0
      src/utils/fabric.ts
  65. 34 0
      src/utils/status.ts
  66. 7 11
      src/views/ag/config/index.vue
  67. 37 3
      src/views/ag/model/index.vue
  68. 269 0
      src/views/demo/AlgorithmConfigTrack/index.vue
  69. 315 0
      src/views/demo/AlgorithmModelTrack/index.vue
  70. 346 0
      src/views/demo/DataSeq/index.vue
  71. 464 0
      src/views/demo/TargetDetection/index.vue
  72. 137 0
      src/views/demo/components/PreviewImages.vue
  73. 171 0
      src/views/demo/components/img-detect.vue
  74. 886 0
      src/views/demo/components/img-maker.vue
  75. 170 0
      src/views/demo/data/amplify.vue
  76. 29 0
      src/views/demo/data/index.scss
  77. 346 63
      src/views/demo/data/index.vue
  78. 525 0
      src/views/demo/toInfrared/index.vue
  79. 401 0
      src/views/demo/traceMerge/index.vue
  80. 449 0
      src/views/demo/trackSequence/index.vue
  81. 78 0
      src/views/demo/utils.ts
  82. 340 0
      src/views/demo/video2image/index.vue
  83. 500 0
      src/views/demo/videoStable/index.vue
  84. 3 3
      src/views/login/index.vue
  85. 1 1
      src/views/system/dict/data.vue
  86. 0 1
      src/views/system/user/index.vue
  87. 1107 351
      src/views/taais/homePage/createTask.vue
  88. 44 25
      src/views/taais/homePage/index.scss
  89. 52 42
      src/views/taais/homePage/index.vue
  90. 1 1
      src/views/taais/homePage/inferResult.vue
  91. 2 2
      src/views/taais/homePage/logPage.vue
  92. 624 0
      src/views/taais/homePage/task/index.vue
  93. 963 0
      src/views/task/bizProcess/index.vue
  94. 300 0
      src/views/task/dataProcess/index.vue
  95. 276 0
      src/views/task/dataSet/index.vue
  96. 140 70
      src/views/task/subtask/index.vue
  97. 37 8
      src/views/task/subtaskDetail/index.vue
  98. 35 35
      src/views/task/task/index.vue
  99. 257 0
      src/views/task/taskConfiguration/index.vue
  100. 557 586
      yarn.lock

+ 1 - 1
.editorconfig

@@ -4,7 +4,7 @@ root = true
 
 [*] # 表示所有文件适用
 charset = utf-8 # 设置文件字符集为 utf-8
-end_of_line = lf # 控制换行类型(lf | cr | crlf)
+# end_of_line = lf  # 控制换行类型(lf | cr | crlf)
 insert_final_newline = true # 始终在文件末尾插入一个新行
 indent_style = space # 缩进风格(tab | space)
 indent_size = 2 # 缩进大小

+ 1 - 1
.env

@@ -1,5 +1,5 @@
 # title
-VITE_GLOB_APP_TITLE = 目标精确捕获系统
+VITE_GLOB_APP_TITLE = 算法任务系统
 
 # 本地运行端口号
 VITE_PORT = 8848

+ 1 - 0
.env.development

@@ -26,6 +26,7 @@ VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
 
 # 开发环境跨域代理,支持配置多个
 VITE_PROXY = [["/api","http://localhost:9090"]]
+#VITE_PROXY = [["/api","http://192.168.101.34/prod-api"]]
 # VITE_PROXY = [["/api","https://mock.mengxuegu.com/mock/629d727e6163854a32e8307e"]]
 # VITE_PROXY = [["/api","https://www.fastmock.site/mock/f81e8333c1a9276214bcdbc170d9e0a0"]]
 # VITE_PROXY = [["/api-easymock","https://mock.mengxuegu.com"],["/api-fastmock","https://www.fastmock.site"]]

+ 2 - 2
.env.production

@@ -20,7 +20,7 @@ VITE_DROP_CONSOLE = true
 
 # 是否开启 VitePWA
 VITE_PWA = true
-
+VITE_API_URL = /api
 # 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
 VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
 # 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
@@ -30,4 +30,4 @@ VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3C
 VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
 
 # 线上环境接口地址
-VITE_API_URL = "http://localhost:8080"
+# VITE_API_URL = "http://localhost:8080"

+ 13 - 13
.eslintrc.cjs

@@ -24,12 +24,12 @@ module.exports = {
   /**
    * "off" 或 0    ==>  关闭规则
    * "warn" 或 1   ==>  打开的规则作为警告(不影响代码执行)
-   * "error" 或 2  ==>  规则作为一个错误(代码不能执行,界面报错)
+   * "warn" 或 2  ==>  规则作为一个错误(代码不能执行,界面报错)
    */
   rules: {
     // eslint (http://eslint.cn/docs/rules)
-    'no-var': 'error', // 要求使用 let 或 const 而不是 var
-    'no-multiple-empty-lines': ['error', { max: 1 }], // 不允许多个空行
+    'no-var': 'warn', // 要求使用 let 或 const 而不是 var
+    // 'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允许多个空行
     'prefer-const': 'off', // 使用 let 关键字声明但在初始分配后从未重新分配的变量,要求使用 const
     'no-use-before-define': 'off', // 禁止在 函数/类/变量 定义之前使用它们
     'prettier/prettier': [
@@ -40,10 +40,10 @@ module.exports = {
       }
     ],
     // typeScript (https://typescript-eslint.io/rules)
-    '@typescript-eslint/no-unused-vars': 'error', // 禁止定义未使用的变量
-    '@typescript-eslint/no-empty-function': 'error', // 禁止空函数
-    '@typescript-eslint/prefer-ts-expect-error': 'error', // 禁止使用 @ts-ignore
-    '@typescript-eslint/ban-ts-comment': 'error', // 禁止 @ts-<directive> 使用注释或要求在指令后进行描述
+    '@typescript-eslint/no-unused-vars': 'warn', // 禁止定义未使用的变量
+    '@typescript-eslint/no-empty-function': 'warn', // 禁止空函数
+    '@typescript-eslint/prefer-ts-expect-error': 'warn', // 禁止使用 @ts-ignore
+    '@typescript-eslint/ban-ts-comment': 'warn', // 禁止 @ts-<directive> 使用注释或要求在指令后进行描述
     '@typescript-eslint/no-inferrable-types': 'off', // 可以轻松推断的显式类型可能会增加不必要的冗长
     '@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间
     '@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型
@@ -52,12 +52,12 @@ module.exports = {
     '@typescript-eslint/no-non-null-assertion': 'off', // 不允许使用后缀运算符的非空断言(!)
 
     // vue (https://eslint.vuejs.org/rules)
-    'vue/script-setup-uses-vars': 'error', // 防止<script setup>使用的变量<template>被标记为未使用,此规则仅在启用该 no-unused-vars 规则时有效
-    'vue/v-slot-style': 'error', // 强制执行 v-slot 指令样式
-    'vue/no-mutating-props': 'error', // 不允许改变组件 prop
-    'vue/custom-event-name-casing': 'error', // 为自定义事件名称强制使用特定大小写
-    'vue/html-closing-bracket-newline': 'error', // 在标签的右括号之前要求或禁止换行
-    'vue/attribute-hyphenation': 'error', // 对模板中的自定义组件强制执行属性命名样式:my-prop="prop"
+    'vue/script-setup-uses-vars': 'warn', // 防止<script setup>使用的变量<template>被标记为未使用,此规则仅在启用该 no-unused-vars 规则时有效
+    'vue/v-slot-style': 'warn', // 强制执行 v-slot 指令样式
+    'vue/no-mutating-props': 'warn', // 不允许改变组件 prop
+    'vue/custom-event-name-casing': 'warn', // 为自定义事件名称强制使用特定大小写
+    'vue/html-closing-bracket-newline': 'warn', // 在标签的右括号之前要求或禁止换行
+    'vue/attribute-hyphenation': 'warn', // 对模板中的自定义组件强制执行属性命名样式:my-prop="prop"
     'vue/attributes-order': 'off', // vue api使用顺序,强制执行属性顺序
     'vue/no-v-html': 'off', // 禁止使用 v-html
     'vue/require-default-prop': 'off', // 此规则要求为每个 prop 为必填时,必须提供默认值

+ 1 - 1
index.html

@@ -2,7 +2,7 @@
 <html lang="en">
   <head>
     <meta charset="UTF-8" />
-    <link rel="icon" type="image/svg+xml" href="/vue.svg" />
+    <link rel="icon" type="image/svg+xml" href="/logo.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title><%= title %></title>
   </head>

Разлика између датотеке није приказан због своје велике величине
+ 222 - 302
package-lock.json


+ 4 - 2
package.json

@@ -39,7 +39,8 @@
     "driver.js": "^1.3.1",
     "echarts": "^5.4.3",
     "echarts-liquidfill": "^3.1.0",
-    "element-plus": "^2.4.3",
+    "element-plus": "2.6.0",
+    "fabric": "^5.3.0",
     "file-saver": "^2.0.5",
     "image-conversion": "^2.1.1",
     "js-cookie": "^3.0.5",
@@ -54,9 +55,10 @@
     "qs": "^6.11.2",
     "screenfull": "^6.0.2",
     "sortablejs": "^1.15.1",
-    "vue": "^3.3.11",
+    "vue": "3.3.13",
     "vue-cropper": "^1.1.2",
     "vue-i18n": "^9.6.4",
+    "vue-konva": "^3.0.2",
     "vue-router": "^4.2.5",
     "vue-types": "^5.1.1",
     "vuedraggable": "^4.1.0"

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
public/logo.svg


+ 148 - 0
src/api/interface/demo/AlgorithmConfigTrack.ts

@@ -0,0 +1,148 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface AlgorithmConfigTrackVO extends BaseEntity {
+    /**
+    * 主键ID
+    */
+        id: string | number;
+
+    /**
+    * 类型
+    */
+        type: string;
+
+    /**
+    * 父id
+    */
+        parentId: string | number;
+
+    /**
+    * 分系统
+    */
+        subsystem: string;
+
+    /**
+    * 算法名称
+    */
+        algorithmName: string;
+
+    /**
+    * 算法地址
+    */
+        algorithmAddress: string;
+
+    /**
+    * 参数配置
+    */
+        parameterConfig: string;
+
+    /**
+    * 备注
+    */
+        remarks: string;
+
+    /**
+    * 系统
+    */
+        system: string;
+
+    }
+
+    export interface AlgorithmConfigTrackForm {
+        /**
+        * 主键ID
+        */
+        id?: string | number;
+
+        /**
+        * 类型
+        */
+        type?: string;
+
+        /**
+        * 父id
+        */
+        parentId?: string | number;
+
+        /**
+        * 分系统
+        */
+        subsystem?: string;
+
+        /**
+        * 算法名称
+        */
+        algorithmName?: string;
+
+        /**
+        * 算法地址
+        */
+        algorithmAddress?: string;
+
+        /**
+        * 参数配置
+        */
+        parameterConfig?: string;
+
+        /**
+        * 备注
+        */
+        remarks?: string;
+
+        /**
+        * 乐观锁
+        */
+        version?: number;
+
+        /**
+        * 系统
+        */
+        system?: string;
+
+    }
+
+    export interface AlgorithmConfigTrackQuery extends PageQuery {
+        /**
+        * 类型
+        */
+        type?: string;
+
+        /**
+        * 父id
+        */
+        parentId?: string | number;
+
+        /**
+        * 分系统
+        */
+        subsystem?: string;
+
+        /**
+        * 算法名称
+        */
+        algorithmName?: string;
+
+        /**
+        * 算法地址
+        */
+        algorithmAddress?: string;
+
+        /**
+        * 参数配置
+        */
+        parameterConfig?: string;
+
+        /**
+        * 备注
+        */
+        remarks?: string;
+
+        /**
+        * 系统
+        */
+        system?: string;
+
+    /**
+    * 日期范围参数
+    */
+    params?: any;
+    }

+ 133 - 0
src/api/interface/demo/AlgorithmModelTrack.ts

@@ -0,0 +1,133 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface AlgorithmModelTrackVO extends BaseEntity {
+    /**
+    * 主键ID
+    */
+        id: string | number;
+
+    /**
+    * 算法
+    */
+        algorithmId: string | number;
+
+    /**
+    * 模型名称
+    */
+        modelName: string;
+
+    /**
+    * 模型
+    */
+        modelAddress: string;
+
+    /**
+    * 训练样本数
+    */
+        sampleNumber: number;
+
+    /**
+    * 训练循环次数
+    */
+        cycleEpoch: number;
+
+    /**
+    * 备注
+    */
+        remarks: string;
+
+    /**
+    * 系统
+    */
+        system: string;
+
+    }
+
+    export interface AlgorithmModelTrackForm {
+        /**
+        * 主键ID
+        */
+        id?: string | number;
+
+        /**
+        * 算法
+        */
+        algorithmId?: string | number;
+
+        /**
+        * 模型名称
+        */
+        modelName?: string;
+
+        /**
+        * 模型
+        */
+        modelAddress?: string;
+
+        /**
+        * 训练样本数
+        */
+        sampleNumber?: number;
+
+        /**
+        * 训练循环次数
+        */
+        cycleEpoch?: number;
+
+        /**
+        * 备注
+        */
+        remarks?: string;
+
+        /**
+        * 乐观锁
+        */
+        version?: number;
+
+        /**
+        * 系统
+        */
+        system?: string;
+
+    }
+
+    export interface AlgorithmModelTrackQuery extends PageQuery {
+        /**
+        * 算法
+        */
+        algorithmId?: string | number;
+
+        /**
+        * 模型名称
+        */
+        modelName?: string;
+
+        /**
+        * 模型
+        */
+        modelAddress?: string;
+
+        /**
+        * 训练样本数
+        */
+        sampleNumber?: number;
+
+        /**
+        * 训练循环次数
+        */
+        cycleEpoch?: number;
+
+        /**
+        * 备注
+        */
+        remarks?: string;
+
+        /**
+        * 系统
+        */
+        system?: string;
+
+    /**
+    * 日期范围参数
+    */
+    params?: any;
+    }

+ 221 - 0
src/api/interface/demo/DataSeq.ts

@@ -0,0 +1,221 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface DataSeqVO extends BaseEntity {
+  /**
+   * id
+   */
+  id: string | number
+
+  /**
+   * 名称
+   */
+  name: string
+
+  /**
+   * 数据类型
+   */
+  dataType: string
+
+  /**
+   * 文件类型
+   */
+  fileType: string
+
+  /**
+   * 目标类型
+   */
+  objectType: string
+
+  /**
+   * 目标子类型
+   */
+  objectSubtype: string
+
+  /**
+   * 批次号
+   */
+  batchNum: string
+
+  /**
+   * 场景
+   */
+  scene: string
+
+  /**
+   * 数据源
+   */
+  dataSource: string
+
+  /**
+   * 采集时间
+   */
+  gatherTime: string
+
+  /**
+   * 采集地点
+   */
+  gatherSpot: string
+
+  /**
+   * 图片url
+   */
+  url: string
+
+  /**
+   * 日志
+   */
+  log: string
+
+  /**
+   * 备注
+   */
+  remarks: string
+}
+
+export interface DataSeqForm {
+  /**
+   * id
+   */
+  id?: string | number
+
+  /**
+   * 名称
+   */
+  name?: string
+
+  /**
+   * 数据类型
+   */
+  dataType?: string
+
+  /**
+   * 文件类型
+   */
+  fileType?: string
+
+  /**
+   * 目标类型
+   */
+  objectType?: string
+
+  /**
+   * 目标子类型
+   */
+  objectSubtype?: string
+
+  /**
+   * 批次号
+   */
+  batchNum?: string
+
+  /**
+   * 场景
+   */
+  scene?: string
+
+  /**
+   * 数据源
+   */
+  dataSource?: string
+
+  /**
+   * 采集时间
+   */
+  gatherTime?: string
+
+  /**
+   * 采集地点
+   */
+  gatherSpot?: string
+
+  /**
+   * 图片url
+   */
+  url?: string
+
+  /**
+   * 日志
+   */
+  log?: string
+
+  /**
+   * 备注
+   */
+  remarks?: string
+
+  /**
+   * 乐观锁
+   */
+  version?: number
+}
+
+export interface DataSeqQuery extends PageQuery {
+  /**
+   * 名称
+   */
+  name?: string
+
+  /**
+   * 数据类型
+   */
+  dataType?: string
+
+  /**
+   * 文件类型
+   */
+  fileType?: string
+
+  /**
+   * 目标类型
+   */
+  objectType?: string
+
+  /**
+   * 目标子类型
+   */
+  objectSubtype?: string
+
+  /**
+   * 批次号
+   */
+  batchNum?: string
+
+  /**
+   * 场景
+   */
+  scene?: string
+
+  /**
+   * 数据源
+   */
+  dataSource?: string
+
+  /**
+   * 采集时间
+   */
+  gatherTime?: string
+
+  /**
+   * 采集地点
+   */
+  gatherSpot?: string
+
+  /**
+   * 图片url
+   */
+  url?: string
+
+  /**
+   * 日志
+   */
+  log?: string
+
+  /**
+   * 备注
+   */
+  remarks?: string
+
+  /**
+   * 日期范围参数
+   */
+  params?: any
+}

+ 239 - 0
src/api/interface/demo/TargetDetection.ts

@@ -0,0 +1,239 @@
+import {PageQuery, BaseEntity} from '@/api/interface/index'
+
+export interface TargetDetectionVO extends BaseEntity {
+  /**
+   * ID
+   */
+  id: string | number;
+
+  /**
+   * 任务名称
+   */
+  name: string;
+
+  /**
+   * 状态
+   0:未开始
+   1:进行中
+   2:完成
+   3:失败
+   4:中断
+   */
+  status: string;
+
+  /**
+   * 开始时间
+   */
+  startTime: string;
+
+  /**
+   * 结束时间
+   */
+  endTime: string;
+
+  /**
+   * 耗时
+   */
+  costSecond: number;
+
+  /**
+   * 日志
+   */
+  log: string;
+
+  /**
+   * 备注
+   */
+  remarks: string;
+
+  /**
+   * $column.columnComment
+   */
+  url: string;
+
+  /**
+   * $column.columnComment
+   */
+  inputOssId: string | number;
+
+  /**
+   * 输入路径
+   */
+  inputPath: string;
+
+  /**
+   * 输出路径
+   */
+  outputPath: string;
+
+  /**
+   * zip文件输出路径
+   */
+  zipFilePath: string;
+
+  /**
+   * 模型的id
+   */
+  algorithmModelId: string | number;
+
+}
+
+export interface TargetDetectionForm {
+  /**
+   * ID
+   */
+  id?: string | number;
+
+  /**
+   * 任务名称
+   */
+  name?: string;
+
+  /**
+   * 状态
+   0:未开始
+   1:进行中
+   2:完成
+   3:失败
+   4:中断
+   */
+  status?: string;
+
+  /**
+   * 开始时间
+   */
+  startTime?: string;
+
+  /**
+   * 结束时间
+   */
+  endTime?: string;
+
+  /**
+   * 耗时
+   */
+  costSecond?: number;
+
+  /**
+   * 日志
+   */
+  log?: string;
+
+  /**
+   * 备注
+   */
+  remarks?: string;
+
+  /**
+   * $column.columnComment
+   */
+  version?: number;
+
+  /**
+   * $column.columnComment
+   */
+  url?: string;
+
+  /**
+   * $column.columnComment
+   */
+  inputOssId?: string | number;
+
+  /**
+   * 输入路径
+   */
+  inputPath?: string;
+
+  /**
+   * 输出路径
+   */
+  outputPath?: string;
+
+  /**
+   * zip文件输出路径
+   */
+  zipFilePath?: string;
+
+  /**
+   * 模型的id
+   */
+  algorithmModelId?: string | number;
+
+}
+
+export interface TargetDetectionQuery extends PageQuery {
+  /**
+   * 任务名称
+   */
+  name?: string;
+
+  /**
+   * 状态
+   0:未开始
+   1:进行中
+   2:完成
+   3:失败
+   4:中断
+   */
+  status?: string;
+
+  /**
+   * 开始时间
+   */
+  startTime?: string;
+
+  /**
+   * 结束时间
+   */
+  endTime?: string;
+
+  /**
+   * 耗时
+   */
+  costSecond?: number;
+
+  /**
+   * 日志
+   */
+  log?: string;
+
+  /**
+   * 备注
+   */
+  remarks?: string;
+
+  /**
+   * $column.columnComment
+   */
+  url?: string;
+
+  /**
+   * $column.columnComment
+   */
+  inputOssId?: string | number;
+
+  /**
+   * 输入路径
+   */
+  inputPath?: string;
+
+  /**
+   * 输出路径
+   */
+  outputPath?: string;
+
+  /**
+   * zip文件输出路径
+   */
+  zipFilePath?: string;
+
+  /**
+   * 模型的id
+   */
+  algorithmModelId?: string | number;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 206 - 0
src/api/interface/demo/toInfrared.ts

@@ -0,0 +1,206 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface ToInfraredVO extends BaseEntity {
+  /**
+   * ID
+   */
+  id: string | number
+
+  /**
+   * 任务名称
+   */
+  name: string
+
+  /**
+    * 状态
+    0:未开始
+    1:进行中
+    2:完成
+    3:失败
+    4:中断
+        */
+  status: string
+
+  /**
+   * 开始时间
+   */
+  startTime: string
+
+  /**
+   * 结束时间
+   */
+  endTime: string
+
+  /**
+   * 耗时
+   */
+  costSecond: number
+
+  /**
+   * 日志
+   */
+  log: string
+
+  /**
+   * 备注
+   */
+  remarks: string
+
+  /**
+   * $column.columnComment
+   */
+  url: string
+
+  /**
+   * $column.columnComment
+   */
+  inputOssId: string | number
+
+  /**
+   * 输入路径
+   */
+  inputPath: string
+
+  /**
+   * 输出路径
+   */
+  outputPath: string
+}
+
+export interface ToInfraredForm {
+  /**
+   * ID
+   */
+  id?: string | number
+
+  /**
+   * 任务名称
+   */
+  name?: string
+
+  /**
+        * 状态
+0:未开始
+1:进行中
+2:完成
+3:失败
+4:中断
+        */
+  status?: string
+
+  /**
+   * 开始时间
+   */
+  startTime?: string
+
+  /**
+   * 结束时间
+   */
+  endTime?: string
+
+  /**
+   * 耗时
+   */
+  costSecond?: number
+
+  /**
+   * 日志
+   */
+  log?: string
+
+  /**
+   * 备注
+   */
+  remarks?: string
+
+  /**
+   * $column.columnComment
+   */
+  version?: number
+
+  /**
+   * $column.columnComment
+   */
+  url?: string
+
+  /**
+   * $column.columnComment
+   */
+  inputOssId?: string | number
+
+  /**
+   * 输入路径
+   */
+  inputPath?: string
+
+  /**
+   * 输出路径
+   */
+  outputPath?: string
+}
+
+export interface ToInfraredQuery extends PageQuery {
+  /**
+   * 任务名称
+   */
+  name?: string
+
+  /**
+        * 状态
+0:未开始
+1:进行中
+2:完成
+3:失败
+4:中断
+        */
+  status?: string
+
+  /**
+   * 开始时间
+   */
+  startTime?: string
+
+  /**
+   * 结束时间
+   */
+  endTime?: string
+
+  /**
+   * 耗时
+   */
+  costSecond?: number
+
+  /**
+   * 日志
+   */
+  log?: string
+
+  /**
+   * 备注
+   */
+  remarks?: string
+
+  /**
+   * $column.columnComment
+   */
+  url?: string
+
+  /**
+   * $column.columnComment
+   */
+  inputOssId?: string | number
+
+  /**
+   * 输入路径
+   */
+  inputPath?: string
+
+  /**
+   * 输出路径
+   */
+  outputPath?: string
+
+  /**
+   * 日期范围参数
+   */
+  params?: any
+}

+ 161 - 0
src/api/interface/demo/traceMerge.ts

@@ -0,0 +1,161 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface TraceMergeVO extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id: string | number
+
+  /**
+   * 任务名称
+   */
+  name: string
+
+  /**
+   * 任务状态
+   */
+  status: string
+
+  /**
+   * 调用算法时所用的参数
+   */
+  parameters: string
+
+  /**
+   * 预处理数据路径
+   */
+  preprocessPath: string
+
+  /**
+   * 结果数据路径
+   */
+  resultPath: string
+
+  /**
+   * 开始时间
+   */
+  startTime: string
+
+  /**
+   * 结束时间
+   */
+  endTime: string
+
+  /**
+   * 耗时
+   */
+  costSecond: number
+
+  /**
+   * 备注
+   */
+  remarks: string
+}
+
+export interface TraceMergeForm {
+  /**
+   * 主键ID
+   */
+  id?: string | number
+
+  /**
+   * 任务名称
+   */
+  name?: string
+
+  /**
+   * 任务状态
+   */
+  status?: string
+
+  /**
+   * 调用算法时所用的参数
+   */
+  parameters?: string
+
+  /**
+   * 预处理数据路径
+   */
+  preprocessPath?: string
+
+  /**
+   * 结果数据路径
+   */
+  resultPath?: string
+
+  /**
+   * 开始时间
+   */
+  startTime?: string
+
+  /**
+   * 结束时间
+   */
+  endTime?: string
+
+  /**
+   * 耗时
+   */
+  costSecond?: number
+
+  /**
+   * 备注
+   */
+  remarks?: string
+
+  /**
+   * 乐观锁
+   */
+  version?: number
+}
+
+export interface TraceMergeQuery extends PageQuery {
+  /**
+   * 任务名称
+   */
+  name?: string
+
+  /**
+   * 任务状态
+   */
+  status?: string
+
+  /**
+   * 调用算法时所用的参数
+   */
+  parameters?: string
+
+  /**
+   * 预处理数据路径
+   */
+  preprocessPath?: string
+
+  /**
+   * 结果数据路径
+   */
+  resultPath?: string
+
+  /**
+   * 开始时间
+   */
+  startTime?: string
+
+  /**
+   * 结束时间
+   */
+  endTime?: string
+
+  /**
+   * 耗时
+   */
+  costSecond?: number
+
+  /**
+   * 备注
+   */
+  remarks?: string
+
+  /**
+   * 日期范围参数
+   */
+  params?: any
+}

+ 221 - 0
src/api/interface/demo/trackSequence.ts

@@ -0,0 +1,221 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface TrackSequenceVO extends BaseEntity {
+  /**
+   * ID
+   */
+  id: string | number
+
+  /**
+   * 任务名称
+   */
+  name: string
+
+  /**
+    * 状态
+        0:未开始
+        1:进行中
+        2:完成
+        3:失败
+        4:中断
+    */
+  status: string
+
+  /**
+   * 开始时间
+   */
+  startTime: string
+
+  /**
+   * 结束时间
+   */
+  endTime: string
+
+  /**
+   * 耗时
+   */
+  costSecond: number
+
+  /**
+   * 日志
+   */
+  log: string
+
+  /**
+   * 备注
+   */
+  remarks: string
+
+  /**
+   * $column.columnComment
+   */
+  url: string
+
+  /**
+   * $column.columnComment
+   */
+  inputOssId: string | number
+
+  /**
+   * 输入路径
+   */
+  inputPath: string
+
+  /**
+   * 输出路径
+   */
+  outputPath: string
+
+  /**
+   * zip文件输出路径
+   */
+  zipFilePath: string
+}
+
+export interface TrackSequenceForm {
+  /**
+   * ID
+   */
+  id?: string | number
+
+  /**
+   * 任务名称
+   */
+  name?: string
+
+  /**
+        * 状态
+0:未开始
+1:进行中
+2:完成
+3:失败
+4:中断
+        */
+  status?: string
+
+  /**
+   * 开始时间
+   */
+  startTime?: string
+
+  /**
+   * 结束时间
+   */
+  endTime?: string
+
+  /**
+   * 耗时
+   */
+  costSecond?: number
+
+  /**
+   * 日志
+   */
+  log?: string
+
+  /**
+   * 备注
+   */
+  remarks?: string
+
+  /**
+   * $column.columnComment
+   */
+  version?: number
+
+  /**
+   * $column.columnComment
+   */
+  url?: string
+
+  /**
+   * $column.columnComment
+   */
+  inputOssId?: string | number
+
+  /**
+   * 输入路径
+   */
+  inputPath?: string
+
+  /**
+   * 输出路径
+   */
+  outputPath?: string
+
+  /**
+   * zip文件输出路径
+   */
+  zipFilePath?: string
+}
+
+export interface TrackSequenceQuery extends PageQuery {
+  /**
+   * 任务名称
+   */
+  name?: string
+
+  /**
+        * 状态
+0:未开始
+1:进行中
+2:完成
+3:失败
+4:中断
+        */
+  status?: string
+
+  /**
+   * 开始时间
+   */
+  startTime?: string
+
+  /**
+   * 结束时间
+   */
+  endTime?: string
+
+  /**
+   * 耗时
+   */
+  costSecond?: number
+
+  /**
+   * 日志
+   */
+  log?: string
+
+  /**
+   * 备注
+   */
+  remarks?: string
+
+  /**
+   * $column.columnComment
+   */
+  url?: string
+
+  /**
+   * $column.columnComment
+   */
+  inputOssId?: string | number
+
+  /**
+   * 输入路径
+   */
+  inputPath?: string
+
+  /**
+   * 输出路径
+   */
+  outputPath?: string
+
+  /**
+   * zip文件输出路径
+   */
+  zipFilePath?: string
+
+  /**
+   * 日期范围参数
+   */
+  params?: any
+}

+ 171 - 0
src/api/interface/demo/video2image.ts

@@ -0,0 +1,171 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface Video2imageVO extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id: string | number
+
+  /**
+   * 视频名称
+   */
+  name: string
+
+  /**
+   * 任务状态
+   */
+  status: string
+
+  /**
+   * 切割好的图片的保存路径
+   */
+  outPath: string
+
+  /**
+   * 开始时间
+   */
+  startTime: string
+
+  /**
+   * 结束时间
+   */
+  endTime: string
+
+  /**
+   * 耗时
+   */
+  costSecond: number
+
+  /**
+   * 日志
+   */
+  log: string
+
+  /**
+   * 备注
+   */
+  remarks: string
+
+  /**
+   * 视频本身保存路径
+   */
+  path: string
+
+  /**
+   * 切割帧率,默认1
+   */
+  fps: number
+}
+
+export interface Video2imageForm {
+  /**
+   * 主键ID
+   */
+  id?: string | number
+
+  /**
+   * 视频名称
+   */
+  name?: string
+
+  /**
+   * 任务状态
+   */
+  status?: string
+
+  /**
+   * 切割好的图片的保存路径
+   */
+  outPath?: string
+
+  /**
+   * 开始时间
+   */
+  startTime?: string
+
+  /**
+   * 结束时间
+   */
+  endTime?: string
+
+  /**
+   * 耗时
+   */
+  costSecond?: number
+
+  /**
+   * 日志
+   */
+  log?: string
+
+  /**
+   * 备注
+   */
+  remarks?: string
+
+  /**
+   * 视频本身保存路径
+   */
+  path?: string
+
+  /**
+   * 切割帧率,默认1
+   */
+  fps?: number
+}
+
+export interface Video2imageQuery extends PageQuery {
+  /**
+   * 视频名称
+   */
+  name?: string
+
+  /**
+   * 任务状态
+   */
+  status?: string
+
+  /**
+   * 切割好的图片的保存路径
+   */
+  outPath?: string
+
+  /**
+   * 开始时间
+   */
+  startTime?: string
+
+  /**
+   * 结束时间
+   */
+  endTime?: string
+
+  /**
+   * 耗时
+   */
+  costSecond?: number
+
+  /**
+   * 日志
+   */
+  log?: string
+
+  /**
+   * 备注
+   */
+  remarks?: string
+
+  /**
+   * 视频本身保存路径
+   */
+  path?: string
+
+  /**
+   * 切割帧率,默认1
+   */
+  fps?: number
+
+  /**
+   * 日期范围参数
+   */
+  params?: any
+}

+ 161 - 0
src/api/interface/demo/videoStable.ts

@@ -0,0 +1,161 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface VideoStableVO extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id: string | number
+
+  /**
+   * 视频名称
+   */
+  name: string
+
+  /**
+   * 任务状态 0未开始 1进行中 2已结束
+   */
+  status: string
+
+  /**
+   * 输入图片集路径
+   */
+  inputPath: string
+
+  /**
+   * 去抖动的图片集路径
+   */
+  outPath: string
+
+  /**
+   * 开始时间
+   */
+  startTime: string
+
+  /**
+   * 结束时间
+   */
+  endTime: string
+
+  /**
+   * 耗时
+   */
+  costSecond: number
+
+  /**
+   * 日志
+   */
+  log: string
+
+  /**
+   * 备注
+   */
+  remarks: string
+}
+
+export interface VideoStableForm {
+  /**
+   * 主键ID
+   */
+  id?: string | number
+
+  /**
+   * 视频名称
+   */
+  name?: string
+
+  /**
+   * 任务状态 0未开始 1进行中 2已结束
+   */
+  status?: string
+
+  /**
+   * 输入图片集路径
+   */
+  inputPath?: string
+
+  /**
+   * 去抖动的图片集路径
+   */
+  outPath?: string
+
+  /**
+   * 开始时间
+   */
+  startTime?: string
+
+  /**
+   * 结束时间
+   */
+  endTime?: string
+
+  /**
+   * 耗时
+   */
+  costSecond?: number
+
+  /**
+   * 日志
+   */
+  log?: string
+
+  /**
+   * 备注
+   */
+  remarks?: string
+
+  /**
+   * 乐观锁
+   */
+  version?: number
+}
+
+export interface VideoStableQuery extends PageQuery {
+  /**
+   * 视频名称
+   */
+  name?: string
+
+  /**
+   * 任务状态 0未开始 1进行中 2已结束
+   */
+  status?: string
+
+  /**
+   * 输入图片集路径
+   */
+  inputPath?: string
+
+  /**
+   * 去抖动的图片集路径
+   */
+  outPath?: string
+
+  /**
+   * 开始时间
+   */
+  startTime?: string
+
+  /**
+   * 结束时间
+   */
+  endTime?: string
+
+  /**
+   * 耗时
+   */
+  costSecond?: number
+
+  /**
+   * 日志
+   */
+  log?: string
+
+  /**
+   * 备注
+   */
+  remarks?: string
+
+  /**
+   * 日期范围参数
+   */
+  params?: any
+}

+ 1 - 1
src/api/interface/index.ts

@@ -51,7 +51,7 @@ export namespace Login {
     uuid: string
     clientId: string
     grantType: string
-    tenantId: string
+    tenantId: string | number
   }
   export interface ResLogin {
     access_token: string

+ 1 - 1
src/api/interface/login.ts

@@ -15,7 +15,7 @@ export type RegisterForm = {
  * 登录请求
  */
 export interface LoginData {
-  tenantId?: number
+  tenantId?: number | string
   username?: string
   password?: string
   rememberMe?: boolean

+ 181 - 0
src/api/interface/task/bizProcess.ts

@@ -0,0 +1,181 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface BizProcessVO extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id: string | number
+
+  /**
+   * 子任务id
+   */
+  subTaskId: string | number
+
+  /**
+   * 任务名称
+   */
+  name: string
+
+  /**
+   * 任务类型
+   */
+  type: string
+
+  /**
+   * 任务状态
+   */
+  status: string
+
+  /**
+   * 算法
+   */
+  algorithmId: string | number
+
+  /**
+   * 模型
+   */
+  modelId: string | number
+
+  /**
+   * 调用算法时所用的参数
+   */
+  parameters: string
+
+  /**
+   * 预处理数据路径
+   */
+  preprocessPath: string
+
+  /**
+   * 结果数据路径
+   */
+  resultPath: string
+
+  /**
+   * 序号
+   */
+  index: number
+
+  /**
+   * 开始时间
+   */
+  startTime: string
+
+  /**
+   * 结束时间
+   */
+  endTime: string
+
+  /**
+   * 耗时
+   */
+  costSecond: number
+
+  /**
+   * 日志
+   */
+  log: string
+}
+
+export interface BizProcessForm {
+  /**
+   * 主键ID
+   */
+  id?: string | number
+
+  /**
+   * 子任务id
+   */
+  subTaskId?: string | number
+
+  /**
+   * 任务名称
+   */
+  name?: string
+
+  /**
+   * 任务类型
+   */
+  type?: string
+
+  /**
+   * 任务状态
+   */
+  status?: string
+
+  /**
+   * 算法
+   */
+  algorithmId?: string | number
+
+  /**
+   * 模型
+   */
+  modelId?: string | number
+
+  /**
+   * 调用算法时所用的参数
+   */
+  parameters?: string
+
+  /**
+   * 预处理数据路径
+   */
+  preprocessPath?: string
+
+  /**
+   * 结果数据路径
+   */
+  resultPath?: string
+
+  /**
+   * 序号
+   */
+  index?: number
+
+  /**
+   * 开始时间
+   */
+  startTime?: string
+
+  /**
+   * 结束时间
+   */
+  endTime?: string
+
+  /**
+   * 耗时
+   */
+  costSecond?: number
+
+  /**
+   * 日志
+   */
+  log?: string
+}
+
+export interface BizProcessQuery extends PageQuery {
+  /**
+   * 子任务id
+   */
+  subTaskId?: string | number
+
+  /**
+   * 任务名称
+   */
+  name?: string
+
+  /**
+   * 任务类型
+   */
+  type?: string
+
+  /**
+   * 任务状态
+   */
+  status?: string
+
+  /**
+   * 日期范围参数
+   */
+  params?: any
+}

+ 171 - 0
src/api/interface/task/dataProcess.ts

@@ -0,0 +1,171 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface DataProcessVO extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id: string | number
+
+  /**
+   * 子任务id
+   */
+  subTaskId: string | number
+
+  /**
+   * 任务名称
+   */
+  name: string
+
+  /**
+   * 任务类型
+   */
+  type: string
+
+  /**
+   * 任务状态
+   */
+  status: string
+
+  /**
+   * 算法
+   */
+  algorithmId: string | number
+
+  /**
+   * 调用算法时所用的参数
+   */
+  parameters: string
+
+  /**
+   * 预处理数据路径
+   */
+  preprocessPath: string
+
+  /**
+   * 结果数据路径
+   */
+  resultPath: string
+
+  /**
+   * 开始时间
+   */
+  startTime: string
+
+  /**
+   * 序号
+   */
+  index: number
+
+  /**
+   * 结束时间
+   */
+  endTime: string
+
+  /**
+   * 耗时
+   */
+  costSecond: number
+
+  /**
+   * 日志
+   */
+  log: string
+}
+
+export interface DataProcessForm {
+  /**
+   * 主键ID
+   */
+  id?: string | number
+
+  /**
+   * 子任务id
+   */
+  subTaskId?: string | number
+
+  /**
+   * 任务名称
+   */
+  name?: string
+
+  /**
+   * 任务类型
+   */
+  type?: string
+
+  /**
+   * 任务状态
+   */
+  status?: string
+
+  /**
+   * 算法
+   */
+  algorithmId?: string | number
+
+  /**
+   * 调用算法时所用的参数
+   */
+  parameters?: string
+
+  /**
+   * 预处理数据路径
+   */
+  preprocessPath?: string
+
+  /**
+   * 结果数据路径
+   */
+  resultPath?: string
+
+  /**
+   * 开始时间
+   */
+  startTime?: string
+
+  /**
+   * 序号
+   */
+  index?: number
+
+  /**
+   * 结束时间
+   */
+  endTime?: string
+
+  /**
+   * 耗时
+   */
+  costSecond?: number
+
+  /**
+   * 日志
+   */
+  log?: string
+}
+
+export interface DataProcessQuery extends PageQuery {
+  /**
+   * 子任务id
+   */
+  subTaskId?: string | number
+
+  /**
+   * 任务名称
+   */
+  name?: string
+
+  /**
+   * 任务类型
+   */
+  type?: string
+
+  /**
+   * 任务状态
+   */
+  status?: string
+
+  /**
+   * 日期范围参数
+   */
+  params?: any
+}

+ 151 - 0
src/api/interface/task/dataSet.ts

@@ -0,0 +1,151 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface DataSetVO extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id: string | number
+
+  /**
+   * 子任务id
+   */
+  subTaskId: string | number
+
+  /**
+   * 任务名称
+   */
+  name: string
+
+  /**
+   * 任务类型
+   */
+  type: string
+
+  /**
+   * 任务状态
+   */
+  status: string
+
+  /**
+   * 调用算法时所用的参数
+   */
+  parameters: string
+
+  /**
+   * 结果数据路径
+   */
+  resultPath: string
+
+  /**
+   * 开始时间
+   */
+  startTime: string
+
+  /**
+   * 序号
+   */
+  index: number
+
+  /**
+   * 结束时间
+   */
+  endTime: string
+
+  /**
+   * 耗时
+   */
+  costSecond: number
+
+  /**
+   * 日志
+   */
+  log: string
+}
+
+export interface DataSetForm {
+  /**
+   * 主键ID
+   */
+  id?: string | number
+
+  /**
+   * 子任务id
+   */
+  subTaskId?: string | number
+
+  /**
+   * 任务名称
+   */
+  name?: string
+
+  /**
+   * 任务类型
+   */
+  type?: string
+
+  /**
+   * 任务状态
+   */
+  status?: string
+
+  /**
+   * 调用算法时所用的参数
+   */
+  parameters?: string
+
+  /**
+   * 结果数据路径
+   */
+  resultPath?: string
+
+  /**
+   * 开始时间
+   */
+  startTime?: string
+
+  /**
+   * 序号
+   */
+  index?: number
+
+  /**
+   * 结束时间
+   */
+  endTime?: string
+
+  /**
+   * 耗时
+   */
+  costSecond?: number
+
+  /**
+   * 日志
+   */
+  log?: string
+}
+
+export interface DataSetQuery extends PageQuery {
+  /**
+   * 子任务id
+   */
+  subTaskId?: string | number
+
+  /**
+   * 任务名称
+   */
+  name?: string
+
+  /**
+   * 任务类型
+   */
+  type?: string
+
+  /**
+   * 任务状态
+   */
+  status?: string
+
+  /**
+   * 日期范围参数
+   */
+  params?: any
+}

+ 33 - 9
src/api/interface/task/subtask.ts

@@ -1,10 +1,5 @@
 import { PageQuery, BaseEntity } from '@/api/interface/index'
 export interface SubtaskVO extends BaseEntity {
-  /**
-   * 主键ID
-   */
-  id: string | number
-
   /**
    * 任务名称
    */
@@ -15,6 +10,11 @@ export interface SubtaskVO extends BaseEntity {
    */
   status: string
 
+  /**
+   * 任务类型
+   */
+  type: string
+
   /**
    * 调用算法时所用的参数
    */
@@ -38,9 +38,9 @@ export interface SubtaskVO extends BaseEntity {
 
 export interface SubtaskForm {
   /**
-   * 主键ID
+   * 任务ID
    */
-  id?: string | number
+  taskId?: string | number
 
   /**
    * 任务名称
@@ -52,6 +52,11 @@ export interface SubtaskForm {
    */
   status?: string
 
+  /**
+   * 任务类型
+   */
+  type?: string
+
   /**
    * 调用算法时所用的参数
    */
@@ -78,12 +83,16 @@ export interface SubtaskForm {
   log?: string
 
   /**
-   * 是否包含详情
+   * 序号
    */
-  hasDetails?: number
+  index?: number
 }
 
 export interface SubtaskQuery extends PageQuery {
+  /**
+   * 任务名称
+   */
+  taskId?: string
   /**
    * 任务名称
    */
@@ -94,6 +103,21 @@ export interface SubtaskQuery extends PageQuery {
    */
   status?: string
 
+  /**
+   * 任务类型
+   */
+  type?: string
+
+  /**
+   * 开始时间
+   */
+  startTime?: string
+
+  /**
+   * 结束时间
+   */
+  endTime?: string
+
   /**
    * 日期范围参数
    */

+ 141 - 0
src/api/interface/task/taskConfiguration.ts

@@ -0,0 +1,141 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface TaskConfigurationVO extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id: string | number
+
+  /**
+   * 算法任务名称
+   */
+  name: string
+
+  /**
+   * 训练算法地址
+   */
+  trainUrl: string
+
+  /**
+   * 训练超参配置
+   */
+  trainParams: string
+
+  /**
+   * 验证算法地址
+   */
+  verifyUrl: string
+
+  /**
+   * 验证超参配置
+   */
+  verifyParams: string
+
+  /**
+   * 测试算法地址
+   */
+  testUrl: string
+
+  /**
+   * 测试超参配置
+   */
+  testParams: string
+
+  /**
+   * 备注
+   */
+  remark: string
+}
+
+export interface TaskConfigurationForm {
+  /**
+   * 主键ID
+   */
+  id?: string | number
+
+  /**
+   * 算法任务名称
+   */
+  name?: string
+
+  /**
+   * 训练算法地址
+   */
+  trainUrl?: string
+
+  /**
+   * 训练超参配置
+   */
+  trainParams?: string
+
+  /**
+   * 验证算法地址
+   */
+  verifyUrl?: string
+
+  /**
+   * 验证超参配置
+   */
+  verifyParams?: string
+
+  /**
+   * 测试算法地址
+   */
+  testUrl?: string
+
+  /**
+   * 测试超参配置
+   */
+  testParams?: string
+
+  /**
+   * 备注
+   */
+  remark?: string
+
+  /**
+   * 乐观锁
+   */
+  version?: number
+}
+
+export interface TaskConfigurationQuery extends PageQuery {
+  /**
+   * 算法任务名称
+   */
+  name?: string
+
+  /**
+   * 训练算法地址
+   */
+  trainUrl?: string
+
+  /**
+   * 训练超参配置
+   */
+  trainParams?: string
+
+  /**
+   * 验证算法地址
+   */
+  verifyUrl?: string
+
+  /**
+   * 验证超参配置
+   */
+  verifyParams?: string
+
+  /**
+   * 测试算法地址
+   */
+  testUrl?: string
+
+  /**
+   * 测试超参配置
+   */
+  testParams?: string
+
+  /**
+   * 日期范围参数
+   */
+  params?: any
+}

+ 8 - 0
src/api/modules/ag/model.ts

@@ -27,6 +27,14 @@ export const addModelApi = (data: ModelForm) => {
   return http.post<any>('/ag/model', data, { loading: false })
 }
 
+export const queryModelList = (data: ModelForm) => {
+  return http.get<any>('/ag/model/algoList', data, { loading: false })
+}
+
+export const addAlgorithmTaskConfigurationApi = (data: ModelForm) => {
+  return http.post<any>('/task/taskConfiguration/importModel', data, { loading: false })
+}
+
 /**
  * @name 修改算法模型配置
  * @param data data

+ 74 - 0
src/api/modules/demo/AlgorithmConfigTrack.ts

@@ -0,0 +1,74 @@
+import http from '@/api'
+import { AlgorithmConfigTrackVO, AlgorithmConfigTrackForm, AlgorithmConfigTrackQuery } from '@/api/interface/demo/AlgorithmConfigTrack'
+/**
+ * @name 查询算法配置列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listAlgorithmConfigTrackApi = (query: AlgorithmConfigTrackQuery) => {
+  return http.get<AlgorithmConfigTrackVO[]>('/demo/AlgorithmConfigTrack/list', query, { loading: true })
+}
+
+/**
+ * @name 查询算法配置详细
+ * @param id id
+ * @returns returns
+ */
+export const getAlgorithmConfigTrackApi = (id: string | number) => {
+  return http.get<AlgorithmConfigTrackVO>(`/demo/AlgorithmConfigTrack/${id}`)
+}
+
+/**
+ * @name 新增算法配置
+ * @param data data
+ * @returns returns
+ */
+export const addAlgorithmConfigTrackApi = (data: AlgorithmConfigTrackForm) => {
+  return http.post<any>('/demo/AlgorithmConfigTrack', data, { loading: false })
+}
+
+/**
+ * @name 修改算法配置
+ * @param data data
+ * @returns returns
+ */
+export const updateAlgorithmConfigTrackApi = (data: AlgorithmConfigTrackForm) => {
+  return http.put<any>('/demo/AlgorithmConfigTrack', data, { loading: false })
+}
+
+/**
+ * @name 删除算法配置
+ * @param id id
+ * @returns returns
+ */
+export const delAlgorithmConfigTrackApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/demo/AlgorithmConfigTrack/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/demo/AlgorithmConfigTrack/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importAlgorithmConfigTrackDataApi = (data: any) => {
+  return http.post('/demo/AlgorithmConfigTrack/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportAlgorithmConfigTrackApi = (data: any) => {
+  return http.downloadPost('/demo/AlgorithmConfigTrack/export', data)
+}
+
+export const enumAlgorithmConfigTrackApi = () => {
+  return http.get('/demo/AlgorithmConfigTrack/enums')
+}

+ 74 - 0
src/api/modules/demo/AlgorithmModelTrack.ts

@@ -0,0 +1,74 @@
+import http from '@/api'
+import { AlgorithmModelTrackVO, AlgorithmModelTrackForm, AlgorithmModelTrackQuery } from '@/api/interface/demo/AlgorithmModelTrack'
+/**
+ * @name 查询算法模型配置列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listAlgorithmModelTrackApi = (query: AlgorithmModelTrackQuery) => {
+  return http.get<AlgorithmModelTrackVO[]>('/demo/AlgorithmModelTrack/list', query, { loading: true })
+}
+
+/**
+ * @name 查询算法模型配置详细
+ * @param id id
+ * @returns returns
+ */
+export const getAlgorithmModelTrackApi = (id: string | number) => {
+  return http.get<AlgorithmModelTrackVO>(`/demo/AlgorithmModelTrack/${id}`)
+}
+
+/**
+ * @name 新增算法模型配置
+ * @param data data
+ * @returns returns
+ */
+export const addAlgorithmModelTrackApi = (data: AlgorithmModelTrackForm) => {
+  return http.post<any>('/demo/AlgorithmModelTrack', data, { loading: false })
+}
+
+/**
+ * @name 修改算法模型配置
+ * @param data data
+ * @returns returns
+ */
+export const updateAlgorithmModelTrackApi = (data: AlgorithmModelTrackForm) => {
+  return http.put<any>('/demo/AlgorithmModelTrack', data, { loading: false })
+}
+
+/**
+ * @name 删除算法模型配置
+ * @param id id
+ * @returns returns
+ */
+export const delAlgorithmModelTrackApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/demo/AlgorithmModelTrack/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/demo/AlgorithmModelTrack/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importAlgorithmModelTrackDataApi = (data: any) => {
+  return http.post('/demo/AlgorithmModelTrack/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportAlgorithmModelTrackApi = (data: any) => {
+  return http.downloadPost('/demo/AlgorithmModelTrack/export', data)
+}
+
+export const enumAlgorithmModelTrackApi = ():any => {
+  return http.get('/demo/AlgorithmModelTrack/enums')
+}

+ 86 - 0
src/api/modules/demo/DataSeq.ts

@@ -0,0 +1,86 @@
+import http from '@/api'
+import { DataSeqVO, DataSeqForm, DataSeqQuery } from '@/api/interface/demo/DataSeq'
+/**
+ * @name 查询数据管理列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listDataSeqApi = (query: DataSeqQuery) => {
+  return http.get<DataSeqVO[]>('/demo/DataSeq/list', query, { loading: true })
+}
+
+/**
+ * @name 查询数据管理详细
+ * @param id id
+ * @returns returns
+ */
+export const getDataSeqApi = (id: string | number) => {
+  return http.get<DataSeqVO>(`/demo/DataSeq/${id}`)
+}
+
+/**
+ * @name 新增数据管理
+ * @param data data
+ * @returns returns
+ */
+export const addDataSeqApi = (data: DataSeqForm) => {
+  return http.post<any>('/demo/DataSeq', data, { loading: false })
+}
+
+/**
+ * @name 修改数据管理
+ * @param data data
+ * @returns returns
+ */
+export const updateDataSeqApi = (data: DataSeqForm) => {
+  return http.put<any>('/demo/DataSeq', data, { loading: false })
+}
+
+/**
+ * @name 删除数据管理
+ * @param id id
+ * @returns returns
+ */
+export const delDataSeqApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/demo/DataSeq/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/demo/DataSeq/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importDataSeqDataApi = (data: any) => {
+  return http.post('/demo/DataSeq/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportDataSeqApi = (data: any) => {
+  return http.downloadPost('/demo/DataSeq/export', data)
+}
+
+/**
+ * @name 导入数据集
+ * @returns returns
+ */
+export const batchAdDataSeqApi = (data: any) => {
+  return http.post('/demo/DataSeq/batchAdd', data)
+}
+
+export const getAllImagesApi = (ossId: any) => {
+  return http.get('/demo/DataSeq/getAllImages/' + ossId)
+}
+
+export const getImagesApi = (inputOdssId: any, subsystem: any, ifInput: any, dir: any) => {
+  return http.get(`/demo/DataSeq/getImages/${inputOdssId}/${subsystem}/${ifInput}/${dir}`)
+}

+ 87 - 0
src/api/modules/demo/TargetDetection.ts

@@ -0,0 +1,87 @@
+import http from '@/api'
+import { TargetDetectionVO, TargetDetectionForm, TargetDetectionQuery } from '@/api/interface/demo/TargetDetection'
+
+/**
+ * @name 查询目标检测列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listTargetDetectionApi = (query: TargetDetectionQuery) => {
+  return http.get<TargetDetectionVO[]>('/demo/TargetDetection/list', query, { loading: true })
+}
+
+/**
+ * @name 查询目标检测详细
+ * @param id id
+ * @returns returns
+ */
+export const getTargetDetectionApi = (id: string | number) => {
+  return http.get<TargetDetectionVO>(`/demo/TargetDetection/${id}`)
+}
+
+/**
+ * @name 新增目标检测
+ * @param data data
+ * @returns returns
+ */
+export const addTargetDetectionApi = (data: TargetDetectionForm) => {
+  return http.post<any>('/demo/TargetDetection', data, { loading: false })
+}
+
+/**
+ * @name 修改目标检测
+ * @param data data
+ * @returns returns
+ */
+export const updateTargetDetectionApi = (data: TargetDetectionForm) => {
+  return http.put<any>('/demo/TargetDetection', data, { loading: false })
+}
+
+/**
+ * @name 删除目标检测
+ * @param id id
+ * @returns returns
+ */
+export const delTargetDetectionApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/demo/TargetDetection/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/demo/TargetDetection/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importTargetDetectionDataApi = (data: any) => {
+  return http.post('/demo/TargetDetection/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportTargetDetectionApi = (data: any) => {
+  return http.downloadPost('/demo/TargetDetection/export', data)
+}
+
+export const startTargetDetectionApi = (id: string | number) => {
+  return http.get<any>(`/demo/TargetDetection/start/${id}`)
+}
+
+export const stopTargetDetectionApi = (id: string | number) => {
+  return http.get('/demo/TargetDetection/stop/' + id)
+}
+
+/**
+ * @name 下载压缩包
+ * @returns returns
+ */
+export const dowloadTargetDetectionApi = (id: string | number): Promise<any> => {
+  return http.downloadGet('/demo/TargetDetection/zip/' + id)
+}

+ 24 - 4
src/api/modules/demo/data.ts

@@ -1,4 +1,11 @@
 import http from '@/api'
+/**
+ * @name 查询batchList
+ * @returns 返回列表
+ */
+export const batchListDataApi = () => {
+  return http.get<any[]>('/demo/data/batchList', {}, { loading: true })
+}
 
 /**
  * @name 查询数据管理列表
@@ -6,7 +13,20 @@ import http from '@/api'
  * @returns 返回列表
  */
 export const listDataApi = (query: any) => {
-  return http.get<any>('/demo/data/list', query, { loading: true })
+  return http.get<any[]>('/demo/data/list', query, { loading: true })
+}
+
+export const getFormSelectsApi = (query: { field: string }) => {
+  return http.get<string[]>('/demo/data/getFromSelects', query, { loading: false })
+}
+
+/**
+ * @name 提交
+ * @param id id
+ * @returns returns
+ */
+export const amplifyApi = (data: any) => {
+  return http.post<any>(`/demo/data/amplify`, data, { loading: false })
 }
 
 /**
@@ -14,7 +34,7 @@ export const listDataApi = (query: any) => {
  * @param id id
  * @returns returns
  */
-export const getDataApi = (id: any) => {
+export const getDataApi = (id: string | number) => {
   return http.get<any>(`/demo/data/${id}`)
 }
 
@@ -41,7 +61,7 @@ export const updateDataApi = (data: any) => {
  * @param id id
  * @returns returns
  */
-export const delDataApi = (id: any) => {
+export const delDataApi = (id: string | number | Array<string | number>) => {
   return http.delete<any>(`/demo/data/${id}`)
 }
 
@@ -58,7 +78,7 @@ export const importTemplateApi = () => {
  * @returns returns
  */
 export const importDataDataApi = (data: any) => {
-  return http.post('/common/zip/upload', data)
+  return http.post('/demo/data/zip/upload', data)
 }
 
 /**

+ 86 - 0
src/api/modules/demo/toInfrared.ts

@@ -0,0 +1,86 @@
+import http from '@/api'
+import { ToInfraredVO, ToInfraredForm, ToInfraredQuery } from '@/api/interface/demo/toInfrared'
+/**
+ * @name 查询可见光转红外列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listToInfraredApi = (query: ToInfraredQuery) => {
+  return http.get<ToInfraredVO[]>('/demo/toInfrared/list', query, { loading: true })
+}
+
+/**
+ * @name 查询可见光转红外详细
+ * @param id id
+ * @returns returns
+ */
+export const getToInfraredApi = (id: string | number) => {
+  return http.get<ToInfraredVO>(`/demo/toInfrared/${id}`)
+}
+
+/**
+ * @name 新增可见光转红外
+ * @param data data
+ * @returns returns
+ */
+export const addToInfraredApi = (data: ToInfraredForm) => {
+  return http.post<any>('/demo/toInfrared', data, { loading: false })
+}
+
+/**
+ * @name 修改可见光转红外
+ * @param data data
+ * @returns returns
+ */
+export const updateToInfraredApi = (data: ToInfraredForm) => {
+  return http.put<any>('/demo/toInfrared', data, { loading: false })
+}
+
+/**
+ * @name 删除可见光转红外
+ * @param id id
+ * @returns returns
+ */
+export const delToInfraredApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/demo/toInfrared/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/demo/toInfrared/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importToInfraredDataApi = (data: any) => {
+  return http.post('/demo/toInfrared/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportToInfraredApi = (data: any) => {
+  return http.downloadPost('/demo/toInfrared/export', data)
+}
+
+export const startToInfraredApi = (id: string | number) => {
+  return http.get('/demo/toInfrared/start/' + id)
+}
+
+export const stopToInfraredApi = (id: string | number) => {
+  return http.get('/demo/toInfrared/stop/' + id)
+}
+
+/**
+ * @name 下载压缩包
+ * @returns returns
+ */
+export const dowloadToInfraredApi = (id: string | number): Promise<any> => {
+  return http.downloadGet('/demo/toInfrared/zip/' + id)
+}

+ 78 - 0
src/api/modules/demo/traceMerge.ts

@@ -0,0 +1,78 @@
+import http from '@/api'
+import { TraceMergeVO, TraceMergeForm, TraceMergeQuery } from '@/api/interface/demo/traceMerge'
+/**
+ * @name 查询多物体融合轨迹识别列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listTraceMergeApi = (query: TraceMergeQuery) => {
+  return http.get<TraceMergeVO[]>('/demo/traceMerge/list', query, { loading: true })
+}
+
+/**
+ * @name 查询多物体融合轨迹识别详细
+ * @param id id
+ * @returns returns
+ */
+export const getTraceMergeApi = (id: string | number) => {
+  return http.get<TraceMergeVO>(`/demo/traceMerge/${id}`)
+}
+
+/**
+ * @name 新增多物体融合轨迹识别
+ * @param data data
+ * @returns returns
+ */
+export const addTraceMergeApi = (data: TraceMergeForm) => {
+  return http.post<any>('/demo/traceMerge', data, { loading: false })
+}
+
+export const executeApi = data => {
+  return http.post<any>('/demo/traceMerge/execute', data, { loading: false })
+}
+
+export const getResApi = data => {
+  return http.get<any>('/demo/traceMerge/result', data, { loading: false })
+}
+
+/**
+ * @name 修改多物体融合轨迹识别
+ * @param data data
+ * @returns returns
+ */
+export const updateTraceMergeApi = (data: TraceMergeForm) => {
+  return http.put<any>('/demo/traceMerge', data, { loading: false })
+}
+
+/**
+ * @name 删除多物体融合轨迹识别
+ * @param id id
+ * @returns returns
+ */
+export const delTraceMergeApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/demo/traceMerge/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/demo/traceMerge/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importTraceMergeDataApi = (data: any) => {
+  return http.post('/demo/traceMerge/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportTraceMergeApi = (data: any) => {
+  return http.downloadPost('/demo/traceMerge/export', data)
+}

+ 86 - 0
src/api/modules/demo/trackSequence.ts

@@ -0,0 +1,86 @@
+import http from '@/api'
+import { TrackSequenceVO, TrackSequenceForm, TrackSequenceQuery } from '@/api/interface/demo/trackSequence'
+/**
+ * @name 查询注视轨迹序列列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listTrackSequenceApi = (query: TrackSequenceQuery) => {
+  return http.get<TrackSequenceVO[]>('/demo/trackSequence/list', query, { loading: true })
+}
+
+/**
+ * @name 查询注视轨迹序列详细
+ * @param id id
+ * @returns returns
+ */
+export const getTrackSequenceApi = (id: string | number) => {
+  return http.get<TrackSequenceVO>(`/demo/trackSequence/${id}`)
+}
+
+/**
+ * @name 新增注视轨迹序列
+ * @param data data
+ * @returns returns
+ */
+export const addTrackSequenceApi = (data: TrackSequenceForm) => {
+  return http.post<any>('/demo/trackSequence', data, { loading: false })
+}
+
+/**
+ * @name 修改注视轨迹序列
+ * @param data data
+ * @returns returns
+ */
+export const updateTrackSequenceApi = (data: TrackSequenceForm) => {
+  return http.put<any>('/demo/trackSequence', data, { loading: false })
+}
+
+/**
+ * @name 删除注视轨迹序列
+ * @param id id
+ * @returns returns
+ */
+export const delTrackSequenceApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/demo/trackSequence/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/demo/trackSequence/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importTrackSequenceDataApi = (data: any) => {
+  return http.post('/demo/trackSequence/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportTrackSequenceApi = (data: any) => {
+  return http.downloadPost('/demo/trackSequence/export', data)
+}
+
+export const startTrackSequenceApi = (id: string | number) => {
+  return http.get('/demo/trackSequence/start/' + id)
+}
+
+export const stopTrackSequenceApi = (id: string | number) => {
+  return http.get('/demo/trackSequence/stop/' + id)
+}
+
+/**
+ * @name 下载压缩包
+ * @returns returns
+ */
+export const dowloadTrackSequenceApi = (id: string | number): Promise<any> => {
+  return http.downloadGet('/demo/trackSequence/zip/' + id)
+}

+ 102 - 0
src/api/modules/demo/video2image.ts

@@ -0,0 +1,102 @@
+import http from '@/api'
+import { Video2imageVO, Video2imageForm, Video2imageQuery } from '@/api/interface/demo/video2image'
+/**
+ * @name 查询视频转图片列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listVideo2imageApi = (query: Video2imageQuery) => {
+  return http.get<Video2imageVO[]>('/demo/video2image/list', query, { loading: true })
+}
+
+/**
+ * @name 查询视频转图片详细
+ * @param id id
+ * @returns returns
+ */
+export const getVideo2imageApi = (id: string | number) => {
+  return http.get<Video2imageVO>(`/demo/video2image/${id}`)
+}
+
+/**
+ * @name 新增视频转图片
+ * @param data data
+ * @returns returns
+ */
+export const addVideo2imageApi = (data: Video2imageForm) => {
+  return http.post<any>('/demo/video2image', data, { loading: false })
+}
+
+/**
+ * @name 修改视频转图片
+ * @param data data
+ * @returns returns
+ */
+export const updateVideo2imageApi = (data: Video2imageForm) => {
+  return http.put<any>('/demo/video2image', data, { loading: false })
+}
+
+/**
+ * @name 删除视频转图片
+ * @param id id
+ * @returns returns
+ */
+export const delVideo2imageApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/demo/video2image/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/demo/video2image/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importVideo2imageDataApi = (data: any) => {
+  return http.post('/demo/video2image/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportVideo2imageApi = (data: any) => {
+  return http.downloadPost('/demo/video2image/export', data)
+}
+
+/**
+ * @name 上传视频
+ * @returns returns
+ */
+export const uploadVideoApi = (data: any) => {
+  return http.post('/demo/video2image/upload', data)
+}
+
+/**
+ * @name 开始视频转图片
+ * @returns returns
+ */
+export const startVideo2imageApi = (id: string | number) => {
+  return http.get('/demo/video2image/start/' + id)
+}
+
+/**
+ * @name 终止视频转图片
+ * @returns returns
+ */
+export const stopVideo2imageApi = (id: string | number) => {
+  return http.get('/demo/video2image/stop/' + id)
+}
+
+/**
+ * @name 下载压缩包
+ * @returns returns
+ */
+export const downloadVideo2imageApi = (id: string | number): Promise<any> => {
+  return http.downloadGet('/demo/video2image/zip/' + id)
+}

+ 91 - 0
src/api/modules/demo/videoStable.ts

@@ -0,0 +1,91 @@
+import http from '@/api'
+import { VideoStableVO, VideoStableForm, VideoStableQuery } from '@/api/interface/demo/videoStable'
+
+/**
+ * @name 查询视频去抖动列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listVideoStableApi = (query: VideoStableQuery) => {
+  return http.get<VideoStableVO[]>('/demo/videoStable/list', query, { loading: true })
+}
+
+/**
+ * @name 查询视频去抖动详细
+ * @param id id
+ * @returns returns
+ */
+export const getVideoStableApi = (id: string | number) => {
+  return http.get<VideoStableVO>(`/demo/videoStable/${id}`)
+}
+
+/**
+ * @name 新增视频去抖动
+ * @param data data
+ * @returns returns
+ */
+export const addVideoStableApi = (data: VideoStableForm) => {
+  return http.post<any>('/demo/videoStable', data, { loading: false })
+}
+
+/**
+ * @name 修改视频去抖动
+ * @param data data
+ * @returns returns
+ */
+export const updateVideoStableApi = (data: VideoStableForm) => {
+  return http.put<any>('/demo/videoStable', data, { loading: false })
+}
+
+/**
+ * @name 删除视频去抖动
+ * @param id id
+ * @returns returns
+ */
+export const delVideoStableApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/demo/videoStable/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/demo/videoStable/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importVideoStableDataApi = (data: any) => {
+  return http.post('/demo/videoStable/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportVideoStableApi = (data: any) => {
+  return http.downloadPost('/demo/videoStable/export', data)
+}
+
+export const startVideoStableApi = (id: String | Number) => {
+  return http.get('/demo/videoStable/start/' + id)
+}
+
+export const stopVideoStableApi = (id: String | Number) => {
+  return http.get('/demo/videoStable/stop/' + id)
+}
+
+export const getCompareImageApi = (taskId: String, idx: String | Number) => {
+  return http.get('/demo/videoStable/compare/' + taskId + '/' + idx)
+}
+
+export const getCompareImageCountApi = (taskId: String | Number) => {
+  return http.get('/demo/videoStable/compare/num/' + taskId)
+}
+
+export const getImagesApi = (ossid: String | Number) => {
+  return http.get('/demo/videoStable/images/' + ossid)
+}

+ 36 - 0
src/api/modules/taais/task.ts

@@ -1,3 +1,4 @@
+import http from '@/api'
 import taskDataList from '@/assets/mock/taskData.json'
 import tableData from '@/assets/mock/tableData.json'
 
@@ -21,3 +22,38 @@ export const getTransferImgList1 = () => {
 export const getTransferImgList2 = () => {
   return tableData.transferImgList2
 }
+// /task/task/create
+/**
+ * @name 得到图像数据
+ * @param type type
+ * @returns returns
+ */
+export const getImageApi = (type: any) => {
+  return http.post<any>(`/task/task/getImage`, type)
+}
+/**
+ * @name 创建算法任务
+ * @param data data
+ * @returns returns
+ */
+export const createTaskApi = (data: any) => {
+  return http.post<any>(`/task/task/create`, data)
+}
+/**
+ * @name 算法查询
+ * @param data data
+ * @returns returns
+ */
+export const getAlgorithmApi = (type: any, subsystem: any) => {
+  return http.post<any>(`/task/task/getAlgorithm/${type}/${subsystem}`)
+  // return http.post<any>(`/task/task/getModel`, data)
+}
+/**
+ * @name 模型查询
+ * @param data data
+ * @returns returns
+ */
+export const getModelApi = (data: any) => {
+  return http.post<any>(`/task/task/getModel/${data}`)
+  // return http.post<any>(`/task/task/getModel`, data)
+}

+ 94 - 0
src/api/modules/task/bizProcess.ts

@@ -0,0 +1,94 @@
+import http from '@/api'
+import { BizProcessVO, BizProcessForm, BizProcessQuery } from '@/api/interface/task/bizProcess'
+/**
+ * @name 查询算法业务处理列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listBizProcessApi = (query: BizProcessQuery) => {
+  return http.get<BizProcessVO[]>('/task/bizProcess/list', query, { loading: false })
+}
+
+/**
+ * @name 查询算法业务处理详细
+ * @param id id
+ * @returns returns
+ */
+export const getBizProcessApi = (id: string | number) => {
+  return http.get<BizProcessVO>(`/task/bizProcess/${id}`)
+}
+
+/**
+ * @name 新增算法业务处理
+ * @param data data
+ * @returns returns
+ */
+export const addBizProcessApi = (data: BizProcessForm) => {
+  return http.post<any>('/task/bizProcess', data, { loading: false })
+}
+
+/**
+ * @name 修改算法业务处理
+ * @param data data
+ * @returns returns
+ */
+export const updateBizProcessApi = (data: BizProcessForm) => {
+  return http.put<any>('/task/bizProcess', data, { loading: false })
+}
+
+/**
+ * @name 删除算法业务处理
+ * @param id id
+ * @returns returns
+ */
+export const delBizProcessApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/task/bizProcess/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/task/bizProcess/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importBizProcessDataApi = (data: any) => {
+  return http.post('/task/bizProcess/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportBizProcessApi = (data: any) => {
+  return http.downloadPost('/task/bizProcess/export', data)
+}
+
+/**
+ * @name 查看训练结果
+ * @returns returns
+ */
+export const getTrainResultApi = (subtaskId: string | number) => {
+  return http.get(`/task/bizProcess/getTrainResult/${subtaskId}`)
+}
+
+/**
+ * @name 查看验证结果
+ * @returns returns
+ */
+export const getVerifyResultApi = (subtaskId: string | number) => {
+  return http.get(`/task/bizProcess/getVerifyResult/${subtaskId}`)
+}
+
+/**
+ * @name 查看测试结果
+ * @returns returns
+ */
+export const getTestResultApi = (subtaskId: string | number) => {
+  return http.get(`/task/bizProcess/getTestResult/${subtaskId}`)
+}

+ 97 - 0
src/api/modules/task/bizProcessNew.ts

@@ -0,0 +1,97 @@
+import http from '@/api'
+/**
+ * @name 查询算法业务处理列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listBizProcessApi = (query: any) => {
+  return http.get<any[]>('/identification/identificationSubtaskDetails/list', query, { loading: false })
+}
+
+export const getImgList = (query: any) => {
+  return http.get<any[]>('/identification/identificationSubtaskDetails/getImgList', query, { loading: false })
+}
+
+/**
+ * @name 查询算法业务处理详细
+ * @param id id
+ * @returns returns
+ */
+export const getBizProcessApi = (id: string | number) => {
+  return http.get<any>(`/identification/identificationSubtaskDetails/${id}`)
+}
+
+/**
+ * @name 新增算法业务处理
+ * @param data data
+ * @returns returns
+ */
+export const addBizProcessApi = (data: any) => {
+  return http.post<any>('/identification/identificationSubtaskDetails', data, { loading: false })
+}
+
+/**
+ * @name 修改算法业务处理
+ * @param data data
+ * @returns returns
+ */
+export const updateBizProcessApi = (data: any) => {
+  return http.put<any>('/identification/identificationSubtaskDetails', data, { loading: false })
+}
+
+/**
+ * @name 删除算法业务处理
+ * @param id id
+ * @returns returns
+ */
+export const delBizProcessApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/identification/identificationSubtaskDetails/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/identification/identificationSubtaskDetails/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importBizProcessDataApi = (data: any) => {
+  return http.post('/identification/identificationSubtaskDetails/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportBizProcessApi = (data: any) => {
+  return http.downloadPost('/identification/identificationSubtaskDetails/export', data)
+}
+
+/**
+ * @name 查看训练结果
+ * @returns returns
+ */
+export const getTrainResultApi = (subtaskId: string | number) => {
+  return http.get(`/identification/identificationSubtaskDetails/getTrainResult/${subtaskId}`)
+}
+
+/**
+ * @name 查看验证结果
+ * @returns returns
+ */
+export const getVerifyResultApi = (subtaskId: string | number) => {
+  return http.get(`/identification/identificationSubtaskDetails/getVerifyResult/${subtaskId}`)
+}
+
+/**
+ * @name 查看测试结果
+ * @returns returns
+ */
+export const getTestResultApi = (subtaskId: string | number) => {
+  return http.get(`/identification/identificationSubtaskDetails/getTestResult/${subtaskId}`)
+}

+ 70 - 0
src/api/modules/task/dataProcess.ts

@@ -0,0 +1,70 @@
+import http from '@/api'
+import { DataProcessVO, DataProcessForm, DataProcessQuery } from '@/api/interface/task/dataProcess'
+/**
+ * @name 查询算法数据处理列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listDataProcessApi = (query: DataProcessQuery) => {
+  return http.get<DataProcessVO[]>('/task/dataProcess/list', query, { loading: true })
+}
+
+/**
+ * @name 查询算法数据处理详细
+ * @param id id
+ * @returns returns
+ */
+export const getDataProcessApi = (id: string | number) => {
+  return http.get<DataProcessVO>(`/task/dataProcess/${id}`)
+}
+
+/**
+ * @name 新增算法数据处理
+ * @param data data
+ * @returns returns
+ */
+export const addDataProcessApi = (data: DataProcessForm) => {
+  return http.post<any>('/task/dataProcess', data, { loading: false })
+}
+
+/**
+ * @name 修改算法数据处理
+ * @param data data
+ * @returns returns
+ */
+export const updateDataProcessApi = (data: DataProcessForm) => {
+  return http.put<any>('/task/dataProcess', data, { loading: false })
+}
+
+/**
+ * @name 删除算法数据处理
+ * @param id id
+ * @returns returns
+ */
+export const delDataProcessApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/task/dataProcess/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/task/dataProcess/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importDataProcessDataApi = (data: any) => {
+  return http.post('/task/dataProcess/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportDataProcessApi = (data: any) => {
+  return http.downloadPost('/task/dataProcess/export', data)
+}

+ 70 - 0
src/api/modules/task/dataSet.ts

@@ -0,0 +1,70 @@
+import http from '@/api'
+import { DataSetVO, DataSetForm, DataSetQuery } from '@/api/interface/task/dataSet'
+/**
+ * @name 查询算法数据集合列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listDataSetApi = (query: DataSetQuery) => {
+  return http.get<DataSetVO[]>('/task/dataSet/list', query, { loading: true })
+}
+
+/**
+ * @name 查询算法数据集合详细
+ * @param id id
+ * @returns returns
+ */
+export const getDataSetApi = (id: string | number) => {
+  return http.get<DataSetVO>(`/task/dataSet/${id}`)
+}
+
+/**
+ * @name 新增算法数据集合
+ * @param data data
+ * @returns returns
+ */
+export const addDataSetApi = (data: DataSetForm) => {
+  return http.post<any>('/task/dataSet', data, { loading: false })
+}
+
+/**
+ * @name 修改算法数据集合
+ * @param data data
+ * @returns returns
+ */
+export const updateDataSetApi = (data: DataSetForm) => {
+  return http.put<any>('/task/dataSet', data, { loading: false })
+}
+
+/**
+ * @name 删除算法数据集合
+ * @param id id
+ * @returns returns
+ */
+export const delDataSetApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/task/dataSet/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/task/dataSet/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importDataSetDataApi = (data: any) => {
+  return http.post('/task/dataSet/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportDataSetApi = (data: any) => {
+  return http.downloadPost('/task/dataSet/export', data)
+}

+ 1 - 1
src/api/modules/task/subtask.ts

@@ -6,7 +6,7 @@ import { SubtaskVO, SubtaskForm, SubtaskQuery } from '@/api/interface/task/subta
  * @returns 返回列表
  */
 export const listSubtaskApi = (query: SubtaskQuery) => {
-  return http.get<SubtaskVO[]>('/task/subtask/list', query, { loading: true })
+  return http.get<SubtaskVO[]>('/task/subtask/list', query, { loading: false })
 }
 
 /**

+ 69 - 0
src/api/modules/task/subtaskDetailNew.ts

@@ -0,0 +1,69 @@
+import http from '@/api'
+/**
+ * @name 查询算法子任务详情列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listSubtaskDetailApi = (query: any) => {
+  return http.get<any[]>('/identification/identificationSubtaskDetails/list', query, { loading: true })
+}
+
+/**
+ * @name 查询算法子任务详情详细
+ * @param id id
+ * @returns returns
+ */
+export const getSubtaskDetailApi = (id: string | number) => {
+  return http.get<any>(`/identification/identificationSubtaskDetails/${id}`)
+}
+
+/**
+ * @name 新增算法子任务详情
+ * @param data data
+ * @returns returns
+ */
+export const addSubtaskDetailApi = (data: any) => {
+  return http.post<any>('/identification/identificationSubtaskDetails', data, { loading: false })
+}
+
+/**
+ * @name 修改算法子任务详情
+ * @param data data
+ * @returns returns
+ */
+export const updateSubtaskDetailApi = (data: any) => {
+  return http.put<any>('/identification/identificationSubtaskDetails', data, { loading: false })
+}
+
+/**
+ * @name 删除算法子任务详情
+ * @param id id
+ * @returns returns
+ */
+export const delSubtaskDetailApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/identification/identificationSubtaskDetails/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/identification/identificationSubtaskDetails/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importSubtaskDetailDataApi = (data: any) => {
+  return http.post('/identification/identificationSubtaskDetails/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportSubtaskDetailApi = (data: any) => {
+  return http.downloadPost('/identification/identificationSubtaskDetails/export', data)
+}

+ 69 - 0
src/api/modules/task/subtaskNew.ts

@@ -0,0 +1,69 @@
+import http from '@/api'
+/**
+ * @name 查询算法子任务列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listSubtaskApi = query => {
+  return http.get<any[]>('/identification/identificationSubtask/list', query, { loading: false })
+}
+
+/**
+ * @name 查询算法子任务详细
+ * @param id id
+ * @returns returns
+ */
+export const getSubtaskApi = (id: string | number) => {
+  return http.get<any>(`/identification/identificationSubtask/${id}`)
+}
+
+/**
+ * @name 新增算法子任务
+ * @param data data
+ * @returns returns
+ */
+export const addSubtaskApi = (data: any) => {
+  return http.post<any>('/identification/identificationSubtask', data, { loading: false })
+}
+
+/**
+ * @name 修改算法子任务
+ * @param data data
+ * @returns returns
+ */
+export const updateSubtaskApi = (data: any) => {
+  return http.put<any>('/identification/identificationSubtask', data, { loading: false })
+}
+
+/**
+ * @name 删除算法子任务
+ * @param id id
+ * @returns returns
+ */
+export const delSubtaskApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/identification/identificationSubtask/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/identification/identificationSubtask/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importSubtaskDataApi = (data: any) => {
+  return http.post('/identification/identificationSubtask/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportSubtaskApi = (data: any) => {
+  return http.downloadPost('/identification/identificationSubtask/export', data)
+}

+ 31 - 0
src/api/modules/task/task.ts

@@ -1,5 +1,15 @@
 import http from '@/api'
 import { TaskVO, TaskForm, TaskQuery } from '@/api/interface/task/task'
+
+/**
+ * @name 新增算法任务
+ * @param data data
+ * @returns returns
+ */
+export const createTaskApi = (data: any) => {
+  return http.post<any>('/identification/identificationTask/create', data, { loading: false })
+}
+
 /**
  * @name 查询算法任务列表
  * @param query 参数
@@ -9,6 +19,23 @@ export const listTaskApi = (query: TaskQuery) => {
   return http.get<TaskVO[]>('/task/task/list', query, { loading: true })
 }
 
+/**
+ * @name 查询算法任务列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listITaskNewApi = (query: TaskQuery) => {
+  return http.get<TaskVO[]>('/identification/identificationTask/list', query, { loading: true })
+}
+
+/**
+ * @name 查询所有算法
+ * @returns 返回列表
+ */
+export const getAlgorithmOptionApi = () => {
+  return http.get<TaskVO[]>('/task/task/getAlgorithmOption')
+}
+
 /**
  * @name 查询算法任务详细
  * @param id id
@@ -18,6 +45,10 @@ export const getTaskApi = (id: string | number) => {
   return http.get<TaskVO>(`/task/task/${id}`)
 }
 
+export const getITaskNewApi = (id: string | number) => {
+  return http.get<any>(`/identification/identificationTask/${id}`)
+}
+
 /**
  * @name 新增算法任务
  * @param data data

+ 70 - 0
src/api/modules/task/taskConfiguration.ts

@@ -0,0 +1,70 @@
+import http from '@/api'
+import { TaskConfigurationVO, TaskConfigurationForm, TaskConfigurationQuery } from '@/api/interface/task/taskConfiguration'
+/**
+ * @name 查询算法任务列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listTaskConfigurationApi = (query: TaskConfigurationQuery) => {
+  return http.get<TaskConfigurationVO[]>('/task/taskConfiguration/list', query, { loading: true })
+}
+
+/**
+ * @name 查询算法任务详细
+ * @param id id
+ * @returns returns
+ */
+export const getTaskConfigurationApi = (id: string | number) => {
+  return http.get<TaskConfigurationVO>(`/task/taskConfiguration/${id}`)
+}
+
+/**
+ * @name 新增算法任务
+ * @param data data
+ * @returns returns
+ */
+export const addTaskConfigurationApi = (data: TaskConfigurationForm) => {
+  return http.post<any>('/task/taskConfiguration', data, { loading: false })
+}
+
+/**
+ * @name 修改算法任务
+ * @param data data
+ * @returns returns
+ */
+export const updateTaskConfigurationApi = (data: TaskConfigurationForm) => {
+  return http.put<any>('/task/taskConfiguration', data, { loading: false })
+}
+
+/**
+ * @name 删除算法任务
+ * @param id id
+ * @returns returns
+ */
+export const delTaskConfigurationApi = (id: string | number | Array<string | number>) => {
+  return http.delete<any>(`/task/taskConfiguration/${id}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/task/taskConfiguration/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importTaskConfigurationDataApi = (data: any) => {
+  return http.post('/task/taskConfiguration/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportTaskConfigurationApi = (data: any) => {
+  return http.downloadPost('/task/taskConfiguration/export', data)
+}

+ 6 - 0
src/api/modules/upload.ts

@@ -11,6 +11,12 @@ export const uploadImg = (params: FormData) => {
   return http.post<any>('/common/upload', params, { cancel: false })
 }
 
+// 图片上传
+export const uploadPure = (params: FormData) => {
+  // return http.post<Upload.ResFileUrl>(PORT1 + `/file/upload/img`, params, { cancel: false })
+  return http.post<any>('/common/uploadPure', params, { cancel: false })
+}
+
 // 视频上传
 export const uploadVideo = (params: FormData) => {
   // return http.post<any>(PORT1 + `/file/upload/video`, params, { cancel: false })

Разлика између датотеке није приказан због своје велике величине
+ 0 - 1
src/assets/images/logo.svg


+ 73 - 7
src/components/FormDialog/index.vue

@@ -8,7 +8,11 @@
     :top="parameter.top"
     draggable
   >
-    <ProFrom ref="proFormRef" :items-options="parameter.itemsOptions" :form-options="_options" :model="parameter.model" />
+    <ProFrom ref="proFormRef" :items-options="parameter.itemsOptions" :form-options="_options" :model="parameter.model">
+      <template #modelAddress="{}">
+        <FileUpload :file-size="4096" :file-type="['pt']" @update:model-value="setModelAddr"/>
+      </template>
+    </ProFrom>
     <template #footer>
       <span class="dialog-footer">
         <el-button type="primary" v-if="parameter.isEdit" :loading="butLoading" @click="handleSubmit">确认</el-button>
@@ -19,9 +23,20 @@
 </template>
 
 <script setup lang="ts" name="FormDialog">
-import { ref, ComputedRef, computed } from 'vue'
+import {ref, ComputedRef, computed, reactive} from 'vue'
 import ProFrom from '@/components/ProForm/index.vue'
-import { ElMessage } from 'element-plus'
+import {ElMessage} from 'element-plus'
+import FileUpload from '@/components/Upload/File.vue'
+
+// import mittBus from '@/utils/mittBus'
+
+interface EmitEvent {
+  (e: 'update'): void
+}
+
+const emits = defineEmits<EmitEvent>()
+
+const videoUploadRef = ref<InstanceType<typeof FileUpload> | null>(null)
 
 export interface FormParameterProps {
   title: string // 标题
@@ -35,6 +50,7 @@ export interface FormParameterProps {
   model?: Record<ProForm.FormItem['prop'], ProForm.FormItem['value']> // 表单数据对象
   getTableList?: () => void // 获取表格数据的Api
 }
+
 // dialog状态
 const dialogVisible = ref(false)
 const butLoading = ref(false)
@@ -55,19 +71,36 @@ const _options: ComputedRef<ProForm.FormOptions> = computed(() => {
   }
   return Object.assign(form, parameter.value.formOptions)
 })
+
+const modelAddr = ref(null)
+const setModelAddr = res => {
+  console.log('modelAddr', res)
+  modelAddr.value = res
+}
+
 const proFormRef = ref<InstanceType<typeof ProFrom> | null>(null)
 // 表单提交校验
 const handleSubmit = () => {
   const formEl = proFormRef.value?.proFormRef
   const formModel = proFormRef.value?.formModel
+  formModel.modelAddress = modelAddr.value
+  console.log('formModel', formModel)
+
   butLoading.value = true
   if (!formEl) return
   formEl.validate(valid => {
     if (valid) {
-      parameter.value.api!({ ...formModel, ...parameter.value.model }).then(res => {
+      let data = {}
+      if (algorithmModelId.value) {
+        data = {...formModel, ...parameter.value.model, algorithmId: algorithmModelId.value}
+      } else {
+        data = {...formModel, ...parameter.value.model}
+      }
+      parameter.value.api!(data).then(res => {
         if (res.code == 200) {
           proFormRef.value?.resetForm(formEl)
           ElMessage.success('操作成功')
+          emits('update')
           dialogVisible.value = false
           parameter.value.getTableList && parameter.value.getTableList()
         } else {
@@ -79,7 +112,14 @@ const handleSubmit = () => {
     butLoading.value = false
   })
 }
-
+// mittBus.on('data:fileName', (fileName: string) => {
+//   if (fileName !== undefined && fileName !== null) {
+//     console.log(proFormRef.value?.formModel)
+//     if(proFormRef.value?.formModel){
+//       proFormRef.value.formModel.name = fileName.split('.')[0]
+//     }
+//   }
+// })
 // 取消按钮,重置表单,关闭弹框
 const handleCancel = () => {
   console.log(parameter.value.model)
@@ -94,9 +134,12 @@ const handleCancel = () => {
   dialogVisible.value = false
 }
 
+let algorithmModelId = ref(0)
+
 // 接收父组件参数
-const openDialog = (params: FormParameterProps) => {
-  parameter.value = { ...parameter.value, ...params }
+const openDialog = (params: FormParameterProps, algoModelId = null) => {
+  algorithmModelId.value = algoModelId
+  parameter.value = {...parameter.value, ...params}
   _options.value.disabled = !parameter.value.isEdit
   butLoading.value = false
   dialogVisible.value = true
@@ -106,3 +149,26 @@ defineExpose({
   openDialog
 })
 </script>
+
+<style scoped>
+.upload-video-box {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 200px;
+  margin-top: 20px;
+  border: 1px dashed #cccccc;
+
+  :deep(.upload-file-uploader) {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    height: 100%;
+    font-size: 18px;
+    text-align: center;
+    cursor: pointer;
+  }
+}
+</style>

+ 151 - 160
src/components/ImportPicDataset/index.vue

@@ -1,6 +1,6 @@
 <template>
   <el-dialog v-model="dialogVisible" :width="parameter.width" :top="parameter.top" :title="`${parameter.title}`" :destroy-on-close="true" draggable>
-    <el-form class="drawer-multiColumn-form" label-width="100px">
+    <el-form class="drawer-multiColumn-form" label-width="100px" :model="formModel" :rules="rules" ref="formRef">
       <el-form-item label="图片数据集压缩文件上传">
         <el-upload
           action="#"
@@ -20,53 +20,52 @@
           </slot>
           <template #tip>
             <slot name="tip">
-              <!-- <div class="el-upload__tip text-center">
-                <el-checkbox v-model="isCover">是否更新已存在的数据</el-checkbox>
-              </div> -->
-              <div class="el-upload__tip text-center">
-                请上传 .zip 标准格式文件,文件最大为 {{ parameter.fileSize }}M。
-                <!-- <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="downloadTemp">
-                  模板下载
-                </el-link> -->
-              </div>
+              <div class="el-upload__tip text-center">请上传 .zip 标准格式文件,文件最大为 {{ parameter.fileSize }}M。</div>
             </slot>
           </template>
         </el-upload>
       </el-form-item>
 
-      <el-form-item label="文件上传">
-        <el-upload
-          action="#"
-          class="upload"
-          :drag="true"
-          :limit="parameter.limit"
-          :multiple="parameter.multiple"
-          :show-file-list="true"
-          :http-request="uploadExcel"
-          :before-upload="beforeExcelUpload"
-          :on-exceed="handleExceed"
-          :accept="parameter.fileType!.join(',')"
-        >
-          <slot name="empty">
-            <el-icon class="el-icon--upload">
-              <upload-filled />
-            </el-icon>
-            <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
-          </slot>
-          <template #tip>
-            <slot name="tip">
-              <!-- <div class="el-upload__tip text-center">
-                <el-checkbox v-model="isCover">是否更新已存在的数据</el-checkbox>
-              </div> -->
-              <div class="el-upload__tip text-center">
-                请上传 .xls , .xlsx 标准格式文件,文件最大为 {{ parameter.fileSize }}M。
-                <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="downloadTemp">
-                  模板下载
-                </el-link>
-              </div>
-            </slot>
-          </template>
-        </el-upload>
+      <!-- 新增的表单字段 -->
+      <el-form-item label="批次号" prop="batchNum">
+        <el-input v-model="formModel.batchNum" placeholder="请输入批次号" />
+      </el-form-item>
+
+      <el-form-item label="目标类型" prop="objectType">
+        <el-input v-model="formModel.objectType" placeholder="请输入目标类型" />
+      </el-form-item>
+
+      <el-form-item label="目标子类型" prop="objectSubtype">
+        <el-input v-model="formModel.objectSubtype" placeholder="请输入目标子类型" />
+      </el-form-item>
+
+      <el-form-item label="场景" prop="scene">
+        <el-input v-model="formModel.scene" placeholder="请输入场景" />
+      </el-form-item>
+
+      <el-form-item label="数据源" prop="dataSource">
+        <el-input v-model="formModel.dataSource" placeholder="请输入数据源" />
+      </el-form-item>
+
+      <el-form-item label="采集地点" prop="gatherSpot">
+        <el-input v-model="formModel.gatherSpot" placeholder="请输入采集地点" />
+      </el-form-item>
+
+      <el-form-item label="采集时间" prop="gatherTime">
+        <el-date-picker v-model="formModel.gatherTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择采集时间" />
+      </el-form-item>
+
+      <el-form-item label="数据类型" prop="dataType">
+        <el-select v-model="formModel.dataType" placeholder="请选择数据类型">
+          <el-option v-for="item in dataTypes" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="是否已标注">
+        <el-radio-group v-model="formModel.labeled">
+          <el-radio :label="true">是</el-radio>
+          <el-radio :label="false">否</el-radio>
+        </el-radio-group>
       </el-form-item>
     </el-form>
     <template #footer>
@@ -79,142 +78,151 @@
 </template>
 
 <script setup lang="ts" name="ImportPicDataset">
-import { ref } from 'vue'
-import { useDownload } from '@/hooks/useDownload'
+import { ref, defineExpose, onMounted } from 'vue'
 import { ElNotification, UploadRequestOptions, UploadRawFile } from 'element-plus'
 
 export interface ParameterProps {
-  title: string // 标题
-  fileSize?: number // 上传文件的大小
-  limit?: number // 上传文件个数
-  top?: string // 离顶部距离
-  width?: number // 弹框宽度
-  multiple?: boolean // 是否支持多选文件
-  fileType?: File.ExcelMimeType[] // 上传文件的类型
-  ZipFileType?: File.ZipMimeType[] //上传zip文件的类型
-  tempApi?: (params: any) => Promise<any> // 下载模板的Api
-  importApi?: (params: any) => Promise<any> // 批量导入的Api
-  getTableList?: () => void // 获取表格数据的Api
+  title: string
+  fileSize?: number
+  limit?: number
+  top?: string
+  width?: number
+  multiple?: boolean
+  fileType?: File.ExcelMimeType[]
+  ZipFileType?: File.ZipMimeType[]
+  tempApi?: (params: any) => Promise<any>
+  importApi?: (params: any) => Promise<any>
+  getTableList?: () => void
 }
 
-// 是否覆盖数据
-// const isCover = ref(false)
-// dialog状态
+const formModel = ref({
+  batchNum: '',
+  objectType: '',
+  objectSubtype: '',
+  scene: '',
+  dataSource: '',
+  gatherSpot: '',
+  gatherTime: '',
+  dataType: '',
+  labeled: false
+})
+
+const dataTypes = ref([
+  { dictValue: '1', dictLabel: '训练' },
+  { dictValue: '2', dictLabel: '测试' }
+  // 添加更多数据类型
+])
+
 const dialogVisible = ref(false)
-// 父组件传过来的参数
 const parameter = ref<ParameterProps>({
   title: '',
   limit: 1,
   width: 500,
   top: '20vh',
   multiple: false,
-  fileSize: 5,
+  fileSize: 4096,
   fileType: ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
   ZipFileType: ['application/zip', 'application/x-zip-compressed', 'application/x-rar-compressed']
 })
 
-// 接收父组件参数
+// 定义表单校验规则
+const rules = ref({
+  batchNum: [{ required: true, message: '批次号不能为空', trigger: 'blur' }],
+  objectType: [{ required: true, message: '目标类型不能为空', trigger: 'blur' }],
+  objectSubtype: [{ required: true, message: '目标子类型不能为空', trigger: 'blur' }],
+  scene: [{ required: true, message: '场景不能为空', trigger: 'blur' }],
+  dataSource: [{ required: true, message: '数据源不能为空', trigger: 'blur' }],
+  gatherSpot: [{ required: true, message: '采集地点不能为空', trigger: 'blur' }],
+  gatherTime: [{ required: true, message: '采集时间不能为空', trigger: 'change' }],
+  dataType: [{ required: true, message: '数据类型不能为空', trigger: 'change' }]
+})
+
+// 用于存储表单引用
+const formRef = ref(null)
+
+// 接收参数并初始化对话框
 const acceptParams = (params: ParameterProps) => {
   parameter.value = { ...parameter.value, ...params }
   dialogVisible.value = true
-}
-
-// Excel 导入模板下载
-const downloadTemp = () => {
-  if (!parameter.value.tempApi) return
-  useDownload(parameter.value.tempApi, `${parameter.value.title}_template_${new Date().getTime()}`)
-}
-// const uploadRef = ref<UploadInstance>()
-
-const formData = new FormData()
-const handleSubmit = async () => {
-  const zipFile = formData.get('file')
-  const excelFile = formData.get('excelFile')
-  console.log(zipFile)
-  console.log(excelFile)
-  // 检查是否同时上传了压缩文件和 Excel 文件
-  if (!zipFile || !excelFile) {
-    ElNotification({
-      title: '温馨提示',
-      message: '请同时上传一个压缩文件和一个Excel文件!',
-      type: 'warning'
-    })
-    return
-  }
-
-  try {
-    // 同时上传了两个文件,可以进行提交处理
-    await parameter.value.importApi!(formData)
-
-    ElNotification({
-      title: '温馨提示',
-      message: `文件上传成功`,
-      type: 'success'
-    })
-
-    parameter.value.getTableList && parameter.value.getTableList()
-    dialogVisible.value = false
-  } catch (error) {
-    ElNotification({
-      title: '温馨提示',
-      message: `文件上传失败,请重新上传`,
-      type: 'error'
-    })
+  formModel.value = {
+    batchNum: '',
+    objectType: '',
+    objectSubtype: '',
+    scene: '',
+    dataSource: '',
+    gatherSpot: '',
+    gatherTime: '',
+    dataType: '',
+    labeled: false
   }
 }
 
-// 压缩文件上传
-const uploadZip = (param: UploadRequestOptions) => {
-  formData.append('file', param.file)
-
-  // parameter.value.getTableList && parameter.value.getTableList()
-  // dialogVisible.value = false
-}
-
-// 文件上传
-const uploadExcel = (param: UploadRequestOptions) => {
-  formData.append('excelFile', param.file)
-
-  // parameter.value.getTableList && parameter.value.getTableList()
-  // dialogVisible.value = false
-}
+const formData = new FormData()
 
-/**
- * @description 文件上传之前判断
- * @param file 上传的文件
- * */
-const beforeExcelUpload = (file: UploadRawFile) => {
-  const isExcel = parameter.value.fileType!.includes(file.type as File.ExcelMimeType)
+const handleSubmit = async () => {
+  // 先进行表单验证
+  formRef.value.validate(async valid => {
+    if (!valid) {
+      ElNotification({
+        title: '温馨提示',
+        message: '请检查表单的输入项!',
+        type: 'warning'
+      })
+      return
+    }
 
-  const fileSize = file.size / 1024 / 1024 < parameter.value.fileSize!
-  if (!isExcel)
-    ElNotification({
-      title: '温馨提示',
-      message: '上传文件只能是 xls / xlsx 格式!',
-      type: 'warning'
-    })
-  if (!fileSize)
-    setTimeout(() => {
+    const zipFile = formData.get('file')
+    if (!zipFile) {
       ElNotification({
         title: '温馨提示',
-        message: `上传文件大小不能超过 ${parameter.value.fileSize}MB!`,
+        message: '请上传一个压缩文件!',
         type: 'warning'
       })
-    }, 0)
-  return isExcel && fileSize
+      return
+    }
+
+    const formDataInfo = new FormData()
+    formDataInfo.append('file', zipFile)
+    for (const key in formModel.value) {
+      if (Object.prototype.hasOwnProperty.call(formModel.value, key)) {
+        formDataInfo.append(key, formModel.value[key])
+      }
+    }
+
+    try {
+      await parameter.value.importApi!(formDataInfo)
+      ElNotification({
+        title: '温馨提示',
+        message: `文件上传成功`,
+        type: 'success'
+      })
+      parameter.value.getTableList && parameter.value.getTableList()
+      dialogVisible.value = false
+    } catch (error) {
+      ElNotification({
+        title: '温馨提示',
+        message: `文件上传失败,请重新上传`,
+        type: 'error'
+      })
+    }
+  })
+}
+
+const uploadZip = (param: UploadRequestOptions) => {
+  formData.append('file', param.file)
 }
 
 const beforeZipUpload = (file: UploadRawFile) => {
   const isZip = parameter.value.ZipFileType!.includes(file.type as File.ZipMimeType)
-
   const fileSize = file.size / 1024 / 1024 < parameter.value.fileSize!
-  if (!isZip)
+  if (!isZip) {
     ElNotification({
       title: '温馨提示',
       message: '上传文件只能是 zip 格式!',
       type: 'warning'
     })
-  if (!fileSize)
+  }
+  if (!fileSize) {
     setTimeout(() => {
       ElNotification({
         title: '温馨提示',
@@ -222,10 +230,10 @@ const beforeZipUpload = (file: UploadRawFile) => {
         type: 'warning'
       })
     }, 0)
+  }
   return isZip && fileSize
 }
 
-// 文件数超出提示
 const handleExceed = () => {
   ElNotification({
     title: '温馨提示',
@@ -234,28 +242,11 @@ const handleExceed = () => {
   })
 }
 
-// 上传错误提示
-// const excelUploadError = () => {
-//   ElNotification({
-//     title: '温馨提示',
-//     message: `批量添加${parameter.value.title}失败,请您重新上传!`,
-//     type: 'error'
-//   })
-// }
-
-// 上传成功提示
-// const excelUploadSuccess = () => {
-//   ElNotification({
-//     title: '温馨提示',
-//     message: `批量添加${parameter.value.title}成功!`,
-//     type: 'success'
-//   })
-// }
-
 defineExpose({
   acceptParams
 })
 </script>
+
 <style lang="scss" scoped>
 @import './index.scss';
 </style>

+ 16 - 3
src/components/ProForm/index.vue

@@ -25,10 +25,16 @@
               <SelectIcon v-model:icon-value="formModel[item.prop]" />
             </template>
             <template v-else-if="item.compOptions.elTagName === 'file-upload'">
-              <FileUpload v-model:model-value="formModel[item.prop]" />
+              <FileUpload
+                v-model:model-value="formModel[item.prop]"
+                :file-size="item.compOptions.fileSize"
+                :file-type="item.compOptions.fileType"
+                v-bind="$attrs"
+              />
             </template>
             <template v-else-if="item.compOptions.elTagName === 'img-upload'">
               <uploadImg v-model:image-url="formModel[item.prop]" />
+              <!--              <el-button @click="() => {console.log(formModel[item.prop])}"/>-->
             </template>
 
             <template v-else-if="item.compOptions.elTagName === 'imgs-upload'">
@@ -46,8 +52,8 @@
       </template>
     </el-row>
     <el-form-item v-if="_formOptions.hasFooter">
-      <slot name="operation" :model="formModel" :pro-form-ref="proFormRef">
-        <el-button type="primary" @click="onSubmit(proFormRef)">{{ _formOptions.submitButtonText }}</el-button>
+      <slot name="operation" :form-model="formModel" :model="formModel" :pro-form-ref="proFormRef">
+        <el-button type="primary" v-if="_formOptions.showSubmitButton" @click="onSubmit(proFormRef)">{{ _formOptions.submitButtonText }}</el-button>
         <el-button v-if="_formOptions.showResetButton" type="info" @click="resetForm(proFormRef)">
           {{ _formOptions.resetButtonText }}
         </el-button>
@@ -146,6 +152,13 @@ props.itemsOptions.forEach(async item => {
   await setEnumMap(item)
 })
 const emits = defineEmits<EmitEvent>()
+// 监听itemsOptions变化
+watch(props, () => {
+  // 处理表单项需要的参数
+  props.itemsOptions.forEach(async item => {
+    await setEnumMap(item)
+  })
+})
 // 根据items初始化model, 如果items有传值就用传递的model数据模型,否则就给上面声明的formModel设置相应的(key,value) [item.prop], item.value是表单的默认值(选填)
 watch(
   () => props.model,

+ 0 - 1
src/components/ProTable/index.vue

@@ -138,7 +138,6 @@ const radio = ref('')
 
 // 表格多选 Hooks
 const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(props.rowKey)
-
 // 表格操作 Hooks
 const { tableData, pageable, searchParam, searchInitParam, getTableList, search, reset, handleSizeChange, handleCurrentChange } = useTable(
   props.requestApi,

+ 1 - 1
src/components/ProTable/interface/index.ts

@@ -1,7 +1,7 @@
 import { VNode, ComponentPublicInstance, Ref } from 'vue'
 import { BreakPoint, Responsive } from '@/components/Grid/interface'
 import { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
-import { ProTableProps } from '@/components/ProTable/index.vue'
+import ProTableProps from '@/components/ProTable/index.vue'
 import ProTable from '@/components/ProTable/index.vue'
 
 export interface EnumProps {

+ 19 - 5
src/components/Upload/File.vue

@@ -16,7 +16,7 @@
       :accept="fileType.join(',')"
       :headers="headers"
     >
-      <el-button icon="icon" type="primary">{{ text }}</el-button>
+      <el-button :icon="icon" type="primary">{{ text }}</el-button>
     </el-upload>
     <!-- 上传提示 -->
     <div class="el-upload__tip" v-if="showTip">
@@ -54,6 +54,7 @@ import { globalHeaders } from '@/api'
 import { OssVO } from '@/api/interface/system/oss'
 import { getListByIdsApi, delOssApi } from '@/api/modules/system/oss'
 import { listToString } from '@/utils/common'
+
 interface UploadFileProps {
   modelValue?: string | number
   disabled?: boolean // 是否禁用上传组件 ==> 非必传(默认为 false)
@@ -64,7 +65,9 @@ interface UploadFileProps {
   text?: string // 按钮文字
   icon?: string
   fileType?: Array<string>
+  uploadApiPath?: string // 上传文件服务器地址
 }
+
 // 默认值
 const props = withDefaults(defineProps<UploadFileProps>(), {
   modelValue: () => '',
@@ -72,18 +75,21 @@ const props = withDefaults(defineProps<UploadFileProps>(), {
   disabled: false,
   limit: 1,
   fileSize: 5,
-  fileType: () => ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'txt', 'pdf'],
+  fileType: () => ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'txt', 'pdf', 'mp4'],
   text: '文件上传',
-  isShowTip: true
+  isShowTip: true,
+  uploadApiPath: '/common/upload',
+  icon: 'upload-filled'
 })
 
 const baseUrl = import.meta.env.VITE_API_URL
-const uploadFileUrl = ref(baseUrl + '/common/upload') // 上传文件服务器地址
+const uploadFileUrl = ref(baseUrl + props.uploadApiPath)
 const headers = ref(globalHeaders())
 const uploadRef = ref<UploadInstance>()
 const number = ref(0)
 const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize))
 const uploadList = ref<any[]>([])
+const uploadFileListExport = ref<any[]>([])
 const emit = defineEmits<{
   'update:modelValue': [value: any]
 }>()
@@ -170,12 +176,16 @@ const uploadSuccess = (response: any | undefined, uploadFile: UploadFile) => {
     return
   }
   uploadList.value.push({ name: response.data.fileName, url: response.data.url, ossId: response.data.ossId })
+  uploadFileListExport.value = []
+  uploadFileListExport.value.push({ name: response.data.fileName, url: response.data.url, ossId: response.data.ossId })
+
+  console.log(uploadList.value)
+
   uploadedSuccessfully()
 }
 
 // 上传结束处理
 const uploadedSuccessfully = () => {
-  debugger
   if (number.value > 0 && uploadList.value.length === number.value) {
     _fileList.value = _fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
     uploadList.value = []
@@ -211,6 +221,10 @@ const uploadError = () => {
 const handleExceed = () => {
   ElMessage.warning(`当前最多只能上传 ${props.limit} 个文件 ,请移除后上传!`)
 }
+
+defineExpose({
+  uploadFileListExport
+})
 </script>
 
 <style scoped lang="scss">

+ 0 - 1
src/components/Upload/FileS3.vue

@@ -175,7 +175,6 @@ const uploadSuccess = (response: any | undefined, uploadFile: UploadFile) => {
 
 // 上传结束处理
 const uploadedSuccessfully = () => {
-  debugger
   if (number.value > 0 && uploadList.value.length === number.value) {
     _fileList.value = _fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
     uploadList.value = []

+ 30 - 8
src/components/Upload/Img.vue

@@ -15,18 +15,24 @@
       :accept="fileType.join(',')"
     >
       <template v-if="imageUrl">
-        <img :src="imageUrl" class="upload-image" />
+        <img :src="'/api' + imageUrl" class="upload-image" />
         <div class="upload-handle" @click.stop>
           <div v-if="!self_disabled" class="handle-icon" @click="editImg">
-            <el-icon><Edit /></el-icon>
+            <el-icon>
+              <Edit />
+            </el-icon>
             <span>编辑</span>
           </div>
           <div class="handle-icon" @click="imgViewVisible = true">
-            <el-icon><ZoomIn /></el-icon>
+            <el-icon>
+              <ZoomIn />
+            </el-icon>
             <span>查看</span>
           </div>
           <div v-if="!self_disabled" class="handle-icon" @click="deleteImg">
-            <el-icon><Delete /></el-icon>
+            <el-icon>
+              <Delete />
+            </el-icon>
             <span>删除</span>
           </div>
         </div>
@@ -34,7 +40,9 @@
       <template v-else>
         <div class="upload-empty">
           <slot name="empty">
-            <el-icon><Plus /></el-icon>
+            <el-icon>
+              <Plus />
+            </el-icon>
             <!-- <span>请上传图片</span> -->
           </slot>
         </div>
@@ -43,7 +51,18 @@
     <div class="el-upload__tip">
       <slot name="tip"></slot>
     </div>
-    <el-image-viewer v-if="imgViewVisible" :url-list="[imageUrl]" @close="imgViewVisible = false" />
+    <!--    <el-dialog id="dialog" v-if="isShowData" :visible="imgViewVisible">-->
+    <!--      <el-image-viewer :url-list="['/api' + imageUrl]" />-->
+    <!--    </el-dialog>-->
+    <!--    <el-image-viewer v-else-if="imgViewVisible" :url-list="['/api' + imageUrl]" @close="imgViewVisible = false" />-->
+
+    <el-image-viewer
+      v-if="imgViewVisible"
+      style="position: absolute; z-index: 9999"
+      :z-index="9999"
+      :url-list="['/api' + imageUrl]"
+      @close="imgViewVisible = false"
+    />
   </div>
 </template>
 
@@ -53,6 +72,7 @@ import { generateUUID } from '@/utils'
 import { uploadImg } from '@/api/modules/upload'
 import { ElNotification, formContextKey, formItemContextKey } from 'element-plus'
 import type { UploadProps, UploadRequestOptions } from 'element-plus'
+import mittBus from '@/utils/mittBus'
 
 interface UploadFileProps {
   imageUrl: string // 图片地址 ==> 必传
@@ -64,6 +84,7 @@ interface UploadFileProps {
   height?: string // 组件高度 ==> 非必传(默认为 150px)
   width?: string // 组件宽度 ==> 非必传(默认为 150px)
   borderRadius?: string // 组件边框圆角 ==> 非必传(默认为 8px)
+  isShowData?: boolean
 }
 
 // 接受父组件参数
@@ -75,7 +96,8 @@ const props = withDefaults(defineProps<UploadFileProps>(), {
   fileType: () => ['image/jpeg', 'image/png', 'image/gif'],
   height: '150px',
   width: '150px',
-  borderRadius: '8px'
+  borderRadius: '8px',
+  isShowData: false
 })
 
 // 生成组件唯一id
@@ -105,8 +127,8 @@ const handleHttpUpload = async (options: UploadRequestOptions) => {
   try {
     const api = props.api ?? uploadImg
     const res = await api(formData)
-
     emit('update:imageUrl', res.data.url)
+    mittBus.emit('data:fileName', res.data.fileName)
     // 调用 el-form 内部的校验方法(可自动校验)
     formItemContext?.prop && formContext?.validateField([formItemContext.prop as string])
   } catch (error) {

+ 1 - 1
src/hooks/useDownload.ts

@@ -19,7 +19,7 @@ export const useDownload = async (
   tempName: string,
   params: any = {},
   isNotify: boolean = true,
-  fileType: string = '.xlsx',
+  fileType: string = '.zip',
   fileName?: string
 ) => {
   if (isNotify) {

+ 8 - 8
src/layouts/components/Header/ToolBarRight.vue

@@ -1,11 +1,11 @@
 <template>
   <div class="tool-bar-ri">
     <div class="header-icon">
-      <SwitchDark />
-      <AssemblySize id="assemblySize" />
+      <!-- <SwitchDark /> -->
+      <!-- <AssemblySize id="assemblySize" /> -->
       <!-- <Language id="language" /> -->
-      <SearchMenu id="searchMenu" />
-      <ThemeSetting id="themeSetting" />
+      <!-- <SearchMenu id="searchMenu" /> -->
+      <!-- <ThemeSetting id="themeSetting" /> -->
       <!-- <Message id="message" /> -->
       <Fullscreen id="fullscreen" />
     </div>
@@ -17,14 +17,14 @@
 <script setup lang="ts">
 // import { computed } from 'vue'
 // import { useUserStore } from '@/stores/modules/user'
-import AssemblySize from './components/AssemblySize.vue'
+// import AssemblySize from './components/AssemblySize.vue'
 // import Language from './components/Language.vue'
-import SearchMenu from './components/SearchMenu.vue'
-import ThemeSetting from './components/ThemeSetting.vue'
+// import SearchMenu from './components/SearchMenu.vue'
+// import ThemeSetting from './components/ThemeSetting.vue'
 // import Message from './components/Message.vue'
 import Fullscreen from './components/Fullscreen.vue'
 import Avatar from './components/Avatar.vue'
-import SwitchDark from '@/components/SwitchDark/index.vue'
+// import SwitchDark from '@/components/SwitchDark/index.vue'
 
 // const userStore = useUserStore()
 // const username = computed(() => userStore.name || 'KM')

+ 49 - 4
src/routers/modules/routerData.json

@@ -25,11 +25,41 @@
         "title": "创建任务",
         "link": "",
         "full": false,
-        "affix": true,
-        "noCache": true,
+        "affix": false,
+        "noCache": false,
+        "activeMenu": "/index"
+      }
+    },
+    {
+      "path": "/createTaskNew",
+      "name": "createTask",
+      "component": "taais/homePage/task/index",
+      "hidden": true,
+      "meta": {
+        "icon": "HomeFilled",
+        "title": "创建任务",
+        "link": "",
+        "full": false,
+        "affix": false,
+        "noCache": false,
         "activeMenu": "/index"
       }
     },
+    {
+      "path": "/data/amplify",
+      "name": "amplify",
+      "component": "demo/data/amplify",
+      "hidden": true,
+      "meta": {
+        "icon": "HomeFilled",
+        "title": "数据扩增",
+        "link": "",
+        "full": false,
+        "affix": false,
+        "noCache": false,
+        "activeMenu": "/demo/data"
+      }
+    },
     {
       "path": "/logPage/:flag",
       "name": "logPage",
@@ -40,7 +70,7 @@
         "title": "日志",
         "link": "",
         "full": false,
-        "affix": true,
+        "affix": false,
         "noCache": true,
         "activeMenu": "/index"
       }
@@ -55,7 +85,22 @@
         "title": "结果",
         "link": "",
         "full": false,
-        "affix": true,
+        "affix": false,
+        "noCache": true,
+        "activeMenu": "/index"
+      }
+    },
+    {
+      "path": "/inferResult",
+      "name": "inferResult",
+      "component": "taais/homePage/inferResult",
+      "hidden": true,
+      "meta": {
+        "icon": "HomeFilled",
+        "title": "结果",
+        "link": "",
+        "full": false,
+        "affix": false,
         "noCache": true,
         "activeMenu": "/index"
       }

+ 1 - 1
src/stores/modules/auth.ts

@@ -29,7 +29,7 @@ export const useAuthStore = defineStore('admin-auth', {
     // Get AuthMenuList
     async getAuthMenuList() {
       const { data } = await getRoutersApi()
-      this.authMenuList = [...staticRouterList.data, ...data]
+      this.authMenuList = [...staticRouterList.data, ...data] as any[]
     },
     // Set RouteName
     async setRouteName(name: string) {

+ 2 - 2
src/stores/modules/global.ts

@@ -7,7 +7,7 @@ export const useGlobalStore = defineStore('admin-global', {
   // 修改默认值之后,需清除 localStorage 数据
   state: (): GlobalState => ({
     // 布局模式 (纵向:vertical | 经典:classic | 横向:transverse | 分栏:columns)
-    layout: 'vertical',
+    layout: 'transverse',
     // element 组件大小
     assemblySize: 'default',
     // 当前系统语言
@@ -39,7 +39,7 @@ export const useGlobalStore = defineStore('admin-global', {
     // 标签页图标
     tabsIcon: true,
     // 页脚
-    footer: true
+    footer: false
   }),
   getters: {},
   actions: {

+ 5 - 0
src/typings/ProForm.d.ts

@@ -37,6 +37,8 @@ declare namespace ProForm {
     | 'time-select'
     | 'transfer' // label不显示 使用插槽吧
     | 'icon' // 图标
+    | 'tree'
+    | File.FileUploadType
   interface FormOptions {
     inline?: boolean
     labelWidth?: string | number
@@ -44,6 +46,7 @@ declare namespace ProForm {
     disabled?: boolean // 不可编辑
     hasFooter?: boolean // 是否显示底部操作按钮
     labelSuffix?: ': ' | string
+    showSubmitButton?: boolean
     showResetButton?: boolean // 是否展示重置按钮
     showCancelButton?: boolean // 是否展示取消按钮
     submitButtonText?: string
@@ -98,6 +101,8 @@ declare namespace ProForm {
     multiple?: boolean
     disabled?: boolean
     elTagName?: ElTagName
+    fileSize?: number // 文件大小
+    fileType?: string[] // 文件类型
     type?: string
     rangeSeparator?: string // 时间范围分隔符
     startPlaceholder?: string // 开始时间Placeholder

+ 18 - 0
src/typings/canvas.d.ts

@@ -0,0 +1,18 @@
+interface canvasPoint {
+  x: number
+  y: number
+  radius?: number
+  fill?: string
+  stroke?: string
+  strokeWidth?: number
+  left?: number
+  top?: number
+  selectable?: boolean
+  hasBorders?: boolean
+  hasControls?: boolean
+  evented?: boolean
+  originX?: string
+  originY?: string
+  id?: number
+  objectCaching?: boolean
+}

+ 106 - 0
src/utils/fabric.ts

@@ -0,0 +1,106 @@
+// 坐标点排序
+export const sortPoints = (aCoord: any) => {
+  let points = JSON.parse(JSON.stringify(aCoord))
+  // (tl tr) (bl,br)
+  const poiT: canvasPoint = linesIntersection(points[0] || points.tl, points[1] || points.tr, points[2] || points.br, points[3] || points.bl)
+  // (tl br) (tr,bl)
+  const poiB: canvasPoint = linesIntersection(points[0] || points.tl, points[3] || points.bl, points[2] || points.br, points[1] || points.tr)
+  if (poiT?.x != -1) {
+    // 第二个和第三个坐标互换
+    changePoint(points[1] || points.tr, points[2] || points.br)
+  } else if (poiB?.x != -1) {
+    // 第一个和第四个坐标互换
+    changePoint(points[0] || points.tl, points[3] || points.bl)
+    // 第二个和第三个坐标互换
+    changePoint(points[1] || points.tr, points[2] || points.br)
+    // 第一个和第二个坐标互换
+    changePoint(points[0] || points.tl, points[1] || points.tr)
+  }
+  return points
+}
+// 坐标点更换
+const changePoint = (poi1, poi2) => {
+  let temp = { x: poi1.x, y: poi1.y }
+  poi1.x = poi2.x
+  poi1.y = poi2.y
+
+  poi2.x = temp.x
+  poi2.y = temp.y
+}
+// 获取线段交点
+// 算法参考:https://www.jb51.net/article/90104.htm
+function linesIntersection(tl, tr, br, bl) {
+  let tn1 = tr.y - tl.y,
+    ty1 = tl.x - tr.x
+  let tn2 = br.y - bl.y,
+    ty2 = bl.x - br.x
+  let denominator = tn1 * ty2 - ty1 * tn2
+  if (denominator == 0) {
+    return { x: -1, y: -1 }
+  }
+  let distC_N2 = tn2 * bl.x + ty2 * bl.y
+  let distA_N2 = tn2 * tl.x + ty2 * tl.y - distC_N2
+  let distB_N2 = tn2 * tr.x + ty2 * tr.y - distC_N2
+
+  if (distA_N2 * distB_N2 >= 0) {
+    return { x: -1, y: -1 }
+  }
+  let distA_N1 = tn1 * tl.x + ty1 * tl.y
+  let distC_N1 = tn1 * bl.x + ty1 * bl.y - distA_N1
+  let distD_N1 = tn1 * br.x + ty1 * br.y - distA_N1
+  if (distC_N1 * distD_N1 >= 0) {
+    return { x: -1, y: -1 }
+  }
+  //计算交点坐标
+  let fraction = distA_N2 / denominator
+  let dx = fraction * ty1,
+    dy = -fraction * tn1
+  return { x: tl.x + dx, y: tl.y + dy }
+}
+
+export const useDrawArea = (
+  params = {
+    src: '',
+    width: 1920,
+    height: 1080,
+    area: ''
+  }
+) => {
+  const { src, width, height, area } = params
+  return new Promise((resolve, reject) => {
+    if (src && src != '' && area && area != '') {
+      let cvs = document.createElement('canvas')
+      let ctx: CanvasRenderingContext2D | null = cvs.getContext('2d')
+      let img = new Image()
+      img.setAttribute('crossOrigin', 'anonymous')
+      img.src = src
+      let points = area.split(',').map(item => {
+        return item.split(';').map(arss => Number(arss))
+      })
+      img.onload = () => {
+        cvs.width = width
+        cvs.height = height
+        if (ctx) {
+          ctx?.drawImage(img, 0, 0, width, height)
+          ctx.strokeStyle = '#ff0000'
+          ctx.lineWidth = 2
+          points.forEach(ar => {
+            ctx?.beginPath()
+            ctx?.moveTo(ar[0], ar[1])
+            ctx?.lineTo(ar[2], ar[3])
+            if (ar.length > 4) {
+              ctx?.lineTo(ar[4], ar[5])
+              ctx?.lineTo(ar[6], ar[7])
+            }
+            ctx?.closePath()
+            ctx?.stroke()
+          })
+        }
+        let url: string = cvs.toDataURL('image/png') || ''
+        resolve(url)
+      }
+    } else {
+      reject('')
+    }
+  })
+}

+ 34 - 0
src/utils/status.ts

@@ -0,0 +1,34 @@
+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'
+  }
+]
+
+export default statusEnums

+ 7 - 11
src/views/ag/config/index.vue

@@ -156,10 +156,6 @@ const setItemsOptions = () => {
       label: '类型',
       prop: 'type',
       rules: [{ required: true, message: '类型不能为空', trigger: 'change' }],
-      options: {
-        labelKey: 'dictLabel',
-        valueKey: 'dictValue'
-      },
       compOptions: {
         elTagName: 'radio-group',
         labelKey: 'dictLabel',
@@ -168,13 +164,13 @@ const setItemsOptions = () => {
         placeholder: '请选择类型'
       }
     },
-    {
-      label: '父id',
-      prop: 'parentId',
-      compOptions: {
-        placeholder: '请输入父id'
-      }
-    },
+    // {
+    //   label: '父id',
+    //   prop: 'parentId',
+    //   compOptions: {
+    //     placeholder: '请输入父id'
+    //   }
+    // },
     {
       label: '分系统',
       prop: 'subsystem',

+ 37 - 3
src/views/ag/model/index.vue

@@ -25,6 +25,7 @@
         <el-button type="primary" link icon="EditPen" v-auth="['ag:model:edit']" @click="openDialog(2, '算法模型配置编辑', scope.row)">
           编辑
         </el-button>
+        <el-button type="primary" link icon="EditPen" @click="downloadPtFile(scope.row)"> 下载 </el-button>
         <el-button type="primary" link icon="Delete" v-auth="['ag:model:remove']" @click="deleteModel(scope.row)"> 删除 </el-button>
       </template>
     </ProTable>
@@ -34,7 +35,7 @@
 </template>
 
 <script setup lang="tsx" name="Model">
-import { ref, reactive } from 'vue'
+import { ref, reactive, onMounted } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useDownload } from '@/hooks/useDownload'
 import { ElMessageBox } from 'element-plus'
@@ -52,9 +53,20 @@ import {
   exportModelApi,
   getModelApi
 } from '@/api/modules/ag/model'
+import { getAlgorithmOptionApi } from '@/api/modules/task/task'
+import { listTaskConfigurationApi } from '@/api/modules/task/taskConfiguration'
+import http from '@/api'
+
+const downloadPtFile = function (row) {
+  console.log(row)
+  if (row.modelAddress && row.modelAddress.length > 0) {
+    window.open('/api' + row.modelAddress, '_blank')
+  }
+}
 
 // ProTable 实例
 const proTable = ref<ProTableInstance>()
+let allAgloData = ref([] as any[])
 
 // 删除算法模型配置信息
 const deleteModel = async (params: any) => {
@@ -154,7 +166,11 @@ const setItemsOptions = () => {
       prop: 'algorithmId',
       rules: [{ required: true, message: '算法不能为空', trigger: 'blur' }],
       compOptions: {
-        placeholder: '请输入算法'
+        elTagName: 'select',
+        labelKey: 'name',
+        valueKey: 'id',
+        enum: allAgloData.value,
+        placeholder: '请选择算法'
       }
     },
     {
@@ -165,12 +181,25 @@ const setItemsOptions = () => {
         placeholder: '请输入模型名称'
       }
     },
+    // {
+    //   label: '模型',
+    //   prop: 'modelAddress',
+    //   rules: [{ required: true, message: '模型不能为空', trigger: 'blur' }],
+    //   compOptions: {
+    //     elTagName: 'file-upload',
+    //     accept: '.pt',
+    //     fileSize: 4096,
+    //     fileType: ['pt']
+    //     // props: { fileSize: 4096, fileType: ['pt'] }
+    //     // props: { accept: '.pt' }
+    //   }
+    // },
     {
       label: '模型',
       prop: 'modelAddress',
       rules: [{ required: true, message: '模型不能为空', trigger: 'blur' }],
       compOptions: {
-        placeholder: '请输入模型'
+        elTagName: 'slot'
       }
     },
     {
@@ -196,4 +225,9 @@ const setItemsOptions = () => {
     }
   ]
 }
+onMounted(() => {
+  listTaskConfigurationApi().then(res => {
+    allAgloData.value = res.data.list
+  })
+})
 </script>

+ 269 - 0
src/views/demo/AlgorithmConfigTrack/index.vue

@@ -0,0 +1,269 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listAlgorithmConfigTrackApi">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['demo:AlgorithmConfigTrack:add']" icon="CirclePlus" @click="openDialog(1, '算法任务配置新增')">
+          新增
+        </el-button>
+        <el-button type="primary" v-auth="['demo:AlgorithmConfigTrack:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button>
+        <el-button type="primary" v-auth="['demo:AlgorithmConfigTrack:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button>
+        <el-button
+          type="danger"
+          v-auth="['demo:AlgorithmConfigTrack:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button type="primary" link icon="View" v-auth="['demo:AlgorithmConfigTrack:query']" @click="openDialog(3, '算法任务配置查看', scope.row)">
+          查看
+        </el-button>
+        <el-button
+          type="primary"
+          link
+          icon="EditPen"
+          v-auth="['demo:AlgorithmConfigTrack:edit']"
+          @click="openDialog(2, '算法任务配置编辑', scope.row)"
+        >
+          编辑
+        </el-button>
+        <el-button type="primary" link icon="Delete" v-auth="['demo:AlgorithmConfigTrack:remove']" @click="deleteAlgorithmConfigTrack(scope.row)">
+          删除
+        </el-button>
+      </template>
+    </ProTable>
+    <FormDialog ref="formDialogRef" />
+    <ImportExcel ref="dialogRef" />
+  </div>
+</template>
+
+<script setup lang="tsx" name="AlgorithmConfigTrack">
+import { ref, reactive } from 'vue'
+import { useHandleData } from '@/hooks/useHandleData'
+import { useDownload } from '@/hooks/useDownload'
+import { ElMessageBox } from 'element-plus'
+import ProTable from '@/components/ProTable/index.vue'
+import ImportExcel from '@/components/ImportExcel/index.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
+import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
+import {
+  listAlgorithmConfigTrackApi,
+  delAlgorithmConfigTrackApi,
+  addAlgorithmConfigTrackApi,
+  updateAlgorithmConfigTrackApi,
+  importTemplateApi,
+  importAlgorithmConfigTrackDataApi,
+  exportAlgorithmConfigTrackApi,
+  getAlgorithmConfigTrackApi
+} from '@/api/modules/demo/AlgorithmConfigTrack'
+import {enumsAlgorithmType, enumsSubSystem} from "@/views/demo/utils";
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除算法任务配置信息
+const deleteAlgorithmConfigTrack = async (params: any) => {
+  await useHandleData(delAlgorithmConfigTrackApi, params.id, '删除【' + params.id + '】算法任务配置')
+  proTable.value?.getTableList()
+}
+
+// 批量删除算法任务配置信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delAlgorithmConfigTrackApi, ids, '删除所选算法任务配置信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出算法任务配置列表
+const downloadFile = async () => {
+  ElMessageBox.confirm('确认导出算法任务配置数据?', '温馨提示', { type: 'warning' }).then(() =>
+    useDownload(exportAlgorithmConfigTrackApi, '算法任务配置列表', proTable.value?.searchParam)
+  )
+}
+
+// 批量添加算法任务配置
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+const batchAdd = () => {
+  const params = {
+    title: '算法任务配置',
+    tempApi: importTemplateApi,
+    importApi: importAlgorithmConfigTrackDataApi,
+    getTableList: proTable.value?.getTableList
+  }
+  dialogRef.value?.acceptParams(params)
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getAlgorithmConfigTrackApi(row?.id || null)
+  }
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : res.data,
+    api: type == 1 ? addAlgorithmConfigTrackApi : updateAlgorithmConfigTrackApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  { type: 'selection', fixed: 'left', width: 70 },
+  { prop: 'id', label: '主键ID' },
+  {
+    prop: 'type',
+    label: '类型',
+    tag: true,
+    enum: enumsAlgorithmType,
+    search: {
+      el: 'select'
+    },
+    width: 120
+  },
+  //   {
+  //     prop: 'parentId',
+  //     label: '父id',
+  //     search: {
+  //       el: 'input'
+  //     },
+  //     width: 120
+  //   },
+  {
+    prop: 'subsystem',
+    label: '分系统',
+    tag: true,
+    enum: enumsSubSystem,
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'algorithmName',
+    label: '算法名称',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'algorithmAddress',
+    label: '算法地址',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'parameterConfig',
+    label: '参数配置',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'remarks',
+    label: '备注',
+    search: {
+      el: 'input'
+    }
+  },
+  //   {
+  //     prop: 'system',
+  //     label: '系统',
+  //     search: {
+  //       el: 'input'
+  //     }
+  //   },
+  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+])
+
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '类型',
+      prop: 'type',
+      rules: [{ required: true, message: '算法类型不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'select',
+        enum: enumsAlgorithmType,
+        placeholder: '请选择算法类型'
+      }
+    },
+    // {
+    //   label: '父id',
+    //   prop: 'parentId',
+    //   rules: [{ required: true, message: '父id不能为空', trigger: 'blur' }],
+    //   compOptions: {
+    //     placeholder: '请输入父id'
+    //   }
+    // },
+    {
+      label: '分系统',
+      prop: 'subsystem',
+      rules: [{ required: true, message: '分系统不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'select',
+        enum: enumsSubSystem,
+        placeholder: '请选择分系统'
+      }
+    },
+    {
+      label: '算法名称',
+      prop: 'algorithmName',
+      rules: [{ required: true, message: '算法名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入算法名称'
+      }
+    },
+    {
+      label: '算法地址',
+      prop: 'algorithmAddress',
+      rules: [{ required: true, message: '算法地址不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入算法地址'
+      }
+    },
+    {
+      label: '参数配置',
+      prop: 'parameterConfig',
+      rules: [{ required: true, message: '参数配置不能为空', trigger: 'blur' }],
+      compOptions: {
+        type: 'textarea',
+        clearable: true,
+        placeholder: '请输入内容'
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remarks',
+      rules: [{ required: false, message: '备注不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+    // {
+    //   label: '系统',
+    //   prop: 'system',
+    //   rules: [{ required: true, message: '系统不能为空', trigger: 'blur' }],
+    //   compOptions: {
+    //     placeholder: '请输入系统'
+    //   }
+    // }
+  ]
+}
+</script>

+ 315 - 0
src/views/demo/AlgorithmModelTrack/index.vue

@@ -0,0 +1,315 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listAlgorithmModelTrackApi">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['demo:AlgorithmModelTrack:add']" icon="CirclePlus" @click="openDialog(1, '算法模型配置新增')">
+          新增
+        </el-button>
+        <!-- <el-button type="primary" v-auth="['demo:AlgorithmModelTrack:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button>
+        <el-button type="primary" v-auth="['demo:AlgorithmModelTrack:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button> -->
+        <el-button
+          type="danger"
+          v-auth="['demo:AlgorithmModelTrack:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button type="primary" link icon="View" v-auth="['demo:AlgorithmModelTrack:query']" @click="openDialog(3, '算法模型配置查看', scope.row)">
+          查看
+        </el-button>
+        <!-- <el-button
+          type="primary"
+          link
+          icon="EditPen"
+          v-auth="['demo:AlgorithmModelTrack:edit']"
+          @click="openDialog(2, '算法模型配置编辑', scope.row)"
+        >
+          编辑
+        </el-button> -->
+        <el-button type="primary" link icon="Delete" v-auth="['demo:AlgorithmModelTrack:remove']" @click="deleteAlgorithmModelTrack(scope.row)">
+          删除
+        </el-button>
+      </template>
+    </ProTable>
+    <FormDialog ref="formDialogRef" />
+    <ImportExcel ref="dialogRef" />
+  </div>
+</template>
+
+<script setup lang="tsx" name="AlgorithmModelTrack">
+import { ref, reactive, onMounted } from 'vue'
+import { useHandleData } from '@/hooks/useHandleData'
+import { useDownload } from '@/hooks/useDownload'
+import { ElMessageBox } from 'element-plus'
+import ProTable from '@/components/ProTable/index.vue'
+import ImportExcel from '@/components/ImportExcel/index.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
+import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
+import {
+  listAlgorithmModelTrackApi,
+  delAlgorithmModelTrackApi,
+  addAlgorithmModelTrackApi,
+  updateAlgorithmModelTrackApi,
+  importTemplateApi,
+  importAlgorithmModelTrackDataApi,
+  exportAlgorithmModelTrackApi,
+  getAlgorithmModelTrackApi
+} from '@/api/modules/demo/AlgorithmModelTrack'
+
+import { enumAlgorithmConfigTrackApi } from '@/api/modules/demo/AlgorithmConfigTrack'
+import { AlgorithmType, SubSystem, enumsModelStatus, enumsAlgorithmType, enumsSubSystem } from '@/views/demo/utils'
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除算法模型配置信息
+const deleteAlgorithmModelTrack = async (params: any) => {
+  await useHandleData(delAlgorithmModelTrackApi, params.id, '删除【' + params.id + '】算法模型配置')
+  proTable.value?.getTableList()
+}
+
+// 批量删除算法模型配置信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delAlgorithmModelTrackApi, ids, '删除所选算法模型配置信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出算法模型配置列表
+const downloadFile = async () => {
+  ElMessageBox.confirm('确认导出算法模型配置数据?', '温馨提示', { type: 'warning' }).then(() =>
+    useDownload(exportAlgorithmModelTrackApi, '算法模型配置列表', proTable.value?.searchParam)
+  )
+}
+
+// 批量添加算法模型配置
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+const batchAdd = () => {
+  const params = {
+    title: '算法模型配置',
+    tempApi: importTemplateApi,
+    importApi: importAlgorithmModelTrackDataApi,
+    getTableList: proTable.value?.getTableList
+  }
+  dialogRef.value?.acceptParams(params)
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getAlgorithmModelTrackApi(row?.id || null)
+  }
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : res.data,
+    api: type == 1 ? addAlgorithmModelTrackApi : updateAlgorithmModelTrackApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  { type: 'selection', fixed: 'left', width: 70 },
+  { prop: 'id', label: 'ID' },
+  {
+    prop: 'modelName',
+    label: '模型名称',
+    search: {
+      el: 'input'
+    },
+    minWidth: 150
+  },
+  {
+    prop: 'algorithmType',
+    label: '算法',
+    search: {
+      el: 'input'
+    },
+    minWidth: 200
+  },
+  {
+    prop: 'type',
+    label: '类型',
+    tag: true,
+    enum: enumsAlgorithmType,
+    search: {
+      el: 'select'
+    },
+    width: 120
+  },
+  //   {
+  //     prop: 'parentId',
+  //     label: '父id',
+  //     search: {
+  //       el: 'input'
+  //     },
+  //     width: 120
+  //   },
+  {
+    prop: 'subsystem',
+    label: '分系统',
+    tag: true,
+    enum: enumsSubSystem,
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'modelStatus',
+    label: '模型状态',
+    tag: true,
+    enum: enumsModelStatus,
+    search: {
+      el: 'select'
+    },
+    width: 120
+  },
+  //   {
+  //     prop: 'sampleNumber',
+  //     label: '训练样本数',
+  //     search: {
+  //       el: 'input'
+  //     },
+  //     width: 120
+  //   },
+  //   {
+  //     prop: 'cycleEpoch',
+  //     label: '训练循环次数',
+  //     search: {
+  //       el: 'input'
+  //     },
+  //     width: 120
+  //   },
+  {
+    prop: 'modelAddress',
+    label: '模型',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'remarks',
+    label: '备注',
+    search: {
+      el: 'input'
+    },
+    width: 200
+  },
+  //   {
+  //     prop: 'system',
+  //     label: '系统',
+  //     search: {
+  //       el: 'input'
+  //     },
+  //     width: 120
+  //   },
+  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+])
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+
+const enumsAlgorithmConfigTrack = ref<any>([])
+
+onMounted(async () => {
+  const result: any = await enumAlgorithmConfigTrackApi()
+  // console.log(result)
+  // console.log(result['data'])
+  for (let item of result['data']) {
+    // console.log(item)
+    // console.log(item['type'])
+    // console.log(item['subsystem'])
+    // console.log(AlgorithmType[item['type']])
+    // console.log(SubSystem[item['subsystem']])
+    // console.log(AlgorithmType)
+    // console.log(SubSystem)
+    // console.log('-------------------')
+    item['label'] = item['label'] + '-' + SubSystem[item['subsystem']] + '-' + AlgorithmType[item['type']]
+  }
+
+  enumsAlgorithmConfigTrack.value = result['data']
+  return result['data']
+})
+
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '算法',
+      prop: 'algorithmId',
+      rules: [{ required: true, message: '算法不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'select',
+        placeholder: '请输入算法',
+        enum: enumsAlgorithmConfigTrack
+      }
+    },
+    {
+      label: '模型名称',
+      prop: 'modelName',
+      rules: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入模型名称'
+      }
+    },
+    {
+      label: '模型',
+      prop: 'modelInputOssId',
+      rules: [{ required: false, message: '模型文件不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'file-upload',
+        fileSize: 4096,
+        fileType: ['pt', 'zip'],
+        placeholder: '请上传模型文件'
+      }
+    },
+    // {
+    //   label: '训练样本数',
+    //   prop: 'sampleNumber',
+    //   rules: [{ required: true, message: '训练样本数不能为空', trigger: 'blur' }],
+    //   compOptions: {
+    //     placeholder: '请输入训练样本数'
+    //   }
+    // },
+    // {
+    //   label: '训练循环次数',
+    //   prop: 'cycleEpoch',
+    //   rules: [{ required: true, message: '训练循环次数不能为空', trigger: 'blur' }],
+    //   compOptions: {
+    //     placeholder: '请输入训练循环次数'
+    //   }
+    // },
+    {
+      label: '备注',
+      prop: 'remarks',
+      rules: [{ required: false, message: '备注不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+    // {
+    //   label: '系统',
+    //   prop: 'system',
+    //   rules: [{ required: true, message: '系统不能为空', trigger: 'blur' }],
+    //   compOptions: {
+    //     placeholder: '请输入系统'
+    //   }
+    // }
+  ]
+}
+</script>

+ 346 - 0
src/views/demo/DataSeq/index.vue

@@ -0,0 +1,346 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listDataSeqApi">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['demo:DataSeq:add']" icon="CirclePlus" @click="openDialog(1, '数据集新增')"> 新增
+        </el-button>
+        <!-- <el-button type="primary" v-auth="['demo:DataSeq:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button>
+        <el-button type="primary" v-auth="['demo:DataSeq:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button> -->
+        <el-button
+          type="danger"
+          v-auth="['demo:DataSeq:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button type="primary" link icon="View" v-auth="['demo:DataSeq:query']"
+                   @click="openDialog(3, '数据管理查看', scope.row)">
+          查看
+        </el-button>
+        <el-button type="primary" link icon="View" v-auth="['demo:DataSeq:query']" @click="preview(scope.row)"> 预览</el-button>
+        <!-- <el-button type="primary" link icon="EditPen" v-auth="['demo:DataSeq:edit']" @click="openDialog(2, '数据管理编辑', scope.row)">
+          编辑
+        </el-button> -->
+        <el-button type="primary" link icon="Delete" v-auth="['demo:DataSeq:remove']" @click="deleteDataSeq(scope.row)"> 删除
+        </el-button>
+      </template>
+    </ProTable>
+    <FormDialog ref="formDialogRef"/>
+    <!-- <ImportExcel ref="dialogRef" /> -->
+    <PreviewImages :visible="dialogVisible" :urls="imageUrls" @close="dialogVisible = false"/>
+  </div>
+</template>
+
+<script setup lang="tsx" name="DataSeq">
+import {ref, reactive} from 'vue'
+import {useHandleData} from '@/hooks/useHandleData'
+import {useDownload} from '@/hooks/useDownload'
+import {ElMessageBox} from 'element-plus'
+import ProTable from '@/components/ProTable/index.vue'
+import ImportExcel from '@/components/ImportExcel/index.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
+import {ProTableInstance, ColumnProps} from '@/components/ProTable/interface'
+import {
+  listDataSeqApi,
+  delDataSeqApi,
+  addDataSeqApi,
+  updateDataSeqApi,
+  importTemplateApi,
+  importDataSeqDataApi,
+  exportDataSeqApi,
+  getDataSeqApi,
+  getAllImagesApi
+} from '@/api/modules/demo/DataSeq'
+import {enumsSubSystem} from '../utils'
+import PreviewImages from "@/views/demo/components/PreviewImages.vue";
+const dialogVisible = ref(false)
+const imageUrls = ref([])
+const preview = async(row) => {
+  console.log("showImages")
+  const data: any = await getAllImagesApi(row.inputOssId)
+  imageUrls.value = data.data
+  dialogVisible.value = true
+}
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除数据管理信息
+const deleteDataSeq = async (params: any) => {
+  await useHandleData(delDataSeqApi, params.id, '删除【' + params.id + '】数据管理')
+  proTable.value?.getTableList()
+}
+
+// 批量删除数据管理信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delDataSeqApi, ids, '删除所选数据管理信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出数据管理列表
+const downloadFile = async () => {
+  ElMessageBox.confirm('确认导出数据管理数据?', '温馨提示', {type: 'warning'}).then(() =>
+    useDownload(exportDataSeqApi, '数据管理列表', proTable.value?.searchParam)
+  )
+}
+
+// 批量添加数据管理
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+const batchAdd = () => {
+  const params = {
+    title: '数据管理',
+    tempApi: importTemplateApi,
+    importApi: importDataSeqDataApi,
+    getTableList: proTable.value?.getTableList
+  }
+  dialogRef.value?.acceptParams(params)
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  let res = {data: {}}
+  if (row?.id) {
+    res = await getDataSeqApi(row?.id || null)
+  }
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : res.data,
+    api: type == 1 ? addDataSeqApi : updateDataSeqApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  {type: 'selection', fixed: 'left', width: 70},
+  {prop: 'id', label: 'id'},
+  {
+    prop: 'name',
+    label: '名称',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'subsystem',
+    label: '分系统',
+    tag: true,
+    enum: enumsSubSystem,
+    search: {
+      el: 'select'
+    },
+    width: 120
+  },
+  {
+    prop: 'objectType',
+    label: '目标类型',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'objectSubtype',
+    label: '目标子类型',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'batchNum',
+    label: '批次号',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'scene',
+    label: '场景',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'dataSource',
+    label: '数据源',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'gatherSpot',
+    label: '采集地点',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'remarks',
+    label: '备注',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'filePath',
+    label: '文件路径',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'unzipPath',
+    label: '解压路径',
+    search: {
+      el: 'input'
+    }
+  },
+  {prop: 'operation', label: '操作', width: 230, fixed: 'right'}
+])
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '名称',
+      prop: 'name',
+      rules: [{required: true, message: '名称不能为空', trigger: 'blur'}],
+      compOptions: {
+        placeholder: '请输入名称'
+      }
+    },
+    {
+      label: '分系统',
+      prop: 'subsystem',
+      rules: [{required: true, message: '分系统不能为空', trigger: 'blur'}],
+      compOptions: {
+        elTagName: 'select',
+        enum: enumsSubSystem,
+        placeholder: '请选择分系统'
+      }
+    },
+    {
+      label: '批次号',
+      prop: 'batchNum',
+      rules: [{required: true, message: '批次号不能为空', trigger: 'blur'}],
+      compOptions: {
+        placeholder: '请输入批次号'
+      }
+    },
+    {
+      label: '上传文件',
+      prop: 'inputOssId',
+      rules: [{required: true, message: '数据集文件不能为空', trigger: 'blur'}],
+      compOptions: {
+        elTagName: 'file-upload',
+        fileSize: 4096,
+        fileType: ['zip'],
+        placeholder: '请上传数据集文件'
+      }
+    },
+    // {
+    //   label: '文件类型',
+    //   prop: 'fileType',
+    //   rules: [{ required: true, message: '文件类型不能为空', trigger: 'blur' }],
+    //   compOptions: {
+    //     placeholder: '请输入文件类型'
+    //   }
+    // },
+    {
+      label: '目标类型',
+      prop: 'objectType',
+      rules: [{required: false, message: '目标类型不能为空', trigger: 'blur'}],
+      compOptions: {
+        placeholder: '请输入目标类型'
+      }
+    },
+    {
+      label: '目标子类型',
+      prop: 'objectSubtype',
+      rules: [{required: false, message: '目标子类型不能为空', trigger: 'blur'}],
+      compOptions: {
+        placeholder: '请输入目标子类型'
+      }
+    },
+    {
+      label: '场景',
+      prop: 'scene',
+      rules: [{required: false, message: '场景不能为空', trigger: 'blur'}],
+      compOptions: {
+        placeholder: '请输入场景'
+      }
+    },
+    {
+      label: '数据源',
+      prop: 'dataSource',
+      rules: [{required: false, message: '数据源不能为空', trigger: 'blur'}],
+      compOptions: {
+        placeholder: '请输入数据源'
+      }
+    },
+    {
+      label: '采集时间',
+      prop: 'gatherTime',
+      rules: [{required: false, message: '采集时间不能为空', trigger: 'change'}],
+      compOptions: {
+        elTagName: 'date-picker',
+        type: 'date',
+        placeholder: '请选择采集时间'
+      }
+    },
+    {
+      label: '采集地点',
+      prop: 'gatherSpot',
+      rules: [{required: false, message: '采集地点不能为空', trigger: 'blur'}],
+      compOptions: {
+        placeholder: '请输入采集地点'
+      }
+    },
+    // {
+    //   label: '图片url',
+    //   prop: 'url',
+    //   rules: [{ required: true, message: '图片url不能为空', trigger: 'blur' }],
+    //   compOptions: {
+    //     placeholder: '请输入图片url'
+    //   }
+    // },
+    // {
+    //   label: '日志',
+    //   prop: 'log',
+    //   rules: [{ required: true, message: '日志不能为空', trigger: 'blur' }],
+    //   compOptions: {
+    //     type: 'textarea',
+    //     clearable: true,
+    //     placeholder: '请输入内容'
+    //   }
+    // },
+    {
+      label: '备注',
+      prop: 'remarks',
+      rules: [{required: false, message: '备注不能为空', trigger: 'blur'}],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 464 - 0
src/views/demo/TargetDetection/index.vue

@@ -0,0 +1,464 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listTargetDetectionApi">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['demo:TargetDetection:add']" icon="CirclePlus" @click="openDialog(1, '目标检测新增')"> 新增 </el-button>
+        <!--        <el-button type="primary" v-auth="['demo:TargetDetection:import']" icon="Upload" plain @click="batchAdd">-->
+        <!--          导入-->
+        <!--        </el-button>-->
+        <!--        <el-button type="primary" v-auth="['demo:TargetDetection:export']" icon="Download" plain @click="downloadFile">-->
+        <!--          导出-->
+        <!--        </el-button>-->
+        <el-button
+          type="danger"
+          v-auth="['demo:TargetDetection:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button type="primary" link icon="View" v-if="scope.row.algorithmModelId != null" @click="openModelDialog(scope.row)">
+          <!--@click="openStartDialog(scope.row)"  -->
+          模型
+        </el-button>
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          v-auth="['demo:trackSequence:start']"
+          v-if="scope.row.status == '0' || scope.row.status == '3' || scope.row.status == '4'"
+          @click="startTargetDetection(scope.row)"
+        >
+          开始
+        </el-button>
+        <el-popconfirm title="确定终止此任务吗?" v-if="scope.row.status == '1'" @confirm="stopTargetDetect(scope.row)">
+          <template #reference>
+            <el-button type="primary" link icon="Delete">终止</el-button>
+          </template>
+        </el-popconfirm>
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          v-auth="['demo:trackSequence:download']"
+          v-if="scope.row.status == '2'"
+          @click="dowloadTargetDetection(scope.row)"
+        >
+          下载
+        </el-button>
+        <!--        <el-button-->
+        <!--          type="primary"-->
+        <!--          link-->
+        <!--          icon="View"-->
+        <!--          v-auth="['demo:TargetDetection:query']"-->
+        <!--          @click="openDialog(3, '目标检测查看', scope.row)"-->
+        <!--        >-->
+        <!--          查看-->
+        <!--        </el-button>-->
+        <!--        <el-button-->
+        <!--          type="primary"-->
+        <!--          link-->
+        <!--          icon="EditPen"-->
+        <!--          v-auth="['demo:TargetDetection:edit']"-->
+        <!--          @click="openDialog(2, '目标检测编辑', scope.row)"-->
+        <!--        >-->
+        <!--          编辑-->
+        <!--        </el-button>-->
+        <el-button type="primary" link icon="Delete" v-auth="['demo:TargetDetection:remove']" @click="deleteTargetDetection(scope.row)">
+          删除
+        </el-button>
+      </template>
+    </ProTable>
+    <FormDialog ref="formDialogRef" />
+    <ImportExcel ref="dialogRef" />
+  </div>
+</template>
+
+<script setup lang="tsx" name="TargetDetection">
+import { ref, reactive, onMounted } from 'vue'
+import { useHandleData } from '@/hooks/useHandleData'
+import { useDownload } from '@/hooks/useDownload'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import ProTable from '@/components/ProTable/index.vue'
+import ImportExcel from '@/components/ImportExcel/index.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
+import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
+import {
+  listTargetDetectionApi,
+  delTargetDetectionApi,
+  addTargetDetectionApi,
+  updateTargetDetectionApi,
+  importTemplateApi,
+  importTargetDetectionDataApi,
+  exportTargetDetectionApi,
+  getTargetDetectionApi,
+  startTargetDetectionApi,
+  dowloadTargetDetectionApi,
+  stopTargetDetectionApi
+} from '@/api/modules/demo/TargetDetection'
+
+import { listDataSeqApi } from '@/api/modules/demo/DataSeq'
+
+import { enumAlgorithmModelTrackApi, getAlgorithmModelTrackApi } from '@/api/modules/demo/AlgorithmModelTrack'
+import { enumAlgorithmConfigTrackApi } from '@/api/modules/demo/AlgorithmConfigTrack'
+import { updateTrackSequenceApi } from '@/api/modules/demo/trackSequence'
+
+const stopTargetDetect = async (params: any) => {
+  const res: any = await stopTargetDetectionApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('终止任务成功!')
+  } else {
+    ElMessage.error('终止任务失败,请检查!')
+  }
+  proTable.value?.getTableList()
+}
+
+import statusEnums from '@/utils/status'
+import { AlgorithmType, SubSystem, SubSystem__, enumsAlgorithmType, enumsSubSystem } from '@/views/demo/utils'
+
+const dowloadTargetDetection = async (params: any) => {
+  await useDownload(dowloadTargetDetectionApi, params.name, params.id, true, '.zip')
+}
+
+const startTargetDetection = async (params: any) => {
+  const res: any = await startTargetDetectionApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('任务已开始,请等待完成!')
+  } else {
+    ElMessage.error('任务开始失败,请检查!')
+  }
+  proTable.value?.getTableList()
+}
+
+const enumsAlgorithmConfigTrack = ref<any>([])
+onMounted(async () => {
+  const result = await enumAlgorithmConfigTrackApi()
+  // console.log(result)
+  // console.log(result['data'])
+  enumsAlgorithmConfigTrack.value = result['data']
+  return result['data']
+})
+
+const openModelDialog = async row => {
+  const algorithmModelId = row.algorithmModelId
+  const result: any = await getAlgorithmModelTrackApi(algorithmModelId)
+
+  // console.log(result.data)
+
+  setItemsOptionsModel()
+  const params = {
+    title: '模型',
+    width: 580,
+    isEdit: false,
+    itemsOptions: itemsOptions,
+    model: result.data,
+    api: updateTrackSequenceApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除目标检测信息
+const deleteTargetDetection = async (params: any) => {
+  await useHandleData(delTargetDetectionApi, params.id, '删除【' + params.id + '】目标检测')
+  proTable.value?.getTableList()
+}
+
+// 批量删除目标检测信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delTargetDetectionApi, ids, '删除所选目标检测信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出目标检测列表
+const downloadFile = async () => {
+  ElMessageBox.confirm('确认导出目标检测数据?', '温馨提示', { type: 'warning' }).then(() =>
+    useDownload(exportTargetDetectionApi, '目标检测列表', proTable.value?.searchParam)
+  )
+}
+
+// 批量添加目标检测
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+const batchAdd = () => {
+  const params = {
+    title: '目标检测',
+    tempApi: importTemplateApi,
+    importApi: importTargetDetectionDataApi,
+    getTableList: proTable.value?.getTableList
+  }
+  dialogRef.value?.acceptParams(params)
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getTargetDetectionApi(row?.id || null)
+  }
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : res.data,
+    api: type == 1 ? addTargetDetectionApi : updateTargetDetectionApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+// 表格配置项
+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: 'status',
+    label: '任务状态',
+    search: {
+      el: 'select'
+    },
+    tag: true,
+    enum: statusEnums,
+    width: 150
+  },
+  {
+    prop: 'type',
+    label: '类型',
+    tag: true,
+    enum: enumsAlgorithmType,
+    width: 120
+  },
+  {
+    prop: 'subsystem',
+    label: '分系统',
+    tag: true,
+    enum: enumsSubSystem,
+    width: 200
+  },
+  {
+    prop: 'algorithmName',
+    label: '算法名称',
+    width: 200
+  },
+  {
+    prop: 'modelName',
+    label: '模型名称',
+    width: 200
+  },
+  // {
+  //   prop: 'algorithmModelId',
+  //   label: '模型',
+  //   search: {
+  //     el: 'input'
+  //   },
+  //   width: 150
+  // },
+  {
+    prop: 'startTime',
+    label: '开始时间',
+    width: 180
+  },
+  {
+    prop: 'endTime',
+    label: '结束时间',
+    width: 180
+  },
+  {
+    prop: 'costSecond',
+    label: '耗时',
+    width: 120
+  },
+  {
+    prop: 'log',
+    label: '日志',
+    width: 120
+  },
+  {
+    prop: 'outputPath',
+    label: '输出路径',
+    width: 120
+  },
+  {
+    prop: 'remarks',
+    label: '备注',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+])
+
+const getImageDataList = ref<any>([])
+onMounted(async () => {
+  const qyery = {
+    subsystem: SubSystem__['目标检测'],
+    pageNum: 1,
+    pageSize: 25
+  }
+  const result: any = await listDataSeqApi(qyery)
+  const data = result['data']['list']
+  for (const item of data) {
+    getImageDataList.value.push({
+      value: item['inputOssId'],
+      label: item['name']
+    })
+  }
+})
+
+const enumsAlgorithmModelTrack = ref<any>([])
+
+onMounted(async () => {
+  const result: any = await enumAlgorithmModelTrackApi()
+  // console.log(result.data);
+  enumsAlgorithmModelTrack.value = []
+
+  for (const item of result.data) {
+    if (SubSystem[item['subsystem']] === '目标检测') {
+      item['label'] = item['label'] + '-' + SubSystem[item['subsystem']] + '-' + AlgorithmType[item['type']] + '-' + item['algorithmName']
+      enumsAlgorithmModelTrack.value.push(item)
+    }
+  }
+})
+
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '任务名称',
+      prop: 'name',
+      rules: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入任务名称'
+      }
+    },
+    {
+      label: '选择数据集',
+      prop: 'inputOssId',
+      rules: [{ required: false, message: '数据集不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'select',
+        placeholder: '请选择或者上传数据集',
+        enum: getImageDataList,
+        clearable: true
+      }
+    },
+    {
+      label: '上传数据集',
+      prop: 'inputOssId',
+      rules: [{ required: true, message: '数据集不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'file-upload',
+        fileSize: 4096,
+        fileType: ['zip'],
+        placeholder: '请上传数据集'
+      }
+    },
+    {
+      label: '选择模型',
+      prop: 'algorithmModelId',
+      rules: [{ required: true, message: '模型不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'select',
+        placeholder: '请选择模型',
+        enum: enumsAlgorithmModelTrack
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remarks',
+      rules: [],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
+
+const setItemsOptionsModel = () => {
+  itemsOptions = [
+    {
+      label: '算法ID',
+      prop: 'algorithmId',
+      rules: [{ required: true, message: '算法不能为空', trigger: 'blur' }],
+      compOptions: {
+        disabled: true,
+        placeholder: '请输入算法'
+      }
+    },
+    {
+      label: '算法类型',
+      prop: 'algorithmType',
+      rules: [{ required: true, message: '算法不能为空', trigger: 'blur' }],
+      compOptions: {
+        disabled: true,
+        elTagName: 'select',
+        placeholder: '请输入算法',
+        enum: enumsAlgorithmConfigTrack
+      }
+    },
+    {
+      label: '算法参数',
+      prop: 'parameterConfig',
+      rules: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入模型名称'
+      }
+    },
+    {
+      label: '模型ID',
+      prop: 'id',
+      rules: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入模型名称'
+      }
+    },
+    {
+      label: '模型名称',
+      prop: 'modelName',
+      rules: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入模型名称'
+      }
+    },
+    {
+      label: '模型保存路径',
+      prop: 'modelAddress',
+      rules: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入模型名称'
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remarks',
+      rules: [{ required: false, message: '备注不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
+</script>

+ 137 - 0
src/views/demo/components/PreviewImages.vue

@@ -0,0 +1,137 @@
+<!--
+# @Date: 2024-10-11 14:37:11
+# @Author: WANGKANG
+# @Blog:
+# @Email:
+# @Filepath: src\views\demo\components\PreviewImages.vue
+# @Description: PreviewImages.vue
+# Copyright 2024 WANGKANG, All Rights Reserved.
+-->
+
+<template>
+  <el-dialog
+    v-model="dialogVisible"
+    :title="'预览 - 共' + currentImageUrls.length + '张 当前第' + (imageIdx + 1) + '张'"
+    width="80%"
+    @open="handleOpen"
+    :before-close="handleClose"
+  >
+    <el-form :inline="true">
+      <el-form-item label="帧率">
+        <el-select v-model="imageFps" placeholder="选择帧率" style="width: 200px" @change="changeFps">
+          <el-option label="0" value="0"></el-option>
+          <el-option label="5" value="5"></el-option>
+          <el-option label="15" value="15"></el-option>
+          <el-option label="30" value="30"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="跳转至">
+        <el-input v-model="newImageIdx" type="number" style="width: 100px" />
+        <el-button type="primary" @click="confirmNewImageIdx" style="margin-left: 10px">确认</el-button>
+      </el-form-item>
+    </el-form>
+    <div class="image-dialog">
+      <el-image :src="currentImageUrls[imageIdx]" style="width: 40%"></el-image>
+    </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 >= currentImageUrls.length - 1">下一个</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ElMessageBox } from 'element-plus'
+import { ref, watch } from 'vue'
+
+const props = defineProps({
+  visible: {
+    type: Boolean, //接受的数据类型
+    default: false //接受默认数据
+  },
+  urls: {
+    type: Array,
+    default: () => []
+  }
+})
+
+watch(
+  () => props.visible,
+  val => {
+    dialogVisible.value = val
+  }
+)
+
+const handleOpen = () => {
+  currentImageUrls.value = props.urls
+  imageIdx.value = 0
+  imageFps.value = 0
+}
+
+const dialogVisible = ref(false)
+const imageFps = ref(0)
+const intervalChangeFps: any = ref()
+const imageIdx = ref(0)
+const currentImageUrls = ref<any[]>([])
+const newImageIdx = ref()
+
+const handleClose = (done: () => void) => {
+  console.log('handleClose')
+  if (intervalChangeFps.value) {
+    clearInterval(intervalChangeFps.value)
+  }
+  done()
+}
+
+const confirmNewImageIdx = () => {
+  const val = parseInt(newImageIdx.value)
+  if (val > 0 && val <= currentImageUrls.value.length) {
+    imageIdx.value = val - 1
+  } else {
+    ElMessageBox.alert('跳转索引有误,请检查!')
+  }
+}
+
+const changeFps = () => {
+  console.log('changeFps')
+  if (intervalChangeFps.value) {
+    clearInterval(intervalChangeFps.value)
+  }
+
+  if (imageFps.value == 0) {
+    return
+  }
+
+  intervalChangeFps.value = setInterval(() => {
+    next_picture()
+  }, 1000 / imageFps.value)
+}
+
+const next_picture = () => {
+  if (imageIdx.value < currentImageUrls.value.length - 1) {
+    imageIdx.value += 1
+  }
+}
+
+const pre_picture = () => {
+  if (imageIdx.value > 0) {
+    imageIdx.value -= 1
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.image-dialog {
+  display: flex;
+  justify-content: center;
+  .el-image {
+    margin-right: 20px;
+    margin-bottom: 20px;
+  }
+}
+.image-dialog-btn {
+  display: flex;
+  justify-content: center;
+  margin-top: 20px;
+}
+</style>

+ 171 - 0
src/views/demo/components/img-detect.vue

@@ -0,0 +1,171 @@
+<template>
+  <el-dialog class="modal-canvas" v-model="visible" title="标注" width="980px" height="700px">
+    <ImgMaker
+      ref="imgMaker"
+      v-if="visible"
+      :src="cover"
+      :area="area"
+      :width="width"
+      :height="height"
+      :class-def="classDef"
+      :json-data="jsonData"
+    ></ImgMaker>
+    <div style="margin-top: 10px; text-align: center">
+      标签
+      <el-button
+        v-for="(item, index) in classes"
+        :key="index"
+        type="primary"
+        @click="onClassChange(item)"
+        :disabled="classDef.label === item.label"
+        >{{ item.name }}</el-button
+      >
+    </div>
+    <div style="margin-top: 10px; text-align: center">
+      <!-- <el-space> -->
+      <el-button type="primary" @click="onClearLast">撤销最后一次的操作</el-button>
+      <el-button type="primary" @click="onClearAll">清空所有</el-button>
+      <el-button type="primary" @click="onCancel">取消</el-button>
+      <el-button type="primary" @click="onSubmit">保存</el-button>
+      <!-- </a-space> -->
+    </div>
+  </el-dialog>
+</template>
+<script lang="ts" setup>
+import { reactive, ref, toRefs, defineProps, watchEffect, watch } from 'vue'
+import ImgMaker from './img-maker.vue'
+const props = defineProps({
+  area: {
+    type: String,
+    default: ''
+  },
+  img: {
+    type: String,
+    default: ''
+  },
+  width: {
+    type: Number,
+    default: 1920
+  },
+  height: {
+    type: Number,
+    default: 1080
+  },
+  classes: {
+    type: Array
+  },
+  jsonData: {
+    type: Array
+  }
+})
+const emit = defineEmits(['success'])
+const imgMaker = ref()
+const state = reactive({
+  visible: false,
+  cover: '',
+  classDef: props.classes && props.classes.length > 0 ? props.classes[0] : { name: 'default', color: '#FF00FF', classValue: 0 }
+})
+
+watch(
+  () => props.classes,
+  value => {
+    state.classDef = value && value.length > 0 ? value[0] : { name: 'default', color: '#FF00FF', classValue: 0 }
+    // console.log(state.classDef)
+  }
+)
+
+const { visible, cover, classDef } = toRefs(state)
+watchEffect(() => {
+  state.cover = props.img
+})
+
+const onClassChange = item => {
+  // console.log('change class choice', item)
+  state.classDef = item
+}
+
+const onClearAll = () => {
+  imgMaker.value.clearAll()
+}
+const onClearLast = () => {
+  imgMaker.value.clearObjLast()
+}
+const onCancel = () => {
+  onClearAll()
+  state.visible = false
+  imgMaker.value.drawType = null
+}
+const onSubmit = () => {
+  let points = imgMaker.value.getData()
+  // console.log('points', points)
+
+  let emitData = {
+    data: '',
+    jsonData: points[0],
+    url: points[1]
+  }
+
+  for (let i = 2; i < points.length; i++) {
+    let label = points[i]['label']
+    // console.log(label)
+    emitData.data += label + ' '
+    let index = -1,
+      yMin = 10000000,
+      xMax = -1
+    for (let j = 0; j < points[i]['nodes'].length; j++) {
+      if (points[i]['nodes'][j].y < yMin) {
+        index = j
+        yMin = points[i]['nodes'][j].y
+        xMax = points[i]['nodes'][j].x
+      } else if (points[i]['nodes'][j].y === yMin && points[i]['nodes'][j].x > xMax) {
+        index = j
+        xMax = points[i]['nodes'][j].x
+      }
+    }
+
+    for (let j = 0; j < points[i]['nodes'].length; j++) {
+      let ptr = (j + index) % points[i]['nodes'].length
+      emitData.data += points[i]['nodes'][ptr].x + ' ' + points[i]['nodes'][ptr].y
+      if (j + 1 !== points[i]['nodes'].length) {
+        emitData.data += ' '
+      }
+    }
+
+    if (i + 1 !== points.length) {
+      emitData.data += ' \r\n'
+    }
+    // if ()
+    // datas += points[i][]
+  }
+
+  // if (points.length > 0) {
+  //   datas = points.map(point => {
+  //     if (point.br) {
+  //       let w = point.tr.x - point.tl.x
+  //       let h = point.bl.y - point.tl.y
+  //
+  //       const algoData = `${point.tl.x / 1920};${point.tl.y / 1080};${w / 1920};${h / 1080}`
+  //       // return `${point.tl.x};${point.tl.y};${point.tr.x};${point.tr.y};${point.br.x};${point.br.y};${point.bl.x};${point.bl.y}`
+  //       return algoData
+  //     } else {
+  //       console.log('all else', point)
+  //       let coors = ''
+  //       Object.keys(point).forEach(item => {
+  //         coors += `${point[item].x};${point[item].y};`
+  //       })
+  //       coors = coors.slice(0, -1)
+  //       return coors
+  //     }
+  //     // return `${point.tl.x};${point.tl.y};${point.tr.x};${point.tr.y}`
+  //   })
+  // }
+  // console.log('datas', datas)
+  // 归一化
+  // datas.push(points[0])
+
+  emit('success', emitData)
+  onClearAll()
+  state.visible = false
+}
+defineExpose({ visible, cover })
+</script>

+ 886 - 0
src/views/demo/components/img-maker.vue

@@ -0,0 +1,886 @@
+<template>
+  <div>
+    <div style="margin-bottom: 10px" v-show="!isPicOnly">
+      <el-button plain type="primary" class="shape-border" @click="drawTypeChange('rectangle')" :disabled="!state.dragMode">标注模式</el-button>
+      <el-button plain type="primary" class="shape-border" @click="enableDragMode" :disabled="state.dragMode">移动图片</el-button>
+
+      <el-button plain type="primary" style="margin-left: 30px" class="shape-border" @click="selectLastObject" :disabled="!!state.activeTarget"
+        >选择最后一个标注(w)</el-button
+      >
+      <el-button plain type="primary" class="shape-border" @click="rotate(8)" :disabled="!state.activeTarget"> 顺时针旋转(r) </el-button>
+      <el-button plain type="primary" class="shape-border" @click="rotate(-8)" :disabled="!state.activeTarget">逆时针旋转(e)</el-button>
+      <el-button plain type="primary" style="margin-left: 30px" class="shape-border" @click="changeZoom(1.1)">放大图片</el-button>
+      <el-button plain type="primary" class="shape-border" @click="changeZoom(0.9)">缩小图片</el-button>
+      <!-- <el-button plain type="primary" class="shape-border" @click="drawPolygon('polygon')">多边形</el-button> -->
+    </div>
+    <canvas :id="props.canvasId" :width="cWidth" :height="cHeight"></canvas>
+  </div>
+</template>
+<script lang="ts" setup>
+import { fabric } from 'fabric'
+import { reactive, watch, onMounted } from 'vue'
+import { ElMessage } from 'element-plus'
+// import { sortPoints } from '@/utils/fabric'
+
+const props = defineProps({
+  src: {
+    type: String,
+    default: ''
+  },
+  area: {
+    type: String,
+    default: ''
+  },
+  width: {
+    type: Number,
+    default: 1920
+  },
+  height: {
+    type: Number,
+    default: 1080
+  },
+  cWidth: {
+    type: Number,
+    default: 960
+  },
+  cHeight: {
+    type: Number,
+    default: 540
+  },
+  classDef: {
+    type: Object
+  },
+  jsonData: {
+    type: Array
+  },
+  isPicOnly: {
+    type: Boolean,
+    default: false
+  },
+  canvasId: {
+    type: String,
+    default: 'canvas'
+  }
+})
+const state = reactive({
+  loading: true,
+  radio: 1,
+  realRadioX: 0.5,
+  realRadioY: 0.5,
+  imgPoint: { x: 0, y: 0 },
+  realPoint: { x: 0, y: 0 },
+  canvas: {} as any,
+  mouseFrom: { x: 0, y: 0 } as canvasPoint,
+  mouseTo: { x: 0, y: 0 } as canvasPoint,
+  dragMode: false,
+  isDragging: false,
+  lastPosX: 0,
+  lastPosY: 0,
+  // drawType: 'rectangle' as string, //当前绘制图像的种类
+  drawType: null as any, //当前绘制图像的种类
+  drawWidth: 5, //笔触宽度
+  color: '#E34F51', //画笔颜色
+  drawingObject: null as any, //当前绘制对象
+  activeTarget: null as any, // 当前点击活跃对象
+  moveCount: 1, //绘制移动计数器
+  doDrawing: false as boolean, // 绘制状态
+  rectPath: '' as string, //矩形绘制路径
+  //polygon 相关参数
+  polygonMode: false as boolean,
+  pointArray: [] as canvasPoint[],
+  lineArray: [] as canvasPoint[],
+  activeShape: false as any,
+  activeLine: '' as any,
+  line: {} as canvasPoint
+})
+watch(
+  () => props.classDef,
+  value => {
+    // console.log(value)
+    state.color = value && value.color ? value.color : '#E34F51'
+  }
+)
+watch(
+  () => state.drawType,
+  value => {
+    // console.log(value)
+    state.canvas.selection = !value
+  }
+)
+watch(
+  () => props.width,
+  value => {
+    state.canvas.setWidth(value)
+  }
+)
+watch(
+  () => props.height,
+  value => {
+    state.canvas.setHeight(value)
+  }
+)
+
+const enableDragMode = () => {
+  state.dragMode = true
+  state.activeTarget = null
+}
+
+const changeZoom = multi => {
+  // 放大
+  state.radio = state.canvas.getZoom() * multi
+  state.canvas.setZoom(state.radio)
+  // state.canvas.setViewport(state.canvas.getCenter()); // 更新视口
+  state.canvas.renderAll()
+}
+
+const loadInit = () => {
+  drawTypeChange('rectangle')
+  state.color = props.classDef && props.classDef.color ? props.classDef.color : '#E34F51'
+  if (!props.isPicOnly) {
+    document.addEventListener('keydown', function (e) {
+      if (e.key === 'w' || e.key === 'W') {
+        selectLastObject()
+        return
+      }
+      if (state.activeTarget) {
+        if (e.key === 'r' || e.key === 'R') {
+          // 旋转矩形对象
+          state.activeTarget.rotate(state.activeTarget.angle + 5)
+          state.canvas.renderAll()
+        } else if (e.key === 'e' || e.key === 'E') {
+          // 旋转矩形对象
+          state.activeTarget.rotate(state.activeTarget.angle - 5)
+          state.canvas.renderAll()
+        }
+      }
+    })
+  }
+
+  if (props.src == '') {
+    return
+  }
+  state.loading = true
+  state.canvas = new fabric.Canvas(props.canvasId, {})
+  state.canvas.selectionColor = 'rgba(0,0,0,0.05)'
+  state.canvas.on('mouse:down', mousedown)
+  state.canvas.on('mouse:move', mousemove)
+  state.canvas.on('mouse:up', mouseup)
+
+  let imgElement = new Image()
+  imgElement.src = props.src
+  imgElement.onload = () => {
+    // console.log(imgElement.height, imgElement.width)
+    // 区域大小/图片原始大小 缩放比例
+    state.radio =
+      props.cWidth / imgElement.width > props.cHeight / imgElement.height ? props.cHeight / imgElement.height : props.cWidth / imgElement.width
+    // console.log('state.radio', state.radio)
+
+    // 屏幕分辨率/图片原始大小
+    state.realRadioX = props.width / imgElement.width
+    state.realRadioY = props.height / imgElement.height
+
+    state.imgPoint.x = Math.floor(imgElement.width / 2)
+    state.imgPoint.y = Math.floor(imgElement.height / 2)
+
+    state.realPoint.x = Math.floor(props.width / 2)
+    state.realPoint.y = Math.floor(props.height / 2)
+
+    let imgInstance = new fabric.Image(imgElement, {
+      selectable: false,
+      width: imgElement.width,
+      height: imgElement.height,
+      scaleX: state.radio,
+      scaleY: state.radio
+    })
+    state.canvas.add(imgInstance)
+    // console.log('props', props)
+    // console.log('state', state)
+    if (props.jsonData && props.jsonData.length > 0) {
+      for (let i = 0; i < props.jsonData.length; i++) {
+        let config = props.jsonData[i]
+        let subArr = config.subArr
+        const lW = imgInstance.width * state.radio
+        const lH = imgInstance.height * state.radio
+        let obj = new fabric.Path(
+          'M ' +
+            subArr[1] * lW +
+            ' ' +
+            subArr[2] * lH +
+            ' L ' +
+            subArr[3] * lW +
+            ' ' +
+            subArr[4] * lH +
+            ' L ' +
+            subArr[5] * lW +
+            ' ' +
+            subArr[6] * lH +
+            ' L ' +
+            subArr[7] * lW +
+            ' ' +
+            subArr[8] * lH +
+            ' z',
+          config
+        )
+        state.canvas.add(obj)
+      }
+      state.canvas.renderAll()
+    }
+    state.radio = state.canvas.getZoom()
+    state.loading = false
+    // console.log('objects', state.canvas.getObjects())
+    // console.log(props.isPicOnly)
+    // console.log(state.realRadioX)
+    // console.log(state.realRadioY)
+  }
+}
+// const drawImage = () => {
+//   if (props.area === '') {
+//     clearAll()
+//     return
+//   }
+//   // console.log(props.area)
+//   let points = props.area.split(',').map(item => {
+//     let areas = item.split(';')
+//     let data = areas.map((ars, index) => {
+//       let arp = 0
+//       let ar = Number(ars)
+//       if (index % 2 == 0) {
+//         let dx = Math.abs(state.realPoint.x > ar ? state.realPoint.x - ar : state.realPoint.x + ar) / state.realRadioX
+//         let rdx = Math.abs(state.imgPoint.x - dx)
+//         arp = rdx
+//       } else {
+//         let dy = Math.abs(state.realPoint.y > ar ? state.realPoint.y - ar : state.realPoint.y + ar) / state.realRadioY
+//         let rdy = Math.abs(state.imgPoint.y - dy)
+//         arp = rdy
+//       }
+//       return Number(arp) * state.radio
+//     })
+//     return data
+//   })
+//   points.forEach(point => {
+//     drawImageObj(point)
+//   })
+// }
+// const drawImageObj = data => {
+//   let path = 'M '
+//   // debugger
+//   let points = [] as any
+//   let len = data.length / 2
+//   for (let i = 0; i < len; i++) {
+//     let idx = i * 2
+//     points.push({ x: data[idx], y: data[idx + 1] })
+//     path += `${data[idx]} ${data[idx + 1]} L `
+//   }
+//   let canvasObject = null as any
+//   if (points[0]?.y === points[1]?.y && points[2]?.y === points[3]?.y && points[0]?.x === points[3]?.x && points[1]?.x - points[2]?.x) {
+//     path = path.replace(/L\s$/g, 'z')
+//     canvasObject = new fabric.Path(path, {
+//       left: data[0],
+//       top: data[1],
+//       stroke: state.color,
+//       selectable: false,
+//       strokeWidth: state.drawWidth,
+//       fill: 'rgba(255, 255, 255, 0)',
+//       hasControls: false
+//     })
+//   } else {
+//     canvasObject = new fabric.Polygon(points, {
+//       stroke: state.color,
+//       strokeWidth: state.drawWidth,
+//       fill: 'rgba(255, 255, 255, 0)',
+//       opacity: 1,
+//       hasBorders: false,
+//       hasControls: false,
+//       evented: false
+//     })
+//   }
+//   canvasObject['points'] = points
+//   state.canvas.add(canvasObject)
+// }
+
+const selectLastObject = () => {
+  let objects = state.canvas.getObjects()
+  state.activeTarget = objects[objects.length - 1]
+  state.activeTarget.set('hasRotatingPoint', true)
+  state.canvas.setActiveObject(state.activeTarget)
+  state.canvas.renderAll()
+}
+
+const rotate = val => {
+  if (!state.activeTarget) {
+    ElMessage.error('请先选择旋转对象!')
+  } else {
+    state.activeTarget.rotate(state.activeTarget.angle + val)
+    state.canvas.renderAll()
+  }
+}
+
+const drawTypeChange = e => {
+  state.dragMode = false
+  state.activeTarget = null
+  state.drawType = e
+  state.canvas.skipTargetFind = !!e
+  if (e == 'pen') {
+    // isDrawingMode为true 才可以自由绘画
+    state.canvas.isDrawingMode = true
+  } else {
+    state.canvas.isDrawingMode = false
+  }
+}
+// 鼠标按下时触发
+const mousedown = e => {
+  if (props.isPicOnly) {
+    return
+  }
+  if (state.dragMode) {
+    state.isDragging = true
+    state.lastPosX = e.e.clientX - state.canvas.getElement().offsetLeft
+    state.lastPosY = e.e.clientY - state.canvas.getElement().offsetTop
+    return
+  }
+  // console.log(state.canvas.getObjects()[1])
+  // console.log(e)
+  const target = e.target
+  // console.log(target)
+  if (target) {
+    if (target.type === 'rect') {
+      state.activeTarget = target
+      state.activeTarget.set('hasRotatingPoint', true)
+      state.canvas.setActiveObject(state.activeTarget)
+      return
+    } else {
+      state.activeTarget = null
+      // state.canvas.discardActiveObject()
+    }
+  }
+  // console.log(state.lastPosX, state.lastPosY)
+  // console.log(state.canvas.viewportTransform)
+  // 记录鼠标按下时的坐标
+  let xy = e.pointer || transformMouse(e.e.offsetX, e.e.offsetY)
+  // console.log(xy)
+  state.mouseFrom.x = (xy.x - state.canvas.viewportTransform[4]) / state.radio
+  state.mouseFrom.y = (xy.y - state.canvas.viewportTransform[5]) / state.radio
+  state.doDrawing = true
+  // console.log(state.drawType)
+  // 绘制多边形
+  if (state.drawType == 'polygon') {
+    state.canvas.skipTargetFind = false
+    try {
+      // 此段为判断是否闭合多边形,点击红点时闭合多边形
+      if (state.pointArray.length > 1) {
+        // e.target.id == this.pointArray[0].id 表示点击了初始红点
+        if (e.target && e.target.id == state.pointArray[0].id) {
+          generatePolygon()
+        }
+      }
+      //未点击红点则继续作画
+      if (state.polygonMode) {
+        addPoint(e)
+      }
+    } catch (error) {
+      console.log(error)
+    }
+  }
+}
+// 鼠标松开执行
+const mouseup = e => {
+  if (state.dragMode) {
+    state.isDragging = false
+    return
+  }
+
+  if (state.activeTarget) {
+    state.activeTarget.setCoords()
+    state.canvas.requestRenderAll()
+    return
+  }
+  let xy = e.pointer || transformMouse(e.e.offsetX, e.e.offsetY)
+  state.mouseTo.x = (xy.x - state.canvas.viewportTransform[4]) / state.radio
+  state.mouseTo.y = (xy.y - state.canvas.viewportTransform[5]) / state.radio
+  state.drawingObject = null
+  state.moveCount = 1
+  if (state.drawType != 'polygon' && state.drawType != 'line') {
+    state.doDrawing = false
+  }
+  // 设置只允许绘制一个
+  // let canvasObj = state.canvas.getObjects();
+  // if(canvasObj.length >2){
+  //   state.canvas.remove(canvasObj[1])
+  // }
+}
+//鼠标移动过程中已经完成了绘制
+const mousemove = e => {
+  if (state.dragMode) {
+    if (state.isDragging) {
+      const deltaMove = {
+        x: e.e.clientX - state.canvas.getElement().offsetLeft - state.lastPosX,
+        y: e.e.clientY - state.canvas.getElement().offsetTop - state.lastPosY
+      }
+      state.lastPosX = e.e.clientX - state.canvas.getElement().offsetLeft
+      state.lastPosY = e.e.clientY - state.canvas.getElement().offsetTop
+      state.canvas.relativePan(new fabric.Point(deltaMove.x, deltaMove.y))
+      // state.canvas.viewportTransform[4] += deltaMove.x
+      // state.canvas.viewportTransform[5] += deltaMove.y
+      // state.canvas.getObjects().forEach(obj => {
+      //   if (obj.type !== 'rect') {
+      //     return
+      //   }
+      //   obj.left = (obj.left - state.canvas.viewportTransform[4]) / state.radio;
+      //   obj.top = (obj.top - state.canvas.viewportTransform[5]) / state.radio;
+      //   obj.setCoords();
+      //   obj.setOptions({
+      //     selectable: true,
+      //     evented: true
+      //   });
+      // });
+      state.canvas.requestRenderAll()
+    }
+    return
+  }
+
+  if (state.activeTarget) {
+    // const pointer = state.canvas.getPointer(e.e)
+    // state.activeTarget.set({
+    //   left: pointer.x,
+    //   top: pointer.y
+    // });
+    state.canvas.requestRenderAll()
+    return
+  }
+  if (state.moveCount % 2 && !state.doDrawing) {
+    //减少绘制频率
+    return
+  }
+  state.moveCount++
+  let xy = e.pointer || transformMouse(e.e.offsetX, e.e.offsetY)
+  if (xy.y >= 0 && xy.x <= props.cWidth && xy.y >= 0 && xy.y <= props.cHeight) {
+    state.mouseTo.x = (xy.x - state.canvas.viewportTransform[4]) / state.radio
+    state.mouseTo.y = (xy.y - state.canvas.viewportTransform[5]) / state.radio
+    // 矩形
+    if (state.drawType == 'rectangle') {
+      if (state.mouseFrom.x < state.mouseTo.x && state.mouseFrom.y < state.mouseTo.y) {
+        drawing()
+      } else {
+        // clearAll();
+      }
+    }
+    if (state.drawType == 'polygon') {
+      if (state.activeLine && state.activeLine.class == 'line') {
+        let pointer = state.canvas.getPointer(e.e)
+        state.activeLine.set({ x2: pointer.x, y2: pointer.y })
+
+        let points = state.activeShape.get('points')
+        points[state.pointArray.length] = {
+          x: pointer.x,
+          y: pointer.y,
+          zIndex: 1
+        }
+        state.activeShape.set({
+          points: points
+        })
+        state.canvas.renderAll()
+      }
+      state.canvas.renderAll()
+    }
+  } else {
+    // clearAll();
+  }
+}
+// 绘制矩形
+const drawing = () => {
+  if (state.drawingObject) {
+    state.canvas.remove(state.drawingObject)
+  }
+  let canvasObject = null
+  let left = state.mouseFrom.x,
+    top = state.mouseFrom.y,
+    mouseFrom = state.mouseFrom,
+    mouseTo = state.mouseTo
+  let path =
+    'M ' +
+    mouseFrom.x +
+    ' ' +
+    mouseFrom.y +
+    ' L ' +
+    mouseTo.x +
+    ' ' +
+    mouseFrom.y +
+    ' L ' +
+    mouseTo.x +
+    ' ' +
+    mouseTo.y +
+    ' L ' +
+    mouseFrom.x +
+    ' ' +
+    mouseTo.y +
+    ' L ' +
+    mouseFrom.x +
+    ' ' +
+    mouseFrom.y +
+    ' z'
+  state.rectPath = path
+  canvasObject = new fabric.Rect({
+    left: left,
+    top: top,
+    width: Math.abs(mouseTo.x - left),
+    height: Math.abs(mouseTo.y - top),
+    stroke: state.color,
+    strokeWidth: state.drawWidth,
+    fill: 'rgba(255, 255, 255, 0)',
+    centeredRotation: true,
+    angle: 0,
+    label: props.classDef.label
+  })
+  // canvasObject = new fabric.Path(path, {
+  //   left: left,
+  //   top: top,
+  //   stroke: state.color,
+  //   selectable: false,
+  //   strokeWidth: state.drawWidth,
+  //   fill: 'rgba(255, 255, 255, 0)',
+  //   hasControls: false
+  // })
+  // console.log(canvasObject)
+  canvasObject.on('selected', event => {
+    // console.log("selected")
+    state.activeTarget = event.target
+  })
+  if (canvasObject) {
+    state.canvas.add(canvasObject)
+    state.drawingObject = canvasObject
+  }
+}
+// 绘制多边形开始,绘制多边形和其他图形不一样,需要单独处理
+// const drawPolygon = type => {
+//   state.drawType = type
+//   state.polygonMode = true
+//   //这里画的多边形,由顶点与线组成
+//   state.pointArray = [] // 顶点集合
+//   state.lineArray = [] //线集合
+//   state.canvas.isDrawingMode = false
+// }
+const addPoint = e => {
+  let random = Math.floor(Math.random() * 10000)
+  let id = new Date().getTime() + random
+  let circle = new fabric.Circle({
+    radius: 5,
+    fill: '#ffffff',
+    stroke: '#333333',
+    strokeWidth: 0.5,
+    left: (e.pointer.x || e.e.layerX) / state.canvas.getZoom(),
+    top: (e.pointer.y || e.e.layerY) / state.canvas.getZoom(),
+    selectable: false,
+    hasBorders: false,
+    hasControls: false,
+    originX: 'center',
+    originY: 'center',
+    id: id,
+    objectCaching: false
+  })
+  if (state.pointArray.length == 0) {
+    circle.set({
+      fill: '#00FFFF'
+    })
+  }
+  let points = [
+    (e.pointer.x || e.e.layerX) / state.canvas.getZoom(),
+    (e.pointer.y || e.e.layerY) / state.canvas.getZoom(),
+    (e.pointer.x || e.e.layerX) / state.canvas.getZoom(),
+    (e.pointer.y || e.e.layerY) / state.canvas.getZoom()
+  ]
+
+  state.line = new fabric.Line(points, {
+    strokeWidth: 2,
+    fill: '#999999',
+    stroke: '#999999',
+    class: 'line',
+    originX: 'center',
+    originY: 'center',
+    selectable: false,
+    hasBorders: false,
+    hasControls: false,
+    evented: false,
+
+    objectCaching: false
+  })
+  if (state.activeShape) {
+    let pos = state.canvas.getPointer(e.e)
+    let points = state.activeShape.get('points')
+    points.push({
+      x: pos.x,
+      y: pos.y
+    })
+    let polygon = new fabric.Polygon(points, {
+      stroke: '#333333',
+      strokeWidth: 1,
+      fill: '#cccccc',
+      opacity: 0.3,
+      selectable: false,
+      hasBorders: false,
+      hasControls: false,
+      evented: false,
+      objectCaching: false
+    })
+    state.canvas.remove(state.activeShape)
+    state.canvas.add(polygon)
+    state.activeShape = polygon
+    state.canvas.renderAll()
+  } else {
+    let polyPoint = [
+      {
+        x: (e.pointer.x || e.e.layerX) / state.canvas.getZoom(),
+        y: (e.pointer.y || e.e.layerY) / state.canvas.getZoom()
+      }
+    ]
+    let polygon = new fabric.Polygon(polyPoint, {
+      stroke: '#333333',
+      strokeWidth: 1,
+      fill: '#cccccc',
+      opacity: 0.3,
+      selectable: false,
+      hasBorders: false,
+      hasControls: false,
+      evented: false,
+      objectCaching: false
+    })
+    state.activeShape = polygon
+    state.canvas.add(polygon)
+  }
+  state.activeLine = state.line
+
+  state.pointArray.push(circle)
+  state.lineArray.push(state.line)
+  state.canvas.add(state.line)
+  state.canvas.add(circle)
+}
+// 绘制不规则
+const generatePolygon = () => {
+  // let points = clearPolygonLines()
+  // let polygon = new fabric.Polygon(sortPoints(points), {
+  //   stroke: state.color,
+  //   strokeWidth: state.drawWidth,
+  //   fill: 'rgba(255, 255, 255, 0)',
+  //   opacity: 1,
+  //   hasBorders: false,
+  //   hasControls: false,
+  //   evented: false
+  // })
+  // state.canvas.add(polygon)
+  let points = [{}]
+  // console.log('state.pointArray', state.pointArray)
+  for (let point in state.pointArray) {
+    point = state.pointArray[point]
+    // console.log(point.left, point.top)
+  }
+  state.pointArray.map(point => {
+    points.push({
+      x: point.left,
+      y: point.top
+    })
+    state.canvas.remove(point)
+  })
+  state.lineArray.map(line => {
+    state.canvas.remove(line)
+  })
+  state.canvas.remove(state.activeShape).remove(state.activeLine)
+  let polygon = new fabric.Polygon(points, {
+    stroke: state.color,
+    strokeWidth: state.drawWidth,
+    fill: 'rgba(255, 255, 255, 0)',
+    opacity: 1,
+    hasBorders: true,
+    hasControls: false
+  })
+  state.canvas.add(polygon)
+  resetPolygon()
+}
+// 坐标转换
+const transformMouse = (mouseX, mouseY) => {
+  return { x: mouseX / 1, y: mouseY / 1 }
+}
+// 重置不规则四边形
+const resetPolygon = () => {
+  state.activeLine = null
+  state.activeShape = null
+  state.polygonMode = false
+  state.doDrawing = false
+  state.drawType = null
+}
+// 清除绘制四边形的四个坐标点
+const clearPolygonLines = () => {
+  let points = [{}]
+  state.pointArray.forEach(point => {
+    points.push({
+      x: point.left,
+      y: point.top
+    })
+    state.canvas.remove(point)
+  })
+  state.lineArray.forEach(line => {
+    state.canvas.remove(line)
+  })
+  state.canvas.remove(state.activeShape).remove(state.activeLine)
+  return points
+}
+// 撤销最后一次的操作
+const clearObjLast = () => {
+  let canvasObjs = state.canvas.getObjects()
+  let len = canvasObjs.length
+  if (len > 1) {
+    state.canvas.remove(canvasObjs[len - 1])
+  }
+}
+// 全部清除
+const clearAll = () => {
+  state.canvas.getObjects().forEach((element, index) => {
+    if (index > 0) {
+      state.canvas.remove(element)
+    }
+  })
+  clearPolygonLines()
+  resetPolygon()
+  state.drawType = 'rectangle'
+}
+const getPoint = pi => {
+  return Math.floor(pi / state.radio)
+}
+const getRealPoint = poi => {
+  let dx = Math.abs(state.imgPoint.x > poi.x ? state.imgPoint.x - poi.x : state.imgPoint.x + poi.x) * state.realRadioX
+  let dy = Math.abs(state.imgPoint.y > poi.y ? state.imgPoint.y - poi.y : state.imgPoint.y + poi.y) * state.realRadioY
+  let rdx = Math.abs(state.realPoint.x - dx)
+  let rdy = Math.abs(state.realPoint.y - dy)
+  let minX = Math.min(Math.floor(rdx), props.width)
+  let minY = Math.min(Math.floor(rdy), props.height)
+  return { x: minX, y: minY }
+}
+// 生成真实分辨率图片的坐标点
+const getData = () => {
+  let datas = [] as any
+  let marks = ['tl', 'tr', 'br', 'bl']
+  // if (state.lineArray.length > 0 && state.lineArray.length < 4) {
+  //   clearPolygonLines()
+  // }
+  datas.push(JSON.stringify(state.canvas.toJSON(['label'])))
+  datas.push(state.canvas.toDataURL('image/png'))
+
+  state.canvas.getObjects().forEach((item, index) => {
+    // console.log(item, index)
+    if (index > 0) {
+      let aCoords = item.aCoords
+      let point = {
+        nodes: [],
+        angle: item.angle,
+        label: item.label
+      }
+      if (item.points) {
+        item.points.forEach((item, idx) => {
+          // if (aCoords[marks[idx]]) {
+          //   aCoords[marks[idx]].x = item.x
+          //   aCoords[marks[idx]].y = item.y
+          // }
+          // if (idx > 0) {
+          point[idx] = getRealPoint({
+            x: item?.x,
+            y: item?.y
+          })
+          // }
+        })
+      } else {
+        marks.forEach(mark => {
+          let poi = {
+            x: getPoint(aCoords[mark].x),
+            y: getPoint(aCoords[mark].y)
+          }
+          // console.log(poi)
+          poi = getRealPoint(poi)
+          // console.log(poi)
+          poi.x = poi.x / state.realRadioX / 1920
+          poi.y = poi.y / state.realRadioY / 1080
+          // console.log(poi)
+          point['nodes'].push(poi)
+          // point['nodes'].push(getRealPoint(poi))
+        })
+      }
+      // if (item.points && item.points.length === 2) {
+      //   delete point['br']
+      //   delete point['bl']
+      // }
+      datas.push(point)
+      // console.log(point)
+    }
+  })
+  // console.log()
+  return datas
+}
+// 获取归一化数据比例
+const normalization = () => {
+  return {
+    realRadioX: state.realRadioX,
+    realRadioY: state.realRadioY
+  }
+}
+defineExpose({ drawType: state.drawType, clearObjLast, clearAll, getData, normalization })
+onMounted(() => {
+  loadInit()
+})
+</script>
+
+<style lang="scss" scoped>
+canvas {
+  border: 1px dashed black;
+}
+.loading-box {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 960px;
+  height: 540px;
+  font-size: 14px;
+  color: #ea5413;
+}
+.draw-btn-group {
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+  width: 960px;
+  margin-top: 10px;
+  .active {
+    .draw-rect {
+      background: #ff00ff;
+      border-color: #ff00ff;
+    }
+  }
+  .shape-box {
+    width: 120px;
+    text-align: left;
+  }
+  .shape-border {
+    display: block;
+    width: 80px;
+    height: 30px;
+    margin-right: 30px;
+    font-size: 12px;
+    text-align: center;
+  }
+  .shape-border-ti {
+    transform: skewX(-45deg);
+  }
+  .draw-icon {
+    display: inline-block;
+    width: 80px;
+    height: 30px;
+  }
+  .draw-rect {
+    width: 80px;
+    border-color: #333333;
+    border-style: solid;
+    border-width: 1px;
+  }
+  .draw-line {
+    position: relative;
+    top: -14px;
+    border-bottom: 2px solid #00ffff;
+  }
+}
+</style>

+ 170 - 0
src/views/demo/data/amplify.vue

@@ -0,0 +1,170 @@
+<template>
+  <div class="amplify-container">
+    <div class="amplify-main">
+      <ProForm :items-options="items" :model="model"> </ProForm>
+      <el-button type="primary" style="margin-left: 50px" @click="showDataSelectionDialog()">选择批次</el-button>
+    </div>
+
+    <el-footer>
+      <el-button class="submit" type="primary" @click="handleSubmit">提交</el-button>
+    </el-footer>
+    <el-dialog v-model="dataDialogVisible" title="选择数据批次" style="width: 70vw">
+      <el-container>
+        <el-table :data="batchDataList" tooltip-effect="dark" style="width: 100%; margin-bottom: 30px" @selection-change="handleSelectionChange">
+          <el-table-column type="selection"> </el-table-column>
+          <el-table-column prop="batchNum" label="所有批次"> </el-table-column>
+          <el-table-column prop="batchSize" label="数量"> </el-table-column>
+        </el-table>
+
+        <el-container style="display: flex; flex-direction: column; align-items: center; justify-content: center; margin: 0 30px">
+          <el-button type="primary" icon="ArrowRightBold" :disabled="!canSelect" @click="clickSelectData"></el-button>
+          <el-button
+            type="primary"
+            style="margin: 10px 0 0"
+            icon="ArrowLeftBold"
+            :disabled="!canDeselect"
+            @click="clickDeselectData"
+          ></el-button>
+        </el-container>
+
+        <el-table :data="selectedBatchDataList" tooltip-effect="dark" style="width: 100%" @selection-change="handleDeselectionChange">
+          <el-table-column type="selection"> </el-table-column>
+          <el-table-column prop="batchNum" label="已选批次"> </el-table-column>
+          <el-table-column prop="batchSize" label="数量"> </el-table-column>
+        </el-table>
+      </el-container>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dataDialogVisible = false">取 消</el-button>
+          <el-button type="primary" @click="dataDialogVisible = false"> 确 定 </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup lang="tsx" name="amplify">
+import { reactive, ref, computed, onMounted } from 'vue'
+import ProForm from '@/components/ProForm/index.vue'
+import { batchListDataApi, amplifyApi } from '@/api/modules/demo/data'
+import { getDictsApi } from '@/api/modules/system/dictData'
+let model = {
+  taskName: 'ssss',
+  transfer: null
+}
+const dataDialogVisible = ref(false)
+let batchDataList = ref(reactive([] as any[]))
+let selectedBatchDataList = ref(reactive([] as any[]))
+let tempSelectedBatchDataList = ref(reactive([] as any[]))
+let tempDeselectedBatchDataList = ref(reactive([] as any[]))
+let parameList = ref(reactive([] as any[]))
+let queryBatchData = ref(reactive([] as any[]))
+onMounted(() => {
+  batchListDataApi().then(res => {
+    // batchDataList.value = reactive(res.data)
+    queryBatchData.value = reactive(res.data)
+  })
+  getDictsApi('expand_data_params').then(res => {
+    parameList.value = reactive(JSON.parse(res.data[0].remark))
+    parameList.value.forEach(item => {
+      items.push({
+        label: item.name,
+        prop: item.agName,
+        span: 12,
+        rules: [{ required: item.required, message: '不能为空' }],
+        compOptions: {
+          elTagName: 'input',
+          clearable: true,
+          placeholder: '请输入..'
+        }
+      })
+      model[`${item.agName}`] = item.defaultValue
+    })
+  })
+})
+const handleSelectionChange = data => {
+  tempSelectedBatchDataList.value = reactive(data)
+}
+let canSelect = computed(() => {
+  return tempSelectedBatchDataList.value.length > 0
+})
+const handleDeselectionChange = data => {
+  tempDeselectedBatchDataList.value = reactive(data)
+}
+const clickSelectData = () => {
+  let set = new Set()
+  for (let i = 0; i < tempSelectedBatchDataList.value.length; i++) {
+    set.add(tempSelectedBatchDataList.value[i].batchNum)
+    selectedBatchDataList.value.push(tempSelectedBatchDataList.value[i])
+  }
+  let newArray = [] as any[]
+  for (let i = 0; i < batchDataList.value.length; i++) {
+    if (!set.has(batchDataList.value[i].batchNum)) {
+      newArray.push(batchDataList.value[i])
+    }
+  }
+  batchDataList.value = reactive(newArray)
+}
+const canDeselect = computed(() => {
+  return tempDeselectedBatchDataList.value.length > 0
+})
+const clickDeselectData = () => {
+  let set = new Set()
+  for (let i = 0; i < tempDeselectedBatchDataList.value.length; i++) {
+    set.add(tempDeselectedBatchDataList.value[i].batchNum)
+    batchDataList.value.push(tempDeselectedBatchDataList.value[i])
+  }
+  let newArray = [] as any[]
+  for (let i = 0; i < selectedBatchDataList.value.length; i++) {
+    if (!set.has(selectedBatchDataList.value[i].batchNum)) {
+      newArray.push(selectedBatchDataList.value[i])
+    }
+  }
+  selectedBatchDataList.value = reactive(newArray)
+}
+const showDataSelectionDialog = () => {
+  // bIsTrainData.value = isTrainData
+  if (selectedBatchDataList.value.length === 0) {
+    batchDataList.value = queryBatchData.value
+  }
+  dataDialogVisible.value = true
+}
+const handleSubmit = () => {
+  let val = [] as any[]
+  for (let i = 0; i < selectedBatchDataList.value.length; i++) {
+    val.push(selectedBatchDataList.value[i].batchNum)
+  }
+  const batchNum = val.join()
+  const otherParams = [] as any[]
+
+  parameList.value.map(item => {
+    item.value = model[`${item.agName}`]
+  })
+  console.log('parameList.value', parameList.value)
+  const data = {
+    batchNum,
+    taskName: model.taskName,
+    otherParams: JSON.stringify(parameList.value)
+  }
+  console.log('data', data)
+  // amplifyApi(data).then(res => {
+  //   console.log(res)
+  // })
+}
+let items: ProForm.ItemsOptions[] = reactive([
+  {
+    label: '任务名称',
+    prop: 'taskName',
+    span: 14,
+    // labelWidth: '90px',
+    rules: [{ required: true, message: '请输入任务名称' }],
+    compOptions: {
+      elTagName: 'input',
+      clearable: true,
+      placeholder: '请输入用户名'
+    }
+  }
+])
+</script>
+<style scoped lang="scss">
+@import './index.scss';
+</style>

+ 29 - 0
src/views/demo/data/index.scss

@@ -0,0 +1,29 @@
+.amplify-container {
+  width: 100%;
+  height: 100%;
+  padding-top: 50px;
+
+  // position: relative;
+  .amplify-main {
+    width: 80%;
+    height: calc(100% - 50px);
+    margin: 0 auto;
+    overflow-y: scroll;
+  }
+  .el-footer {
+    position: absolute;
+    bottom: 0;
+    left: 50%;
+    height: 50px;
+    .submit {
+      right: 0;
+    }
+  }
+}
+::-webkit-scrollbar {
+  display: none;
+}
+.dialog-footer {
+  // margin-top: 30px;
+  margin: 0 auto;
+}

+ 346 - 63
src/views/demo/data/index.vue

@@ -1,11 +1,19 @@
 <template>
   <div class="table-box">
+    <el-image-viewer v-if="imgViewVisible" :url-list="['/api' + imageUrl]" @close="imgViewVisible = false" />
     <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listDataApi" :init-param="initParam">
+      <template #yuan="scope">
+        <el-image style="width: 200px; height: 200px" :src="'/api' + scope.row.url" @click="loadImg(scope.row.url)"></el-image>
+        <!--        <uploadImg :is-show-data="true" :disabled="true" :image-url="scope.row.url" @click="loadImg(scope.row.url)"/>-->
+        <!--        <el-image style="width: 100px" :src="'/api' + scope.row.url" @click="markImg(scope.row)" />-->
+      </template>
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
         <el-button type="primary" v-auth="['demo:data:add']" :icon="CirclePlus" @click="openDialog(1, '数据新增')"> 新增 </el-button>
         <el-button type="primary" v-auth="['demo:data:import']" :icon="Upload" plain @click="batchAdd"> 导入数据集 </el-button>
-        <el-button type="primary" v-auth="['demo:data:export']" :icon="Download" plain @click="downloadFile"> 导出 </el-button>
+        <el-button type="primary" v-auth="['demo:data:export']" :icon="Download" plain @click="downloadFile(scope.selectedListIds)"> 导出 </el-button>
+        <el-button type="primary" v-auth="['system:user:add']" :icon="CirclePlus" @click="dataAmplify()"> 数据增广 </el-button>
+
         <el-button
           type="danger"
           v-auth="['demo:data:remove']"
@@ -19,6 +27,8 @@
       </template>
       <!-- 表格操作 -->
       <template #operation="scope">
+        <!-- <el-button type="primary" link :icon="EditPen" v-auth="['demo:data:edit']" @click="openDialog(2, '数据标注', scope.row)"> 标注 </el-button> -->
+        <el-button type="primary" link :icon="EditPen" v-auth="['demo:data:edit']" @click="markImg(scope.row)"> 标注 </el-button>
         <el-button type="primary" link :icon="EditPen" v-auth="['demo:data:edit']" @click="openDialog(2, '数据编辑', scope.row)"> 编辑 </el-button>
         <el-button type="primary" link :icon="View" v-auth="['demo:data:query']" @click="openDialog(3, '数据查看', scope.row)"> 查看 </el-button>
         <el-button type="primary" link :icon="Delete" v-auth="['demo:data:remove']" @click="deleteData(scope.row)"> 删除 </el-button>
@@ -27,22 +37,35 @@
 
     <FormDialog ref="formDialogRef" />
     <ImportPicDataset ref="dialogRef" />
+    <ImgDetect
+      ref="imgDetect"
+      :img="cover"
+      :area="area"
+      :width="width"
+      :height="height"
+      @success="handleImgSuccess"
+      :classes="classes"
+      :json-data="jsonData"
+    >
+    </ImgDetect>
   </div>
 </template>
 
 <script setup lang="tsx" name="Data">
-import { ref, reactive } from 'vue'
+import { ref, reactive, toRefs, onMounted } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useDownload } from '@/hooks/useDownload'
-import { ElMessageBox } from 'element-plus'
+import { ElMessage, ElMessageBox } from 'element-plus'
 import ProTable from '@/components/ProTable/index.vue'
-// import ImportExcel from '@/components/ImportExcel/index.vue'
 import FormDialog from '@/components/FormDialog/index.vue'
 import ImportPicDataset from '@/components/ImportPicDataset/index.vue'
-// import uploadImgs from '@/components/Upload/Imgs.vue'
-// import uploadImg from '@/components/Upload/Img.vue'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 import { Delete, EditPen, Download, Upload, View, CirclePlus } from '@element-plus/icons-vue'
+// import { fabric } from 'fabric'
+// import { useDrawArea } from '@/utils/fabric'
+import { getDictsApi } from '@/api/modules/system/dictData'
+import ImgDetect from '../components/img-detect.vue'
+import uploadImg from '@/components/Upload/Img.vue'
 import {
   listDataApi,
   delDataApi,
@@ -53,9 +76,57 @@ import {
   exportDataApi,
   getDataApi
 } from '@/api/modules/demo/data'
+import { listDataApi as listDictDataApi } from '@/api/modules/system/dictData'
+import { uploadPure } from '@/api/modules/upload'
+import http from '@/api'
+import { useRouter } from 'vue-router'
+
+onMounted(() => {
+  state.cacheData.url = 'http://localhost:9090/profile/upload/2024/08/08/144745610/test.png'
+  // handleImgSuccess({
+  //   data: 'test change data',
+  //   jsonData: 'jsonData12345 test change',
+  //   url: 'testurl'
+  // })
+})
+
+const imageUrl = ref('')
+const imgViewVisible = ref(false)
+const loadImg = url => {
+  imageUrl.value = url
+  imgViewVisible.value = true
+}
+
+const imgDetect = ref()
+const state = reactive({
+  area: '' as string,
+  width: 1920 as number,
+  height: 1080 as number,
+  cover: '',
+  classes: [],
+  //   [
+  //   {
+  //     name: '飞机',
+  //     color: '#ea5413',
+  //     label: 'plane'
+  //   },
+  //   {
+  //     name: '汽车',
+  //     color: '#ff00ff',
+  //     label: 'car'
+  //   }
+  // ],
+  jsonData: [],
+  cacheData: {}
+  // '{"version":"5.3.0","objects":[{"type":"image","version":"5.3.0","originX":"left","originY":"top","left":0,"top":0,"width":918,"height":789,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":0,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":0.68,"scaleY":0.68,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"cropX":0,"cropY":0,"src":"http://localhost:8848/api/profile/upload/2024/08/08/144745610/3-3.jpg","crossOrigin":null,"filters":[]},{"type":"rect","version":"5.3.0","originX":"left","originY":"top","left":181.38,"top":251.38,"width":244,"height":162,"fill":"rgba(255, 255, 255, 0)","stroke":"#E34F51","strokeWidth":5,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":-25,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"rx":0,"ry":0}]}'
+})
+const { area, width, height, cover, classes, jsonData } = toRefs(state)
 
 // ProTable 实例
 const proTable = ref<ProTableInstance>()
+// const getImageUrl = name => {
+//   return new URL(name, import.meta.url).href
+// }
 
 const initParam = reactive({ type: 1 })
 // 删除数据管理信息
@@ -64,6 +135,205 @@ const deleteData = async (params: any) => {
   proTable.value?.getTableList()
 }
 
+// 标注图片
+const markImg = data => {
+  console.log('data is', data)
+  state.cacheData = data
+  state.jsonData = []
+  if (!data.url || data.url.length === 0) {
+    ElMessage.warning('缺失图像,暂时不能进行区域添加!')
+    return
+  }
+
+  listDictDataApi({
+    pageNum: 1,
+    pageSize: 10,
+    dictType: 'class_definition'
+  })
+    .then(res => {
+      // console.log(res)
+      state.classes = []
+      for (let i = 0; i < res.data.list.length; i++) {
+        state.classes.push({
+          name: res.data.list[i].dictLabel,
+          color: res.data.list[i].cssClass,
+          label: res.data.list[i].dictValue
+        })
+      }
+
+      if (state.cacheData.labelurl && state.cacheData.labelurl.length > 0) {
+        console.log('get label jsonData', state.cacheData.labelurl)
+        http
+          .get<any>(state.cacheData.labelurl)
+          .then(res => {
+            state.jsonData = []
+            console.log(res)
+            let arr = res.replace('\r', '').split('\n')
+            console.log(arr)
+            for (let i = 0; i < arr.length; i++) {
+              let subArr = arr[i].split(' ')
+              // console.log(subArr)
+              let cssVal = '#000000'
+              let label = '-1'
+              for (let j = 0; j < state.classes.length; j++) {
+                if (state.classes[j].label === subArr[0]) {
+                  cssVal = state.classes[j].color
+                  label = state.classes[j].label
+                  break
+                }
+              }
+              state.jsonData.push({
+                subArr: subArr,
+                pathString:
+                  'M ' +
+                  subArr[1] * 1920 +
+                  ' ' +
+                  subArr[2] * 1080 +
+                  ' L ' +
+                  subArr[3] * 1920 +
+                  ' ' +
+                  subArr[4] * 1080 +
+                  ' L ' +
+                  subArr[5] * 1920 +
+                  ' ' +
+                  subArr[6] * 1080 +
+                  ' L ' +
+                  subArr[7] * 1920 +
+                  ' ' +
+                  subArr[8] * 1080 +
+                  ' z',
+                // left: 0,
+                // top: 0,
+                fill: '',
+                stroke: cssVal,
+                strokeWidth: 5,
+                label: label
+              })
+            }
+            // console.log(state.jsonData)
+            imgDetect.value.visible = true
+          })
+          .catch(err => {
+            console.log(err)
+          })
+      } else {
+        imgDetect.value.visible = true
+      }
+    })
+    .catch(err => {
+      console.log(err)
+    })
+
+  state.cover = '/api' + data.url
+
+  // area 代表后端的传来的标注数据
+  // console.log(state.cover)
+
+  // const area = []
+  // if (state.cover != '') {
+  //   if (area.length != 0) {
+  //     handleImgSuccess(area)
+  //   }
+  //
+  //   console.log("true???")
+  // } else {
+  //   ElMessage.warning('缺失图像,暂时不能进行区域添加!')
+  // }
+}
+// const getList = () => {
+//   useDrawArea({
+//     src: state.cover,
+//     width: state.width,
+//     height: state.height,
+//     area: state.area
+//   })
+//     .then(url => {
+//       state.cover = url as string
+//     })
+//     .catch(error => {
+//       console.log(error)
+//     })
+// }
+const handleImgSuccess = data => {
+  // console.log(data)
+  state.jsonData = data['jsonData']
+  state.cover = data['url']
+
+  state.cacheData.labelurl = data['data']
+
+  state.cacheData.increment = 'NONE'
+
+  let arr = state.cacheData.url.split('upload')
+  // console.log(arr)
+  let filenames = arr[arr.length - 1].split('.')
+  filenames[filenames.length - 1] = 'txt'
+  // console.log(filenames.join('.'))
+  let filename = filenames.join('.')
+  filename = filename.replace(/\\/g, '/')
+  if (filename.startsWith('/')) {
+    filename = filename.substring(1)
+  }
+  // console.log(filename)
+
+  labelFile(data['data'], filename).then(res => {
+    // console.log(res)
+    if (res.code === 200) {
+      state.cacheData.labelurl = res.data.url
+      updateDataApi(state.cacheData)
+        .then(res => {
+          // console.log(res)
+          if (res.data) {
+            ElMessage.success('操作成功')
+          }
+        })
+        .catch(err => {
+          console.log(err)
+        })
+    }
+  })
+
+  // data.forEach(item => {
+  //   if (item.startsWith('{')) {
+  //     return
+  //   }
+  //   const area = item.split(';')
+  //   // try=tly blx=tlx brx=trx bry=bly
+  //   // mark: 当前用的应该是左上角定点位置和长宽,应该替换为存储点
+  //   const tlx = Math.round(Number(area[0]) * 1920)
+  //   const tly = Math.round(Number(area[1]) * 1080)
+  //   const trx = tlx + Math.round(Number(area[2]) * 1920)
+  //   const bly = tly + Math.round(Number(area[3]) * 1080)
+  //   state.area += `${tlx};${tly};${trx};${tly};${trx};${bly};${tlx};${bly},`
+  // })
+  // // console.log('state.area', state.area)
+  // state.area.slice(0, -1)
+  // getList()
+}
+
+// 创建并提交标注txt文件
+const labelFile = (data, filename) => {
+  // 创建Blob对象
+  const blob = new Blob([data], { type: 'text/plain' })
+
+  // 创建表单数据并添加文件
+  const formData = new FormData()
+
+  formData.append('file', blob, filename)
+
+  return uploadPure(formData)
+}
+
+const labeledTypeData = [
+  {
+    label: '是',
+    value: true
+  },
+  {
+    label: '否',
+    value: false
+  }
+]
+
 // 批量删除数据管理信息
 const batchDelete = async (ids: string[]) => {
   await useHandleData(delDataApi, ids, '删除所选数据信息')
@@ -72,7 +342,8 @@ const batchDelete = async (ids: string[]) => {
 }
 
 // 导出数据管理列表
-const downloadFile = async () => {
+const downloadFile = async (ids: string[]) => {
+  proTable.value.searchParam['selectedIds'] = ids
   ElMessageBox.confirm('确认导出数据管理数据?', '温馨提示', { type: 'warning' }).then(() =>
     useDownload(exportDataApi, '数据管理列表', proTable.value?.searchParam)
   )
@@ -90,6 +361,11 @@ const batchAdd = () => {
   dialogRef.value?.acceptParams(params)
 }
 
+const router = useRouter()
+const dataAmplify = () => {
+  router.push(`/data/amplify`)
+}
+
 const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
 // 打开弹框的功能
 const openDialog = async (type: number, title: string, row?: any) => {
@@ -114,26 +390,18 @@ const openDialog = async (type: number, title: string, row?: any) => {
 // 表格配置项
 const columns = reactive<ColumnProps<any>[]>([
   { type: 'selection', fixed: 'left', width: 70 },
-
+  { prop: 'yuan', label: '原图', width: 200 },
   {
-    prop: 'name',
-    label: '名称',
-    search: {
-      el: 'input'
-    },
-    width: 120
-  },
-  {
-    prop: 'dataType',
-    label: '数据类型',
+    prop: 'batchNum',
+    label: '批次号',
     search: {
       el: 'input'
     },
     width: 120
   },
   {
-    prop: 'fileType',
-    label: '文件类型',
+    prop: 'name',
+    label: '名称',
     search: {
       el: 'input'
     },
@@ -156,24 +424,24 @@ const columns = reactive<ColumnProps<any>[]>([
     width: 120
   },
   {
-    prop: 'batchNum',
-    label: '批次号',
+    prop: 'scene',
+    label: '场景',
     search: {
       el: 'input'
     },
     width: 120
   },
   {
-    prop: 'scene',
-    label: '场景',
+    prop: 'dataSource',
+    label: '数据源',
     search: {
       el: 'input'
     },
     width: 120
   },
   {
-    prop: 'dataSource',
-    label: '数据源',
+    prop: 'gatherSpot',
+    label: '采集地点',
     search: {
       el: 'input'
     },
@@ -189,21 +457,31 @@ const columns = reactive<ColumnProps<any>[]>([
     width: 120
   },
   {
-    prop: 'gatherSpot',
-    label: '采集地点',
-    search: {
-      el: 'input'
-    },
-    width: 120
-  },
-  {
-    prop: 'increment',
-    label: '扩增方式',
+    prop: 'dataType',
+    label: '数据类型',
+    enum: () => getDictsApi('data_type'),
     search: {
-      el: 'input'
+      el: 'tree-select'
     },
+    fieldNames: { label: 'dictLabel', value: 'dictValue' },
     width: 120
   },
+  // {
+  //   prop: 'fileType',
+  //   label: '文件类型',
+  //   search: {
+  //     el: 'input'
+  //   },
+  //   width: 120
+  // },
+  // {
+  //   prop: 'increment',
+  //   label: '扩增方式',
+  //   search: {
+  //     el: 'input'
+  //   },
+  //   width: 120
+  // },
   {
     prop: 'labeled',
     label: '是否标注',
@@ -219,35 +497,27 @@ let formItems: ProForm.ItemsOptions[] = []
 const setFormItems = () => {
   formItems = [
     {
-      label: '图',
+      label: '图',
       prop: 'url',
       compOptions: {
         elTagName: 'img-upload',
-        placeholder: '请选择上传图'
+        placeholder: '请选择上传图'
       }
     },
     {
-      label: '名称',
-      prop: 'name',
-      rules: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
+      label: '批次号',
+      prop: 'batchNum',
+      rules: [{ required: true, message: '批次号不能为空', trigger: 'blur' }],
       compOptions: {
-        placeholder: '请输入名称'
-      }
-    },
-    {
-      label: '数据类型',
-      prop: 'dataType',
-      rules: [{ required: true, message: '数据类型不能为空', trigger: 'blur' }],
-      compOptions: {
-        placeholder: '请输入数据类型'
+        placeholder: '请输入批次号'
       }
     },
     {
-      label: '文件类型',
-      prop: 'fileType',
-      rules: [{ required: true, message: '文件类型不能为空', trigger: 'blur' }],
+      label: '名称',
+      prop: 'name',
+      rules: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
       compOptions: {
-        placeholder: '请输入文件类型'
+        placeholder: '请输入名称'
       }
     },
     {
@@ -283,6 +553,14 @@ const setFormItems = () => {
         placeholder: '请输入数据源'
       }
     },
+    {
+      label: '采集地点',
+      prop: 'gatherSpot',
+      rules: [{ required: true, message: '采集地点不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入采集地点'
+      }
+    },
     {
       label: '采集时间',
       prop: 'gatherTime',
@@ -295,19 +573,24 @@ const setFormItems = () => {
       }
     },
     {
-      label: '采集地点',
-      prop: 'gatherSpot',
-      rules: [{ required: true, message: '采集地点不能为空', trigger: 'blur' }],
+      label: '数据类型',
+      prop: 'dataType',
+      rules: [{ required: true, message: '数据类型不能为空', trigger: 'change' }],
       compOptions: {
-        placeholder: '请输入采集地点'
+        elTagName: 'select',
+        labelKey: 'dictLabel',
+        valueKey: 'dictValue',
+        enum: () => getDictsApi('data_type'),
+        placeholder: '请选择数据类型'
       }
     },
     {
-      label: '扩增方式',
-      prop: 'increment',
-      rules: [{ required: true, message: '扩增方式不能为空', trigger: 'blur' }],
+      label: '是否标注',
+      prop: 'labeled',
+      rules: [{ required: true, message: '请选择是否标注' }],
       compOptions: {
-        placeholder: '请输入扩增方式'
+        elTagName: 'radio-group',
+        enum: labeledTypeData
       }
     }
   ]

+ 525 - 0
src/views/demo/toInfrared/index.vue

@@ -0,0 +1,525 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listToInfraredApi">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['demo:toInfrared:add']" icon="CirclePlus" @click="openDialog(1, '可见光转红外新增')"> 新增 </el-button>
+        <!-- <el-button type="primary" v-auth="['demo:toInfrared:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button>
+        <el-button type="primary" v-auth="['demo:toInfrared:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button> -->
+        <el-button
+          type="danger"
+          v-auth="['demo:toInfrared:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button type="primary" link icon="View" v-if="scope.row.algorithmModelId != null" @click="openModelDialog(scope.row)">
+          <!--@click="openStartDialog(scope.row)"  -->
+          详情
+        </el-button>
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          v-auth="['demo:ToInfrared:start']"
+          v-if="scope.row.status == '0' || scope.row.status == '3' || scope.row.status == '4'"
+          @click="startToInfrared(scope.row)"
+        >
+          <!--@click="openStartDialog(scope.row)"  -->
+          开始
+        </el-button>
+        <el-popconfirm title="确定终止此任务吗?" @confirm="stopToInfrared(scope.row)" v-if="scope.row.status == '1'">
+          <template #reference>
+            <el-button type="primary" link icon="Delete"> 终止</el-button>
+          </template>
+        </el-popconfirm>
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          v-auth="['demo:DataSeq:query']"
+          v-if="scope.row.status == '2' && scope.row.type == AlgorithmType2['预测/推理']"
+          @click="preview(scope.row)"
+        >
+          预览
+        </el-button>
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          v-auth="['demo:ToInfrared:download']"
+          v-if="scope.row.status == '2'"
+          @click="dowloadToInfrared(scope.row)"
+        >
+          下载
+        </el-button>
+        <!-- <el-button type="primary" link icon="View" v-auth="['demo:toInfrared:query']" @click="openDialog(3, '查看', scope.row)"> 查看 </el-button> -->
+        <!-- <el-button type="primary" link icon="EditPen"v-auth="['demo:toInfrared:edit']" @click="openDialog(2, '编辑', scope.row)"> 编辑 </el-button> -->
+        <el-button
+          type="primary"
+          link
+          icon="Delete"
+          v-auth="['demo:toInfrared:remove']"
+          @click="deleteToInfrared(scope.row)"
+          :disabled="scope.row.status == '1'"
+        >
+          删除
+        </el-button>
+      </template>
+    </ProTable>
+    <FormDialog ref="formDialogRef" />
+    <ImportExcel ref="dialogRef" />
+    <PreviewImages :visible="dialogVisible" :urls="imageUrls" @close="dialogVisible = false" />
+  </div>
+</template>
+
+<script setup lang="tsx" name="ToInfrared">
+import { ref, reactive, onMounted } from 'vue'
+import { useHandleData } from '@/hooks/useHandleData'
+import { useDownload } from '@/hooks/useDownload'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import ProTable from '@/components/ProTable/index.vue'
+import ImportExcel from '@/components/ImportExcel/index.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
+import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
+import {
+  listToInfraredApi,
+  delToInfraredApi,
+  addToInfraredApi,
+  updateToInfraredApi,
+  importTemplateApi,
+  importToInfraredDataApi,
+  exportToInfraredApi,
+  startToInfraredApi,
+  stopToInfraredApi,
+  dowloadToInfraredApi,
+  getToInfraredApi
+} from '@/api/modules/demo/toInfrared'
+
+import { getImagesApi, listDataSeqApi } from '@/api/modules/demo/DataSeq'
+
+import { enumAlgorithmModelTrackApi } from '@/api/modules/demo/AlgorithmModelTrack'
+import { getAlgorithmModelTrackApi } from '@/api/modules/demo/AlgorithmModelTrack'
+import { enumAlgorithmConfigTrackApi } from '@/api/modules/demo/AlgorithmConfigTrack'
+import statusEnums from '@/utils/status'
+import { AlgorithmType, SubSystem, SubSystem__, enumsAlgorithmType, enumsSubSystem, AlgorithmType2 } from '@/views/demo/utils'
+import PreviewImages from '@/views/demo/components/PreviewImages.vue'
+import { Row } from 'element-plus/es/components/table-v2/src/components'
+
+const dialogVisible = ref(false)
+const imageUrls = ref([])
+const preview = async row => {
+  console.log('showImages:', row.inputOssId)
+  const data: any = await getImagesApi(row.inputOssId, '_to_infrared', false, 'predict')
+  imageUrls.value = data.data
+  dialogVisible.value = true
+}
+
+const openModelDialog = async row => {
+  const algorithmModelId = row.algorithmModelId
+  const result: any = await getAlgorithmModelTrackApi(algorithmModelId)
+
+  // console.log(result.data)
+  setItemsOptionsModel()
+  const params = {
+    title: '模型',
+    width: 580,
+    isEdit: false,
+    itemsOptions: itemsOptions,
+    model: result.data,
+    api: updateToInfraredApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+const startToInfrared = async (params: any) => {
+  const res: any = await startToInfraredApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('任务已开始,请等待完成!')
+  } else {
+    ElMessage.error('任务开始失败,请检查!')
+  }
+  proTable.value?.getTableList()
+}
+
+const stopToInfrared = async (params: any) => {
+  const res: any = await stopToInfraredApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('终止任务成功!')
+  } else {
+    ElMessage.error('终止任务失败,请检查!')
+  }
+  proTable.value?.getTableList()
+}
+const dowloadToInfrared = async (params: any) => {
+  if (params.type == AlgorithmType2['训练']) {
+    await useDownload(dowloadToInfraredApi, params.name, params.id, true, '.pt')
+  } else if (params.type == AlgorithmType2['预测/推理']) {
+    await useDownload(dowloadToInfraredApi, params.name, params.id, true, '.zip')
+  }
+}
+
+const openStartDialog = async (row: any) => {
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getToInfraredApi(row?.id || null)
+  }
+  // 重置表单
+  setItemsOptions2()
+  const params = {
+    title: '开始任务',
+    width: 580,
+    isEdit: true,
+    itemsOptions: itemsOptions,
+    model: res.data,
+    api: startToInfraredApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除可见光转红外信息
+const deleteToInfrared = async (params: any) => {
+  await useHandleData(delToInfraredApi, params.id, '删除【' + params.id + '】可见光转红外')
+  proTable.value?.getTableList()
+}
+
+// 批量删除可见光转红外信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delToInfraredApi, ids, '删除所选可见光转红外信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出可见光转红外列表
+const downloadFile = async () => {
+  ElMessageBox.confirm('确认导出可见光转红外数据?', '温馨提示', { type: 'warning' }).then(() =>
+    useDownload(exportToInfraredApi, '可见光转红外列表', proTable.value?.searchParam)
+  )
+}
+
+// 批量添加可见光转红外
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+const batchAdd = () => {
+  const params = {
+    title: '可见光转红外',
+    tempApi: importTemplateApi,
+    importApi: importToInfraredDataApi,
+    getTableList: proTable.value?.getTableList
+  }
+  dialogRef.value?.acceptParams(params)
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getToInfraredApi(row?.id || null)
+  }
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : res.data,
+    api: type == 1 ? addToInfraredApi : updateToInfraredApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+// 表格配置项
+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: 'status',
+    label: '任务状态',
+    search: {
+      el: 'select'
+    },
+    tag: true,
+    enum: statusEnums,
+    width: 150
+  },
+  {
+    prop: 'type',
+    label: '类型',
+    tag: true,
+    enum: enumsAlgorithmType,
+    width: 120
+  },
+  {
+    prop: 'subsystem',
+    label: '分系统',
+    tag: true,
+    enum: enumsSubSystem,
+    width: 200
+  },
+  {
+    prop: 'algorithmName',
+    label: '算法名称',
+    width: 200
+  },
+  {
+    prop: 'modelName',
+    label: '模型名称',
+    width: 200
+  },
+  // {
+  //   prop: 'algorithmModelId',
+  //   label: '模型',
+  //   search: {
+  //     el: 'input'
+  //   },
+  //   width: 150
+  // },
+  {
+    prop: 'startTime',
+    label: '开始时间',
+    width: 180
+  },
+  {
+    prop: 'endTime',
+    label: '结束时间',
+    width: 180
+  },
+  {
+    prop: 'costSecond',
+    label: '耗时',
+    width: 120
+  },
+  {
+    prop: 'log',
+    label: '日志',
+    width: 120
+  },
+  {
+    prop: 'outputPath',
+    label: '输出路径',
+    width: 120
+  },
+  {
+    prop: 'remarks',
+    label: '备注',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+])
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '任务名称',
+      prop: 'name',
+      rules: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入任务名称'
+      }
+    },
+    {
+      label: '选择数据集',
+      prop: 'inputOssId',
+      rules: [{ required: false, message: '数据集不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'select',
+        placeholder: '请选择或者上传数据集',
+        enum: getImageDataList,
+        clearable: true
+      }
+    },
+    {
+      label: '上传数据集',
+      prop: 'inputOssId',
+      rules: [{ required: false, message: '数据集不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'file-upload',
+        fileSize: 4096,
+        fileType: ['zip'],
+        placeholder: '请上传数据集'
+      }
+    },
+    {
+      label: '选择模型',
+      prop: 'algorithmModelId',
+      rules: [{ required: true, message: '模型不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'select',
+        placeholder: '请选择模型',
+        enum: enumsAlgorithmModelTrack
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remarks',
+      rules: [],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
+
+const getImageDataList = ref<any>([])
+onMounted(async () => {
+  const qyery = {
+    subsystem: SubSystem__['可见光转红外'],
+    pageNum: 1,
+    pageSize: 25
+  }
+  const result: any = await listDataSeqApi(qyery)
+  const data = result['data']['list']
+  for (const item of data) {
+    getImageDataList.value.push({
+      value: item['inputOssId'],
+      label: item['name']
+    })
+  }
+})
+
+const enumsAlgorithmModelTrack = ref<any>([])
+
+onMounted(async () => {
+  const result: any = await enumAlgorithmModelTrackApi()
+  // console.log(result.data);
+  enumsAlgorithmModelTrack.value = []
+  for (const item of result.data) {
+    if (SubSystem[item['subsystem']] === '可见光转红外') {
+      item['label'] = item['label'] + '-' + SubSystem[item['subsystem']] + '-' + AlgorithmType[item['type']] + '-' + item['algorithmName']
+      enumsAlgorithmModelTrack.value.push(item)
+    }
+  }
+})
+
+const setItemsOptions2 = () => {
+  itemsOptions = [
+    {
+      label: '任务名称',
+      prop: 'name',
+      rules: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        disabled: true,
+        placeholder: '请输入任务名称'
+      }
+    },
+    {
+      label: '选择模型',
+      prop: 'algorithmModel',
+      rules: [{ required: true, message: '模型不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'select',
+        placeholder: '请选择模型',
+        enum: enumsAlgorithmModelTrack
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remarks',
+      rules: [],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
+
+const enumsAlgorithmConfigTrack = ref<any>([])
+onMounted(async () => {
+  const result = await enumAlgorithmConfigTrackApi()
+  enumsAlgorithmConfigTrack.value = result['data']
+  for (const item of enumsAlgorithmConfigTrack.value) {
+    item['label'] = item['label'] + '-' + SubSystem[item['subsystem']] + '-' + AlgorithmType[item['type']]
+  }
+  return result['data']
+})
+
+const setItemsOptionsModel = () => {
+  itemsOptions = [
+    {
+      label: '算法ID',
+      prop: 'algorithmId',
+      rules: [{ required: true, message: '算法不能为空', trigger: 'blur' }],
+      compOptions: {
+        disabled: true,
+        placeholder: '请输入算法'
+      }
+    },
+    {
+      label: '算法类型',
+      prop: 'algorithmType',
+      rules: [{ required: true, message: '算法不能为空', trigger: 'blur' }],
+      compOptions: {
+        disabled: true,
+        elTagName: 'select',
+        placeholder: '请输入算法',
+        enum: enumsAlgorithmConfigTrack
+      }
+    },
+    {
+      label: '算法参数',
+      prop: 'parameterConfig',
+      rules: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入模型名称'
+      }
+    },
+    {
+      label: '模型ID',
+      prop: 'id',
+      rules: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入模型名称'
+      }
+    },
+    {
+      label: '模型名称',
+      prop: 'modelName',
+      rules: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入模型名称'
+      }
+    },
+    {
+      label: '模型保存路径',
+      prop: 'modelAddress',
+      rules: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入模型名称'
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remarks',
+      rules: [{ required: false, message: '备注不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
+</script>

+ 401 - 0
src/views/demo/traceMerge/index.vue

@@ -0,0 +1,401 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listTraceMergeApi">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['demo:traceMerge:add']" icon="CirclePlus" @click="createTask"> 新增 </el-button>
+        <el-button type="primary" v-auth="['demo:traceMerge:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button>
+        <el-button type="primary" v-auth="['demo:traceMerge:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button>
+        <el-button
+          type="danger"
+          v-auth="['demo:traceMerge:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button type="primary" link icon="View" v-auth="['demo:traceMerge:query']" @click="openDialog(3, '多物体融合轨迹识别查看', scope.row)">
+          查看
+        </el-button>
+        <!--        <el-button type="primary" link icon="EditPen" v-auth="['demo:traceMerge:edit']" @click="openDialog(2, '多物体融合轨迹识别编辑', scope.row)">-->
+        <!--          编辑-->
+        <!--        </el-button>-->
+        <!--        <el-button type="primary" link icon="Delete" v-auth="['demo:traceMerge:remove']" @click="deleteTraceMerge(scope.row)"> 删除 </el-button>-->
+        <el-button type="primary" link icon="View" @click="execute(scope.row)"> 执行任务 </el-button>
+        <el-button type="primary" link icon="View" @click="display(scope.row)"> 展示结果 </el-button>
+        <el-button type="primary" link icon="View" @click="showLog(scope.row)"> 查看日志 </el-button>
+      </template>
+    </ProTable>
+    <FormDialog ref="formDialogRef" />
+    <ImportExcel ref="dialogRef" />
+
+    <el-dialog v-model="createTaskDialogVisible" title="新增任务">
+      <el-container style="display: flex; flex-direction: column; justify-content: center">
+        <el-container>
+          <span style="min-width: 80px">任务名称</span>
+          <el-input v-model="params.name" placeholder="请输入任务名称"></el-input>
+        </el-container>
+        <el-container style="margin-top: 20px">
+          <span style="min-width: 80px">任务文件</span>
+          <file @update:model-value="updateFiles" :file-size="20" :file-type="['mat']"></file>
+        </el-container>
+      </el-container>
+      <span class="dialog-footer">
+        <el-button type="primary" @click="submitCreateTask">确 定</el-button>
+        <el-button @click="createTaskDialogVisible = false">取 消</el-button>
+      </span>
+    </el-dialog>
+
+    <el-dialog v-model="displayDialogVisible" title="执行结果">
+      <el-container direction="vertical">
+        <el-container v-for="(item, index) in resultData" :key="index" direction="horizontal" style="place-items: center center">
+          <span> {{ item.split('/')[item.split('/').length - 1] }} </span>
+          <el-image :src="'/api/profile' + item" style="max-width: 600px; max-height: 300px"></el-image>
+        </el-container>
+      </el-container>
+    </el-dialog>
+
+    <el-dialog v-model="logDialogVisible" title="查看日志">
+      <el-container direction="vertical">
+        <el-container v-for="(item, index) in logData" :key="index">
+          {{ item }}
+        </el-container>
+      </el-container>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="tsx" name="TraceMerge">
+import { reactive, ref } from 'vue'
+import { useHandleData } from '@/hooks/useHandleData'
+import { useDownload } from '@/hooks/useDownload'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import ProTable from '@/components/ProTable/index.vue'
+import ImportExcel from '@/components/ImportExcel/index.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
+import { ColumnProps, ProTableInstance } from '@/components/ProTable/interface'
+import {
+  addTraceMergeApi,
+  delTraceMergeApi,
+  executeApi,
+  exportTraceMergeApi,
+  getResApi,
+  getTraceMergeApi,
+  importTemplateApi,
+  importTraceMergeDataApi,
+  listTraceMergeApi,
+  updateTraceMergeApi
+} from '@/api/modules/demo/traceMerge'
+import File from '@/components/Upload/File.vue'
+import { getDictsApi } from '@/api/modules/system/dictData'
+import http from '@/api'
+
+const logDialogVisible = ref(false)
+const logData = ref([])
+const showLog = async function (row) {
+  let path = row.resultPath.split('ObjectDetection_Web')
+  path = path[path.length - 1]
+  let res = await http.get('/profile' + path + '/fusion.log')
+  // let res = 'hello \r\n world!'
+  logData.value = res.split('\n')
+  logDialogVisible.value = true
+}
+const createTaskDialogVisible = ref(false)
+const params = ref({
+  preprocessPath: null,
+  name: ''
+})
+
+const resultData = ref([])
+const displayDialogVisible = ref(false)
+const display = function (row) {
+  // console.log(row)
+  getResApi({ taskId: row.id }).then(res => {
+    if (res.code !== 200) {
+      ElMessage.error(res.msg)
+      return
+    }
+    let arr = row.resultPath.split('ObjectDetection_Web')
+    let pathPrefix = arr[arr.length - 1]
+    resultData.value = []
+    for (let i = 1; i < res.data[0]; i++) {
+      resultData.value.push(pathPrefix + '/number' + i + '.png')
+    }
+    console.log('resultData', resultData.value)
+    displayDialogVisible.value = true
+  })
+}
+
+const execute = function (row) {
+  executeApi({ taskId: row.id }).then(res => {
+    console.log(res)
+    if (res.code == 200) {
+      ElMessage.success('开始执行!')
+    } else {
+      ElMessage.error('执行失败: ' + res.msg)
+    }
+  })
+}
+
+const createTask = function () {
+  createTaskDialogVisible.value = true
+}
+
+const submitCreateTask = function () {
+  if (params.value.preprocessPath == null || params.value.preprocessPath === '') {
+    ElMessage.error('请上传mat文件!')
+    return
+  } else if (params.value.name == null || params.value.name === '') {
+    ElMessage.error('请输入任务名称!')
+    return
+  }
+  addTraceMergeApi(params.value)
+    .then(res => {
+      console.log(res)
+      if (res.code === 200) {
+        ElMessage.success('创建成功!')
+        createTaskDialogVisible.value = false
+      } else {
+        ElMessage.error(res.msg)
+      }
+    })
+    .catch(err => {
+      console.log(err)
+      ElMessage.error('创建错误!')
+    })
+}
+
+const updateFiles = function (filePath) {
+  console.log('filepath', filePath)
+  params.value.preprocessPath = filePath
+}
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除多物体融合轨迹识别信息
+const deleteTraceMerge = async (params: any) => {
+  await useHandleData(delTraceMergeApi, params.id, '删除【' + params.id + '】多物体融合轨迹识别')
+  proTable.value?.getTableList()
+}
+
+// 批量删除多物体融合轨迹识别信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delTraceMergeApi, ids, '删除所选多物体融合轨迹识别信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出多物体融合轨迹识别列表
+const downloadFile = async () => {
+  ElMessageBox.confirm('确认导出多物体融合轨迹识别数据?', '温馨提示', { type: 'warning' }).then(() =>
+    useDownload(exportTraceMergeApi, '多物体融合轨迹识别列表', proTable.value?.searchParam)
+  )
+}
+
+// 批量添加多物体融合轨迹识别
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+const batchAdd = () => {
+  const params = {
+    title: '多物体融合轨迹识别',
+    tempApi: importTemplateApi,
+    importApi: importTraceMergeDataApi,
+    getTableList: proTable.value?.getTableList
+  }
+  dialogRef.value?.acceptParams(params)
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getTraceMergeApi(row?.id || null)
+  }
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : res.data,
+    api: type == 1 ? addTraceMergeApi : updateTraceMergeApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  { type: 'selection', fixed: 'left', width: 70 },
+  { prop: 'id', label: '主键ID' },
+  {
+    prop: 'name',
+    label: '任务名称',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'status',
+    label: '任务状态',
+    search: {
+      el: 'input'
+    },
+    width: 120,
+    tag: true,
+    enum: () => getDictsApi('biz_task_status'),
+    fieldNames: { label: 'dictLabel', value: 'dictValue' }
+  },
+  {
+    prop: 'parameters',
+    label: '调用算法时所用的参数',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'preprocessPath',
+    label: '预处理数据路径',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'resultPath',
+    label: '结果数据路径',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'startTime',
+    label: '开始时间',
+    search: {
+      el: 'date-picker',
+      props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
+    },
+    width: 120
+  },
+  {
+    prop: 'endTime',
+    label: '结束时间',
+    search: {
+      el: 'date-picker',
+      props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
+    },
+    width: 120
+  },
+  {
+    prop: 'costSecond',
+    label: '耗时',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'remarks',
+    label: '备注',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+])
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '任务名称',
+      prop: 'name',
+      rules: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入任务名称'
+      }
+    },
+    {
+      label: '任务状态',
+      prop: 'status',
+      rules: [{ required: true, message: '任务状态不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入任务状态'
+      }
+    },
+    {
+      label: '调用算法时所用的参数',
+      prop: 'parameters',
+      rules: [{ required: true, message: '调用算法时所用的参数不能为空', trigger: 'blur' }],
+      compOptions: {
+        type: 'textarea',
+        clearable: true,
+        placeholder: '请输入内容'
+      }
+    },
+    {
+      label: '预处理数据路径',
+      prop: 'preprocessPath',
+      rules: [{ required: true, message: '预处理数据路径不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入预处理数据路径'
+      }
+    },
+    {
+      label: '结果数据路径',
+      prop: 'resultPath',
+      rules: [{ required: true, message: '结果数据路径不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入结果数据路径'
+      }
+    },
+    {
+      label: '开始时间',
+      prop: 'startTime',
+      rules: [{ required: true, message: '开始时间不能为空', trigger: 'change' }],
+      compOptions: {
+        elTagName: 'date-picker',
+        type: 'date',
+        placeholder: '请选择开始时间'
+      }
+    },
+    {
+      label: '结束时间',
+      prop: 'endTime',
+      rules: [{ required: true, message: '结束时间不能为空', trigger: 'change' }],
+      compOptions: {
+        elTagName: 'date-picker',
+        type: 'date',
+        placeholder: '请选择结束时间'
+      }
+    },
+    {
+      label: '耗时',
+      prop: 'costSecond',
+      rules: [{ required: true, message: '耗时不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入耗时'
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remarks',
+      rules: [{ required: true, message: '备注不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
+</script>

+ 449 - 0
src/views/demo/trackSequence/index.vue

@@ -0,0 +1,449 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listTrackSequenceApi">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['demo:trackSequence:add']" icon="CirclePlus" @click="openDialog(1, '注视轨迹序列新增')"> 新增 </el-button>
+        <!-- <el-button type="primary" v-auth="['demo:trackSequence:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button>
+        <el-button type="primary" v-auth="['demo:trackSequence:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button> -->
+        <el-button
+          type="danger"
+          v-auth="['demo:trackSequence:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button type="primary" link icon="View" v-if="scope.row.algorithmModelId != null" @click="openModelDialog(scope.row)">
+          <!--@click="openStartDialog(scope.row)"  -->
+          模型
+        </el-button>
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          v-auth="['demo:trackSequence:start']"
+          v-if="scope.row.status == '0' || scope.row.status == '3' || scope.row.status == '4'"
+          @click="startTrackSequence(scope.row)"
+        >
+          开始
+        </el-button>
+        <el-popconfirm title="确定终止此任务吗?" @confirm="stopTrackSequence(scope.row)" v-if="scope.row.status == '1'">
+          <template #reference>
+            <el-button type="primary" link icon="Delete"> 终止</el-button>
+          </template>
+        </el-popconfirm>
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          v-auth="['demo:trackSequence:download']"
+          v-if="scope.row.status == '2'"
+          @click="dowloadTrackSequence(scope.row)"
+        >
+          下载
+        </el-button>
+        <!-- <el-button type="primary" link icon="View" v-auth="['demo:toInfrared:query']" @click="openDialog(3, '查看', scope.row)"> 查看 </el-button> -->
+        <!-- <el-button type="primary" link icon="EditPen"v-auth="['demo:toInfrared:edit']" @click="openDialog(2, '编辑', scope.row)"> 编辑 </el-button> -->
+        <el-button
+          type="primary"
+          link
+          icon="Delete"
+          v-auth="['demo:toInfrared:remove']"
+          @click="deleteTrackSequence(scope.row)"
+          :disabled="scope.row.status == '1'"
+        >
+          删除
+        </el-button>
+      </template>
+    </ProTable>
+    <FormDialog ref="formDialogRef" />
+    <ImportExcel ref="dialogRef" />
+  </div>
+</template>
+
+<script setup lang="tsx" name="TrackSequence">
+import { ref, reactive, onMounted } from 'vue'
+import { useHandleData } from '@/hooks/useHandleData'
+import { useDownload } from '@/hooks/useDownload'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import ProTable from '@/components/ProTable/index.vue'
+import ImportExcel from '@/components/ImportExcel/index.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
+import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
+import {
+  listTrackSequenceApi,
+  delTrackSequenceApi,
+  addTrackSequenceApi,
+  updateTrackSequenceApi,
+  importTemplateApi,
+  importTrackSequenceDataApi,
+  exportTrackSequenceApi,
+  getTrackSequenceApi,
+  startTrackSequenceApi,
+  stopTrackSequenceApi,
+  dowloadTrackSequenceApi
+} from '@/api/modules/demo/trackSequence'
+import { enumAlgorithmModelTrackApi } from '@/api/modules/demo/AlgorithmModelTrack'
+import { getAlgorithmModelTrackApi } from '@/api/modules/demo/AlgorithmModelTrack'
+import { enumAlgorithmConfigTrackApi } from '@/api/modules/demo/AlgorithmConfigTrack'
+
+import { listDataSeqApi } from '@/api/modules/demo/DataSeq'
+import statusEnums from '@/utils/status'
+import { AlgorithmType, SubSystem, SubSystem__, enumsAlgorithmType, enumsSubSystem } from '@/views/demo/utils'
+
+const enumsAlgorithmConfigTrack = ref<any>([])
+onMounted(async () => {
+  const result = await enumAlgorithmConfigTrackApi()
+  // console.log(result)
+  // console.log(result['data'])
+  enumsAlgorithmConfigTrack.value = result['data']
+  return result['data']
+})
+
+const openModelDialog = async row => {
+  const algorithmModelId = row.algorithmModelId
+  const result: any = await getAlgorithmModelTrackApi(algorithmModelId)
+
+  // console.log(result.data)
+
+  setItemsOptionsModel()
+  const params = {
+    title: '模型',
+    width: 580,
+    isEdit: false,
+    itemsOptions: itemsOptions,
+    model: result.data,
+    api: updateTrackSequenceApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+const startTrackSequence = async (params: any) => {
+  const res: any = await startTrackSequenceApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('任务已开始,请等待完成!')
+  } else {
+    ElMessage.error('任务开始失败,请检查!')
+  }
+  proTable.value?.getTableList()
+}
+
+const stopTrackSequence = async (params: any) => {
+  const res: any = await stopTrackSequenceApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('终止任务成功!')
+  } else {
+    ElMessage.error('终止任务失败,请检查!')
+  }
+  proTable.value?.getTableList()
+}
+
+const dowloadTrackSequence = async (params: any) => {
+  await useDownload(dowloadTrackSequenceApi, params.name, params.id, true, '.zip')
+}
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除注视轨迹序列信息
+const deleteTrackSequence = async (params: any) => {
+  await useHandleData(delTrackSequenceApi, params.id, '删除【' + params.id + '】注视轨迹序列')
+  proTable.value?.getTableList()
+}
+
+// 批量删除注视轨迹序列信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delTrackSequenceApi, ids, '删除所选注视轨迹序列信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出注视轨迹序列列表
+const downloadFile = async () => {
+  ElMessageBox.confirm('确认导出注视轨迹序列数据?', '温馨提示', { type: 'warning' }).then(() =>
+    useDownload(exportTrackSequenceApi, '注视轨迹序列列表', proTable.value?.searchParam)
+  )
+}
+
+// 批量添加注视轨迹序列
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+const batchAdd = () => {
+  const params = {
+    title: '注视轨迹序列',
+    tempApi: importTemplateApi,
+    importApi: importTrackSequenceDataApi,
+    getTableList: proTable.value?.getTableList
+  }
+  dialogRef.value?.acceptParams(params)
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getTrackSequenceApi(row?.id || null)
+  }
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : res.data,
+    api: type == 1 ? addTrackSequenceApi : updateTrackSequenceApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+// 表格配置项
+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: 'status',
+    label: '任务状态',
+    search: {
+      el: 'select'
+    },
+    tag: true,
+    enum: statusEnums,
+    width: 150
+  },
+  {
+    prop: 'type',
+    label: '类型',
+    tag: true,
+    enum: enumsAlgorithmType,
+    width: 120
+  },
+  {
+    prop: 'subsystem',
+    label: '分系统',
+    tag: true,
+    enum: enumsSubSystem,
+    width: 200
+  },
+  {
+    prop: 'algorithmName',
+    label: '算法名称',
+    width: 200
+  },
+  {
+    prop: 'modelName',
+    label: '模型名称',
+    width: 200
+  },
+  // {
+  //   prop: 'algorithmModelId',
+  //   label: '模型',
+  //   search: {
+  //     el: 'input'
+  //   },
+  //   width: 150
+  // },
+  {
+    prop: 'startTime',
+    label: '开始时间',
+    width: 180
+  },
+  {
+    prop: 'endTime',
+    label: '结束时间',
+    width: 180
+  },
+  {
+    prop: 'costSecond',
+    label: '耗时',
+    width: 120
+  },
+  {
+    prop: 'log',
+    label: '日志',
+    width: 120
+  },
+  {
+    prop: 'outputPath',
+    label: '输出路径',
+    width: 120
+  },
+  {
+    prop: 'remarks',
+    label: '备注',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+])
+
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '任务名称',
+      prop: 'name',
+      rules: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入任务名称'
+      }
+    },
+    {
+      label: '选择数据集',
+      prop: 'inputOssId',
+      rules: [{ required: false, message: '数据集不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'select',
+        placeholder: '请选择或者上传数据集',
+        enum: getImageDataList,
+        clearable: true
+      }
+    },
+    {
+      label: '上传数据集',
+      prop: 'inputOssId',
+      rules: [{ required: false, message: '数据集不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'file-upload',
+        fileSize: 4096,
+        fileType: ['zip'],
+        placeholder: '请上传数据集'
+      }
+    },
+    {
+      label: '选择模型',
+      prop: 'algorithmModelId',
+      rules: [{ required: true, message: '模型不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'select',
+        placeholder: '请选择模型',
+        enum: enumsAlgorithmModelTrack
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remarks',
+      rules: [],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
+
+const setItemsOptionsModel = () => {
+  itemsOptions = [
+    {
+      label: '算法ID',
+      prop: 'algorithmId',
+      rules: [{ required: true, message: '算法不能为空', trigger: 'blur' }],
+      compOptions: {
+        disabled: true,
+        placeholder: '请输入算法'
+      }
+    },
+    {
+      label: '算法类型',
+      prop: 'algorithmType',
+      rules: [{ required: true, message: '算法不能为空', trigger: 'blur' }],
+      compOptions: {
+        disabled: true,
+        elTagName: 'select',
+        placeholder: '请输入算法',
+        enum: enumsAlgorithmConfigTrack
+      }
+    },
+    {
+      label: '算法参数',
+      prop: 'parameterConfig',
+      rules: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入模型名称'
+      }
+    },
+    {
+      label: '模型ID',
+      prop: 'id',
+      rules: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入模型名称'
+      }
+    },
+    {
+      label: '模型名称',
+      prop: 'modelName',
+      rules: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入模型名称'
+      }
+    },
+    {
+      label: '模型保存路径',
+      prop: 'modelAddress',
+      rules: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入模型名称'
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remarks',
+      rules: [{ required: false, message: '备注不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
+
+const getImageDataList = ref<any>([])
+onMounted(async () => {
+  const qyery = {
+    subsystem: SubSystem__['注释轨迹序列'],
+    pageNum: 1,
+    pageSize: 25
+  }
+  const result: any = await listDataSeqApi(qyery)
+  const data = result['data']['list']
+  for (const item of data) {
+    getImageDataList.value.push({
+      value: item['inputOssId'],
+      label: item['name']
+    })
+  }
+})
+
+const enumsAlgorithmModelTrack = ref<any>([])
+
+onMounted(async () => {
+  const result: any = await enumAlgorithmModelTrackApi()
+  // console.log(result.data);
+  enumsAlgorithmModelTrack.value = []
+
+  for (const item of result.data) {
+    if (SubSystem[item['subsystem']] === '注释轨迹序列') {
+      item['label'] = item['label'] + '-' + SubSystem[item['subsystem']] + '-' + AlgorithmType[item['type']] + '-' + item['algorithmName']
+      enumsAlgorithmModelTrack.value.push(item)
+    }
+  }
+})
+</script>

+ 78 - 0
src/views/demo/utils.ts

@@ -0,0 +1,78 @@
+/*
+ * @Datetime : 2024/9/28 15:51
+ * @Author   : WANGKANG
+ * @Email    : 1686617586@qq.com
+ * @Blog     :
+ * @File     : utils.vue
+ * @brief    : 组件工具包
+ * Copyright 2024 WANGKANG, All Rights Reserved.
+ */
+
+export const AlgorithmType = {
+  '0': '训练',
+  '1': '测试',
+  '2': '预测/推理'
+}
+
+export const SubSystem = {
+  '0': '可见光转红外',
+  '1': '目标检测',
+  '2': '注释轨迹序列'
+}
+
+export const SubSystem__ = {
+  可见光转红外: '0',
+  目标检测: '1',
+  注释轨迹序列: '2'
+}
+
+export const AlgorithmType2 = {
+  训练: '0',
+  测试: '1',
+  '预测/推理': '2'
+}
+
+export const enumsAlgorithmType = [
+  { label: '训练', value: '0' },
+  { label: '测试', value: '1' },
+  { label: '预测/推理', value: '2' }
+]
+
+export const enumsSubSystem = [
+  { label: '可见光转红外', value: '0' },
+  { label: '目标检测', value: '1' },
+  { label: '注释轨迹序列', value: '2' }
+]
+
+export const enumsModelStatus = [
+  {
+    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'
+  }
+]

+ 340 - 0
src/views/demo/video2image/index.vue

@@ -0,0 +1,340 @@
+<!--
+  @Datetime : 2024/8/30 17:42
+  @Author   : WANGKANG
+  @Email    : 1686617586@qq.com
+  @Blog     :
+  @File     : index.vue.vue
+  @brief    : 视频转图片
+-->
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listVideo2imageApi">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['demo:video2image:add']" icon="CirclePlus" @click="openDialog(1, '新增')"> 新增 </el-button>
+        <!-- <el-button type="primary" v-auth="['demo:video2image:import']" icon="Upload" plain @click="batchAdd" v-if="false"> 导入 </el-button>
+        <el-button type="primary" v-auth="['demo:video2image:export']" icon="Download" plain @click="downloadFile" v-if="false"> 导出 </el-button> -->
+        <el-button
+          type="danger"
+          v-auth="['demo:video2image:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          v-auth="['demo:video2image:start']"
+          @click="startVideo2image(scope.row)"
+          v-if="scope.row.status == '0' || scope.row.status == '3' || scope.row.status == '4'"
+        >
+          开始
+        </el-button>
+        <el-popconfirm title="确定终止此任务吗?" @confirm="stopVideo2image(scope.row)" v-if="scope.row.status == '1'">
+          <template #reference>
+            <el-button type="primary" link icon="Delete"> 终止 </el-button>
+          </template>
+        </el-popconfirm>
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          v-auth="['demo:video2image:download']"
+          @click="dowloadVideo2image(scope.row)"
+          v-if="scope.row.status == '2'"
+        >
+          下载
+        </el-button>
+        <!-- <el-button type="primary" link icon="View" v-auth="['demo:video2image:query']" @click="openDialog(3, '查看', scope.row)"> 查看 </el-button> -->
+        <!-- <el-button type="primary" link icon="EditPen" v-auth="['demo:video2image:edit']" @click="openDialog(2, '编辑', scope.row)"> 编辑 </el-button> -->
+        <el-button
+          type="primary"
+          link
+          icon="Delete"
+          v-auth="['demo:video2image:remove']"
+          @click="deleteVideo2image(scope.row)"
+          :disabled="scope.row.status == '1'"
+        >
+          删除
+        </el-button>
+      </template>
+    </ProTable>
+    <FormDialog ref="formDialogRef"></FormDialog>
+  </div>
+</template>
+
+<script setup lang="tsx" name="Video2image">
+import { ref, reactive } 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 FormDialog from '@/components/FormDialog/index.vue'
+import { ProTableInstance, ColumnProps, EnumProps } from '@/components/ProTable/interface'
+import {
+  listVideo2imageApi,
+  delVideo2imageApi,
+  addVideo2imageApi,
+  updateVideo2imageApi,
+  importTemplateApi,
+  importVideo2imageDataApi,
+  exportVideo2imageApi,
+  getVideo2imageApi,
+  startVideo2imageApi,
+  stopVideo2imageApi,
+  downloadVideo2imageApi
+} from '@/api/modules/demo/video2image'
+
+const startVideo2image = async (params: any) => {
+  const res = await startVideo2imageApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('任务已开始,请等待完成!')
+  } else {
+    ElMessage.error('任务开始失败,请检查!')
+  }
+  proTable.value?.getTableList()
+}
+
+const stopVideo2image = async (params: any) => {
+  const res = await stopVideo2imageApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('终止任务成功!')
+  } else {
+    ElMessage.error('终止任务失败,请检查!')
+  }
+  proTable.value?.getTableList()
+}
+
+const dowloadVideo2image = async (params: any) => {
+  await useDownload(downloadVideo2imageApi, params.name, params.id, true, '.zip')
+}
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除视频转图片信息
+const deleteVideo2image = async (params: any) => {
+  await useHandleData(delVideo2imageApi, params.id, '删除【' + params.id + '】视频转图片')
+  proTable.value?.getTableList()
+}
+
+// 批量删除视频转图片信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delVideo2imageApi, ids, '删除所选视频转图片信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出视频转图片列表
+const downloadFile = async () => {
+  ElMessageBox.confirm('确认导出视频转图片数据?', '温馨提示', { type: 'warning' }).then(() =>
+    useDownload(exportVideo2imageApi, '视频转图片列表', proTable.value?.searchParam)
+  )
+}
+
+// 批量添加视频转图片
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+const batchAdd = () => {
+  const params = {
+    title: '视频转图片',
+    tempApi: importTemplateApi,
+    importApi: importVideo2imageDataApi,
+    getTableList: proTable.value?.getTableList
+  }
+  dialogRef.value?.acceptParams(params)
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getVideo2imageApi(row?.id || null)
+  }
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : res.data,
+    api: type == 1 ? addVideo2imageApi : updateVideo2imageApi,
+    getTableList: proTable.value?.getTableList,
+    showVideoUpload: type == 1 ? true : false
+  }
+  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: 'status',
+    label: '任务状态',
+    search: {
+      el: 'select'
+    },
+    tag: true,
+    enum: statusEnums
+  },
+  {
+    prop: 'fps',
+    label: '切割帧率',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'outPath',
+    label: '输出路径',
+    width: 120
+  },
+  {
+    prop: 'startTime',
+    label: '开始时间',
+    // search: {
+    //   el: 'date-picker',
+    //   props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
+    // },
+    width: 180
+  },
+  {
+    prop: 'endTime',
+    label: '结束时间',
+    // search: {
+    //   el: 'date-picker',
+    //   props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
+    // },
+    width: 180
+  },
+  {
+    prop: 'costSecond',
+    label: '耗时',
+    width: 120
+  },
+  {
+    prop: 'log',
+    label: '日志',
+    width: 120
+  },
+  {
+    prop: 'remarks',
+    label: '备注',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'path',
+    label: '视频路径',
+    width: 150
+  },
+  { prop: 'operation', label: '操作', width: 170, fixed: 'right' }
+])
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '任务名称',
+      prop: 'name',
+      rules: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入任务名称'
+      }
+    },
+    {
+      label: '切割帧率',
+      prop: 'fps',
+      rules: [
+        { required: true, message: '切割帧率不能为空', trigger: 'blur' }
+        // {
+        //   type: 'number',
+        //   message: '切割帧率必须为数字',
+        //   trigger: 'blur'
+        // },
+        // { min: 1, message: '切割帧率必须大于等于1', trigger: 'blur' },
+        // { max: 60, message: '切割帧率必须小于等于60', trigger: 'blur' }
+      ],
+      compOptions: {
+        type: 'number',
+        placeholder: '请输入切割帧率,范围:1-60'
+      }
+    },
+    {
+      label: '上传视频',
+      prop: 'inputOssId',
+      rules: [{ required: true, message: '视频不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'file-upload',
+        fileSize: 4096,
+        fileType: ['mp4', 'avi', 'rmvb', 'mov', 'wmv', 'flv'],
+        placeholder: '请上传视频文件'
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remarks',
+      rules: [{ required: false, message: '备注不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
+</script>
+
+<style scoped></style>

+ 500 - 0
src/views/demo/videoStable/index.vue

@@ -0,0 +1,500 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listVideoStableApi">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['demo:videoStable:add']" icon="CirclePlus" @click="openDialog(1, '视频去抖动新增')"> 新增 </el-button>
+        <!--        <el-button type="primary" v-auth="['demo:videoStable:import']" icon="Upload" plain @click="batchAdd"> 导入</el-button>-->
+        <!--        <el-button type="primary" v-auth="['demo:videoStable:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button>-->
+        <el-button
+          type="danger"
+          v-auth="['demo:videoStable: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="startVideoStable(scope.row)"
+          v-if="scope.row.status == '0' || scope.row.status == '3' || scope.row.status == '4'"
+        >
+          开始
+        </el-button>
+        <el-popconfirm title="确定终止此任务吗?" @confirm="stopVideoStable(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="compareVideoStable(scope.row)" v-if="scope.row.status == '2'"> 预览 </el-button>
+        <el-button type="primary" link icon="View" v-auth="['demo:videoStable:query']" @click="openDialog(3, '视频去抖动查看', scope.row)">
+          查看
+        </el-button>
+        <!-- <el-button type="primary" link icon="EditPen" v-auth="['demo:videoStable:edit']" @click="openDialog(2, '视频去抖动编辑', scope.row)">
+          编辑
+        </el-button> -->
+        <el-button type="primary" link icon="Delete" v-auth="['demo:videoStable:remove']" @click="deleteVideoStable(scope.row)"> 删除 </el-button>
+      </template>
+    </ProTable>
+    <FormDialog ref="formDialogRef" />
+    <ImportExcel ref="dialogRef" />
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="80%">
+      <el-form :inline="true">
+        <el-form-item label="帧率">
+          <el-select v-model="imageFps" placeholder="选择帧率" style="width: 200px" @change="changeFps">
+            <el-option label="0" value="0"></el-option>
+            <el-option label="5" value="5"></el-option>
+            <el-option label="15" value="15"></el-option>
+            <el-option label="30" value="30"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="跳转至">
+          <el-input v-model="newImageIdx" type="number" style="width: 100px" />
+          <el-button type="primary" @click="confirmNewImageIdx" style="margin-left: 10px">确认</el-button>
+        </el-form-item>
+      </el-form>
+      <div class="image-dialog">
+        <el-image :src="imageUrlList[imageIdx].inputUrl" style="width: 45%"></el-image>
+        <el-image :src="imageUrlList[imageIdx].outputUrl" style="width: 45%"></el-image>
+      </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 >= imageUrlList.length - 1">下一个</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="tsx" name="VideoStable">
+import { ref, reactive } 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 FormDialog from '@/components/FormDialog/index.vue'
+import { ProTableInstance, ColumnProps, EnumProps } from '@/components/ProTable/interface'
+import {
+  listVideoStableApi,
+  delVideoStableApi,
+  addVideoStableApi,
+  updateVideoStableApi,
+  importTemplateApi,
+  importVideoStableDataApi,
+  exportVideoStableApi,
+  getVideoStableApi,
+  startVideoStableApi,
+  stopVideoStableApi,
+  // getCompareImageApi,
+  // getCompareImageCountApi,
+  getImagesApi
+} from '@/api/modules/demo/videoStable'
+
+const dialogVisible = ref(false)
+const imageIdx = ref(0)
+const imageUrlList: any = ref([])
+const newImageIdx = ref('')
+
+// 直接缓存所有图片
+const dialogTitle = ref('')
+const imageFps = ref(0)
+const intervalChangeFps: any = ref()
+
+const changeFps = () => {
+  console.log('changeFps')
+  if (intervalChangeFps.value) {
+    clearInterval(intervalChangeFps.value)
+  }
+
+  if (imageFps.value == 0) {
+    return
+  }
+  imageIdx.value = 1
+  intervalChangeFps.value = setInterval(() => {
+    next_picture()
+  }, 1000 / imageFps.value)
+}
+
+const startVideoStable = async (params: any) => {
+  const res = await startVideoStableApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('任务已经开始,请等待')
+  } else {
+    ElMessage.error('任务开始失败,请检查!')
+  }
+  proTable.value?.getTableList()
+}
+
+const stopVideoStable = async (params: any) => {
+  const res = await stopVideoStableApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('任务终止成功')
+  } else {
+    ElMessage.error('任务终止失败!')
+  }
+  proTable.value?.getTableList()
+}
+
+const confirmNewImageIdx = () => {
+  const val = parseInt(newImageIdx.value)
+  if (val > 0 && val <= imageUrlList.value.length) {
+    imageIdx.value = val - 1
+  } else {
+    ElMessageBox.alert('跳转索引有误,请检查!')
+  }
+}
+
+const compareVideoStable = async (params: any) => {
+  console.log('compareVideoStable')
+  const data: any = await getImagesApi(params.inputOssId)
+
+  imageUrlList.value = data.data
+  imageIdx.value = 0
+
+  dialogTitle.value = '预览: 第' + (imageIdx.value + 1) + '张图片 共' + imageUrlList.value.length + '张图片'
+  dialogVisible.value = true
+}
+const next_picture = async () => {
+  if (imageIdx.value < imageUrlList.value.length - 1) {
+    imageIdx.value = imageIdx.value + 1
+  }
+  dialogTitle.value = '预览: 第' + (imageIdx.value + 1) + '张图片 共' + imageUrlList.value.length + '张图片'
+}
+const pre_picture = async () => {
+  if (imageIdx.value > 0) {
+    imageIdx.value = imageIdx.value - 1
+  }
+  dialogTitle.value = '预览: 第' + (imageIdx.value + 1) + '张图片 共' + imageUrlList.value.length + '张图片'
+}
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除视频去抖动信息
+const deleteVideoStable = async (params: any) => {
+  await useHandleData(delVideoStableApi, params.id, '删除任务【' + params.name + '】?')
+  proTable.value?.getTableList()
+}
+
+// 批量删除视频去抖动信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delVideoStableApi, ids, '删除所选任务?')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出视频去抖动列表
+const downloadFile = async () => {
+  ElMessageBox.confirm('确认导出视频去抖动数据?', '温馨提示', { type: 'warning' }).then(() =>
+    useDownload(exportVideoStableApi, '视频去抖动列表', proTable.value?.searchParam)
+  )
+}
+
+// 批量添加视频去抖动
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+const batchAdd = () => {
+  const params = {
+    title: '视频去抖动',
+    tempApi: importTemplateApi,
+    importApi: importVideoStableDataApi,
+    getTableList: proTable.value?.getTableList
+  }
+  dialogRef.value?.acceptParams(params)
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getVideoStableApi(row?.id || null)
+  }
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : res.data,
+    api: type == 1 ? addVideoStableApi : updateVideoStableApi,
+    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: 'status',
+    label: '任务状态',
+    search: {
+      el: 'select'
+    },
+    width: 100,
+    tag: true,
+    enum: statusEnums
+  },
+  {
+    prop: 'startTime',
+    label: '开始时间',
+    // search: {
+    //   el: 'date-picker',
+    //   props: {
+    //     type: 'datetimerange',
+    //     valueFormat: 'YYYY-MM-DD HH:mm:ss'
+    //   }
+    // },
+    width: 180
+  },
+  {
+    prop: 'endTime',
+    label: '结束时间',
+    // search: {
+    //   el: 'date-picker',
+    //   props: {
+    //     type: 'datetimerange',
+    //     valueFormat: 'YYYY-MM-DD HH:mm:ss'
+    //   }
+    // },
+    width: 180
+  },
+  {
+    prop: 'costSecond',
+    label: '耗时',
+    search: {
+      el: 'input'
+    },
+    width: 80
+  },
+  {
+    prop: 'log',
+    label: '日志',
+    width: 120
+  },
+
+  {
+    prop: 'block_size',
+    label: '网格大小',
+    width: 120
+  },
+  {
+    prop: 'radius',
+    label: '扩散半径',
+    width: 120
+  },
+  {
+    prop: 'buffer_size',
+    label: '缓冲区大小',
+    width: 120
+  },
+  {
+    prop: 'cornerquality',
+    label: '角点质量',
+    width: 130
+  },
+  {
+    prop: 'cornerminDistance',
+    label: '角点最小距离',
+    width: 180
+  },
+  {
+    prop: 'lklevel',
+    label: '光流层级',
+    width: 120
+  },
+  {
+    prop: 'lkwinSiz',
+    label: '光流窗口大小',
+    width: 120
+  },
+  {
+    prop: 'remarks',
+    label: '备注',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'operation',
+    label: '操作',
+    width: 230,
+    fixed: 'right'
+  }
+])
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '任务名称',
+      prop: 'name',
+      rules: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入任务名称'
+      }
+    },
+    {
+      label: '图片集压缩包',
+      prop: 'inputOssId',
+      rules: [{ required: true, message: '图片集压缩包不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'file-upload',
+        fileSize: 4096,
+        fileType: ['zip'],
+        placeholder: '请上传图片集压缩包'
+      }
+    },
+    {
+      label: '网格大小',
+      prop: 'block_size',
+      rules: [],
+      compOptions: {
+        type: 'input',
+        clearable: true,
+        placeholder: '默认50'
+      }
+    },
+    {
+      label: '扩散半径',
+      prop: 'radius',
+      rules: [],
+      compOptions: {
+        type: 'input',
+        clearable: true,
+        placeholder: '默认500'
+      }
+    },
+    {
+      label: '缓冲区大小',
+      prop: 'buffer_size',
+      rules: [],
+      compOptions: {
+        type: 'input',
+        clearable: true,
+        placeholder: '默认200'
+      }
+    },
+    {
+      label: '角点质量',
+      prop: 'cornerquality',
+      rules: [],
+      compOptions: {
+        type: 'input',
+        clearable: true,
+        placeholder: '默认0.2'
+      }
+    },
+    {
+      label: '角点最小距离',
+      prop: 'cornerminDistance',
+      rules: [],
+      compOptions: {
+        type: 'input',
+        clearable: true,
+        placeholder: '默认5'
+      }
+    },
+    {
+      label: '光流层级',
+      prop: 'lklevel',
+      rules: [],
+      compOptions: {
+        type: 'input',
+        clearable: true,
+        placeholder: '默认3'
+      }
+    },
+    {
+      label: '光流窗口大小',
+      prop: 'lkwinSiz',
+      rules: [],
+      compOptions: {
+        type: 'input',
+        clearable: true,
+        placeholder: '默认15'
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remarks',
+      rules: [
+        {
+          required: false,
+          message: '备注不能为空',
+          trigger: 'blur'
+        }
+      ],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
+</script>
+
+<style lang="scss" scoped>
+.image-dialog {
+  display: flex;
+  justify-content: center;
+  .el-image {
+    margin-right: 20px;
+    margin-bottom: 20px;
+  }
+}
+.image-dialog-btn {
+  display: flex;
+  justify-content: center;
+  margin-top: 20px;
+}
+</style>

+ 3 - 3
src/views/login/index.vue

@@ -1,14 +1,14 @@
 <template>
   <div class="login-container flx-center">
     <div class="login-box">
-      <SwitchDark class="dark" />
+      <!-- <SwitchDark class="dark" /> -->
       <div class="login-left">
         <img class="login-left-img" src="@/assets/images/login_left.png" alt="login" />
       </div>
       <div class="login-form">
         <div class="login-logo">
           <img class="login-icon" src="@/assets/images/logo.svg" alt="" />
-          <h2 class="logo-text">KM-Admin</h2>
+          <h2 class="logo-text">算法任务系统</h2>
         </div>
         <LoginForm />
       </div>
@@ -18,7 +18,7 @@
 
 <script setup lang="ts" name="login">
 import LoginForm from './components/LoginForm.vue'
-import SwitchDark from '@/components/SwitchDark/index.vue'
+// import SwitchDark from '@/components/SwitchDark/index.vue'
 </script>
 
 <style scoped lang="scss">

+ 1 - 1
src/views/system/dict/data.vue

@@ -101,7 +101,7 @@ const openDialog = async (type: number, title: string, row?: any) => {
     width: 500,
     isEdit: type !== 3,
     itemsOptions: itemsOptions,
-    model: type == 1 ? { dictType: defaultDictType.value, version: 0 } : { ...res.data, version: 0 },
+    model: type == 1 ? { dictType: defaultDictType.value, version: 0 } : res.data,
     api: type == 1 ? addDataApi : updateDataApi,
     getTableList: proTable.value?.getTableList
   }

+ 0 - 1
src/views/system/user/index.vue

@@ -153,7 +153,6 @@ const roleOptions = ref<any[]>([])
 // 打开弹框的功能
 const openDialog = async (type: number, title: string, row?: any) => {
   let res = await getUserApi(row?.userId || null)
-  debugger
   postOptions.value = res.data.posts
   roleOptions.value = res.data.roles
   // 表单项配置

+ 1107 - 351
src/views/taais/homePage/createTask.vue

@@ -1,125 +1,342 @@
 <template>
   <div class="createTask-bigBox">
-    <dv-border-box1 ref="borderRef" style="width: 1000px; height: 650px; margin: 0 auto">
+    <dv-border-box1 ref="borderRef" style="width: 80%; height: calc(100% - 20px); margin: 0 auto">
       <div class="createTask-container">
-        <h3 class="title">{{ title }}</h3>
-        <h4 class="title2" v-if="pageIndex === 4">训练算法</h4>
-        <div v-for="(item, index) in formItems" :key="index">
-          <ProForm :items-options="item.items" :form-options="_options" :model="item.model" class="proform">
-            <template #transfer1="{ formModel }">
-              <el-transfer filterable v-model="formModel.transfer1" :data="transferImg1">
-                <template #default="{ option }">
-                  <el-image
-                    style="width: 50px; height: 50px"
-                    :preview-src-list="[getImageUrl(option.label)]"
-                    :src="getImageUrl(option.label)"
-                  ></el-image>
-                </template>
-              </el-transfer>
+        <h3 class="title" style="margin-top: 0">{{ title }}</h3>
+        <!-- <h4 class="title2" v-if="pageIndex === 4">训练算法</h4> -->
+        <div v-for="(item, index) in formItems" :key="index" class="createTask-main">
+          <ProForm ref="proFormRef" :items-options="item.items" :form-options="_options" :model="item.model" class="proform">
+            <template #selectTask>
+              <el-tree
+                style="max-width: 600px"
+                ref="selectTaskTreeRef"
+                :default-checked-keys="formItem.selectTask"
+                :default-expanded-keys="formItem.selectTask"
+                :props="defaultProps"
+                :check-on-click-node="true"
+                :data="data1"
+                node-key="id"
+                show-checkbox
+                @check="
+                  (click, checked) => {
+                    changeTree(click, checked)
+                  }
+                "
+              />
             </template>
-            <template #transfer2="{ formModel }">
-              <el-transfer filterable v-model="formModel.transfer2" :data="transferImg2">
-                <template #default="{ option }">
-                  <el-image
-                    style="width: 50px; height: 50px"
-                    :preview-src-list="[getImageUrl(option.label)]"
-                    :src="getImageUrl(option.label)"
-                  ></el-image>
-                </template>
-              </el-transfer>
+            <template #proTable="{}">
+              <el-tabs
+                tab-position="left"
+                v-model="activeName"
+                style="min-height: 450px"
+                class="demo-tabs"
+                :before-leave="beforeTabLeave"
+                @tab-click="handleClick"
+              >
+                <el-tab-pane label="数据一" name="one">
+                  <div class="table-box">
+                    <ProTable
+                      ref="proTable"
+                      row-key="id"
+                      :columns="dataColumns"
+                      :request-api="listDataApi"
+                      :tool-button="false"
+                      :init-param="initParam1"
+                      height="45vh"
+                    >
+                    </ProTable>
+                  </div>
+                </el-tab-pane>
+                <el-tab-pane label="数据二" name="two" :disabled="tabTwo">
+                  <div class="table-box">
+                    <ProTable
+                      ref="proTable"
+                      row-key="id"
+                      :columns="dataColumns"
+                      :request-api="listDataApi"
+                      :init-param="initParam2"
+                      :tool-button="false"
+                      height="45vh"
+                    >
+                    </ProTable>
+                  </div>
+                </el-tab-pane>
+                <el-tab-pane label="数据三" name="three" :disabled="tabThree">
+                  <div class="table-box">
+                    <ProTable
+                      ref="proTable"
+                      row-key="id"
+                      :columns="dataColumns"
+                      :request-api="listDataApi"
+                      :init-param="initParam3"
+                      :tool-button="false"
+                      height="45vh"
+                    >
+                    </ProTable>
+                  </div>
+                </el-tab-pane>
+                <el-tab-pane label="数据四" name="four" :disabled="tabFour">
+                  <div class="table-box">
+                    <ProTable
+                      ref="proTable"
+                      row-key="id"
+                      :columns="dataColumns"
+                      :request-api="listDataApi"
+                      :init-param="initParam4"
+                      :tool-button="false"
+                      height="45vh"
+                    >
+                    </ProTable>
+                  </div>
+                </el-tab-pane>
+              </el-tabs>
             </template>
-            <template #username1="{ formModel }">
-              <el-input v-model="formModel.username1" />
+            <template #operation="{}">
+              <div class="footBtn">
+                <el-button class="btn back" v-if="pageIndex === 1" @click="onReturn()"> 返回 </el-button>
+                <el-button class="btn back" v-else @click="onBack()"> 上一步 </el-button>
+                <el-button
+                  class="btn add"
+                  type="primary"
+                  v-if="(pageIndex === 2 || pageIndex === 6 || pageIndex === 10) && model.taskType === '1'"
+                  style="margin-left: -15px"
+                  @click="addNewData()"
+                >
+                  增加新一组数据
+                </el-button>
+                <el-button
+                  class="btn add"
+                  style="margin-left: -15px"
+                  v-if="(pageIndex === 5 || pageIndex === 9 || pageIndex === 13) && model.taskType === '2'"
+                  @click="onAdd(pageIndex)"
+                >
+                  增加算法
+                </el-button>
+                <el-button class="btn next" type="success" @click="nextBtnText === '提交' ? onSubmit() : onNext()">
+                  {{ nextBtnText }}
+                </el-button>
+              </div>
             </template>
           </ProForm>
         </div>
       </div>
-      <el-button class="btn back" v-if="pageIndex === 1" @click="onReturn()"> 返回 </el-button>
-      <el-button class="btn back" v-else @click="onBack()"> 上一步 </el-button>
-      <el-button class="btn add" style="margin-left: -15px" v-if="pageIndex === 4 || pageIndex === 7" @click="onAdd()"> 增加 </el-button>
-      <el-button class="btn next" type="success" @click="onNext()"> {{ nextBtnText }} </el-button>
     </dv-border-box1>
   </div>
 </template>
 <script setup lang="tsx" name="createTask">
-import { ref, ComputedRef, computed, watch } from 'vue'
+import { ref, ComputedRef, computed, watch, reactive } from 'vue'
 import ProForm from '@/components/ProForm/index.vue'
 import { useRouter } from 'vue-router'
-import { getTransferImgList1, getTransferImgList2 } from '@/api/modules/taais/task'
+import { getModelApi, getAlgorithmApi, createTaskApi } from '@/api/modules/taais/task'
+import ProTable from '@/components/ProTable/index.vue'
+import { ColumnProps, ProTableInstance } from '@/components/ProTable/interface'
+import { listDataApi } from '@/api/modules/demo/data'
+import { ElMessage, ElTree } from 'element-plus'
+import { getDictsApi } from '@/api/modules/system/dictData'
+import type { TabsPaneContext } from 'element-plus'
+// :request-api="getImageApi(1)" getImageApi,, Select, TabsPaneContext
+const activeName = ref('one')
+let tabTwo = ref(true)
+let tabThree = ref(true)
+let tabFour = ref(true)
+let initParam1 = reactive({ type: 1 })
+let initParam2 = reactive({ type: 2 })
+let initParam3 = reactive({ type: 3 })
+let initParam4 = reactive({ type: 4 })
+const proTable = ref<ProTableInstance>()
 let model = ref({
-  enhanceModel: '',
-  pretreatmentModel: ''
+  taskType: '2'
 })
-const router = useRouter()
-let pageIndex = ref<number>(1)
-let title = ref('目标精准捕获任务选择')
-let nextBtnText = ref('下一步')
-const transferImg1 = getTransferImgList1()
-const transferImg2 = getTransferImgList2()
-const enumData = [
-  {
-    label: 'rtdetr',
-    value: '1'
+
+let formItem = reactive({
+  taskName: '',
+  selectTask: [2, 5] as any,
+  trainDataSelect: [] as any[],
+  trainDataEnhancement: {
+    algorithmId: null,
+    params: []
   },
-  {
-    label: 'yolov5',
-    value: '2'
+  trainDataExpansion: {
+    algorithmId: null,
+    modelId: null,
+    params: []
   },
-  {
-    label: 'yolov8',
-    value: '3'
-  }
-]
-const enumDataModel = [
-  {
-    label: 'rtdetrModel',
-    value: '1'
+  train: [{}],
+  testDataSelect: [] as any[],
+  testDataEnhancement: {
+    algorithmId: null,
+    modelId: null,
+    params: []
   },
-  {
-    label: 'yolov5Model',
-    value: '2'
+  testDataExpansion: {
+    algorithmId: null,
+    modelId: null,
+    params: []
   },
-  {
-    label: 'yolov8Model',
-    value: '3'
-  }
-]
+  test: [{}],
+  reasoningDataSelect: [] as any[],
+  reasoningDataEnhancement: {
+    algorithmId: null,
+    modelId: null,
+    params: []
+  },
+  reasoningDataExpansion: {
+    algorithmId: null,
+    modelId: null,
+    params: []
+  },
+  reasoning: [{}]
+})
+let prevTreeData = ref([3])
+const router = useRouter()
+let subSystem, selectTrainAgloId, selectTestAgloId, selectReasoningAgloId
+const trainActiveTab = ref()
+const testActiveTab = ref()
+const reasoningActiveTab = ref()
+let pageIndex = ref<number>(1)
+const selectTaskTreeRef = ref<InstanceType<typeof ElTree>>()
+
+let title = ref('算法任务选择')
+let nextBtnText = ref('下一步')
 const data1 = [
   {
+    id: 98,
     label: '训练',
+    disabled: true,
     children: [
       {
+        id: 2,
         label: '训练数据选择',
         children: []
       },
       {
-        label: '训练数据预处理',
+        id: 3,
+        label: '训练数据增广',
         children: []
       },
       {
+        id: 5,
         label: '训练',
         children: []
       }
     ]
   },
   {
-    label: '推理',
+    id: 99,
+    label: '验证',
+    disabled: true,
     children: [
       {
-        label: '推理数据选择',
+        id: 6,
+        label: '验证数据选择',
         children: []
       },
       {
-        label: '推理数据预处理',
+        id: 9,
+        label: '验证',
+        children: []
+      }
+    ]
+  },
+  {
+    id: 100,
+    label: '测试',
+    disabled: true,
+    children: [
+      {
+        id: 10,
+        label: '测试数据选择',
         children: []
       },
       {
-        label: '推理',
+        id: 13,
+        label: '测试',
         children: []
       }
     ]
   }
 ]
+const taskTypeData = [
+  {
+    label: '单数据多算法',
+    value: '2'
+  },
+  {
+    label: '多数据单算法',
+    value: '1'
+  }
+]
+const dataColumns = reactive<ColumnProps<any>[]>([
+  { type: 'selection', fixed: 'left', width: 70 },
+  {
+    prop: 'name',
+    label: '名称',
+    width: 120
+  },
+  {
+    prop: 'dataType',
+    label: '数据类型',
+    width: 120
+  },
+  {
+    prop: 'fileType',
+    label: '文件类型',
+    width: 120
+  },
+  {
+    prop: 'batchNum',
+    label: '批次号',
+    search: {
+      el: 'input',
+      defaultValue: ''
+    },
+    width: 120
+  },
+  {
+    prop: 'objectType',
+    label: '目标类型',
+    search: {
+      el: 'input',
+      defaultValue: ''
+    },
+    width: 120
+  },
+  {
+    prop: 'objectSubtype',
+    label: '目标子类型',
+    search: {
+      el: 'input',
+      defaultValue: ''
+    },
+    width: 120
+  },
+
+  {
+    prop: 'scene',
+    label: '场景',
+    width: 120
+  },
+  {
+    prop: 'dataSource',
+    label: '数据源',
+    width: 120
+  },
+  {
+    prop: 'gatherTime',
+    label: '采集时间',
+    width: 120
+  },
+  {
+    prop: 'gatherSpot',
+    label: '采集地点',
+    width: 120
+  },
+  {
+    prop: 'increment',
+    label: '扩增方式',
+    width: 120
+  }
+])
 const defaultProps = {
   children: 'children',
   label: 'label'
@@ -134,492 +351,1031 @@ const _options: ComputedRef<ProForm.FormOptions> = computed(() => {
   return Object.assign(form)
 })
 
-let items: ProForm.ItemsOptions[] = [
+let items = reactive<ProForm.ItemsOptions[]>([
+  // 1
   {
-    // formItemOptions: {
     label: '任务名称',
     prop: 'taskName',
     span: 12,
+    rules: [{ required: true, message: '请输入任务名称' }],
     show: () => {
       return pageIndex.value === 1 ? true : false
     },
-    // },
     compOptions: {
       elTagName: 'input',
       clearable: true,
-      placeholder: '请输入任务名称'
+      placeholder: '请输入任务名称',
+      onChange: val => {
+        formItem.taskName = val
+      }
     }
   },
   {
-    // formItemOptions: {
     label: '任务选择',
-    prop: 'treeName',
+    prop: 'selectTask',
     show: () => {
       return pageIndex.value === 1 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'tree',
-      enum: data1,
-      props: defaultProps,
-      style: 'max-width: 600px',
-      defaultExpandAll: true,
-      showCheckbox: true
+      elTagName: 'slot'
     }
   },
   {
-    // formItemOptions: {
-    label: '选择训练数据',
-    prop: 'transfer1',
+    label: '任务类型',
+    prop: 'taskType',
     show: () => {
-      return pageIndex.value === 2 ? true : false
+      return pageIndex.value === 1 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'slot',
-      filterable: true
+      elTagName: 'radio-group',
+      enum: taskTypeData
     }
   },
   {
-    // formItemOptions: {
-    label: '增强算法',
-    prop: 'enhanceAlgo',
-    span: 14,
+    label: '选择子系统',
+    prop: 'subConfig',
+    span: 12,
+    rules: [{ required: true, message: '请选择子系统' }],
     show: () => {
-      return pageIndex.value === 3 ? true : false
+      return pageIndex.value === 1 ? true : false
     },
-    // },
     compOptions: {
       elTagName: 'select',
-      enum: enumData,
+      labelKey: 'dictLabel',
+      valueKey: 'dictValue',
+      enum: () => getDictsApi('biz_sub_config'),
       onChange: val => {
-        model.value.enhanceModel = val
+        subSystem = val
       }
     }
   },
+  // 2  6  10
   {
-    // formItemOptions: {
-    label: '增强模型',
-    prop: 'enhanceModel',
-    span: 14,
+    label: '',
+    hideLabelSuffix: true,
+    labelWidth: '0px',
+    prop: 'proTable',
     show: () => {
-      return pageIndex.value === 3 ? true : false
+      return pageIndex.value === 2 || pageIndex.value === 6 || pageIndex.value === 10 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'select',
-      enum: enumDataModel
+      elTagName: 'slot'
     }
   },
+  // 3
   {
-    // formItemOptions: {
-    label: '参数1',
-    prop: 'threeParameter1',
+    label: '选择增广算法',
+    prop: 'trainEnhanceAlgo',
     span: 14,
+    rules: [{ required: true, message: '选择增广算法' }],
     show: () => {
       return pageIndex.value === 3 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
-    }
-  },
-  {
-    // formItemOptions: {
-    label: '参数2',
-    prop: 'threeParameter2',
-    span: 14,
-    show: () => {
-      return pageIndex.value === 3 ? true : false
-    },
-    // },
-    compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
+      elTagName: 'select',
+      labelKey: 'algorithmName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        formItem.trainDataEnhancement.algorithmId = val
+        // agloChange('trainEnhanceModel', val)
+        // getAgloParams(4, 'trainDataEnhancement', 3, val)
+        getAlgorithmApi(4, subSystem).then(res => {
+          res.data.forEach(item => {
+            if (item.id === val) {
+              const params = eval('(' + item.parameterConfig + ')')
+              formItem['trainDataEnhancement'].params = params
+              addForm(params, 3, 'trainDataEnhancement')
+            }
+          })
+        })
+      }
     }
   },
+  // {
+  //   label: '选择增广模型',
+  //   prop: 'trainEnhanceModel',
+  //   span: 14,
+  //   rules: [{ required: true, message: '选择增广模型' }],
+  //   show: () => {
+  //     return pageIndex.value === 3 ? true : false
+  //   },
+  //   compOptions: {
+  //     elTagName: 'select',
+  //     labelKey: 'modelName',
+  //     valueKey: 'id',
+  //     enum: null,
+  //     onChange: val => {
+  //       getAgloParams(4, 'trainDataEnhancement', 3, val)
+  //     }
+  //   }
+  // },
+  // 4
   {
-    // formItemOptions: {
-    label: '参数3',
-    prop: 'threeParameter3',
+    label: '选择扩充算法',
+    prop: 'trainExpansionAlgo',
     span: 14,
+    rules: [{ required: true, message: '选择扩充算法' }],
     show: () => {
-      return pageIndex.value === 3 ? true : false
+      return pageIndex.value === 4 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
+      elTagName: 'select',
+      labelKey: 'algorithmName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        formItem.trainDataExpansion.algorithmId = val
+        agloChange('trainExpansionModel', val, false)
+      }
     }
   },
   {
-    // formItemOptions: {
-    label: '参数4',
-    prop: 'threeParameter4',
+    label: '选择扩充模型',
+    prop: 'trainExpansionModel',
     span: 14,
+    rules: [{ required: true, message: '选择扩充模型' }],
     show: () => {
-      return pageIndex.value === 3 ? true : false
+      return pageIndex.value === 4 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
+      elTagName: 'select',
+      labelKey: 'modelName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        getAgloParams(5, 'trainDataExpansion', 4, val)
+      }
     }
   },
+  // 5
   {
-    // formItemOptions: {
-    label: '训练算法',
+    label: '选择训练算法',
     prop: 'trainAlgo',
     span: 14,
+    rules: [{ required: true, message: '请选择训练算法' }],
     show: () => {
-      return pageIndex.value === 4 ? true : false
+      return pageIndex.value === 5 ? true : false
     },
-    // },
     compOptions: {
       elTagName: 'select',
-      enum: enumData
+      labelKey: 'algorithmName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        selectTrainAgloId = val
+        formItem.train[0]['algorithmId'] = val
+        agloChange('trainModel', val, false)
+      }
     }
   },
   {
-    // formItemOptions: {
-    label: '预训练模型权重文件路径',
-    prop: 'fourParameter1',
-    labelWidth: 180,
+    label: '选择训练模型',
+    prop: 'trainModel',
     span: 14,
+    rules: [{ required: true, message: '请选择训练模型' }],
     show: () => {
-      return pageIndex.value === 4 ? true : false
+      return pageIndex.value === 5 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
+      elTagName: 'select',
+      labelKey: 'modelName',
+      valueKey: 'id',
+      // enum: () => getModelApi(1),
+      enum: null,
+      onChange: val => {
+        getAlgorithmApi(1, subSystem).then(res => {
+          res.data.forEach(item => {
+            if (item.id === selectTrainAgloId) {
+              const params = eval('(' + item.parameterConfig + ')')
+              formItem.train[0]['modelId'] = val
+              formItem.train[0]['params'] = params
+              // (参数信息,第几页,数组名称)
+              addForm(params, 5, 'train[0]')
+            }
+          })
+        })
+      }
     }
   },
+  // 7
   {
-    // formItemOptions: {
-    label: '模型结构配置文件路径',
-    prop: 'fourParameter2',
-    labelWidth: 180,
+    label: '选择增强算法',
+    prop: 'testEnhanceAlgo',
     span: 14,
+    rules: [{ required: true, message: '选择增强算法' }],
     show: () => {
-      return pageIndex.value === 4 ? true : false
+      return pageIndex.value === 7 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
+      elTagName: 'select',
+      labelKey: 'algorithmName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        formItem.testDataEnhancement['algorithmId'] = val
+        agloChange('testEnhanceModel', val, false)
+      }
     }
   },
   {
-    // formItemOptions: {
-    label: '训练轮数',
-    prop: 'fourParameter3',
-    labelWidth: 180,
+    label: '选择增强模型',
+    prop: 'testEnhanceModel',
     span: 14,
+    rules: [{ required: true, message: '选择增强模型' }],
     show: () => {
-      return pageIndex.value === 4 ? true : false
+      return pageIndex.value === 7 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
+      elTagName: 'select',
+      labelKey: 'modelName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        if (val === 1) {
+          return
+        } else {
+          getAgloParams(4, 'testDataEnhancement', 7, val)
+        }
+      }
     }
   },
+  // 8
   {
-    // formItemOptions: {
-    label: '余弦学习率调度器',
-    prop: 'fourParameter4',
-    labelWidth: 180,
+    label: '选择扩充算法',
+    prop: 'testExpansionAlgo',
     span: 14,
+    rules: [{ required: true, message: '选择扩充算法' }],
     show: () => {
-      return pageIndex.value === 4 ? true : false
-    },
-    // },
-    compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
-    }
-  },
-  {
-    // formItemOptions: {
-    label: '选择推理数据',
-    prop: 'transfer2',
-    show: () => {
-      return pageIndex.value === 5 ? true : false
+      return pageIndex.value === 8 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'slot',
-      filterable: true
+      elTagName: 'select',
+      labelKey: 'algorithmName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        formItem.testDataExpansion['algorithmId'] = val
+        agloChange('testExpansionModel', val, false)
+      }
     }
   },
   {
-    // formItemOptions: {
-    label: '预处理算法',
-    prop: 'pretreatmentAlgo',
+    label: '选择扩充模型',
+    prop: 'testExpansionModel',
     span: 14,
+    rules: [{ required: true, message: '选择扩充模型' }],
     show: () => {
-      return pageIndex.value === 6 ? true : false
+      return pageIndex.value === 8 ? true : false
     },
-    // },
     compOptions: {
       elTagName: 'select',
-      enum: enumData,
+      labelKey: 'modelName',
+      valueKey: 'id',
+      enum: null,
       onChange: val => {
-        model.value.pretreatmentModel = val
+        if (val === 1) {
+          return
+        } else {
+          getAgloParams(5, 'testDataExpansion', 8, val)
+        }
       }
     }
   },
+  // 9
   {
-    // formItemOptions: {
-    label: '预处理模型',
-    prop: 'pretreatmentModel',
+    label: '选择验证算法',
+    prop: 'testAlgo',
     span: 14,
+    rules: [{ required: true, message: '请选择验证算法' }],
     show: () => {
-      return pageIndex.value === 6 ? true : false
+      return pageIndex.value === 9 ? true : false
     },
-    // },
     compOptions: {
       elTagName: 'select',
-      enum: enumDataModel
-    }
-  },
-  {
-    // formItemOptions: {
-    label: '参数1',
-    prop: 'sixParameter1',
-    span: 12,
-    show: () => {
-      return pageIndex.value === 6 ? true : false
-    },
-    // },
-    compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
-    }
-  },
-  {
-    // formItemOptions: {
-    label: '参数2',
-    prop: 'sixParameter2',
-    span: 12,
-    show: () => {
-      return pageIndex.value === 6 ? true : false
-    },
-    // },
-    compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
+      labelKey: 'algorithmName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        selectTestAgloId = val
+        formItem.test[0]['algorithmId'] = val
+        agloChange('testModel', val, true)
+      }
     }
   },
   {
-    // formItemOptions: {
-    label: '参数3',
-    prop: 'sixParameter3',
-    span: 12,
+    label: '选择验证模型',
+    prop: 'testModel',
+    span: 14,
+    rules: [{ required: true, message: '请选择验证模型' }],
     show: () => {
-      return pageIndex.value === 6 ? true : false
+      return pageIndex.value === 9 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
+      elTagName: 'select',
+      labelKey: 'modelName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        // getAgloParams(1, 'test[0]', 9, val, 0)
+        getAlgorithmApi(2, subSystem).then(res => {
+          res.data.forEach(item => {
+            if (item.id === selectTestAgloId) {
+              const params = eval('(' + item.parameterConfig + ')')
+              formItem.test[0]['modelId'] = val
+              formItem.test[0]['params'] = params
+              // (参数信息,第几页,数组名称)
+              addForm(params, 9, 'test[0]')
+            }
+          })
+        })
+      }
     }
   },
+  // 11
   {
-    // formItemOptions: {
-    label: '参数4',
-    prop: 'sixParameter4',
-    span: 12,
+    label: '选择增强算法',
+    prop: 'reasoningEnhanceAlgo',
+    span: 14,
+    rules: [{ required: true, message: '选择增强算法' }],
     show: () => {
-      return pageIndex.value === 6 ? true : false
+      return pageIndex.value === 11 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
+      elTagName: 'select',
+      labelKey: 'algorithmName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        formItem.reasoningDataEnhancement['algorithmId'] = val
+        agloChange('reasoningEnhanceModel', val, false)
+      }
     }
   },
   {
-    // formItemOptions: {
-    label: '推理算法',
-    prop: 'inferAlgo',
+    label: '选择增强模型',
+    prop: 'reasoningEnhanceModel',
     span: 14,
+    rules: [{ required: true, message: '选择增强模型' }],
     show: () => {
-      return pageIndex.value === 7 ? true : false
+      return pageIndex.value === 11 ? true : false
     },
-    // },
     compOptions: {
       elTagName: 'select',
-      enum: enumData
+      labelKey: 'modelName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        if (val === 1) {
+          return
+        } else {
+          getAgloParams(4, 'reasoningDataEnhancement', 11, val)
+        }
+      }
     }
   },
+  // 12
   {
-    // formItemOptions: {
-    label: '目标置信度阈值',
-    prop: 'sevenParameter1',
-    labelWidth: 180,
+    label: '选择扩充算法',
+    prop: 'reasoningExpansionAlgo',
     span: 14,
+    rules: [{ required: true, message: '选择扩充算法' }],
     show: () => {
-      return pageIndex.value === 7 ? true : false
+      return pageIndex.value === 12 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
+      elTagName: 'select',
+      labelKey: 'algorithmName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        formItem.reasoningDataExpansion['algorithmId'] = val
+        agloChange('reasoningExpansionModel', val, false)
+      }
     }
   },
   {
-    // formItemOptions: {
-    label: '非极大值抑制的IoU阈值',
-    prop: 'sevenParameter2',
-    labelWidth: 180,
+    label: '选择扩充模型',
+    prop: 'reasoningExpansionModel',
+    rules: [{ required: true, message: '选择扩充模型' }],
     span: 14,
     show: () => {
-      return pageIndex.value === 7 ? true : false
+      return pageIndex.value === 12 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
+      elTagName: 'select',
+      labelKey: 'modelName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        if (val === 1) {
+          return
+        } else {
+          getAgloParams(5, 'reasoningDataExpansion', 12, val)
+        }
+      }
     }
   },
+  // 13
   {
-    // formItemOptions: {
-    label: '测试图片最大检测器数',
-    prop: 'sevenParameter3',
-    labelWidth: 180,
+    label: '选择测试算法',
+    prop: 'reasoningAlgo',
     span: 14,
+    rules: [{ required: true, message: '请选择测试算法' }],
     show: () => {
-      return pageIndex.value === 7 ? true : false
+      return pageIndex.value === 13 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
+      elTagName: 'select',
+      labelKey: 'algorithmName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        selectReasoningAgloId = val
+        formItem.reasoning[0]['algorithmId'] = val
+        agloChange('reasoningModel', val, true)
+      }
     }
   },
   {
-    // formItemOptions: {
-    label: '使用半精度推理(FP16)',
-    prop: 'sevenParameter4',
-    labelWidth: 180,
+    label: '选择测试模型',
+    prop: 'reasoningModel',
     span: 14,
+    rules: [{ required: true, message: '请选择测试模型' }],
     show: () => {
-      return pageIndex.value === 7 ? true : false
+      return pageIndex.value === 13 ? true : false
     },
-    // },
     compOptions: {
-      elTagName: 'input',
-      clearable: true,
-      placeholder: '请输入...'
+      elTagName: 'select',
+      labelKey: 'modelName',
+      valueKey: 'id',
+      enum: null,
+      onChange: val => {
+        getAlgorithmApi(3, subSystem).then(res => {
+          res.data.forEach(item => {
+            if (item.id === selectReasoningAgloId) {
+              const params = eval('(' + item.parameterConfig + ')')
+              formItem.reasoning[0]['modelId'] = val
+              formItem.reasoning[0]['params'] = params
+              // (参数信息,第几页,数组名称)
+              addForm(params, 13, 'reasoning[0]')
+            }
+          })
+        })
+      }
     }
   }
-]
-let formItemsTemp4 = ref([
-  {
-    items,
-    model: model.value
-  }
-])
-let formItemsTemp7 = ref([
-  {
-    items,
-    model: model.value
-  }
 ])
+// let formItemsTemp2 = ref([
+//   {
+//     items,
+//     model: model.value
+//   }
+// ])
+// let formItemsTemp7 = ref([
+//   {
+//     items,
+//     model: model.value
+//   }
+// ])
 let formItems = ref([
   {
     items,
     model: model.value
   }
 ])
+let addTrainAgloIndex = 1
+let addTestAgloIndex = 1
+let addReasoningAgloIndex = 1
+const proFormRef = ref<InstanceType<typeof ProForm> | null>(null)
 // 增加训练算法
-const onAdd = () => {
-  const data = {
-    enhanceModel: '',
-    pretreatmentModel: ''
+const onAdd = pageNum => {
+  let arrayName, agloIndex, type, name
+  switch (pageNum) {
+    case 5:
+      arrayName = 'train'
+      agloIndex = addTrainAgloIndex
+      type = 1
+      name = '训练'
+      break
+    case 9:
+      arrayName = 'test'
+      agloIndex = addTestAgloIndex
+      type = 2
+      name = '验证'
+      break
+    case 13:
+      arrayName = 'reasoning'
+      agloIndex = addReasoningAgloIndex
+      type = 3
+      name = '测试'
+      break
+    default:
+      break
   }
-  formItems.value.push({
-    items,
-    model: data
+  const formEl = proFormRef.value![0].proFormRef
+  if (!formEl) return
+  formEl.validate(valid => {
+    if (valid) {
+      if (agloIndex >= 4) {
+        ElMessage.warning('最多选择四种算法,已选择了四种')
+        return
+      }
+      getAlgorithmApi(type, subSystem).then(res1 => {
+        formItems.value[0].items.push({
+          label: `选择${name}算法`,
+          prop: `${arrayName}Aglo${agloIndex}`,
+          span: 14,
+          rules: [{ required: true, message: `请选择${name}算法` }],
+          show: () => {
+            return pageIndex.value === pageNum ? true : false
+          },
+          compOptions: {
+            elTagName: 'select',
+            labelKey: 'algorithmName',
+            valueKey: 'id',
+            enum: res1.data,
+            onChange: agloId => {
+              formItem[`${arrayName}`].push({})
+              if (!formItem[`${arrayName}`][`${agloIndex}`]['algorithmId']) {
+                formItem[`${arrayName}`][`${agloIndex}`]['algorithmId'] = agloId
+                getModelApi(agloId).then(res2 => {
+                  if (type === 2 || type === 3) {
+                    res2.data.unshift({
+                      algorithmId: null,
+                      id: 1,
+                      modelAddress: null,
+                      modelName: '基于训练生成的模型'
+                    })
+                  }
+                  formItems.value[0].items.push({
+                    label: `选择${name}模型`,
+                    prop: `${arrayName}Model${agloIndex}`,
+                    span: 14,
+                    rules: [{ required: true, message: `请选择${name}模型` }],
+                    show: () => {
+                      return pageIndex.value === pageNum ? true : false
+                    },
+                    compOptions: {
+                      elTagName: 'select',
+                      labelKey: 'modelName',
+                      valueKey: 'id',
+                      enum: res2.data,
+                      onChange: modelId => {
+                        res1.data.forEach(agloItem => {
+                          if (agloItem.id === agloId) {
+                            const params = eval('(' + agloItem.parameterConfig + ')')
+                            formItem[`${arrayName}`][`${agloIndex}`]['modelId'] = modelId
+                            formItem[`${arrayName}`][`${agloIndex}`]['params'] = params
+                            addForm(params, pageNum, `${arrayName}[${agloIndex}]`)
+                          }
+                        })
+                      }
+                    }
+                  })
+                })
+              } else {
+                formItem[`${arrayName}`].splice(-1, 1)
+                formItem[`${arrayName}`][`${agloIndex}`]['algorithmId'] = agloId
+                const column = formItems.value[0].items.find(column => column.prop === `${arrayName}Model${agloIndex}`)
+                if (column) {
+                  getModelApi(agloId).then(newRes => {
+                    column.compOptions.enum = newRes.data
+                  })
+                }
+              }
+            }
+          }
+        })
+      })
+      if (pageNum === 5) addTrainAgloIndex++
+      if (pageNum === 9) addTestAgloIndex++
+      if (pageNum === 13) addReasoningAgloIndex++
+    }
   })
 }
-const getImageUrl = name => {
-  return new URL(`../../../assets/taaisImg/${name}`, import.meta.url).href
+// 增加新数据
+const addNewData = () => {
+  switch (activeName.value) {
+    case 'one':
+      const table0Ref = proTable.value![0]
+      if (table0Ref.tableData.length === 0) {
+        ElMessage.warning('请正确选择该组数据')
+      } else {
+        tabTwo.value = false
+        activeName.value = 'two'
+      }
+      break
+    case 'two':
+      const table1Ref = proTable.value![1]
+      if (table1Ref.tableData.length === 0) {
+        ElMessage.warning('请正确选择该组数据')
+      } else {
+        tabThree.value = false
+        activeName.value = 'three'
+      }
+      break
+    case 'three':
+      const table2Ref = proTable.value![2]
+      if (table2Ref.tableData.length === 0) {
+        ElMessage.warning('请正确选择该组数据')
+      } else {
+        tabFour.value = false
+        activeName.value = 'four'
+      }
+      break
+    default:
+      break
+  }
+}
+// 增加表单事件
+const addForm = (params, index, arrayName) => {
+  params.forEach(item => {
+    let i = formItems.value[0].items.length
+    formItems.value[0].items.push({
+      label: item.name,
+      prop: `task${i}`,
+      span: 12,
+      rules: [
+        { required: item.required, message: `${item.name}不能为空` },
+        { pattern: new RegExp(`${item.validate}`), message: item.prompt }
+      ],
+      show: () => {
+        return pageIndex.value === index ? true : false
+      },
+      compOptions: {
+        elTagName: 'input',
+        clearable: true,
+        placeholder: item.prompt,
+        onChange: val1 => {
+          let label = item.name
+          if (arrayName.includes(']')) {
+            // 截取数组和下标
+            let array = arrayName.slice(0, -3)
+            let index = arrayName.slice(-2, -1)
+            const item = formItem[`${array}`][`${index}`]['params'].find(item => item.name === label)
+            if (item) {
+              item.value = val1
+            }
+          } else {
+            const item1 = formItem[`${arrayName}`]['params'].find(item => item.name === label)
+            if (item1) {
+              item1.value = val1
+            }
+          }
+        }
+      }
+    })
+    formItems.value[0].model[`task${i}`] = item.defaultValue
+  })
+}
+const agloChange = (propName, agloId, flag) => {
+  const Column = formItems.value[0].items.find(column => column.prop === propName)
+  if (Column) {
+    getModelApi(agloId).then(res => {
+      Column.compOptions.enum = res.data as any[]
+      if (flag) {
+        Column.compOptions.enum.unshift({
+          algorithmId: null,
+          id: 1,
+          modelAddress: null,
+          modelName: '基于训练生成的模型'
+        })
+      }
+    })
+  }
+}
+const getAlgo = (propName, type) => {
+  const column = formItems.value[0].items.find(column => column.prop === propName)
+  if (column) {
+    getAlgorithmApi(type, subSystem).then(res => {
+      column.compOptions.enum = res.data as any[]
+    })
+  }
+}
+// 算法类型:1-5、算法类型名称:如训练数据增强、页码、模型Id、模型的参数下标
+const getAgloParams = (agloType, agloTypeName, page, modelId) => {
+  const algoId = formItem[agloTypeName].algorithmId
+  getAlgorithmApi(agloType, subSystem).then(res => {
+    res.data.forEach(item => {
+      if (item.id === algoId) {
+        const params = eval('(' + item.parameterConfig + ')')
+        formItem[agloTypeName].modelId = modelId
+        formItem[agloTypeName].params = params
+        // (参数信息,第几页,数组名称)
+        addForm(params, page, agloTypeName)
+      }
+    })
+  })
 }
+// 返回按钮
 const onReturn = () => {
   router.push(`/index`)
 }
 let flag = ref<number>(0) //跳转到日志页面的flag,0为训练日志、1为推理日志
+// 下一步
 const onNext = () => {
-  if (pageIndex.value === 7) {
-    router.push({ path: `/logPage/${flag.value}`, query: { type: 1 } })
+  const formEl = proFormRef.value![0].proFormRef
+  if (!formEl) return
+  formEl.validate(valid => {
+    if (valid) {
+      if (formItem.selectTask.length === 0 && pageIndex.value === 1) {
+        ElMessage.warning('请选择任务')
+        return
+      }
+      if (pageIndex.value === 2 || pageIndex.value === 6 || pageIndex.value === 10) {
+        let dataName, flag
+        if (pageIndex.value === 2) dataName = 'trainDataSelect'
+        else if (pageIndex.value === 6) dataName = 'testDataSelect'
+        else if (pageIndex.value === 10) dataName = 'reasoningDataSelect'
+        switch (activeName.value) {
+          case 'one':
+            flag = 0
+            break
+          case 'two':
+            flag = 1
+            break
+          case 'three':
+            flag = 2
+            break
+          case 'false':
+            flag = 3
+            break
+          default:
+            break
+        }
+        formItem[dataName] = []
+        for (let i = 0; i <= flag; i++) {
+          if (proTable.value![i].tableData.length === 0) {
+            ElMessage.warning('数据存在空集,请正确选择数据')
+            return
+          }
+          let obj = proTable.value![i].searchParam
+          for (let key in obj) {
+            if (obj.hasOwnProperty(key) && obj[key] === '') {
+              delete obj[key]
+            }
+          }
+          formItem[dataName].push({
+            conditionSelected: true,
+            condition: proTable.value![i].searchParam
+          })
+        }
+        activeName.value = 'one'
+        tabTwo.value = true
+        tabThree.value = true
+        tabFour.value = true
+      }
+      if (pageIndex.value === formItem.selectTask[formItem.selectTask.length - 2]) {
+        nextBtnText.value = '提交'
+      }
+      if (pageIndex.value === formItem.selectTask[formItem.selectTask.length - 1]) {
+        console.log('flag.value', flag.value)
+        return
+        // router.push({ path: `/logPage/${flag.value}`, query: { type: 1 } })
+      }
+
+      pageIndex.value = formItem.selectTask[formItem.selectTask.findIndex(page => page === pageIndex.value) + 1]
+    }
+  })
+}
+// 上一步
+const onBack = () => {
+  nextBtnText.value = '下一步'
+  if (pageIndex.value === formItem.selectTask[0]) {
+    pageIndex.value = 1
+    return
+  }
+  pageIndex.value = formItem.selectTask[formItem.selectTask.findIndex(page => page === pageIndex.value) - 1]
+}
+const changeTree = (newVal, allVal) => {
+  const newId = newVal.id
+  const dependencyMap = new Map([
+    [
+      [3, 4, 2, 5],
+      [2, 5]
+    ], // 如果包含3或4,则添加2和5
+    [
+      [7, 8, 6, 9],
+      [6, 9]
+    ],
+    [
+      [11, 12, 10, 13],
+      [10, 13]
+    ]
+  ])
+  const updateCheckedKeys = keysSet => {
+    prevTreeData.value = Array.from(keysSet)
+    selectTaskTreeRef.value![0].setCheckedKeys(Array.from(keysSet), false)
+  }
+
+  if (!allVal.checkedKeys.includes(newId)) {
+    if (newId === 2 || newId === 5) {
+      const data = delNumber(2, 5, allVal.checkedKeys)
+      updateCheckedKeys(new Set(data))
+    } else if (newId === 6 || newId === 9) {
+      const data = delNumber(6, 9, allVal.checkedKeys)
+      updateCheckedKeys(new Set(data))
+    } else if (newId === 10 || newId === 13) {
+      const data = delNumber(10, 13, allVal.checkedKeys)
+      updateCheckedKeys(new Set(data))
+    }
   } else {
-    pageIndex.value++
+    for (const [keysToCheck, keysToAdd] of dependencyMap.entries()) {
+      if (keysToCheck.includes(newId)) {
+        const keysSet = new Set([...allVal.checkedKeys, ...keysToAdd])
+        updateCheckedKeys(keysSet)
+        break
+      }
+    }
   }
+
+  formItem.selectTask = selectTaskTreeRef.value![0].getCheckedKeys().filter(item => item < 90)
 }
-const onBack = () => {
-  if (pageIndex.value === 1) return
-  pageIndex.value--
+// 树组件去除节点函数
+const delNumber = (start, end, dataList) => {
+  return dataList.filter(number => !(number >= start && number <= end)).sort((a, b) => a - b)
 }
-watch(
-  () => pageIndex.value,
-  (value, oldValue) => {
-    if (oldValue === 4) formItemsTemp4.value = formItems.value
-    if (oldValue === 7) formItemsTemp7.value = formItems.value
-    if (value === 4) formItems.value = formItemsTemp4.value
-    if (value === 7) formItems.value = formItemsTemp7.value
-    else {
-      formItems.value = [
-        {
-          items,
-          model: model.value
+const onSubmit = () => {
+  findParams(formItem)
+  createTaskApi(formItem).then(res => {
+    console.log('createTask', res)
+    // const taskId=res
+    router.push({ path: `/task/subtask/`, query: { id: res.data } })
+  })
+}
+const findParams = obj => {
+  for (const key in obj) {
+    if (obj[key] !== null && typeof obj[key] === 'object') {
+      // 如果值是数组,则遍历数组中的每个元素
+      if (Array.isArray(obj[key])) {
+        obj[key].forEach(item => {
+          if (item.params) {
+            item.params.forEach(paramItem => {
+              if (!paramItem.value) {
+                paramItem.value = paramItem.defaultValue
+              }
+            })
+            item.params = JSON.stringify(item.params)
+          }
+          if (item.condition) {
+            item.condition = JSON.stringify(item.condition)
+          }
+        })
+      } else {
+        // 如果值是对象
+        if (obj[key]['params']) {
+          obj[key]['params'].forEach(paramItem => {
+            if (!paramItem.value) {
+              paramItem.value = paramItem.defaultValue
+            }
+          })
+          obj[key]['params'] = JSON.stringify(obj[key]['params'])
         }
-      ]
+        if (obj[key]['condition']) {
+          obj[key]['condition'] = JSON.stringify(obj[key]['condition'])
+        }
+      }
     }
-    0
+  }
+}
+const beforeTabLeave = (activeName, oldActiveName) => {
+  let dataSelect1
+  if (pageIndex.value === 2) {
+    dataSelect1 = 'trainDataSelect'
+  } else if (pageIndex.value === 6) {
+    dataSelect1 = 'testDataSelect'
+  } else if (pageIndex.value === 10) {
+    dataSelect1 = 'reasoningDataSelect'
+  }
 
+  switch (oldActiveName) {
+    case 'one':
+      Object.assign(initParam1, formItem[dataSelect1]?.[0]?.condition)
+      break
+    case 'two':
+      Object.assign(initParam2, formItem[dataSelect1]?.[1]?.condition)
+      break
+    case 'three':
+      Object.assign(initParam3, formItem[dataSelect1]?.[2]?.condition)
+      break
+    case 'four':
+      Object.assign(initParam4, formItem[dataSelect1]?.[3]?.condition)
+      break
+    default:
+      break
+  }
+}
+const handleClick = (tab: TabsPaneContext) => {
+  let dataSelect
+  if (pageIndex.value === 2) {
+    dataSelect = 'trainDataSelect'
+    trainActiveTab.value = tab
+  } else if (pageIndex.value === 6) {
+    dataSelect = 'testDataSelect'
+    testActiveTab.value = tab
+  } else if (pageIndex.value === 10) {
+    dataSelect = 'reasoningDataSelect'
+    reasoningActiveTab.value = tab
+  }
+  const tabName = tab.paneName
+  switch (tabName) {
+    case 'one':
+      const data1 = formItem[dataSelect]?.[0]?.condition
+      console.log('data1', data1)
+      updateDefaultValue(data1?.objectType, data1?.objectSubtype, data1?.batchNum)
+      break
+    case 'two':
+      const data2 = formItem[dataSelect]?.[1]?.condition
+      console.log('data2', data2)
+      updateDefaultValue(data2?.objectType, data2?.objectSubtype, data2?.batchNum)
+      break
+    case 'three':
+      const data3 = formItem[dataSelect]?.[2]?.condition
+      console.log('data3', data3)
+      updateDefaultValue(data3?.objectType, data3?.objectSubtype, data3?.batchNum)
+      break
+    case 'four':
+      const data4 = formItem[dataSelect]?.[3]?.condition
+      updateDefaultValue(data4?.objectType, data4?.objectSubtype, data4?.batchNum)
+      break
+    default:
+      break
+  }
+}
+const updateDefaultValue = (objectType, objectSubtype, batchNum) => {
+  dataColumns.map(column => {
+    if (column.prop === 'objectType') {
+      column.search!.defaultValue = objectType
+    } else if (column.prop === 'objectSubtype') {
+      column.search!.defaultValue = objectSubtype
+    } else if (column.prop === 'batchNum') {
+      column.search!.defaultValue = batchNum
+    }
+  })
+}
+watch(
+  () => pageIndex.value,
+  value => {
     switch (value) {
       case 1:
-        title.value = '任务创建:目标精准捕获任务选择'
+        title.value = '任务创建:算法任务选择'
         nextBtnText.value = '下一步'
         break
       case 2:
-        title.value = '任务创建:训练数据选择'
+        title.value = '训练数据选择'
+        if (trainActiveTab.value) {
+          handleClick(trainActiveTab.value)
+        }
         break
       case 3:
-        title.value = '任务创建:训练数据预处理'
+        title.value = '训练数据增广'
+        getAlgo('trainEnhanceAlgo', 4)
         break
       case 4:
-        title.value = '任务创建:训练参数设置'
+        title.value = '训练数据扩充'
+        getAlgo('trainExpansionAlgo', 5)
         break
       case 5:
-        title.value = '任务创建:推理数据选择'
+        title.value = '训练算法选择'
+        getAlgo('trainAlgo', 1)
         break
       case 6:
-        title.value = '任务创建:推理数据预处理'
-        nextBtnText.value = '下一步'
+        title.value = '验证数据选择'
+        if (testActiveTab.value) {
+          handleClick(testActiveTab.value)
+        }
         break
       case 7:
-        title.value = '任务创建:推理参数设置'
-        nextBtnText.value = '提交'
+        title.value = '测试数据增强'
+        getAlgo('testEnhanceAlgo', 4)
+        break
+      case 8:
+        title.value = '测试数据扩充'
+        getAlgo('testExpansionAlgo', 5)
+        break
+      case 9:
+        title.value = '验证算法选择'
+        getAlgo('testAlgo', 2)
+        break
+      case 10:
+        title.value = '测试数据选择'
+        if (reasoningActiveTab.value) {
+          handleClick(reasoningActiveTab.value)
+        }
+        break
+      case 11:
+        title.value = '推理数据增强'
+        getAlgo('reasoningEnhanceAlgo', 4)
+        break
+      case 12:
+        title.value = '推理数据扩充'
+        getAlgo('reasoningExpansionAlgo', 5)
+        nextBtnText.value = '下一步'
+        break
+      case 13:
+        title.value = '测试算法选择'
+        getAlgo('reasoningAlgo', 3)
         break
       default:
         break

+ 44 - 25
src/views/taais/homePage/index.scss

@@ -6,15 +6,19 @@
     padding: 60px 65px 60px 85px;
   }
 }
-:deep(.card) {
-  width: 100%;
-  height: 100%;
-  background-image: url('../../../assets/taaisImg/53bg.png');
-  background-repeat: no-repeat;
-  background-size: 100% 100%;
-
-  // color: black;
+:deep(::-webkit-scrollbar) {
+  display: none;
 }
+
+// :deep(.card) {
+//   width: 100%;
+//   height: 100%;
+//   background-image: url('../../../assets/taaisImg/53bg.png');
+//   background-repeat: no-repeat;
+//   background-size: 100% 100%;
+
+//   // color: black;
+// }
 :deep(.el-table) {
   // --el-table-border-color: transparent;
   --el-table-border-color: #bdbdbe7b;
@@ -49,10 +53,11 @@
   .title2 {
     text-align: center;
   }
-  .btn {
-    position: absolute;
-    bottom: 20px;
-  }
+
+  // .btn {
+  //   position: absolute;
+  //   bottom: 20px;
+  // }
   .back {
     left: 100px;
   }
@@ -64,38 +69,53 @@
   }
 }
 %box-basic {
-  width: 70%;
-  height: 700px;
+  width: 90%;
   padding: 20px;
   margin: 0 auto;
 }
 .createTask-container {
   position: relative;
-  width: 70%;
-  height: 520px;
+  width: 90%;
+  height: 85%;
   padding: 20px;
   margin: 0 auto;
-  margin-top: 100px;
+  margin-top: 1%;
   overflow: hidden;
-  overflow-y: scroll;
+  .footBtn {
+    position: fixed;
+    bottom: 4%;
+    left: 10%;
+    display: flex;
+    justify-content: space-evenly;
+    width: 86%;
+  }
+  .createTask-main {
+    height: 100%;
+    overflow: hidden;
+    overflow-y: scroll;
+  }
 }
 .logPage-container {
   @extend %box-basic;
 
   position: relative;
   width: 90%;
-  margin-top: 50px;
   .btn {
-    margin-top: 10px;
-    margin-left: 100px;
+    margin-top: 20px;
+
+    // margin-left: 100px;
+  }
+  .prev {
+    position: absolute;
+    left: 150px;
   }
   .next {
     position: absolute;
-    right: 120px;
+    right: 150px;
   }
   .log {
     width: 90%;
-    height: 550px; /* 根据需要调整 */
+    height: 65vh; /* 根据需要调整 */
     padding: 10px;
 
     // padding-bottom: 80px;
@@ -119,11 +139,10 @@
 
   width: 90%;
   padding: 3px 20px;
-  margin-top: 50px;
   .table {
     position: relative;
     width: 100%;
-    height: 600px;
+    height: 70vh;
     margin-top: -15px;
     .btn {
       margin-top: 10px;

+ 52 - 42
src/views/taais/homePage/index.vue

@@ -2,27 +2,32 @@
   <div class="home-container">
     <dv-border-box1 ref="borderRef" style="width: 100%; height: 100%; margin: 0 auto">
       <div class="table-box">
-        <ProTable
+        <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listITaskNewApi" :tool-button="false">
+          <template #tableHeader="">
+            <!--            <el-button type="primary" v-auth="['system:user:add']" :icon="CirclePlus" @click="createTask()"> 创建任务 </el-button>-->
+            <el-button type="primary" v-auth="['system:user:add']" :icon="CirclePlus" @click="createTaskNew()"> 创建任务 </el-button>
+          </template>
+          <!-- 表格操作 -->
+          <template #operation="scope">
+            <el-button type="primary" link icon="View" v-auth="['task:task:query']" @click="viewDetails(scope.row)"> 查看详情 </el-button>
+            <!--            <el-button type="primary" link :icon="View" @click="openDialog(3, '任务查看', scope.row)">查看</el-button>-->
+          </template>
+        </ProTable>
+        <!-- <ProTable
           ref="proTable"
-          row-key="userId"
-          :data="taskDataList.data"
-          :indent="20"
+          row-key="id"
           :columns="columns"
-          :data-callback="dataCallback"
+          :request-api="listTaskApi"
           :request-auto="false"
-          :init-param="initParam"
           :tool-button="false"
-          :search-col="{ xs: 1, sm: 1, md: 2, lg: 3, xl: 3 }"
         >
-          <!-- 表格 header 按钮 -->
           <template #tableHeader="">
             <el-button type="primary" v-auth="['system:user:add']" :icon="CirclePlus" @click="createTask()"> 创建任务 </el-button>
           </template>
-          <!-- 表格操作 -->
           <template #operation="scope">
             <el-button type="primary" link :icon="View" @click="openDialog(3, '任务查看', scope.row)">查看</el-button>
           </template>
-        </ProTable>
+        </ProTable> -->
       </div>
     </dv-border-box1>
   </div>
@@ -37,8 +42,11 @@ import { CirclePlus, View } from '@element-plus/icons-vue'
 import { useRouter } from 'vue-router'
 import { ColumnProps, ProTableInstance } from '@/components/ProTable/interface'
 import { addUserApi, updateUserApi, deptTreeSelectApi } from '@/api/modules/system/user'
-import { getTaskApi } from '@/api/modules/taais/task'
-import taskDataList from '@/assets/mock/taskData.json'
+// import { getTaskApi } from '@/api/modules/taais/task'
+// import { listTaskApi, getTaskApi } from '@/api/modules/task/task'
+import { listITaskNewApi, getITaskNewApi } from '@/api/modules/task/task'
+import { getDictsApi } from '@/api/modules/system/dictData'
+// import taskDataList from '@/assets/mock/taskData.json'
 // import { getDictsApi } from '@/api/modules/system/dictData'
 onMounted(() => {
   getTreeFilter()
@@ -57,18 +65,17 @@ const getTreeFilter = async () => {
   treeFilterData.value = data
   initParam.deptId = treeFilterData.value[0].id
 }
-const dataCallback = (data: any) => {
-  return data
+// 查看详情
+const viewDetails = row => {
+  console.log(row)
+  router.push({ path: `/task/subtask/`, query: { id: row.id } })
 }
 // 批量添加用户
 const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
 // 打开弹框的功能
 const openDialog = async (type: number, title: string, row?: any) => {
-  let res = getTaskApi(row?.taskId || null)
-  console.log('res', res)
-
-  // postOptions.value = res.data.posts
-  // roleOptions.value = res.data.roles
+  console.log(row)
+  let res = getITaskNewApi(row?.id || null)
   // 表单项配置
   const fieldList: Form.FieldItem[] = [
     {
@@ -92,34 +99,37 @@ const openDialog = async (type: number, title: string, row?: any) => {
   formDialogRef.value?.openDialog(params)
 }
 const router = useRouter()
-// 增加任务
-const createTask = () => {
-  router.push(`/createTask`)
-}
-// 跳转详情页
-// const toDetail = (row: { tableId: any }) => {
-//   router.push(`/tool/tool-edit/index/${row.tableId}`)
+// // 增加任务
+// const createTask = () => {
+//   router.push(`/createTask`)
 // }
-// const getTaskApi
+
+const createTaskNew = () => {
+  router.push(`/createTaskNew`)
+}
 // 表格配置项
 const columns = reactive<ColumnProps<User.ResUserList>[]>([
   // { type: 'selection', fixed: 'left', width: 70 },
-  { prop: 'taskName', label: '任务名称' },
+  { prop: 'name', label: '任务名称' },
+  // {
+  //   prop: 'status',
+  //   label: '任务状态',
+  //   tag: true,
+  //   enum: () => getDictsApi('biz_task_status'),
+  //   fieldNames: { label: 'dictLabel', value: 'dictValue' }
+  // },
+  {
+    prop: 'createTime',
+    label: '创建时间'
+    // search: {
+    //   el: 'input'
+    // }
+  },
   {
-    prop: 'status',
-    label: '用户状态'
-    // enum: () => getDictsApi('sys_normal_disable'),
-    // fieldNames: { label: 'dictLabel', value: 'dictValue' },
-    // render: scope => {
-    //   return (
-    //     <el-switch
-    //       model-value={scope.row.status}
-    //       active-text={scope.row.status === '1' ? '禁用' : '启用'}
-    //       active-value={'0'}
-    //       inactive-value={'1'}
-    //       onClick={() => changeStatus(scope.row)}
-    //     />
-    //   )
+    prop: 'updateTime',
+    label: '更新时间'
+    // search: {
+    //   el: 'input'
     // }
   },
   { prop: 'operation', label: '操作', width: 230, fixed: 'right' }

+ 1 - 1
src/views/taais/homePage/inferResult.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="bigBox">
-    <dv-border-box1 ref="borderRef" style="width: 1200px; height: 730px; margin: 0 auto">
+    <dv-border-box1 ref="borderRef" style="width: 80%; height: 100%; margin: 0 auto">
       <div class="trainResult-container">
         <h4>推理结果</h4>
         <div class="table">

+ 2 - 2
src/views/taais/homePage/logPage.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="bigBox">
-    <dv-border-box1 ref="borderRef" style="width: 1200px; height: 700px; margin: 0 auto">
+    <dv-border-box1 ref="borderRef" style="width: 80%; height: 100%; margin: 0 auto">
       <div class="logPage-container">
         <el-divider content-position="left">
           <span style="font-size: 18px; font-weight: 700">{{ title }}</span>
@@ -8,7 +8,7 @@
         <div class="log">
           <div class="p" v-for="(item, index) in logInfo" :key="index">{{ item }}</div>
         </div>
-        <el-button class="btn" @click="goTrainResult()"> 上一步 </el-button>
+        <el-button class="btn prev" @click="goTrainResult()"> 上一步 </el-button>
         <el-button type="primary" v-if="over" class="btn next" @click="onResult()"> 查看结果 </el-button>
       </div>
     </dv-border-box1>

+ 624 - 0
src/views/taais/homePage/task/index.vue

@@ -0,0 +1,624 @@
+<template>
+  <div class="createTask-bigBox">
+    <dv-border-box1 ref="borderRef" style="width: 80%; height: calc(100% - 20px); margin: 0 auto">
+      <el-container style="height: 30px"></el-container>
+      <el-container>
+        <span class="span_class">任务名称</span>
+        <el-input style="width: 300px" type="text" placeholder="请输入任务名称" v-model="formData.taskName"> </el-input>
+      </el-container>
+
+      <el-container style="height: 10px"></el-container>
+      <el-container>
+        <span class="span_class">任务类型</span>
+        <el-radio v-model="formData.taskType" label="1">单数据多算法任务</el-radio>
+        <el-radio v-model="formData.taskType" label="2">多数据单算法任务</el-radio>
+      </el-container>
+
+      <el-container style="height: 10px"></el-container>
+      <el-container>
+        <span class="span_class">算法任务</span>
+        <el-checkbox-group v-model="formData.tasks">
+          <el-checkbox label="1">训练</el-checkbox>
+          <el-checkbox label="2">测试</el-checkbox>
+        </el-checkbox-group>
+      </el-container>
+
+      <el-container style="height: 10px"></el-container>
+      <el-container>
+        <span class="span_class">选择算法</span>
+        <el-container style="display: flex; flex-direction: column">
+          <el-container v-for="(item, index) in formData.algorithms" :key="index" style="margin-top: 5px">
+            <el-select style="width: 200px" v-model="formData.algoIdx[index]" placeholder="请选择" @change="onAlgorithmModelSelect(index)">
+              <el-option v-for="(item1, index) in algorithms" :key="item1.id" :label="item1.name" :value="index"> </el-option>
+            </el-select>
+            <el-button style="margin-left: 20px" @click="showSuperParameterConfig(item, index)"> 超参配置 </el-button>
+            <span class="span_class1">选择模型</span>
+            <el-select style="width: 200px" v-model="formData.algoModelId[index]" placeholder="未选择将使用默认模型">
+              <el-option v-for="item1 in formData.algoModelList[index]" :key="item1.id" :label="item1.modelName" :value="item1.id"> </el-option>
+            </el-select>
+            <el-button @click="showAddModelDialog(item, index)"> 导入模型 </el-button>
+            <el-button v-show="formData.algorithms.length > 1" @click="removeItem(index)"> 删除算法 </el-button>
+            <el-button v-show="formData.taskType === '1' && index + 1 === formData.algorithms.length" @click="appendNewItem"> 添加算法 </el-button>
+          </el-container>
+        </el-container>
+      </el-container>
+
+      <el-container style="height: 10px"></el-container>
+      <el-container>
+        <span class="span_class">选择训练数据</span>
+        <el-container style="display: flex; flex-direction: column">
+          <el-container v-for="(item, index) in formData.data" :key="index" style="margin-top: 5px">
+            <el-button v-show="formData.data" @click="showDataSelectionDialog(index, true)"> 选择批次 </el-button>
+            <span v-if="!item || item.length === 0" class="span_class" style="font-size: 15px; color: red">未完成</span>
+            <span v-else class="span_class" style="font-size: 15px; color: greenyellow">已选择</span>
+            <el-button v-show="formData.data.length > 1" style="margin-left: 20px" @click="removeDataItem(index, true)"> 删除数据 </el-button>
+            <el-button
+              v-show="formData.taskType === '2' && index + 1 === formData.data.length"
+              style="margin-left: 20px"
+              @click="appendNewDataItem(true)"
+            >
+              添加数据
+            </el-button>
+          </el-container>
+        </el-container>
+      </el-container>
+
+      <el-container style="height: 10px"></el-container>
+      <el-container>
+        <span class="span_class">选择测试数据</span>
+        <el-container style="display: flex; flex-direction: column">
+          <el-container v-for="(item, index) in formData.testData" :key="index" style="margin-top: 5px">
+            <el-button v-show="formData.testData" @click="showDataSelectionDialog(index, false)"> 选择批次 </el-button>
+            <span v-if="!item || item.length === 0" class="span_class" style="font-size: 15px; color: red">未完成</span>
+            <span v-else class="span_class" style="font-size: 15px; color: greenyellow">已选择</span>
+            <el-button v-show="formData.testData.length > 1" style="margin-left: 20px" @click="removeDataItem(index, false)"> 删除数据 </el-button>
+            <el-button
+              v-show="formData.taskType === '2' && index + 1 === formData.testData.length"
+              style="margin-left: 20px"
+              @click="appendNewDataItem(false)"
+            >
+              添加数据
+            </el-button>
+          </el-container>
+        </el-container>
+      </el-container>
+
+      <el-container style="height: 10px"></el-container>
+      <el-container>
+        <span class="span_class">是否训练扩增</span>
+        <el-checkbox v-model="formData.expandData">扩增</el-checkbox>
+        <el-button v-show="formData.expandData" style="margin-left: 20px" @click="showExpandDataSuperParameterConfig"> 超参配置 </el-button>
+      </el-container>
+
+      <el-form-item style="margin-top: 20px; margin-left: 30px">
+        <el-button type="primary" @click="submit"> 提交 </el-button>
+
+        <el-button @click="cancel"> 取消 </el-button>
+      </el-form-item>
+    </dv-border-box1>
+
+    <FormDialog ref="formDialogRef" @update="handleUpdateModelList" />
+
+    <el-dialog v-model="expandDataDialogVisible" title="数据扩增超参配置">
+      <!--      <span style="font-size: 16px; color: darkorange; font-weight: bold"> 数据扩增超参 </span>-->
+      <el-container v-for="(item, index) in expandDataConfig" :key="index" style="margin-top: 5px">
+        <span class="span_class"> {{ item.name }}</span>
+        <el-input style="width: 300px" type="text" :placeholder="'请输入' + item.name" v-model="item.defaultValue"> </el-input>
+      </el-container>
+      <el-container style="height: 10px"></el-container>
+
+      <span class="dialog-footer">
+        <el-button type="primary" @click="submitExpandDataConfigDialog">确 定</el-button>
+        <el-button @click="expandDataDialogVisible = false">取 消</el-button>
+      </span>
+    </el-dialog>
+
+    <el-dialog v-model="dialogVisible" :title="superParameterFormData.title">
+      <span style="font-size: 16px; font-weight: bold; color: darkorange"> 训练超参 </span>
+      <el-container v-for="(item, index) in superParameterFormData.trainParams" :key="index">
+        <span class="span_class"> {{ item.name }}</span>
+        <el-input style="width: 300px" type="text" :placeholder="'请输入' + item.name" v-model="item.defaultValue"> </el-input>
+      </el-container>
+      <el-container style="height: 10px"></el-container>
+
+      <span style="font-size: 16px; font-weight: bold; color: darkorange"> 验证超参 </span>
+      <el-container v-for="(item, index) in superParameterFormData.verifyParams" :key="index">
+        <span class="span_class"> {{ item.name }}</span>
+        <el-input style="width: 300px" type="text" :placeholder="'请输入' + item.name" v-model="item.defaultValue"> </el-input>
+      </el-container>
+      <el-container style="height: 10px"></el-container>
+
+      <span style="font-size: 16px; font-weight: bold; color: darkorange"> 测试超参 </span>
+      <el-container v-for="(item, index) in superParameterFormData.testParams" :key="index">
+        <span class="span_class"> {{ item.name }}</span>
+        <el-input style="width: 300px" type="text" :placeholder="'请输入' + item.name" v-model="item.defaultValue"> </el-input>
+      </el-container>
+      <el-container style="height: 10px"></el-container>
+
+      <span class="dialog-footer">
+        <el-button type="primary" @click="submitConfigDialog">确 定</el-button>
+        <el-button @click="dialogVisible = false">取 消</el-button>
+      </span>
+    </el-dialog>
+
+    <el-dialog v-model="dataDialogVisible" title="选择数据批次" style="width: 70vw">
+      <el-container>
+        <el-table :data="batchDataList" tooltip-effect="dark" style="width: 100%" @selection-change="handleSelectionChange">
+          <el-table-column type="selection" width="55"> </el-table-column>
+          <el-table-column prop="batchNum" label="所有批次" width="120"> </el-table-column>
+          <el-table-column prop="batchSize" label="数量" width="80"> </el-table-column>
+        </el-table>
+
+        <el-container style="display: flex; flex-direction: column; justify-content: center; margin-right: 10px; margin-left: 10px">
+          <el-button type="primary" :disabled="!canSelect" @click="clickSelectData"> {{ '=>' }} </el-button>
+          <el-container style="height: 10px"></el-container>
+          <el-button type="primary" :disabled="!canDeselect" @click="clickDeselectData"> {{ '<=' }} </el-button>
+        </el-container>
+
+        <el-table :data="selectedBatchDataList" tooltip-effect="dark" style="width: 100%" @selection-change="handleDeselectionChange">
+          <el-table-column type="selection" width="55"> </el-table-column>
+          <el-table-column prop="batchNum" label="已选批次" width="120"> </el-table-column>
+          <el-table-column prop="batchSize" label="数量" width="80"> </el-table-column>
+        </el-table>
+      </el-container>
+
+      <el-container style="height: 10px"></el-container>
+      <span class="dialog-footer">
+        <el-button type="primary" @click="submitDataDialog">确 定</el-button>
+        <el-button @click="dataDialogVisible = false">取 消</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { reactive, ref, onMounted, computed } from 'vue'
+import { addModelApi, queryModelList } from '@/api/modules/ag/model'
+import { createTaskApi } from '@/api/modules/task/task'
+import FormDialog from '@/components/FormDialog/index.vue'
+import { useRouter } from 'vue-router'
+import { listTaskConfigurationApi } from '@/api/modules/task/taskConfiguration'
+import { batchListDataApi } from '@/api/modules/demo/data'
+import { listDataApi } from '@/api/modules/system/dictData'
+import { ElMessage } from 'element-plus'
+const valid = data => {
+  let ret = {
+    code: 400,
+    message: 'OK'
+  }
+  if (!data.taskName || data.taskName.length === 0) {
+    ret.message = '任务名称不能为空'
+    return ret
+  }
+  if (!data.taskItemList || data.taskItemList.length === 0) {
+    ret.message = '请选择算法任务'
+    return ret
+  }
+  data.algTaskList.forEach(obj => {
+    if (!obj || !obj.id) {
+      ret.message = '请选择算法模型'
+    }
+  })
+  if (ret.message !== 'OK') {
+    return ret
+  }
+  if (data.taskItemList.includes('1')) {
+    data.trainBatchNumList.forEach(obj => {
+      if (!obj) {
+        ret.message = '请选择训练数据集'
+      }
+    })
+    if (ret.message !== 'OK') {
+      return ret
+    }
+  }
+  if (data.taskItemList.includes('2')) {
+    data.testBatchNumList.forEach(obj => {
+      if (!obj) {
+        ret.message = '请选择测试数据集'
+      }
+    })
+    if (ret.message !== 'OK') {
+      return ret
+    }
+  }
+  // if (data.hasTrainAugmentation) {
+  //   if (!data.trainAugmentationParams || data.trainAugmentationParams.length === 0) {
+  //     ret.message = '请配置扩增参数'
+  //     return ret
+  //   }
+  // }
+  ret.code = 200
+  return ret
+}
+
+const submit = () => {
+  console.log('submit data, formdata is: ', formData)
+  const params = {
+    taskName: formData.taskName,
+    taskType: formData.taskType,
+    taskItemList: formData.tasks,
+    algTaskList: formData.algorithms,
+    trainBatchNumList: formData.data,
+    testBatchNumList: formData.testData,
+    hasTrainAugmentation: formData.expandData,
+    trainAugmentationParams: null
+  }
+  let result = valid(params)
+  // console.log(result)
+  if (result.code !== 200) {
+    ElMessage.error(result.message)
+    return
+  }
+
+  let __idx = 0
+  params.algTaskList.forEach(obj => {
+    obj.algorithmId = obj.id
+    // if (!obj.trainParams) {
+    //   return
+    // }
+    let o1 = JSON.parse(obj.trainParams)
+    let o2 = JSON.parse(obj.verifyParams)
+    let o3 = JSON.parse(obj.testParams)
+    if (formData.algoModelId[__idx] && formData.algoModelId[__idx].length > 0) {
+      const __trained = {
+        agName: 'pretrained_model',
+        defaultValue: true,
+        modelId: formData.algoModelId[__idx]
+      }
+      o1.push(__trained)
+      o2.push(__trained)
+      o3.push(__trained)
+    }
+
+    obj.params = JSON.stringify(o1) + ';;;' + JSON.stringify(o2) + ';;;' + JSON.stringify(o3)
+    __idx++
+  })
+  // console.log('submit', params)
+
+  createTaskApi(params)
+    .then(res => {
+      // console.log(res)
+      if (res.code !== 200) {
+        ElMessage.error(res.msg)
+      } else {
+        router.push(`/index`)
+      }
+    })
+    .catch(err => {
+      console.log(err)
+    })
+}
+
+const loadExpandDataParams = () => {
+  listDataApi({
+    dictType: 'expand_data_params',
+    pageNum: 1,
+    pageSize: 10000
+  })
+    .then(res => {
+      // console.log(res)
+      expandDataConfig.value = reactive([])
+      res.data.list.forEach(obj => {
+        expandDataConfig.value.push({
+          name: obj.dictLabel,
+          defaultValue: obj.dictValue
+        })
+      })
+      expandDataDialogVisible.value = true
+    })
+    .catch(err => {
+      console.log(err)
+    })
+}
+
+const showExpandDataSuperParameterConfig = () => {
+  loadExpandDataParams()
+}
+// 数据扩增超参修改保存
+const submitExpandDataConfigDialog = () => {
+  // console.log(expandDataConfig)
+  formData.expandConfig = JSON.stringify(expandDataConfig.value)
+  expandDataDialogVisible.value = false
+}
+
+const expandDataDialogVisible = ref(false)
+
+let expandDataConfig = ref(
+  reactive([
+    // {
+    //   name: 'config1',
+    //   defaultValue: 'a'
+    // }
+  ])
+)
+
+const submitConfigDialog = () => {
+  dialogVisible.value = false
+  formData.algorithms[superParameterFormData.index].trainParams = JSON.stringify(superParameterFormData.trainParams)
+  formData.algorithms[superParameterFormData.index].verifyParams = JSON.stringify(superParameterFormData.verifyParams)
+  formData.algorithms[superParameterFormData.index].testParams = JSON.stringify(superParameterFormData.testParams)
+}
+// 选择批次的数据结果
+const submitDataDialog = () => {
+  // console.log(selectedBatchDataList.value)
+  let val = ''
+  for (let i = 0; i < selectedBatchDataList.value.length; i++) {
+    val += selectedBatchDataList.value[i].batchNum + ','
+  }
+  if (bIsTrainData.value) {
+    formData.data[selectDataIndex] = val.substring(0, val.length - 1)
+  } else {
+    formData.testData[selectDataIndex] = val.substring(0, val.length - 1)
+  }
+  dataDialogVisible.value = false
+}
+const canSelect = computed(() => {
+  // console.log(tempSelectedBatchDataList)
+  return tempSelectedBatchDataList.value.length > 0
+})
+const canDeselect = computed(() => {
+  return tempDeselectedBatchDataList.value.length > 0
+})
+
+const handleUpdateModelList = () => {
+  updateModelList(UPDATE_INDEX.value.id, UPDATE_INDEX.value.idx)
+}
+
+onMounted(() => {
+  listTaskConfigurationApi({ pageNum: 1, pageSize: 10000 }).then(res => {
+    // console.log(res)
+    algorithms = reactive(res.data.list)
+  })
+  batchListDataApi().then(res => {
+    // console.log(res)
+    queryBatchData.value = reactive(res.data)
+  })
+})
+let queryBatchData = ref(reactive([]))
+const handleSelectionChange = data => {
+  tempSelectedBatchDataList.value = reactive(data)
+  // console.log(data)
+}
+const handleDeselectionChange = data => {
+  tempDeselectedBatchDataList.value = reactive(data)
+  // console.log(data)
+}
+const clickSelectData = () => {
+  let set = new Set()
+  for (let i = 0; i < tempSelectedBatchDataList.value.length; i++) {
+    set.add(tempSelectedBatchDataList.value[i].batchNum)
+    selectedBatchDataList.value.push(tempSelectedBatchDataList.value[i])
+  }
+  let newArray = []
+  for (let i = 0; i < batchDataList.value.length; i++) {
+    if (!set.has(batchDataList.value[i].batchNum)) {
+      newArray.push(batchDataList.value[i])
+    }
+  }
+  batchDataList.value = reactive(newArray)
+  // console.log(batchDataList)
+}
+const clickDeselectData = () => {
+  let set = new Set()
+  for (let i = 0; i < tempDeselectedBatchDataList.value.length; i++) {
+    set.add(tempDeselectedBatchDataList.value[i].batchNum)
+    batchDataList.value.push(tempDeselectedBatchDataList.value[i])
+  }
+  let newArray = []
+  for (let i = 0; i < selectedBatchDataList.value.length; i++) {
+    if (!set.has(selectedBatchDataList.value[i].batchNum)) {
+      newArray.push(selectedBatchDataList.value[i])
+    }
+  }
+  selectedBatchDataList.value = reactive(newArray)
+}
+const router = useRouter()
+let bIsTrainData = ref(false)
+let dialogVisible = ref(false)
+let batchDataList = ref(reactive([]))
+let selectedBatchDataList = ref(reactive([]))
+let tempSelectedBatchDataList = ref(reactive([]))
+let tempDeselectedBatchDataList = ref(reactive([]))
+
+const formData = reactive({
+  taskName: null,
+  taskType: '1',
+  tasks: [],
+  algorithms: [{}],
+  algoIdx: [null],
+  algoModelId: [null],
+  algoModelList: [[]],
+  // 训练数据
+  data: [null],
+  // 测试数据
+  testData: [null],
+  expandData: false,
+  expandConfig: null
+})
+let selectDataIndex = null
+const dataDialogVisible = ref(false)
+const showDataSelectionDialog = (index, isTrainData) => {
+  bIsTrainData.value = isTrainData
+  batchDataList.value = queryBatchData.value
+  selectedBatchDataList.value = reactive([])
+  tempSelectedBatchDataList.value = reactive([])
+  tempDeselectedBatchDataList.value = reactive([])
+  selectDataIndex = index
+  fillCurrentDataDialogList(index, isTrainData)
+  dataDialogVisible.value = true
+}
+
+const fillCurrentDataDialogList = (index, isTrainData) => {
+  let list = []
+  try {
+    list = bIsTrainData.value ? formData.data[index]?.split(',') : formData.testData[index]?.split(',')
+  } catch (e) {
+    console.log(e)
+  }
+  let selected = new Set(list)
+  batchDataList.value = []
+  selectedBatchDataList.value = []
+
+  batchListDataApi().then(res => {
+    // console.log(res)
+    for (let i = 0; i < res.data.length; i++) {
+      if (selected.has(res.data[i].batchNum)) {
+        selectedBatchDataList.value.push(res.data[i])
+      } else {
+        batchDataList.value.push(res.data[i])
+      }
+    }
+  })
+}
+
+let superParameterFormData = reactive({})
+
+const showSuperParameterConfig = (item, index) => {
+  superParameterFormData = reactive({
+    index: index,
+    trainParams: JSON.parse(item.trainParams),
+    testParams: JSON.parse(item.testParams),
+    verifyParams: JSON.parse(item.verifyParams),
+    title: '超参配置'
+  })
+  // console.log(superParameterFormData)
+  dialogVisible.value = true
+}
+
+let algorithms = reactive([])
+
+const onAlgorithmModelSelect = index => {
+  formData.algorithms[index] = algorithms[formData.algoIdx[index]]
+  updateModelList(formData.algorithms[index].id, index)
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+
+let UPDATE_INDEX = ref({ id: null, idx: null })
+const showAddModelDialog = (item, index) => {
+  let _id = null
+  try {
+    _id = item.id
+  } catch (e) {
+    ElMessage.error('请先选择算法模型')
+    return
+  }
+  if (!_id || _id.length === 0) {
+    ElMessage.error('请先选择算法模型')
+    return
+  }
+  UPDATE_INDEX.value = {
+    id: _id,
+    idx: index
+  }
+  const params = {
+    title: '算法模型配置',
+    width: 580,
+    isEdit: true,
+    itemsOptions: [
+      {
+        label: '模型名称',
+        prop: 'modelName',
+        rules: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
+        compOptions: {
+          placeholder: '请输入模型名称'
+        }
+      },
+      {
+        label: '模型',
+        prop: 'modelAddress',
+        rules: [], //{ required: true, message: '模型不能为空', trigger: 'blur' }
+        compOptions: {
+          elTagName: 'slot'
+        }
+      },
+      {
+        label: '训练样本数',
+        prop: 'sampleNumber',
+        compOptions: {
+          placeholder: '请输入训练样本数'
+        }
+      },
+      {
+        label: '训练循环次数',
+        prop: 'cycleEpoch',
+        compOptions: {
+          placeholder: '请输入训练循环次数'
+        }
+      }
+    ],
+    model: {},
+    api: addModelApi
+  }
+  console.log('check item:', item)
+  formDialogRef.value?.openDialog(params, item.id)
+}
+
+// 获取算法模型绑定列表
+const updateModelList = (id, index) => {
+  // 获取算法列表?
+  queryModelList({ id: id }).then(res => {
+    console.log('algorithm task model get: ', res)
+    if (res.code === 200) {
+      formData.algoModelList[index] = [{ id: null, modelName: '未选择' }, ...res.data]
+    }
+  })
+}
+
+const appendNewItem = () => {
+  formData.algorithms.push({})
+  formData.algoIdx.push(null)
+  formData.algoModelId.push(null)
+  formData.algoModelList.push([])
+}
+const appendNewDataItem = isTrainData => {
+  if (isTrainData) {
+    formData.data.push([])
+  } else {
+    formData.testData.push([])
+  }
+}
+const removeItem = idx => {
+  formData.algorithms.splice(idx, 1)
+  formData.algoIdx.splice(idx, 1)
+  formData.algoModelId.splice(idx, 1)
+  formData.algoModelList.splice(idx, 1)
+}
+const removeDataItem = (idx, isTrainData) => {
+  if (isTrainData) {
+    formData.data.splice(idx, 1)
+  } else {
+    formData.testData.splice(idx, 1)
+  }
+}
+const cancel = () => {
+  // console.log('cancel')
+  router.push(`/index`)
+}
+</script>
+
+<style scoped>
+.createTask-bigBox {
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  background-image: url('../../../assets/taaisImg/53bg.png');
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+}
+.span_class {
+  display: flex;
+  align-self: center;
+  justify-content: center;
+  width: 120px;
+  font-size: 13px;
+}
+.span_class1 {
+  display: flex;
+  align-self: center;
+  justify-content: center;
+  width: 65px;
+  font-size: 13px;
+}
+</style>

+ 963 - 0
src/views/task/bizProcess/index.vue

@@ -0,0 +1,963 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :data="bizProcessList">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <!--        #tableHeader="scope"-->
+        <!-- <el-button type="primary" v-auth="['identification:identificationSubtaskDetails:add']" icon="CirclePlus" @click="openDialog(1, '算法业务处理新增')"> 新增 </el-button>
+        <el-button type="primary" v-auth="['identification:identificationSubtaskDetails:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button> -->
+        <!--        <el-button type="primary" v-auth="['identification:identificationSubtaskDetails:export']" icon="Download" plain @click="downloadFile">-->
+        <!--          导出-->
+        <!--        </el-button>-->
+        <!--        <el-button-->
+        <!--          type="danger"-->
+        <!--          v-auth="['identification:identificationSubtaskDetails:remove']"-->
+        <!--          icon="Delete"-->
+        <!--          plain-->
+        <!--          :disabled="!scope.isSelected"-->
+        <!--          @click="batchDelete(scope.selectedListIds)"-->
+        <!--        >-->
+        <!--          批量删除-->
+        <!--        </el-button>-->
+        <el-button type="primary" icon="View" @click="showCompareResult"> 验证指标对比 </el-button>
+        <!--        <el-button type="primary" icon="View" @click="showValResult(true)"> 验证结果 </el-button>-->
+        <el-button type="primary" icon="View" @click="showValResult(false && scope.row)"> 测试结果 </el-button>
+        <el-button type="primary" icon="View" @click="showValResult(true)"> 验证结果 </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <!--        <el-button-->
+        <!--          type="primary"-->
+        <!--          link-->
+        <!--          icon="View"-->
+        <!--          v-auth="['identification:identificationSubtaskDetails:query']"-->
+        <!--          @click="openDialog(3, '算法业务处理查看', scope.row)"-->
+        <!--        >-->
+        <!--          查看详情-->
+        <!--        </el-button>-->
+        <el-button v-if="scope.row.name.indexOf('训练') === -1" type="primary" link icon="Refresh" @click="reRunTask(scope.row)">
+          执行训练
+        </el-button>
+        <el-button v-else type="primary" link icon="Refresh" @click="startTask(scope.row)"> 开始训练 </el-button>
+
+        <el-button type="primary" link icon="finished" @click="showResult(scope.row)"> 结果图 </el-button>
+
+        <el-button v-show="scope.row.name.indexOf('训练') === -1" type="primary" link icon="search" @click="showExecutedTime(scope.row)">
+          执行时间
+        </el-button>
+
+        <el-button type="primary" link icon="document" v-auth="['identification:identificationSubtaskDetails:query']" @click="viewLog(scope.row)">
+          查看日志
+        </el-button>
+
+        <el-button type="primary" link icon="Refresh" @click="exportData(scope.row)"> 导出结果 </el-button>
+
+        <!--        <el-button type="primary" link icon="EditPen" v-auth="['identification:identificationSubtaskDetails:edit']" @click="openDialog(2, '算法业务处理编辑', scope.row)">-->
+        <!--          编辑-->
+        <!--        </el-button>-->
+        <!--        <el-button type="primary" link icon="Delete" v-auth="['identification:identificationSubtaskDetails:remove']" @click="deleteBizProcess(scope.row)"> 删除 </el-button>-->
+      </template>
+    </ProTable>
+    <FormDialog ref="formDialogRef" />
+    <ImportExcel ref="dialogRef" />
+
+    <el-dialog v-model="executedTimeVisible" title="执行时间统计" width="70%">
+      <el-container v-for="(item, index) in executedTimeData.data" :key="index">
+        <el-container style="display: flex; flex-direction: column">
+          <h4 v-if="index === 0" style="color: greenyellow">单幅图像最短运行时间</h4>
+          <h4 v-else-if="index === 1" style="color: greenyellow">单幅图像最长运行时间</h4>
+          <h4 v-else-if="index === 2" style="color: greenyellow">单幅图像平均运行时间</h4>
+          <div style="display: flex; flex-direction: row">
+            <h5>前处理用时: {{ item['preprocess'] }}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</h5>
+            <h5>推理用时: {{ item['inference'] }}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</h5>
+            <h5>后处理用时: {{ item['postprocess'] }}</h5>
+          </div>
+        </el-container>
+      </el-container>
+      <el-container>
+        <div v-if="executedTimeData.isVal">
+          <h4 style="color: greenyellow">虚警率</h4>
+          <h5>{{ executedTimeData.falseAlarmRate }} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</h5>
+          <h4 style="color: greenyellow">漏检率</h4>
+          <h5>{{ executedTimeData.omitDetectRate }}</h5>
+        </div>
+      </el-container>
+    </el-dialog>
+
+    <el-dialog v-model="dialogVisible" title="日志" width="70%">
+      <div class="log" ref="logRef">
+        <div class="p" v-for="(item, index) in logInfo" :key="index">{{ item }}</div>
+      </div>
+    </el-dialog>
+    <el-dialog v-model="resultVisible" title="对比结果" width="70%">
+      <div v-if="!resultsFlag" class="resultShow">
+        <el-row class="headerRow">
+          <el-col class="col" :span="spanNum" v-for="(agloName, index) in resultsData['agNameList']" :key="index">
+            <span>{{ agloName }}</span>
+          </el-col>
+        </el-row>
+        <el-row class="row">
+          <el-col class="col" :span="spanNum" v-for="(RCurveUrl, index) in resultsData['rcureList']" :key="index">
+            <div v-if="index === 0" :span="4" class="oneCol">{{ RCurveUrl }}</div>
+            <ImagePreview
+              class="img"
+              v-else
+              :width="100"
+              :height="100"
+              :src="'/api/profile' + RCurveUrl"
+              :preview-src-list="['/api/profile' + RCurveUrl]"
+            />
+          </el-col>
+        </el-row>
+        <el-row class="row">
+          <el-col class="col" :span="spanNum" v-for="(PCurveUrl, index) in resultsData['pcureList']" :key="index">
+            <div v-if="index === 0" class="oneCol">{{ PCurveUrl }}</div>
+            <ImagePreview
+              class="img"
+              v-else
+              :width="100"
+              :height="100"
+              :src="'/api/profile' + PCurveUrl"
+              :preview-src-list="['/api/profile' + PCurveUrl]"
+            />
+          </el-col>
+        </el-row>
+        <el-row class="row">
+          <el-col class="col" :span="spanNum" v-for="(F1CurveUrl, index) in resultsData['f1cureList']" :key="index">
+            <div v-if="index === 0" class="oneCol">{{ F1CurveUrl }}</div>
+            <ImagePreview
+              class="img"
+              v-else
+              :width="100"
+              :height="100"
+              :src="'/api/profile' + F1CurveUrl"
+              :preview-src-list="['/api/profile' + F1CurveUrl]"
+            />
+          </el-col>
+        </el-row>
+      </div>
+      <div v-if="resultsFlag" class="resultShow">
+        <el-row class="headerRow">
+          <el-col class="col" :span="spanNum" v-for="(agloName, index) in testResultsData['agNameList']" :key="index">
+            <span>{{ agloName }}</span>
+          </el-col>
+        </el-row>
+        <el-row class="row" v-for="(item, index) in testResultsData['resultList']" :key="index">
+          <el-col class="col" :span="spanNum" v-for="(url, index1) in item" :key="index1">
+            <!-- <span>{{ url }}</span> -->
+            <ImagePreview class="img" :width="100" :height="100" :src="'/api/profile' + url" :preview-src-list="['/api/profile' + url]" />
+          </el-col>
+        </el-row>
+      </div>
+    </el-dialog>
+
+    <el-dialog v-model="resultDialogVisible" title="执行结果">
+      <!--      style="width: 70vw"-->
+      <el-card :body-style="{ padding: '0px' }">
+        <div style="padding: 14px">
+          <span>P_curve</span>
+        </div>
+        <el-image :src="PATH_PREFIX + refSelectData.resultPath + '/P_curve.png'"></el-image>
+      </el-card>
+
+      <el-card :body-style="{ padding: '0px' }" style="margin-top: 10px">
+        <div style="padding: 14px">
+          <span>R_curve</span>
+        </div>
+        <el-image :src="PATH_PREFIX + refSelectData.resultPath + '/R_curve.png'"></el-image>
+      </el-card>
+
+      <el-card :body-style="{ padding: '0px' }" style="margin-top: 10px">
+        <div style="padding: 14px">
+          <span>PR_curve</span>
+        </div>
+        <el-image :src="PATH_PREFIX + refSelectData.resultPath + '/PR_curve.png'"></el-image>
+      </el-card>
+
+      <el-card :body-style="{ padding: '0px' }" style="margin-top: 10px">
+        <div style="padding: 14px">
+          <span>F1_curve</span>
+        </div>
+        <el-image :src="PATH_PREFIX + refSelectData.resultPath + '/F1_curve.png'"></el-image>
+      </el-card>
+    </el-dialog>
+
+    <el-dialog v-model="compareDialogVisible" title="验证指标对比">
+      <!--      style="width: 70vw"-->
+      <el-container style="display: flex; flex-direction: row">
+        <el-container v-for="(item, index) in valListData" :key="index" style="display: flex; flex-direction: column">
+          <!--          <el-button @click="console.log(item)">test</el-button>-->
+          <span style="font-size: 18px"> {{ item.name }} </span>
+          <el-card :body-style="{ padding: '0px' }" style="margin-top: 3px">
+            <div style="padding: 14px">
+              <span>P_curve</span>
+            </div>
+            <el-image :src="PATH_PREFIX + item.resultPath + '/P_curve.png'"></el-image>
+          </el-card>
+
+          <el-card :body-style="{ padding: '0px' }" style="margin-top: 10px">
+            <div style="padding: 14px">
+              <span>R_curve</span>
+            </div>
+            <el-image :src="PATH_PREFIX + item.resultPath + '/R_curve.png'"></el-image>
+          </el-card>
+
+          <el-card :body-style="{ padding: '0px' }" style="margin-top: 10px">
+            <div style="padding: 14px">
+              <span>PR_curve</span>
+            </div>
+            <el-image :src="PATH_PREFIX + item.resultPath + '/PR_curve.png'"></el-image>
+          </el-card>
+
+          <el-card :body-style="{ padding: '0px' }" style="margin-top: 10px">
+            <div style="padding: 14px">
+              <span>F1_curve</span>
+            </div>
+            <el-image :src="PATH_PREFIX + item.resultPath + '/F1_curve.png'"></el-image>
+          </el-card>
+        </el-container>
+      </el-container>
+    </el-dialog>
+
+    <el-dialog v-model="valDialogVisible" :title="titleMsg" style="width: 85vw; height: 85vh">
+      <!--      style="width: 70vw"-->
+      <el-container style="display: flex; flex-direction: row">
+        <el-container v-for="(item, index) in imgDataList" :key="index" style="display: flex; flex-direction: column">
+          <!--          <el-button @click="console.log(item)">test</el-button>-->
+
+          <el-container v-for="(_item, _index) in item" :key="_index">
+            <span
+              v-if="_item.name"
+              :style="{
+                color: _item.color ? _item.color : '#ffffff',
+                width: '80px',
+                'margin-right': '20px'
+              }"
+            >
+              {{ _item.name }}
+            </span>
+            <el-image v-if="_item.srcUrl" :src="_item.srcUrl" style="width: 200px; height: 200px"></el-image>
+            <span v-if="_item.imgUrl">标签</span>
+            <ImgMaker
+              :canvas-id="_item.name + '_' + _index"
+              style="width: 200px; height: 200px"
+              ref="imgMaker"
+              v-if="_item.imgUrl"
+              :is-pic-only="true"
+              :src="_item.imgUrl"
+              :width="200"
+              :height="200"
+              :c-width="200"
+              :c-height="200"
+              :class-def="typeDefs"
+              :json-data="_item.jsonData"
+            ></ImgMaker>
+            <el-image v-if="_item.resUrl" :src="_item.resUrl" style="width: 200px; height: 200px"></el-image>
+          </el-container>
+        </el-container>
+      </el-container>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="tsx" name="BizProcess">
+import { ref, reactive, onMounted, onUnmounted, watch, nextTick } 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 FormDialog from '@/components/FormDialog/index.vue'
+import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
+import ImgMaker from '../../demo/components/img-maker'
+import {
+  listBizProcessApi,
+  delBizProcessApi,
+  addBizProcessApi,
+  updateBizProcessApi,
+  exportBizProcessApi,
+  getBizProcessApi,
+  getTrainResultApi,
+  getVerifyResultApi,
+  getTestResultApi,
+  getImgList
+} from '@/api/modules/task/bizProcessNew'
+// getTrainResultApi,getVerifyResultApi,getTestResultApi
+import { getSubtaskApi } from '@/api/modules/task/subtask'
+import { getDictsApi, listDataApi as listDictDataApi } from '@/api/modules/system/dictData'
+import { useRoute } from 'vue-router'
+import ImagePreview from '@/components/ImagePreview/index.vue'
+import http from '@/api'
+const route = useRoute()
+const PATH_PREFIX = 'api/profile/task'
+let resultDialogVisible = ref(false)
+let refSelectData = ref(reactive({}))
+const reRunTask = row => {
+  http.post<any>('/identification/identificationSubtaskDetails/execute', { taskId: row.id }, { loading: false }).then(res => {
+    if (res.code !== 200) {
+      ElMessage.error(res.msg)
+    } else {
+      ElMessage.success('启动成功')
+    }
+  })
+}
+const startTask = row => {
+  http.post<any>('/identification/identificationSubtaskDetails/startTask', { taskId: row.id }, { loading: false }).then(res => {
+    if (res.code !== 200) {
+      ElMessage.error(res.msg)
+    } else {
+      ElMessage.success('启动成功')
+    }
+  })
+}
+const typeDefs = ref(reactive([]))
+
+const exportData = row => {
+  http.get<any>('/identification/identificationSubtaskDetails/resultZip', { taskId: row.id }, { loading: false }).then(res => {
+    console.log(res)
+    if (res.code === 200) {
+      window.open(res.msg, '_blank')
+    }
+  })
+}
+
+let executedTimeVisible = ref(false)
+let executedTimeData = ref({})
+const showExecutedTime = row => {
+  try {
+    console.log('row', row)
+    executedTimeData.value.isVal = row.name.includes('验证')
+    http.get('/profile/task' + row.resultPath + '/time.json').then(res => {
+      executedTimeData.value.data = []
+      executedTimeData.value.data.push(res[0].min)
+      executedTimeData.value.data.push(res[0].max)
+      executedTimeData.value.data.push(res[0].average)
+      if (executedTimeData.value.isVal) {
+        setTimeout(() => {
+          http.get('/profile/task' + row.resultPath + '/matrics.json').then(res => {
+            executedTimeData.value.omitDetectRate = res[1].omit_detect_rate
+            executedTimeData.value.falseAlarmRate = res[0].false_alarm_rate
+          })
+        }, 550)
+      }
+    })
+  } catch (e) {
+    ElMessage.error('解析JSON错误,请检查任务是否已完成')
+    return
+  }
+  executedTimeVisible.value = true
+}
+
+let imgDataList = ref(reactive([]))
+let titleMsg = ref('')
+let valDialogVisible = ref(false)
+const showValResult = async isVal => {
+  let hasInit = false
+  titleMsg.value = isVal ? '验证结果对比' : '测试结果对比'
+  imgDataList.value = reactive([])
+  for (let i = 0; i < listData.value.length; i++) {
+    if (listData.value[i].name.indexOf(isVal ? '验证' : '测试') !== -1) {
+      console.log(listData.value[i])
+      if (!hasInit) {
+        hasInit = true
+        let res = await getImgList({
+          taskId: listData.value[i].preprocessPath.substring(1).split('/')[0],
+          subPath: 'images'
+        })
+        console.log('res data', res)
+        imgDataList.value.push([])
+        for (let j = 0; j < res.data.length; j++) {
+          let jList = res.data[j].split('.')
+          jList[jList.length - 1] = 'txt'
+          let obj = {
+            name: res.data[j],
+            srcUrl: 'api/profile/task' + listData.value[i].preprocessPath + (isVal ? '/images/' : '/') + res.data[j]
+            // imgUrl: 'api/profile/task' + listData.value[i].preprocessPath + '/images/' + res.data[j],
+            // labelUrl: 'api/profile/task' + listData.value[i].preprocessPath + '/labels/' + jList.join('.')
+          }
+
+          imgDataList.value[imgDataList.value.length - 1].push(obj)
+          if (isVal) {
+            obj.imgUrl = 'api/profile/task' + listData.value[i].preprocessPath + '/images/' + res.data[j]
+            obj.labelUrl = '/profile/task' + listData.value[i].preprocessPath + '/labels/' + jList.join('.')
+            console.log('url is', obj.labelUrl)
+            setDetail(obj)
+          }
+        }
+      }
+      let res = await getImgList({
+        taskId: listData.value[i].preprocessPath.substring(1).split('/')[0],
+        subPath: 'images'
+      })
+      console.log(res.data)
+      if (isVal) {
+        await loadVerifyResult(listData.value[i].name, '/profile/task' + listData.value[i].preprocessPath + '/result/verify_result.txt')
+      }
+      console.log('load result', verifyResult.value)
+      imgDataList.value.push([])
+      for (let j = 0; j < res.data.length; j++) {
+        console.log('temp log', verifyResult.value)
+        let jList = res.data[j].split('.')
+        jList[jList - 1] = 'txt'
+        imgDataList.value[imgDataList.value.length - 1].push({
+          resUrl: 'api/profile/task' + listData.value[i].resultPath + '/' + res.data[j],
+          name: listData.value[i].name,
+          picName: res.data[j],
+          color: verifyResult.value[res.data[j]] ? '#00ff00' : '#ff0000'
+        })
+      }
+    }
+  }
+  console.log('datalist', imgDataList)
+  valDialogVisible.value = true
+}
+
+const verifyResult = ref({})
+const loadVerifyResult = async (name, filepath) => {
+  // console.log('filepath', filepath)
+  try {
+    verifyResult.value = {}
+    let res = await http.get(filepath)
+    console.log(res)
+    // verifyResult.value[name] = {}
+    let arr = res.replace('\r', '').split('\n')
+    arr.forEach(str => {
+      let vals = str.split(' ')
+      verifyResult.value[vals[0]] = vals[1] === '1'
+    })
+    console.log('verifyResult', verifyResult.value)
+  } catch (e) {
+    verifyResult.value = {}
+  }
+}
+
+const setDetail = obj => {
+  http.get<any>(obj.labelUrl).then(res => {
+    obj.jsonData = []
+    // console.log('result', res)
+    let arr = res.replace('\r', '').split('\n')
+    // console.log(arr)
+    for (let i = 0; i < arr.length; i++) {
+      let subArr = arr[i].split(' ')
+      // console.log(subArr)
+      let cssVal = '#000000'
+      let label = '-1'
+      for (let j = 0; j < typeDefs.value.length; j++) {
+        if (typeDefs.value[j].label === subArr[0]) {
+          cssVal = typeDefs.value[j].color
+          label = typeDefs.value[j].label
+          break
+        }
+      }
+      const ww = 200
+      const hh = 200
+      obj.jsonData.push({
+        subArr: subArr,
+        pathString:
+          'M ' +
+          subArr[1] * ww +
+          ' ' +
+          subArr[2] * hh +
+          ' L ' +
+          subArr[3] * ww +
+          ' ' +
+          subArr[4] * hh +
+          ' L ' +
+          subArr[5] * ww +
+          ' ' +
+          subArr[6] * hh +
+          ' L ' +
+          subArr[7] * ww +
+          ' ' +
+          subArr[8] * hh +
+          ' z',
+        // left: 0,
+        // top: 0,
+        fill: '',
+        stroke: cssVal,
+        strokeWidth: 5,
+        label: label
+      })
+    }
+  })
+}
+
+let compareDialogVisible = ref(false)
+const valListData = ref([])
+let listData = ref(reactive([]))
+const showCompareResult = () => {
+  console.log(listData.value)
+  valListData.value = listData.value.filter(item => {
+    return item.name.includes('验证')
+  })
+  compareDialogVisible.value = true
+  console.log('vallist', valListData.value)
+}
+const showResult = row => {
+  refSelectData.value = reactive(row)
+  resultDialogVisible.value = true
+  console.log(row)
+}
+const subTaskId = route.query.id as string
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+const dialogVisible = ref(false)
+const resultVisible = ref(false)
+let resultsData = ref({})
+let testResultsData = ref({})
+let resultsFlag = ref(false)
+let spanNum = ref<number>(4)
+// let logList = ref()
+const logRef = ref<HTMLElement | null>(null) // 显式声明 logRef 的类型;
+let logInfo = ref([] as any[])
+let taskType = ref()
+let taskStatus = ref()
+
+let bizProcessList = ref()
+// 每隔10秒请求一下列表
+const timer = ref() // 定时器
+const refreshList = () => {
+  setTimeout(() => {
+    listBizProcessApi({
+      pageNum: 1,
+      pageSize: 100,
+      subtaskId: subTaskId
+    }).then(res => {
+      bizProcessList.value = res.data['list'].sort((a, b) => b.index - a.index)
+      listData.value = reactive(res.data['list'].sort((a, b) => b.index - a.index))
+    })
+  }, 0)
+}
+onMounted(() => {
+  listDictDataApi({
+    pageNum: 1,
+    pageSize: 10,
+    dictType: 'class_definition'
+  }).then(res => {
+    // console.log(res)
+    for (let i = 0; i < res.data.list.length; i++) {
+      typeDefs.value.push({
+        name: res.data.list[i].dictLabel,
+        color: res.data.list[i].cssClass,
+        label: res.data.list[i].dictValue
+      })
+    }
+  })
+
+  getSubtaskApi(subTaskId).then(res => {
+    taskType.value = res.data.type
+    taskStatus.value = res.data.status
+  })
+  refreshList()
+  timer.value = setInterval(() => {
+    refreshList()
+  }, 10000)
+  // 组件挂载后,logRef 将指向实际的 DOM 元素
+  if (logRef.value) {
+    // 操作 logRef 对应的 DOM 元素
+    logRef.value.scrollTop = logRef.value.scrollHeight
+  }
+})
+// 删除算法业务处理信息
+const deleteBizProcess = async (params: any) => {
+  await useHandleData(delBizProcessApi, params.id, '删除【' + params.id + '】算法业务处理')
+  proTable.value?.getTableList()
+}
+
+// 批量删除算法业务处理信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delBizProcessApi, ids, '删除所选算法业务处理信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出算法业务处理列表
+const downloadFile = async () => {
+  ElMessageBox.confirm('确认导出算法业务处理数据?', '温馨提示', { type: 'warning' }).then(() =>
+    useDownload(exportBizProcessApi, '算法业务处理列表', proTable.value?.searchParam)
+  )
+}
+
+// 批量添加算法业务处理
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+// const batchAdd = () => {
+//   const params = {
+//     title: '算法业务处理',
+//     tempApi: importTemplateApi,
+//     importApi: importBizProcessDataApi,
+//     getTableList: proTable.value?.getTableList
+//   }
+//   dialogRef.value?.acceptParams(params)
+// }
+
+// 对比结果
+const contrastResults = () => {
+  const statusFlag = bizProcessList.value.every(item => {
+    return item.status == '2'
+  })
+  if (statusFlag) {
+    switch (taskType.value) {
+      case '1':
+        getTrainResultApi(subTaskId).then(res => {
+          resultsFlag.value = false
+          handleResultData(res.data)
+          resultVisible.value = true
+        })
+        break
+      case '2':
+        getVerifyResultApi(subTaskId).then(res => {
+          resultsFlag.value = false
+          handleResultData(res.data)
+          resultVisible.value = true
+        })
+        break
+      case '3':
+        getTestResultApi(subTaskId).then(res => {
+          console.log('333', res)
+          resultsFlag.value = true
+          testResultsData.value = res.data as any
+          const num = testResultsData.value['agNameList'].length
+          spanNum.value = 24 / num <= 4 ? 4 : Math.floor(24 / num)
+          resultVisible.value = true
+        })
+        break
+      default:
+        break
+    }
+  } else {
+    ElMessage.warning(`所有算法状态为‘已完成’,才可以对比`)
+  }
+}
+const handleResultData = data => {
+  resultsData.value = data
+  resultsData.value['agNameList'].unshift('')
+  const num = resultsData.value['agNameList'].length + 1
+  spanNum.value = 24 / num <= 4 ? 4 : Math.floor(24 / num)
+  resultsData.value['rcureList'].unshift('R_curve')
+  resultsData.value['pcureList'].unshift('P_curve')
+  resultsData.value['f1cureList'].unshift('F1_curve')
+}
+// 查看日志
+let timer2 = ref()
+const viewLog = row => {
+  // if (row.status === '0') {
+  //   ElMessage.warning('算法状态为待处理,暂无日志')
+  //   return
+  // }
+  const url = `/api/profile/task` + row.resultPath + '/log/log.log'
+  logShow(url)
+  dialogVisible.value = true
+  timer2.value = setInterval(() => {
+    if (dialogVisible.value) {
+      logShow(url)
+    }
+  }, 5000)
+}
+// 日志读取
+const logShow = (url: any) => {
+  fetchLogFile(url)
+    .then(text => {
+      logInfo.value = []
+      logInfo.value = text.split('\n')
+      nextTick(() => {
+        const logContainer = logRef.value
+        if (logContainer) {
+          ;(logContainer as HTMLElement).scrollTop = (logContainer as HTMLElement).scrollHeight
+        }
+      })
+    })
+    .catch(error => {
+      console.error('Failed to fetch the log file:', error)
+      ElMessage.error('日志读取错误')
+    })
+}
+const fetchLogFile = async url => {
+  try {
+    const response = await fetch(url, { method: 'GET' })
+    if (!response.ok) {
+      throw new Error(`HTTP error! status: ${response.status}`)
+    }
+    return await response.text()
+  } catch (error) {
+    throw error
+  }
+}
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getBizProcessApi(row?.id || null)
+  }
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : res.data,
+    api: type == 1 ? addBizProcessApi : updateBizProcessApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+onUnmounted(() => {
+  clearInterval(timer.value)
+  timer.value = null
+})
+watch(
+  () => dialogVisible.value,
+  val => {
+    if (!val) {
+      clearInterval(timer2.value)
+      timer2.value = null
+    }
+  }
+)
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  { type: 'selection', fixed: 'left', width: 70 },
+  // { prop: 'id', label: '主键ID' },
+  // {
+  //   prop: 'subTaskId',
+  //   label: '子任务id',
+  //   search: {
+  //     el: 'input'
+  //   },
+  //   width: 120
+  // },
+  {
+    prop: 'name',
+    label: '任务名称',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  // {
+  //   prop: 'type',
+  //   label: '任务类型',
+  //   search: {
+  //     el: 'input'
+  //   },
+  //   width: 120
+  // },
+  {
+    prop: 'status',
+    label: '任务状态',
+    tag: true,
+    enum: () => getDictsApi('biz_task_status'),
+    search: {
+      el: 'tree-select'
+    },
+    width: 100,
+    fieldNames: { label: 'dictLabel', value: 'dictValue' }
+  },
+  // {
+  //   prop: 'algorithmId',
+  //   label: '算法',
+  //   width: 120
+  // },
+  // {
+  //   prop: 'modelId',
+  //   label: '模型',
+  //   width: 120
+  // },
+  {
+    prop: 'parameters',
+    label: '调用算法时所用的参数'
+  },
+  {
+    prop: 'preprocessPath',
+    label: '预处理数据路径'
+  },
+  {
+    prop: 'resultPath',
+    label: '结果数据路径'
+  },
+  {
+    prop: 'index',
+    label: '序号',
+    width: 120
+  },
+  {
+    prop: 'startTime',
+    label: '开始时间'
+  },
+  {
+    prop: 'endTime',
+    label: '结束时间'
+  },
+  {
+    prop: 'costSecond',
+    label: '耗时(ms)'
+  },
+  {
+    prop: 'remarks',
+    label: '日志'
+  },
+  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+])
+// 结果表格配置项
+// const resultColumns = reactive<ColumnProps<any>[]>([])
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '子任务id',
+      prop: 'subTaskId',
+      compOptions: {
+        placeholder: '请输入子任务id'
+      }
+    },
+    {
+      label: '任务名称',
+      prop: 'name',
+      compOptions: {
+        placeholder: '请输入任务名称'
+      }
+    },
+    {
+      label: '任务类型',
+      prop: 'type',
+      compOptions: {
+        placeholder: '请输入任务类型'
+      }
+    },
+    {
+      label: '任务状态',
+      prop: 'status',
+      compOptions: {
+        elTagName: 'select',
+        labelKey: 'dictLabel',
+        valueKey: 'dictValue',
+        enum: () => getDictsApi('biz_task_status'),
+        placeholder: '请选择任务状态'
+      }
+    },
+    {
+      label: '算法',
+      prop: 'algorithmId',
+      compOptions: {
+        placeholder: '请输入算法'
+      }
+    },
+    {
+      label: '模型',
+      prop: 'modelId',
+      compOptions: {
+        placeholder: '请输入模型'
+      }
+    },
+    {
+      label: '调用算法时所用的参数',
+      prop: 'parameters',
+      compOptions: {
+        type: 'textarea',
+        clearable: true,
+        placeholder: '请输入内容'
+      }
+    },
+    {
+      label: '预处理数据路径',
+      prop: 'preprocessPath',
+      compOptions: {
+        placeholder: '请输入预处理数据路径'
+      }
+    },
+    {
+      label: '结果数据路径',
+      prop: 'resultPath',
+      compOptions: {
+        placeholder: '请输入结果数据路径'
+      }
+    },
+    {
+      label: '序号',
+      prop: 'index',
+      compOptions: {
+        placeholder: '请输入序号'
+      }
+    },
+    {
+      label: '开始时间',
+      prop: 'startTime',
+      compOptions: {
+        elTagName: 'date-picker',
+        type: 'date',
+        placeholder: '请选择开始时间'
+      }
+    },
+    {
+      label: '结束时间',
+      prop: 'endTime',
+      compOptions: {
+        elTagName: 'date-picker',
+        type: 'date',
+        placeholder: '请选择结束时间'
+      }
+    },
+    {
+      label: '耗时',
+      prop: 'costSecond',
+      compOptions: {
+        placeholder: '请输入耗时'
+      }
+    },
+    {
+      label: '日志',
+      prop: 'log',
+      compOptions: {
+        type: 'textarea',
+        clearable: true,
+        placeholder: '请输入内容'
+      }
+    }
+  ]
+}
+</script>
+<style scoped lang="scss">
+.log {
+  width: 90%;
+  height: 60vh; /* 根据需要调整 */
+  padding: 10px;
+
+  // padding-bottom: 80px;
+  margin-left: 50px;
+  overflow-y: auto;
+  font-family: 'Courier New', monospace;
+  color: #4aff84;
+  background-color: #1e1e1e;
+}
+.p {
+  padding-left: 10px;
+  margin-bottom: 5px;
+  border-left: 3px solid #4aff84;
+}
+.resultShow {
+  width: 100%;
+  height: 60vh;
+  overflow: hidden;
+  overflow: scroll scroll;
+  .headerRow {
+    height: 50px;
+    font-size: 1.2rem;
+    line-height: 50px;
+    text-align: center;
+  }
+  .row {
+    .col {
+      text-align: center;
+      .oneCol {
+        margin-top: 45px;
+        font-size: 1.2rem;
+      }
+      .img {
+        margin: 10px 0;
+      }
+    }
+  }
+}
+</style>

+ 300 - 0
src/views/task/dataProcess/index.vue

@@ -0,0 +1,300 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listDataProcessApi">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['task:dataProcess:add']" icon="CirclePlus" @click="openDialog(1, '算法数据处理新增')"> 新增 </el-button>
+        <el-button type="primary" v-auth="['task:dataProcess:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button>
+        <el-button type="primary" v-auth="['task:dataProcess:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button>
+        <el-button
+          type="danger"
+          v-auth="['task:dataProcess:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button type="primary" link icon="View" v-auth="['task:dataProcess:query']" @click="openDialog(3, '算法数据处理查看', scope.row)">
+          查看
+        </el-button>
+        <el-button type="primary" link icon="EditPen" v-auth="['task:dataProcess:edit']" @click="openDialog(2, '算法数据处理编辑', scope.row)">
+          编辑
+        </el-button>
+        <el-button type="primary" link icon="Delete" v-auth="['task:dataProcess:remove']" @click="deleteDataProcess(scope.row)"> 删除 </el-button>
+      </template>
+    </ProTable>
+    <FormDialog ref="formDialogRef" />
+    <ImportExcel ref="dialogRef" />
+  </div>
+</template>
+
+<script setup lang="tsx" name="DataProcess">
+import { ref, reactive } from 'vue'
+import { useHandleData } from '@/hooks/useHandleData'
+import { useDownload } from '@/hooks/useDownload'
+import { ElMessageBox } from 'element-plus'
+import ProTable from '@/components/ProTable/index.vue'
+import ImportExcel from '@/components/ImportExcel/index.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
+import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
+import {
+  listDataProcessApi,
+  delDataProcessApi,
+  addDataProcessApi,
+  updateDataProcessApi,
+  importTemplateApi,
+  importDataProcessDataApi,
+  exportDataProcessApi,
+  getDataProcessApi
+} from '@/api/modules/task/dataProcess'
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除算法数据处理信息
+const deleteDataProcess = async (params: any) => {
+  await useHandleData(delDataProcessApi, params.id, '删除【' + params.id + '】算法数据处理')
+  proTable.value?.getTableList()
+}
+
+// 批量删除算法数据处理信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delDataProcessApi, ids, '删除所选算法数据处理信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出算法数据处理列表
+const downloadFile = async () => {
+  ElMessageBox.confirm('确认导出算法数据处理数据?', '温馨提示', { type: 'warning' }).then(() =>
+    useDownload(exportDataProcessApi, '算法数据处理列表', proTable.value?.searchParam)
+  )
+}
+
+// 批量添加算法数据处理
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+const batchAdd = () => {
+  const params = {
+    title: '算法数据处理',
+    tempApi: importTemplateApi,
+    importApi: importDataProcessDataApi,
+    getTableList: proTable.value?.getTableList
+  }
+  dialogRef.value?.acceptParams(params)
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getDataProcessApi(row?.id || null)
+  }
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : res.data,
+    api: type == 1 ? addDataProcessApi : updateDataProcessApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  { type: 'selection', fixed: 'left', width: 70 },
+  { prop: 'id', label: '主键ID' },
+  {
+    prop: 'subTaskId',
+    label: '子任务id',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'name',
+    label: '任务名称',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'type',
+    label: '任务类型',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'status',
+    label: '任务状态',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'algorithmId',
+    label: '算法',
+    width: 120
+  },
+  {
+    prop: 'parameters',
+    label: '调用算法时所用的参数',
+    width: 120
+  },
+  {
+    prop: 'preprocessPath',
+    label: '预处理数据路径',
+    width: 120
+  },
+  {
+    prop: 'resultPath',
+    label: '结果数据路径',
+    width: 120
+  },
+  {
+    prop: 'startTime',
+    label: '开始时间',
+    width: 120
+  },
+  {
+    prop: 'index',
+    label: '序号',
+    width: 120
+  },
+  {
+    prop: 'endTime',
+    label: '结束时间',
+    width: 120
+  },
+  {
+    prop: 'costSecond',
+    label: '耗时',
+    width: 120
+  },
+  {
+    prop: 'log',
+    label: '日志',
+    width: 120
+  },
+  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+])
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '子任务id',
+      prop: 'subTaskId',
+      compOptions: {
+        placeholder: '请输入子任务id'
+      }
+    },
+    {
+      label: '任务名称',
+      prop: 'name',
+      compOptions: {
+        placeholder: '请输入任务名称'
+      }
+    },
+    {
+      label: '任务类型',
+      prop: 'type',
+      compOptions: {
+        placeholder: '请输入任务类型'
+      }
+    },
+    {
+      label: '任务状态',
+      prop: 'status',
+      compOptions: {
+        placeholder: '请输入任务状态'
+      }
+    },
+    {
+      label: '算法',
+      prop: 'algorithmId',
+      compOptions: {
+        placeholder: '请输入算法'
+      }
+    },
+    {
+      label: '调用算法时所用的参数',
+      prop: 'parameters',
+      compOptions: {
+        type: 'textarea',
+        clearable: true,
+        placeholder: '请输入内容'
+      }
+    },
+    {
+      label: '预处理数据路径',
+      prop: 'preprocessPath',
+      compOptions: {
+        placeholder: '请输入预处理数据路径'
+      }
+    },
+    {
+      label: '结果数据路径',
+      prop: 'resultPath',
+      compOptions: {
+        placeholder: '请输入结果数据路径'
+      }
+    },
+    {
+      label: '开始时间',
+      prop: 'startTime',
+      compOptions: {
+        elTagName: 'date-picker',
+        type: 'date',
+        placeholder: '请选择开始时间'
+      }
+    },
+    {
+      label: '序号',
+      prop: 'index',
+      compOptions: {
+        placeholder: '请输入序号'
+      }
+    },
+    {
+      label: '结束时间',
+      prop: 'endTime',
+      compOptions: {
+        elTagName: 'date-picker',
+        type: 'date',
+        placeholder: '请选择结束时间'
+      }
+    },
+    {
+      label: '耗时',
+      prop: 'costSecond',
+      compOptions: {
+        placeholder: '请输入耗时'
+      }
+    },
+    {
+      label: '日志',
+      prop: 'log',
+      compOptions: {
+        type: 'textarea',
+        clearable: true,
+        placeholder: '请输入内容'
+      }
+    }
+  ]
+}
+</script>

+ 276 - 0
src/views/task/dataSet/index.vue

@@ -0,0 +1,276 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listDataSetApi">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['task:dataSet:add']" icon="CirclePlus" @click="openDialog(1, '算法数据集合新增')"> 新增 </el-button>
+        <el-button type="primary" v-auth="['task:dataSet:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button>
+        <el-button type="primary" v-auth="['task:dataSet:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button>
+        <el-button
+          type="danger"
+          v-auth="['task:dataSet:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button type="primary" link icon="View" v-auth="['task:dataSet:query']" @click="openDialog(3, '算法数据集合查看', scope.row)">
+          查看
+        </el-button>
+        <el-button type="primary" link icon="EditPen" v-auth="['task:dataSet:edit']" @click="openDialog(2, '算法数据集合编辑', scope.row)">
+          编辑
+        </el-button>
+        <el-button type="primary" link icon="Delete" v-auth="['task:dataSet:remove']" @click="deleteDataSet(scope.row)"> 删除 </el-button>
+      </template>
+    </ProTable>
+    <FormDialog ref="formDialogRef" />
+    <ImportExcel ref="dialogRef" />
+  </div>
+</template>
+
+<script setup lang="tsx" name="DataSet">
+import { ref, reactive } from 'vue'
+import { useHandleData } from '@/hooks/useHandleData'
+import { useDownload } from '@/hooks/useDownload'
+import { ElMessageBox } from 'element-plus'
+import ProTable from '@/components/ProTable/index.vue'
+import ImportExcel from '@/components/ImportExcel/index.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
+import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
+import {
+  listDataSetApi,
+  delDataSetApi,
+  addDataSetApi,
+  updateDataSetApi,
+  importTemplateApi,
+  importDataSetDataApi,
+  exportDataSetApi,
+  getDataSetApi
+} from '@/api/modules/task/dataSet'
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除算法数据集合信息
+const deleteDataSet = async (params: any) => {
+  await useHandleData(delDataSetApi, params.id, '删除【' + params.id + '】算法数据集合')
+  proTable.value?.getTableList()
+}
+
+// 批量删除算法数据集合信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delDataSetApi, ids, '删除所选算法数据集合信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出算法数据集合列表
+const downloadFile = async () => {
+  ElMessageBox.confirm('确认导出算法数据集合数据?', '温馨提示', { type: 'warning' }).then(() =>
+    useDownload(exportDataSetApi, '算法数据集合列表', proTable.value?.searchParam)
+  )
+}
+
+// 批量添加算法数据集合
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+const batchAdd = () => {
+  const params = {
+    title: '算法数据集合',
+    tempApi: importTemplateApi,
+    importApi: importDataSetDataApi,
+    getTableList: proTable.value?.getTableList
+  }
+  dialogRef.value?.acceptParams(params)
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getDataSetApi(row?.id || null)
+  }
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : res.data,
+    api: type == 1 ? addDataSetApi : updateDataSetApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  { type: 'selection', fixed: 'left', width: 70 },
+  { prop: 'id', label: '主键ID' },
+  {
+    prop: 'subTaskId',
+    label: '子任务id',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'name',
+    label: '任务名称',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'type',
+    label: '任务类型',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'status',
+    label: '任务状态',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'parameters',
+    label: '调用算法时所用的参数',
+    width: 120
+  },
+  {
+    prop: 'resultPath',
+    label: '结果数据路径',
+    width: 120
+  },
+  {
+    prop: 'startTime',
+    label: '开始时间',
+    width: 120
+  },
+  {
+    prop: 'index',
+    label: '序号',
+    width: 120
+  },
+  {
+    prop: 'endTime',
+    label: '结束时间',
+    width: 120
+  },
+  {
+    prop: 'costSecond',
+    label: '耗时',
+    width: 120
+  },
+  {
+    prop: 'log',
+    label: '日志',
+    width: 120
+  },
+  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+])
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '子任务id',
+      prop: 'subTaskId',
+      compOptions: {
+        placeholder: '请输入子任务id'
+      }
+    },
+    {
+      label: '任务名称',
+      prop: 'name',
+      compOptions: {
+        placeholder: '请输入任务名称'
+      }
+    },
+    {
+      label: '任务类型',
+      prop: 'type',
+      compOptions: {
+        placeholder: '请输入任务类型'
+      }
+    },
+    {
+      label: '任务状态',
+      prop: 'status',
+      compOptions: {
+        placeholder: '请输入任务状态'
+      }
+    },
+    {
+      label: '调用算法时所用的参数',
+      prop: 'parameters',
+      compOptions: {
+        type: 'textarea',
+        clearable: true,
+        placeholder: '请输入内容'
+      }
+    },
+    {
+      label: '结果数据路径',
+      prop: 'resultPath',
+      compOptions: {
+        placeholder: '请输入结果数据路径'
+      }
+    },
+    {
+      label: '开始时间',
+      prop: 'startTime',
+      compOptions: {
+        elTagName: 'date-picker',
+        type: 'date',
+        placeholder: '请选择开始时间'
+      }
+    },
+    {
+      label: '序号',
+      prop: 'index',
+      compOptions: {
+        placeholder: '请输入序号'
+      }
+    },
+    {
+      label: '结束时间',
+      prop: 'endTime',
+      compOptions: {
+        elTagName: 'date-picker',
+        type: 'date',
+        placeholder: '请选择结束时间'
+      }
+    },
+    {
+      label: '耗时',
+      prop: 'costSecond',
+      compOptions: {
+        placeholder: '请输入耗时'
+      }
+    },
+    {
+      label: '日志',
+      prop: 'log',
+      compOptions: {
+        type: 'textarea',
+        clearable: true,
+        placeholder: '请输入内容'
+      }
+    }
+  ]
+}
+</script>

+ 140 - 70
src/views/task/subtask/index.vue

@@ -1,31 +1,39 @@
 <template>
   <div class="table-box">
-    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listSubtaskApi">
+    <ProTable ref="proTable" :is-show-search="false" :columns="columns" row-key="id" :data="subTaskList">
       <!-- 表格 header 按钮 -->
-      <template #tableHeader="scope">
-        <el-button type="primary" v-auth="['task:subtask:add']" icon="CirclePlus" @click="openDialog(1, '算法子任务新增')"> 新增 </el-button>
-        <el-button type="primary" v-auth="['task:subtask:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button>
-        <el-button type="primary" v-auth="['task:subtask:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button>
-        <el-button
-          type="danger"
-          v-auth="['task:subtask:remove']"
-          icon="Delete"
-          plain
-          :disabled="!scope.isSelected"
-          @click="batchDelete(scope.selectedListIds)"
-        >
-          批量删除
-        </el-button>
-      </template>
+      <!--      <template #tableHeader="scope">-->
+      <!-- <el-button type="primary" v-auth="['identification:identificationTask:add']" icon="CirclePlus" @click="openDialog(1, '算法子任务新增')"> 新增 </el-button>
+        <el-button type="primary" v-auth="['identification:identificationTask:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button> -->
+      <!--        <el-button type="primary" v-auth="['identification:identificationTask:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button>-->
+      <!--        <el-button-->
+      <!--          type="danger"-->
+      <!--          v-auth="['identification:identificationTask:remove']"-->
+      <!--          icon="Delete"-->
+      <!--          plain-->
+      <!--          :disabled="!scope.isSelected"-->
+      <!--          @click="batchDelete(scope.selectedListIds)"-->
+      <!--        >-->
+      <!--          批量删除-->
+      <!--        </el-button>-->
+      <!--      </template>-->
       <!-- 表格操作 -->
       <template #operation="scope">
-        <el-button type="primary" link icon="View" v-auth="['task:subtask:query']" @click="openDialog(3, '算法子任务查看', scope.row)">
-          查看
+        <el-button type="primary" link icon="View" v-auth="['identification:identificationTask:query']" @click="viewDetails(scope.row)">
+          查看详情
         </el-button>
-        <el-button type="primary" link icon="EditPen" v-auth="['task:subtask:edit']" @click="openDialog(2, '算法子任务编辑', scope.row)">
+        <el-button
+          type="primary"
+          link
+          icon="EditPen"
+          v-auth="['identification:identificationTask:edit']"
+          @click="openDialog(2, '算法子任务编辑', scope.row)"
+        >
           编辑
         </el-button>
-        <el-button type="primary" link icon="Delete" v-auth="['task:subtask:remove']" @click="deleteSubtask(scope.row)"> 删除 </el-button>
+        <el-button type="primary" link icon="Delete" v-auth="['identification:identificationTask:remove']" @click="deleteSubtask(scope.row)">
+          删除
+        </el-button>
       </template>
     </ProTable>
     <FormDialog ref="formDialogRef" />
@@ -34,29 +42,48 @@
 </template>
 
 <script setup lang="tsx" name="Subtask">
-import { ref, reactive } from 'vue'
+import { ref, reactive, onUnmounted, onMounted } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useDownload } from '@/hooks/useDownload'
 import { ElMessageBox } from 'element-plus'
 import ProTable from '@/components/ProTable/index.vue'
 import ImportExcel from '@/components/ImportExcel/index.vue'
 import FormDialog from '@/components/FormDialog/index.vue'
+import { useRoute, useRouter } from 'vue-router'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
-import {
-  listSubtaskApi,
-  delSubtaskApi,
-  addSubtaskApi,
-  updateSubtaskApi,
-  importTemplateApi,
-  importSubtaskDataApi,
-  exportSubtaskApi,
-  getSubtaskApi
-} from '@/api/modules/task/subtask'
+import { listSubtaskApi, delSubtaskApi, addSubtaskApi, updateSubtaskApi, exportSubtaskApi, getSubtaskApi } from '@/api/modules/task/subtaskNew'
 import { getDictsApi } from '@/api/modules/system/dictData'
-
+const router = useRouter()
+const route = useRoute()
+const taskId = route.query.id as string
 // ProTable 实例
 const proTable = ref<ProTableInstance>()
+let subTaskList = ref()
 
+// 每隔10秒请求一下列表
+const timer = ref() // 定时器
+const refreshList = () => {
+  setTimeout(() => {
+    listSubtaskApi({
+      pageNum: 1,
+      pageSize: 100,
+      taskId: taskId
+    }).then(res => {
+      subTaskList.value = res.data['list']
+    })
+  }, 0)
+}
+onMounted(() => {
+  // timer
+  refreshList()
+  timer.value = setInterval(() => {
+    refreshList()
+  }, 10000)
+})
+// 查看详情
+const viewDetails = row => {
+  router.push({ path: `/task/bizProcess/`, query: { id: row.id } })
+}
 // 删除算法子任务信息
 const deleteSubtask = async (params: any) => {
   await useHandleData(delSubtaskApi, params.id, '删除【' + params.id + '】算法子任务')
@@ -79,15 +106,15 @@ const downloadFile = async () => {
 
 // 批量添加算法子任务
 const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
-const batchAdd = () => {
-  const params = {
-    title: '算法子任务',
-    tempApi: importTemplateApi,
-    importApi: importSubtaskDataApi,
-    getTableList: proTable.value?.getTableList
-  }
-  dialogRef.value?.acceptParams(params)
-}
+// const batchAdd = () => {
+//   const params = {
+//     title: '算法子任务',
+//     tempApi: importTemplateApi,
+//     importApi: importSubtaskDataApi,
+//     getTableList: proTable.value?.getTableList
+//   }
+//   dialogRef.value?.acceptParams(params)
+// }
 
 const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
 // 打开弹框的功能
@@ -109,49 +136,84 @@ const openDialog = async (type: number, title: string, row?: any) => {
   }
   formDialogRef.value?.openDialog(params)
 }
-
+onUnmounted(() => {
+  clearInterval(timer.value)
+  timer.value = null
+})
 // 表格配置项
 const columns = reactive<ColumnProps<any>[]>([
   { type: 'selection', fixed: 'left', width: 70 },
+  { prop: 'id', label: '主键ID' },
   {
     prop: 'name',
-    label: '任务名称',
+    label: '任务类型',
     search: {
       el: 'input'
     }
   },
   {
-    prop: 'status',
-    label: '任务状态',
-    tag: true,
-    enum: () => getDictsApi('biz_task_status'),
+    prop: 'createTime',
+    label: '创建时间',
     search: {
-      el: 'tree-select'
-    },
-    fieldNames: { label: 'dictLabel', value: 'dictValue' },
-    width: 120
-  },
-  {
-    prop: 'startTime',
-    label: '开始时间',
-    width: 200
-  },
-  {
-    prop: 'endTime',
-    label: '结束时间',
-    width: 200
-  },
-  {
-    prop: 'costSecond',
-    label: '耗时',
-    width: 120
+      el: 'input'
+    }
   },
-  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+  // {
+  //   prop: 'status',
+  //   label: '任务状态',
+  //   tag: true,
+  //   enum: () => getDictsApi('biz_task_status'),
+  //   search: {
+  //     el: 'tree-select'
+  //   },
+  //   fieldNames: { label: 'dictLabel', value: 'dictValue' }
+  // },
+  // {
+  //   prop: 'type',
+  //   label: '任务类型',
+  //   tag: true,
+  //   enum: () => getDictsApi('biz_ag_type'),
+  //   fieldNames: { label: 'dictLabel', value: 'dictValue' }
+  // },
+  // {
+  //   prop: 'parameters',
+  //   label: '调用算法时所用的参数'
+  // },
+  // {
+  //   prop: 'startTime',
+  //   label: '开始时间',
+  //   search: {
+  //     el: 'date-picker',
+  //     props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
+  //   },
+  //   width: 120
+  // },
+  // {
+  //   prop: 'endTime',
+  //   label: '结束时间',
+  //   search: {
+  //     el: 'date-picker',
+  //     props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
+  //   },
+  //   width: 120
+  // },
+  // {
+  //   prop: 'costSecond',
+  //   label: '耗时'
+  // },
+  { prop: 'operation', label: '操作', width: 270, fixed: 'right' }
 ])
 // 表单配置项
 let itemsOptions: ProForm.ItemsOptions[] = []
 const setItemsOptions = () => {
   itemsOptions = [
+    {
+      label: '任务ID',
+      prop: 'taskId',
+      compOptions: {
+        placeholder: '请输入任务ID'
+      }
+    },
     {
       label: '任务名称',
       prop: 'name',
@@ -172,6 +234,14 @@ const setItemsOptions = () => {
         placeholder: '请选择任务状态'
       }
     },
+    {
+      label: '任务类型',
+      prop: 'type',
+      rules: [{ required: true, message: '任务类型不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入任务类型'
+      }
+    },
     {
       label: '调用算法时所用的参数',
       prop: 'parameters',
@@ -216,10 +286,10 @@ const setItemsOptions = () => {
       }
     },
     {
-      label: '是否包含详情',
-      prop: 'hasDetails',
+      label: '序号',
+      prop: 'index',
       compOptions: {
-        placeholder: '请输入是否包含详情'
+        placeholder: '请输入序号'
       }
     }
   ]

+ 37 - 8
src/views/task/subtaskDetail/index.vue

@@ -3,14 +3,23 @@
     <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listSubtaskDetailApi">
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
-        <el-button type="primary" v-auth="['task:subtaskDetail:add']" icon="CirclePlus" @click="openDialog(1, '算法子任务详情新增')">
+        <el-button
+          type="primary"
+          v-auth="['identification:identificationSubtaskDetails:add']"
+          icon="CirclePlus"
+          @click="openDialog(1, '算法子任务详情新增')"
+        >
           新增
         </el-button>
-        <el-button type="primary" v-auth="['task:subtaskDetail:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button>
-        <el-button type="primary" v-auth="['task:subtaskDetail:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button>
+        <el-button type="primary" v-auth="['identification:identificationSubtaskDetails:import']" icon="Upload" plain @click="batchAdd">
+          导入
+        </el-button>
+        <el-button type="primary" v-auth="['identification:identificationSubtaskDetails:export']" icon="Download" plain @click="downloadFile">
+          导出
+        </el-button>
         <el-button
           type="danger"
-          v-auth="['task:subtaskDetail:remove']"
+          v-auth="['identification:identificationSubtaskDetails:remove']"
           icon="Delete"
           plain
           :disabled="!scope.isSelected"
@@ -21,13 +30,33 @@
       </template>
       <!-- 表格操作 -->
       <template #operation="scope">
-        <el-button type="primary" link icon="View" v-auth="['task:subtaskDetail:query']" @click="openDialog(3, '算法子任务详情查看', scope.row)">
+        <el-button
+          type="primary"
+          link
+          icon="View"
+          v-auth="['identification:identificationSubtaskDetails:query']"
+          @click="openDialog(3, '算法子任务详情查看', scope.row)"
+        >
           查看
         </el-button>
-        <el-button type="primary" link icon="EditPen" v-auth="['task:subtaskDetail:edit']" @click="openDialog(2, '算法子任务详情编辑', scope.row)">
+        <el-button
+          type="primary"
+          link
+          icon="EditPen"
+          v-auth="['identification:identificationSubtaskDetails:edit']"
+          @click="openDialog(2, '算法子任务详情编辑', scope.row)"
+        >
           编辑
         </el-button>
-        <el-button type="primary" link icon="Delete" v-auth="['task:subtaskDetail:remove']" @click="deleteSubtaskDetail(scope.row)"> 删除 </el-button>
+        <el-button
+          type="primary"
+          link
+          icon="Delete"
+          v-auth="['identification:identificationSubtaskDetails:remove']"
+          @click="deleteSubtaskDetail(scope.row)"
+        >
+          删除
+        </el-button>
       </template>
     </ProTable>
     <FormDialog ref="formDialogRef" />
@@ -53,7 +82,7 @@ import {
   importSubtaskDetailDataApi,
   exportSubtaskDetailApi,
   getSubtaskDetailApi
-} from '@/api/modules/task/subtaskDetail'
+} from '@/api/modules/task/subtaskDetailNew'
 import { getDictsApi } from '@/api/modules/system/dictData'
 
 // ProTable 实例

+ 35 - 35
src/views/task/task/index.vue

@@ -19,7 +19,7 @@
       </template>
       <!-- 表格操作 -->
       <template #operation="scope">
-        <el-button type="primary" link icon="View" v-auth="['task:task:query']" @click="openDialog(3, '算法任务查看', scope.row)"> 查看 </el-button>
+        <el-button type="primary" link icon="View" v-auth="['task:task:query']" @click="viewDetails(scope.row)"> 查看详情 </el-button>
         <el-button type="primary" link icon="EditPen" v-auth="['task:task:edit']" @click="openDialog(2, '算法任务编辑', scope.row)"> 编辑 </el-button>
         <el-button type="primary" link icon="Delete" v-auth="['task:task:remove']" @click="deleteTask(scope.row)"> 删除 </el-button>
       </template>
@@ -34,6 +34,7 @@ import { ref, reactive } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useDownload } from '@/hooks/useDownload'
 import { ElMessageBox } from 'element-plus'
+import { useRouter } from 'vue-router'
 import ProTable from '@/components/ProTable/index.vue'
 import ImportExcel from '@/components/ImportExcel/index.vue'
 import FormDialog from '@/components/FormDialog/index.vue'
@@ -49,7 +50,7 @@ import {
   getTaskApi
 } from '@/api/modules/task/task'
 import { getDictsApi } from '@/api/modules/system/dictData'
-
+const router = useRouter()
 // ProTable 实例
 const proTable = ref<ProTableInstance>()
 
@@ -105,6 +106,10 @@ const openDialog = async (type: number, title: string, row?: any) => {
   }
   formDialogRef.value?.openDialog(params)
 }
+// 查看详情
+const viewDetails = row => {
+  router.push({ path: `/task/subtask/`, query: { id: row.id } })
+}
 
 // 表格配置项
 const columns = reactive<ColumnProps<any>[]>([
@@ -124,36 +129,35 @@ const columns = reactive<ColumnProps<any>[]>([
     search: {
       el: 'tree-select'
     },
-    fieldNames: { label: 'dictLabel', value: 'dictValue' },
-    width: 120
-  },
-  {
-    prop: 'startTime',
-    label: '开始时间',
-    search: {
-      el: 'date-picker',
-      props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
-    },
-    width: 200
-  },
-  {
-    prop: 'endTime',
-    label: '结束时间',
-    search: {
-      el: 'date-picker',
-      props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
-    },
-    width: 200
-  },
-  {
-    prop: 'costSecond',
-    label: '耗时',
-    search: {
-      el: 'input'
-    },
-    width: 120
+    fieldNames: { label: 'dictLabel', value: 'dictValue' }
   },
-  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+  // {
+  //   prop: 'startTime',
+  //   label: '开始时间',
+  //   search: {
+  //     el: 'date-picker',
+  //     props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
+  //   },
+  //   width: 200
+  // },
+  // {
+  //   prop: 'endTime',
+  //   label: '结束时间',
+  //   search: {
+  //     el: 'date-picker',
+  //     props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
+  //   },
+  //   width: 200
+  // },
+  // {
+  //   prop: 'costSecond',
+  //   label: '耗时',
+  //   search: {
+  //     el: 'input'
+  //   },
+  //   width: 120
+  // },
+  { prop: 'operation', label: '操作', fixed: 'right' }
 ])
 // 表单配置项
 let itemsOptions: ProForm.ItemsOptions[] = []
@@ -170,10 +174,6 @@ const setItemsOptions = () => {
     {
       label: '任务状态',
       prop: 'status',
-      options: {
-        labelKey: 'dictLabel',
-        valueKey: 'dictValue'
-      },
       compOptions: {
         elTagName: 'select',
         labelKey: 'dictLabel',

+ 257 - 0
src/views/task/taskConfiguration/index.vue

@@ -0,0 +1,257 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listTaskConfigurationApi">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['task:taskConfiguration:add']" icon="CirclePlus" @click="openDialog(1, '算法任务新增')"> 新增 </el-button>
+        <el-button type="primary" v-auth="['task:taskConfiguration:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button>
+        <el-button type="primary" v-auth="['task:taskConfiguration:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button>
+        <el-button
+          type="danger"
+          v-auth="['task:taskConfiguration:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button type="primary" link icon="View" v-auth="['task:taskConfiguration:query']" @click="openDialog(3, '算法任务查看', scope.row)">
+          查看
+        </el-button>
+        <el-button type="primary" link icon="EditPen" v-auth="['task:taskConfiguration:edit']" @click="openDialog(2, '算法任务编辑', scope.row)">
+          编辑
+        </el-button>
+        <el-button type="primary" link icon="Delete" v-auth="['task:taskConfiguration:remove']" @click="deleteTaskConfiguration(scope.row)">
+          删除
+        </el-button>
+      </template>
+    </ProTable>
+    <FormDialog ref="formDialogRef" />
+    <ImportExcel ref="dialogRef" />
+  </div>
+</template>
+
+<script setup lang="tsx" name="TaskConfiguration">
+import { ref, reactive } from 'vue'
+import { useHandleData } from '@/hooks/useHandleData'
+import { useDownload } from '@/hooks/useDownload'
+import { ElMessageBox } from 'element-plus'
+import ProTable from '@/components/ProTable/index.vue'
+import ImportExcel from '@/components/ImportExcel/index.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
+import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
+import {
+  listTaskConfigurationApi,
+  delTaskConfigurationApi,
+  addTaskConfigurationApi,
+  updateTaskConfigurationApi,
+  importTemplateApi,
+  importTaskConfigurationDataApi,
+  exportTaskConfigurationApi,
+  getTaskConfigurationApi
+} from '@/api/modules/task/taskConfiguration'
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除算法任务信息
+const deleteTaskConfiguration = async (params: any) => {
+  await useHandleData(delTaskConfigurationApi, params.id, '删除【' + params.id + '】算法任务')
+  proTable.value?.getTableList()
+}
+
+// 批量删除算法任务信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delTaskConfigurationApi, ids, '删除所选算法任务信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 导出算法任务列表
+const downloadFile = async () => {
+  ElMessageBox.confirm('确认导出算法任务数据?', '温馨提示', { type: 'warning' }).then(() =>
+    useDownload(exportTaskConfigurationApi, '算法任务列表', proTable.value?.searchParam)
+  )
+}
+
+// 批量添加算法任务
+const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
+const batchAdd = () => {
+  const params = {
+    title: '算法任务',
+    tempApi: importTemplateApi,
+    importApi: importTaskConfigurationDataApi,
+    getTableList: proTable.value?.getTableList
+  }
+  dialogRef.value?.acceptParams(params)
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  let res = { data: {} }
+  if (row?.id) {
+    res = await getTaskConfigurationApi(row?.id || null)
+  }
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 580,
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : res.data,
+    api: type == 1 ? addTaskConfigurationApi : updateTaskConfigurationApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  { type: 'selection', fixed: 'left', width: 70 },
+  { prop: 'id', label: '主键ID' },
+  {
+    prop: 'name',
+    label: '算法任务名称',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'trainUrl',
+    label: '训练算法地址',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'trainParams',
+    label: '训练超参配置',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'verifyUrl',
+    label: '验证算法地址',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'verifyParams',
+    label: '验证超参配置',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'testUrl',
+    label: '测试算法地址',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'testParams',
+    label: '测试超参配置',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'remark',
+    label: '备注',
+    width: 120
+  },
+  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+])
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '算法任务名称',
+      prop: 'name',
+      rules: [{ required: true, message: '算法任务名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入算法任务名称'
+      }
+    },
+    {
+      label: '训练算法地址',
+      prop: 'trainUrl',
+      rules: [{ required: true, message: '训练算法地址不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入训练算法地址'
+      }
+    },
+    {
+      label: '训练超参配置',
+      prop: 'trainParams',
+      rules: [{ required: true, message: '训练超参配置不能为空', trigger: 'blur' }],
+      compOptions: {
+        type: 'textarea',
+        clearable: true,
+        placeholder: '请输入内容'
+      }
+    },
+    {
+      label: '验证算法地址',
+      prop: 'verifyUrl',
+      rules: [{ required: true, message: '验证算法地址不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入验证算法地址'
+      }
+    },
+    {
+      label: '验证超参配置',
+      prop: 'verifyParams',
+      rules: [{ required: true, message: '验证超参配置不能为空', trigger: 'blur' }],
+      compOptions: {
+        type: 'textarea',
+        clearable: true,
+        placeholder: '请输入内容'
+      }
+    },
+    {
+      label: '测试算法地址',
+      prop: 'testUrl',
+      rules: [{ required: true, message: '测试算法地址不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入测试算法地址'
+      }
+    },
+    {
+      label: '测试超参配置',
+      prop: 'testParams',
+      rules: [{ required: true, message: '测试超参配置不能为空', trigger: 'blur' }],
+      compOptions: {
+        type: 'textarea',
+        clearable: true,
+        placeholder: '请输入内容'
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remark',
+      rules: [{ required: true, message: '备注不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
+</script>

Разлика између датотеке није приказан због своје велике величине
+ 557 - 586
yarn.lock


Неке датотеке нису приказане због велике количине промена