ソースを参照

Merge branch 'develop' into dev-lzy

# Conflicts:
#	src/routers/modules/routerData.json
#	src/views/demo/data/index.vue
allen 10 ヶ月 前
コミット
778f067f11

+ 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.development

@@ -25,8 +25,8 @@ VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3C
 VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
 
 # 开发环境跨域代理,支持配置多个
-# VITE_PROXY = [["/api","http://localhost:9090"]]
 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 为必填时,必须提供默认值

+ 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
+}

+ 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/demo/data.ts

@@ -1,4 +1,12 @@
 import http from '@/api'
+/**
+ * @name 查询batchList
+ * @returns 返回列表
+ */
+export const batchListDataApi = () => {
+  return http.get<any[]>('/demo/data/batchList', {}, { loading: true })
+}
+
 /**
  * @name 查询数据管理列表
  * @param query 参数

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

@@ -0,0 +1,94 @@
+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 downloadVideo2imageApi = (id: string | number): Promise<any> => {
+  return http.downloadGet('/demo/video2image/zip/' + id)
+}

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

@@ -0,0 +1,80 @@
+import http from '@/api'
+// @ts-expect-error
+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 getCompareImageApi = (taskName: String, idx: String | Number) => {
+  return http.get('/demo/videoStable/compare/' + taskName + '/' + idx)
+}

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

@@ -0,0 +1,93 @@
+import http from '@/api'
+/**
+ * @name 查询算法业务处理列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listBizProcessApi = (query: any) => {
+  return http.get<any[]>('/identification/identificationSubtaskDetails/list', 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}`)
+}

+ 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)
+}

+ 23 - 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,15 @@ 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 返回列表
@@ -26,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 })

+ 38 - 2
src/components/FormDialog/index.vue

@@ -13,6 +13,9 @@
         <FileUpload :file-size="4096" :file-type="['pt']" />
       </template>
     </ProFrom>
+    <div class="upload-video-box" v-if="parameter.showVideoUpload">
+      <FileUpload ref="videoUploadRef" :limit="1" :file-size="4096" :file-type="['mp4', 'avi', 'rmvb', 'mov', 'wmv', 'flv']" />
+    </div>
     <template #footer>
       <span class="dialog-footer">
         <el-button type="primary" v-if="parameter.isEdit" :loading="butLoading" @click="handleSubmit">确认</el-button>
@@ -29,6 +32,8 @@ import { ElMessage } from 'element-plus'
 import FileUpload from '@/components/Upload/File.vue'
 // import mittBus from '@/utils/mittBus'
 
+const videoUploadRef = ref<InstanceType<typeof FileUpload> | null>(null)
+
 export interface FormParameterProps {
   title: string // 标题
   width?: number // 弹框宽度
@@ -40,7 +45,9 @@ export interface FormParameterProps {
   itemsOptions: ProForm.ItemsOptions[] // 动态表单字段配置
   model?: Record<ProForm.FormItem['prop'], ProForm.FormItem['value']> // 表单数据对象
   getTableList?: () => void // 获取表格数据的Api
+  showVideoUpload: boolean // 是否显示视频上传
 }
+
 // dialog状态
 const dialogVisible = ref(false)
 const butLoading = ref(false)
@@ -51,7 +58,8 @@ const parameter = ref<FormParameterProps>({
   top: '10vh',
   itemsOptions: [],
   formOptions: {},
-  isEdit: true
+  isEdit: true,
+  showVideoUpload: false
 })
 const _options: ComputedRef<ProForm.FormOptions> = computed(() => {
   const form = {
@@ -66,11 +74,17 @@ const proFormRef = ref<InstanceType<typeof ProFrom> | null>(null)
 const handleSubmit = () => {
   const formEl = proFormRef.value?.proFormRef
   const formModel = proFormRef.value?.formModel
+
+  let urlModel = {}
+  if (parameter.value.showVideoUpload) {
+    console.log(videoUploadRef.value.uploadFileListExport)
+    urlModel = { url: videoUploadRef.value?.uploadFileListExport[0].url }
+  }
   butLoading.value = true
   if (!formEl) return
   formEl.validate(valid => {
     if (valid) {
-      parameter.value.api!({ ...formModel, ...parameter.value.model }).then(res => {
+      parameter.value.api!({ ...formModel, ...parameter.value.model, ...urlModel }).then(res => {
         if (res.code == 200) {
           proFormRef.value?.resetForm(formEl)
           ElMessage.success('操作成功')
@@ -119,3 +133,25 @@ 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>

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

@@ -29,6 +29,7 @@
             </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'">

+ 19 - 4
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,6 +176,11 @@ 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()
 }
 
@@ -210,6 +221,10 @@ const uploadError = () => {
 const handleExceed = () => {
   ElMessage.warning(`当前最多只能上传 ${props.limit} 个文件 ,请移除后上传!`)
 }
+
+defineExpose({
+  uploadFileListExport
+})
 </script>
 
 <style scoped lang="scss">

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

@@ -51,6 +51,11 @@
     <div class="el-upload__tip">
       <slot name="tip"></slot>
     </div>
+    <!--    <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" :url-list="['/api' + imageUrl]" @close="imgViewVisible = false" />
   </div>
 </template>
@@ -73,6 +78,7 @@ interface UploadFileProps {
   height?: string // 组件高度 ==> 非必传(默认为 150px)
   width?: string // 组件宽度 ==> 非必传(默认为 150px)
   borderRadius?: string // 组件边框圆角 ==> 非必传(默认为 8px)
+  isShowData?: boolean
 }
 
 // 接受父组件参数
@@ -84,7 +90,8 @@ const props = withDefaults(defineProps<UploadFileProps>(), {
   fileType: () => ['image/jpeg', 'image/png', 'image/gif'],
   height: '150px',
   width: '150px',
-  borderRadius: '8px'
+  borderRadius: '8px',
+  isShowData: false
 })
 
 // 生成组件唯一id

+ 15 - 0
src/routers/modules/routerData.json

@@ -30,6 +30,21 @@
         "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": "/amplify",
       "name": "amplify",

+ 105 - 24
src/views/demo/components/img-detect.vue

@@ -1,7 +1,27 @@
 <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"></ImgMaker>
-    <div style=" margin-top: 10px;text-align: center">
+    <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>
@@ -12,7 +32,7 @@
   </el-dialog>
 </template>
 <script lang="ts" setup>
-import { reactive, ref, toRefs, defineProps, watchEffect } from 'vue'
+import { reactive, ref, toRefs, defineProps, watchEffect, watch } from 'vue'
 import ImgMaker from './img-maker.vue'
 const props = defineProps({
   area: {
@@ -30,19 +50,40 @@ const props = defineProps({
   height: {
     type: Number,
     default: 1080
+  },
+  classes: {
+    type: Array
+  },
+  jsonData: {
+    type: Array
   }
 })
 const emit = defineEmits(['success'])
 const imgMaker = ref()
 const state = reactive({
   visible: false,
-  cover: ''
+  cover: '',
+  classDef: props.classes && props.classes.length > 0 ? props.classes[0] : { name: 'default', color: '#FF00FF', classValue: 0 }
 })
-const { visible, cover } = toRefs(state)
+
+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()
 }
@@ -58,31 +99,71 @@ const onSubmit = () => {
   let points = imgMaker.value.getData()
   // console.log('points', points)
 
-  let datas = []
-  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
+  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
+      }
+    }
 
-        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 {
-        let coors = ''
-        Object.keys(point).forEach(item => {
-          coors += `${point[item].x};${point[item].y};`
-        })
-        coors = coors.slice(0, -1)
-        return coors
+    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 += ' '
       }
-      // return `${point.tl.x};${point.tl.y};${point.tr.x};${point.tr.y}`
-    })
+    }
+
+    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', datas)
+  emit('success', emitData)
   onClearAll()
   state.visible = false
 }

+ 320 - 83
src/views/demo/components/img-maker.vue

@@ -1,7 +1,16 @@
 <template>
   <div>
     <div style="margin-bottom: 10px">
-      <el-button plain type="primary" class="shape-border" @click="drawTypeChange('rectangle')">矩形</el-button>
+      <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="canvas" :width="cWidth" :height="cHeight"></canvas>
@@ -10,6 +19,7 @@
 <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({
@@ -36,11 +46,17 @@ const props = defineProps({
   cHeight: {
     type: Number,
     default: 540
+  },
+  classDef: {
+    type: Object
+  },
+  jsonData: {
+    type: Array
   }
 })
 const state = reactive({
   loading: true,
-  radio: 0.5,
+  radio: 1,
   realRadioX: 0.5,
   realRadioY: 0.5,
   imgPoint: { x: 0, y: 0 },
@@ -48,11 +64,16 @@ const state = reactive({
   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: 1, //笔触宽度
+  drawWidth: 5, //笔触宽度
   color: '#E34F51', //画笔颜色
   drawingObject: null as any, //当前绘制对象
+  activeTarget: null as any, // 当前点击活跃对象
   moveCount: 1, //绘制移动计数器
   doDrawing: false as boolean, // 绘制状态
   rectPath: '' as string, //矩形绘制路径
@@ -64,9 +85,17 @@ const state = reactive({
   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
   }
 )
@@ -83,7 +112,39 @@ watch(
   }
 )
 
+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'
+  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
   }
@@ -93,6 +154,44 @@ const loadInit = () => {
   state.canvas.on('mouse:down', mousedown)
   state.canvas.on('mouse:move', mousemove)
   state.canvas.on('mouse:up', mouseup)
+  // // 添加鼠标滚轮缩放功能
+  // state.canvas.on('mouse:wheel', event => {
+  //   const delta = event.delta
+  //   const zoom = state.canvas.getZoom()
+  //   const point = new fabric.Point(event.x, event.y)
+  //
+  //   if (delta > 0) {
+  //     // 放大
+  //     state.canvas.zoomToPoint(point, zoom * 1.1)
+  //     // state.radio *= 1.1
+  //   } else {
+  //     // 缩小
+  //     state.canvas.zoomToPoint(point, zoom * 0.9)
+  //     // state.radio *= 0.9
+  //   }
+  //   state.canvas.renderAll()
+  //   event.e.preventDefault()
+  //   event.e.stopPropagation()
+  // })
+
+  // nice try!
+  // console.log(props.jsonData)
+  // if (props.jsonData && props.jsonData.length > 0) {
+  //   // console.log('load')
+  //   state.canvas.loadFromJSON(props.jsonData, () => {
+  //     let objects = state.canvas.getObjects()
+  //     // console.log(objects)
+  //     objects[0].selectable = false
+  //     for (let i = 1; i < objects.length; i++) {
+  //       objects[i].selectable = true
+  //     }
+  //     state.canvas.renderAll()
+  //   })
+  //   // console.log(state.canvas)
+  //   state.loading = false
+  //   return
+  // }
+
   let imgElement = new Image()
   imgElement.src = props.src
   imgElement.onload = () => {
@@ -110,6 +209,7 @@ const loadInit = () => {
 
     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,
@@ -118,75 +218,105 @@ const loadInit = () => {
       scaleY: state.radio
     })
     state.canvas.add(imgInstance)
-    drawImage()
-    state.canvas.renderAll()
+    if (props.jsonData && props.jsonData.length > 0) {
+      for (let i = 0; i < props.jsonData.length; i++) {
+        let config = props.jsonData[i]
+        let obj = new fabric.Path(config.pathString, config)
+        state.canvas.add(obj)
+      }
+      state.canvas.renderAll()
+    }
+    state.radio = state.canvas.getZoom()
     state.loading = false
+    // console.log(state.realRadioX)
+    // console.log(state.realRadioY)
   }
 }
-const drawImage = () => {
-  if (props.area === '') {
-    clearAll()
-    return
-  }
-  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 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 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
-    })
+
+const rotate = val => {
+  if (!state.activeTarget) {
+    ElMessage.error('请先选择旋转对象!')
   } 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
-    })
+    state.activeTarget.rotate(state.activeTarget.angle + val)
+    state.canvas.renderAll()
   }
-  canvasObject['points'] = points
-  state.canvas.add(canvasObject)
 }
+
 const drawTypeChange = e => {
+  state.dragMode = false
+  state.activeTarget = null
   state.drawType = e
   state.canvas.skipTargetFind = !!e
   if (e == 'pen') {
@@ -198,12 +328,36 @@ const drawTypeChange = e => {
 }
 // 鼠标按下时触发
 const mousedown = e => {
+  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)
-
-  state.mouseFrom.x = xy.x
-  state.mouseFrom.y = xy.y
+  // 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
@@ -226,9 +380,19 @@ const mousedown = e => {
 }
 // 鼠标松开执行
 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.mouseTo.y = xy.y
+  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') {
@@ -242,6 +406,43 @@ const mouseup = e => {
 }
 //鼠标移动过程中已经完成了绘制
 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
@@ -249,8 +450,8 @@ const mousemove = e => {
   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.mouseTo.y = xy.y
+    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) {
@@ -314,14 +515,31 @@ const drawing = () => {
     mouseFrom.y +
     ' z'
   state.rectPath = path
-  canvasObject = new fabric.Path(path, {
+  canvasObject = new fabric.Rect({
     left: left,
     top: top,
+    width: Math.abs(mouseTo.x - left),
+    height: Math.abs(mouseTo.y - top),
     stroke: state.color,
-    selectable: false,
     strokeWidth: state.drawWidth,
     fill: 'rgba(255, 255, 255, 0)',
-    hasControls: false
+    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)
@@ -445,8 +663,11 @@ const generatePolygon = () => {
   // })
   // state.canvas.add(polygon)
   let points = [{}]
-  console.log('state.pointArray', state.pointArray)
-
+  // 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,
@@ -535,10 +756,18 @@ const getData = () => {
   // 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 = {}
+      let point = {
+        nodes: [],
+        angle: item.angle,
+        label: item.label
+      }
       if (item.points) {
         item.points.forEach((item, idx) => {
           // if (aCoords[marks[idx]]) {
@@ -558,17 +787,25 @@ const getData = () => {
             x: getPoint(aCoords[mark].x),
             y: getPoint(aCoords[mark].y)
           }
-          point[mark] = getRealPoint(poi)
+          // 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']
-      }
+      // if (item.points && item.points.length === 2) {
+      //   delete point['br']
+      //   delete point['bl']
+      // }
       datas.push(point)
+      // console.log(point)
     }
   })
+  // console.log()
   return datas
 }
 // 获取归一化数据比例

+ 211 - 25
src/views/demo/data/index.vue

@@ -2,7 +2,8 @@
   <div class="table-box">
     <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listDataApi" :init-param="initParam">
       <template #yuan="scope">
-        <el-image style="width: 100px" :src="'/api' + scope.row.url" @click="markImg(scope.row)" />
+        <uploadImg :is-show-data="true" :disabled="true" :image-url="scope.row.url" />
+        <!--        <el-image style="width: 100px" :src="'/api' + scope.row.url" @click="markImg(scope.row)" />-->
       </template>
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
@@ -24,6 +25,7 @@
       <!-- 表格操作 -->
       <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>
@@ -32,12 +34,22 @@
 
     <FormDialog ref="formDialogRef" />
     <ImportPicDataset ref="dialogRef" />
-    <ImgDetect ref="imgDetect" :img="cover" :area="area" :width="width" :height="height" @success="handleImgSuccess"></ImgDetect>
+    <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, toRefs } from 'vue'
+import { ref, reactive, toRefs, onMounted } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useDownload } from '@/hooks/useDownload'
 import { useRouter } from 'vue-router'
@@ -49,7 +61,9 @@ 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,
   getFormSelectsApi,
@@ -61,15 +75,43 @@ 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'
+
+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 imgDetect = ref()
 const state = reactive({
   area: '' as string,
   width: 1920 as number,
   height: 1080 as number,
-  cover: ''
+  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 } = toRefs(state)
+const { area, width, height, cover, classes, jsonData } = toRefs(state)
 
 // ProTable 实例
 const proTable = ref<ProTableInstance>()
@@ -85,17 +127,107 @@ const deleteData = async (params: any) => {
 
 // 标注图片
 const markImg = data => {
-  state.cover = '/api' + data.url
-  // area 代表后端的传来的标注数据
-  const area = []
-  if (state.cover != '') {
-    if (area.length != 0) {
-      handleImgSuccess(area)
-    }
-    imgDetect.value.visible = true
-  } else {
+  // console.log(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')
+        http
+          .get<any>(state.cacheData.labelurl)
+          .then(res => {
+            state.jsonData = []
+            console.log(res)
+            let arr = res.split('\r\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({
+                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 initParam = reactive({ type: 1 })
 
@@ -114,18 +246,72 @@ const getList = () => {
     })
 }
 const handleImgSuccess = data => {
-  data.forEach(item => {
-    const area = item.split(';')
-    // try=tly blx=tlx brx=trx bry=bly
-    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(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)
+        })
+    }
   })
-  // console.log('state.area', state.area)
-  state.area.slice(0, -1)
-  getList()
+
+  // 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)
 }
 
 // 批量删除数据管理信息

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

@@ -0,0 +1,281 @@
+<!--
+  @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 !== '2'"
+        >
+          图片提取
+        </el-button>
+        <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)"> 删除 </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 } from '@/components/ProTable/interface'
+import {
+  listVideo2imageApi,
+  delVideo2imageApi,
+  addVideo2imageApi,
+  updateVideo2imageApi,
+  importTemplateApi,
+  importVideo2imageDataApi,
+  exportVideo2imageApi,
+  getVideo2imageApi,
+  startVideo2imageApi,
+  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('开始视频转图片开始失败,请检查!')
+  }
+}
+
+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 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
+  },
+  {
+    prop: 'outPath',
+    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: 'log',
+    label: '日志',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'remarks',
+    label: '备注',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'path',
+    label: '视频本身保存路径',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'fps',
+    label: '切割帧率,默认1',
+    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: 'remarks',
+      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'
+      }
+    }
+  ]
+}
+</script>
+
+<style scoped></style>

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

@@ -0,0 +1,388 @@
+<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 !== '2'"> 开始去抖动 </el-button>
+        <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="'图片对比: ' + taskName + '第' + imageIdx + '张图片对比'" width="80%">
+      <div class="image-dialog">
+        <el-image :src="'data:image/png;base64,' + imageBase64List.origin" style="width: 45%"></el-image>
+        <el-image :src="'data:image/png;base64,' + imageBase64List.stable" style="width: 45%"></el-image>
+      </div>
+      <div class="image-dialog-btn">
+        <el-button type="primary" @click="pre_picture" :disabled="imageIdx === 1">上一个</el-button>
+        <el-button type="primary" @click="next_picture">下一个</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 } from '@/components/ProTable/interface'
+import {
+  listVideoStableApi,
+  delVideoStableApi,
+  addVideoStableApi,
+  updateVideoStableApi,
+  importTemplateApi,
+  importVideoStableDataApi,
+  exportVideoStableApi,
+  getVideoStableApi,
+  startVideoStableApi,
+  getCompareImageApi
+} from '@/api/modules/demo/videoStable'
+
+const dialogVisible = ref(false)
+const taskName = ref('')
+const imageIdx = ref(1)
+const imageBase64List = ref({
+  origin: '',
+  stable: ''
+})
+
+const startVideoStable = async (params: any) => {
+  const res = await startVideoStableApi(params.id)
+  if (res.code === 200) {
+    ElMessage.success('开始去抖动成功')
+  } else {
+    ElMessage.error('开始去抖动失败')
+  }
+  proTable.value?.getTableList()
+}
+
+const loadImageData = async (taskName: string, imageIdx: number) => {
+  const res: any = await getCompareImageApi(taskName, imageIdx)
+  imageBase64List.value.origin = res.origin
+  imageBase64List.value.stable = res.stable
+}
+const compareVideoStable = async (params: any) => {
+  taskName.value = params.name
+  imageIdx.value = 1
+  await loadImageData(taskName.value, imageIdx.value)
+
+  dialogVisible.value = true
+}
+const next_picture = async () => {
+  imageIdx.value = imageIdx.value + 1
+  await loadImageData(taskName.value, imageIdx.value)
+}
+const pre_picture = async () => {
+  if (imageIdx.value > 1) {
+    imageIdx.value = imageIdx.value - 1
+    await loadImageData(taskName.value, imageIdx.value)
+  }
+}
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除视频去抖动信息
+const deleteVideoStable = async (params: any) => {
+  await useHandleData(delVideoStableApi, params.id, '删除【' + params.id + '】视频去抖动')
+  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 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: '任务状态 0未开始 1进行中 2已结束',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'inPath',
+    label: '输入图片集路径',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  {
+    prop: 'outPath',
+    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: 'log',
+    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: 'inPath',
+      rules: [{ required: true, message: '输入图片集路径不能为空', trigger: 'blur' }],
+      compOptions: {
+        type: 'block_size',
+        clearable: true,
+        placeholder: '请输入内容'
+      }
+    },
+    {
+      label: '输出路径',
+      prop: 'outPath',
+      rules: [{ required: true, message: '去抖动的图片集路径不能为空', trigger: 'blur' }],
+      compOptions: {
+        type: 'block_size',
+        clearable: true,
+        placeholder: '请输入内容'
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remarks',
+      rules: [
+        {
+          required: false,
+          message: '备注不能为空',
+          trigger: 'blur'
+        }
+      ],
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    },
+    {
+      label: 'block_size',
+      prop: 'block_size',
+      rules: [{ required: true, message: 'block_size不能为空', trigger: 'blur' }],
+      compOptions: {
+        type: 'input',
+        clearable: true,
+        placeholder: '请输入内容',
+        value: 50
+      }
+    },
+    {
+      label: 'radius',
+      prop: 'radius',
+      rules: [{ required: true, message: 'radius不能为空', trigger: 'blur' }],
+      compOptions: {
+        type: 'block_size',
+        clearable: true,
+        placeholder: '请输入内容',
+        value: 500
+      }
+    },
+    {
+      label: 'buffer_size',
+      prop: 'buffer_size',
+      rules: [{ required: true, message: 'buffer_size不能为空', trigger: 'blur' }],
+      compOptions: {
+        type: 'block_size',
+        clearable: true,
+        placeholder: '请输入内容',
+        value: 200
+      }
+    },
+    {
+      label: 'cornerquality',
+      prop: 'cornerquality',
+      rules: [{ required: true, message: 'cornerquality不能为空', trigger: 'blur' }],
+      compOptions: {
+        type: 'block_size',
+        clearable: true,
+        placeholder: '请输入内容',
+        value: 0.2
+      }
+    },
+    {
+      label: 'cornerminDistance',
+      prop: 'cornerminDistance',
+      rules: [{ required: true, message: 'cornerminDistance不能为空', trigger: 'blur' }],
+      compOptions: {
+        type: 'block_size',
+        clearable: true,
+        placeholder: '请输入内容',
+        value: 5
+      }
+    },
+    {
+      label: 'lklevel',
+      prop: 'lklevel',
+      rules: [{ required: true, message: 'lklevel不能为空', trigger: 'blur' }],
+      compOptions: {
+        type: 'block_size',
+        clearable: true,
+        placeholder: '请输入内容',
+        value: 3
+      }
+    },
+    {
+      label: 'lkwinSiz',
+      prop: 'lkwinSiz',
+      rules: [{ required: true, message: 'lkwinSiz不能为空', trigger: 'blur' }],
+      compOptions: {
+        type: 'block_size',
+        clearable: true,
+        placeholder: '请输入内容',
+        value: 15
+      }
+    }
+  ]
+}
+</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>

+ 16 - 8
src/views/taais/homePage/index.vue

@@ -2,14 +2,15 @@
   <div class="home-container">
     <dv-border-box1 ref="borderRef" style="width: 100%; height: 100%; margin: 0 auto">
       <div class="table-box">
-        <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listTaskApi" :tool-button="false">
+        <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="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>
+            <!--            <el-button type="primary" link :icon="View" @click="openDialog(3, '任务查看', scope.row)">查看</el-button>-->
           </template>
         </ProTable>
         <!-- <ProTable
@@ -42,7 +43,8 @@ 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 { listTaskApi, getTaskApi } from '@/api/modules/task/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'
@@ -65,13 +67,15 @@ const getTreeFilter = async () => {
 }
 // 查看详情
 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(row)
+  let res = getITaskNewApi(row?.id || null)
   // 表单项配置
   const fieldList: Form.FieldItem[] = [
     {
@@ -95,9 +99,13 @@ const openDialog = async (type: number, title: string, row?: any) => {
   formDialogRef.value?.openDialog(params)
 }
 const router = useRouter()
-// 增加任务
-const createTask = () => {
-  router.push(`/createTask`)
+// // 增加任务
+// const createTask = () => {
+//   router.push(`/createTask`)
+// }
+
+const createTaskNew = () => {
+  router.push(`/createTaskNew`)
 }
 // 表格配置项
 const columns = reactive<ColumnProps<User.ResUserList>[]>([

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

@@ -0,0 +1,563 @@
+<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="onModelSelect(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>
+            <el-button @click="showAddModelDialog"> 导入模型 </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" />
+
+    <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 } from '@/api/modules/ag/model'
+import { createTaskApi, getAlgorithmOptionApi } 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 = () => {
+  const params = {
+    taskName: formData.taskName,
+    taskType: formData.taskType,
+    taskItemList: formData.tasks,
+    algTaskList: formData.algorithms,
+    trainBatchNumList: formData.data,
+    testBatchNumList: formData.testData,
+    hasTrainAugmentation: formData.expandData,
+    trainAugmentationParams: formData.expandConfig
+  }
+  let result = valid(params)
+  // console.log(result)
+  if (result.code !== 200) {
+    ElMessage.error(result.message)
+    return
+  }
+
+  params.algTaskList.forEach(obj => {
+    obj.algorithmId = obj.id
+    // if (!obj.trainParams) {
+    //   return
+    // }
+    obj.params =
+      JSON.stringify(JSON.parse(obj.trainParams)) +
+      ';;;' +
+      JSON.stringify(JSON.parse(obj.verifyParams)) +
+      ';;;' +
+      JSON.stringify(JSON.parse(obj.testParams))
+  })
+  // 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
+})
+
+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],
+  // 训练数据
+  data: [null],
+  // 测试数据
+  testData: [null],
+  expandData: false,
+  expandConfig: ''
+})
+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
+  dataDialogVisible.value = true
+}
+
+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 onModelSelect = index => {
+  formData.algorithms[index] = algorithms[formData.algoIdx[index]]
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+
+const showAddModelDialog = () => {
+  getAlgorithmOptionApi().then(res => {
+    let allAgloData = {
+      value: res.data
+    }
+    const params = {
+      title: '算法模型配置',
+      width: 580,
+      isEdit: true,
+      itemsOptions: [
+        {
+          label: '算法',
+          prop: 'algorithmId',
+          rules: [{ required: true, message: '算法不能为空', trigger: 'blur' }],
+          compOptions: {
+            elTagName: 'select',
+            labelKey: 'name',
+            valueKey: 'id',
+            enum: allAgloData.value,
+            placeholder: '请选择算法'
+          }
+        },
+        {
+          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: '请输入训练循环次数'
+          }
+        },
+        {
+          label: '备注',
+          prop: 'remarks',
+          compOptions: {
+            placeholder: '请输入备注'
+          }
+        }
+      ],
+      model: {},
+      api: addModelApi
+    }
+
+    formDialogRef.value?.openDialog(params)
+  })
+}
+
+const appendNewItem = () => {
+  formData.algorithms.push({})
+  formData.algoIdx.push(null)
+}
+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)
+}
+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;
+}
+</style>

+ 37 - 11
src/views/task/bizProcess/index.vue

@@ -3,12 +3,14 @@
     <ProTable ref="proTable" :columns="columns" row-key="id" :data="bizProcessList">
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
-        <!-- <el-button type="primary" v-auth="['task:bizProcess:add']" icon="CirclePlus" @click="openDialog(1, '算法业务处理新增')"> 新增 </el-button>
-        <el-button type="primary" v-auth="['task:bizProcess:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button> -->
-        <el-button type="primary" v-auth="['task:bizProcess:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button>
+        <!-- <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="['task:bizProcess:remove']"
+          v-auth="['identification:identificationSubtaskDetails:remove']"
           icon="Delete"
           plain
           :disabled="!scope.isSelected"
@@ -16,18 +18,42 @@
         >
           批量删除
         </el-button>
-        <el-button type="primary" v-auth="['task:bizProcess:add']" icon="View" @click="contrastResults()"> 对比结果 </el-button>
+        <el-button type="primary" v-auth="['identification:identificationSubtaskDetails:add']" icon="View" @click="contrastResults()">
+          对比结果
+        </el-button>
       </template>
       <!-- 表格操作 -->
       <template #operation="scope">
-        <el-button type="primary" link icon="View" v-auth="['task:bizProcess: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:bizProcess: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:bizProcess:remove']" @click="deleteBizProcess(scope.row)"> 删除 </el-button>
-        <el-button type="primary" link icon="View" v-auth="['task:bizProcess:query']" @click="viewLog(scope.row)"> 查看日志 </el-button>
+        <el-button
+          type="primary"
+          link
+          icon="Delete"
+          v-auth="['identification:identificationSubtaskDetails:remove']"
+          @click="deleteBizProcess(scope.row)"
+        >
+          删除
+        </el-button>
+        <el-button type="primary" link icon="View" v-auth="['identification:identificationSubtaskDetails:query']" @click="viewLog(scope.row)">
+          查看日志
+        </el-button>
       </template>
     </ProTable>
     <FormDialog ref="formDialogRef" />
@@ -120,7 +146,7 @@ import {
   getTrainResultApi,
   getVerifyResultApi,
   getTestResultApi
-} from '@/api/modules/task/bizProcess'
+} from '@/api/modules/task/bizProcessNew'
 // getTrainResultApi,getVerifyResultApi,getTestResultApi
 import { getSubtaskApi } from '@/api/modules/task/subtask'
 import { getDictsApi } from '@/api/modules/system/dictData'
@@ -150,7 +176,7 @@ const refreshList = () => {
     listBizProcessApi({
       pageNum: 1,
       pageSize: 100,
-      subTaskId
+      subtaskId: subTaskId
     }).then(res => {
       bizProcessList.value = res.data['list'].sort((a, b) => b.index - a.index)
     })

+ 18 - 8
src/views/task/subtask/index.vue

@@ -3,12 +3,12 @@
     <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="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="['task:subtask:remove']"
+          v-auth="['identification:identificationTask:remove']"
           icon="Delete"
           plain
           :disabled="!scope.isSelected"
@@ -19,11 +19,21 @@
       </template>
       <!-- 表格操作 -->
       <template #operation="scope">
-        <el-button type="primary" link icon="View" v-auth="['task:subtask: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="View" v-auth="['identification:identificationTask:query']" @click="viewDetails(scope.row)">
+          查看详情
+        </el-button>
+        <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" />
@@ -41,7 +51,7 @@ 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, 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()

+ 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 实例

+ 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>