index.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. <template>
  2. <div class="table-box">
  3. <ProTable ref="proTable" :columns="columns" row-key="id" :request-api="listDataAugmentationApi3">
  4. <!-- 表格 header 按钮 -->
  5. <template #tableHeader="scope">
  6. <el-button type="primary" v-auth="['demo:DataAugmentation:add']" icon="CirclePlus" @click="openDialog(1, '任务新增')"> 新增 </el-button>
  7. <!-- <el-button type="primary" v-auth="['demo:DataAugmentation:import']" icon="Upload" plain @click="batchAdd"> 导入</el-button> -->
  8. <!-- <el-button type="primary" v-auth="['demo:DataAugmentation:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button> -->
  9. <el-button
  10. type="danger"
  11. v-auth="['demo:DataAugmentation:remove']"
  12. icon="Delete"
  13. plain
  14. :disabled="!scope.isSelected"
  15. @click="batchDelete(scope.selectedListIds)"
  16. >
  17. 批量删除
  18. </el-button>
  19. </template>
  20. <!-- 表格操作 -->
  21. <template #operation="scope">
  22. <el-button type="primary" link icon="View" @click="startDataAugmentation(scope.row)" v-if="scope.row.status == '0'"> 开始 </el-button>
  23. <el-popconfirm title="确定终止此任务吗?" @confirm="stopDataAugmentation(scope.row)" v-if="scope.row.status == '1'">
  24. <template #reference>
  25. <el-button type="primary" link icon="View"> 终止 </el-button>
  26. </template>
  27. </el-popconfirm>
  28. <el-button type="primary" link icon="View" @click="openResultDialog(scope.row.id)" v-if="scope.row.status == '2'"> 预览 </el-button>
  29. <el-button type="primary" link icon="View" @click="downloadFile(scope.row)" v-if="scope.row.status == '2'"> 导出 </el-button>
  30. <el-button
  31. type="primary"
  32. link
  33. icon="View"
  34. @click="openMetricDialog(scope.row.id)"
  35. v-if="(scope.row.taskType == '图像增强' || scope.row.taskType == '图像逆光') && scope.row.status == '2'"
  36. >
  37. 指标
  38. </el-button>
  39. <el-button type="primary" link icon="View" v-auth="['demo:DataAugmentation:query']" @click="openDialog(3, '任务查看', scope.row)">
  40. 查看
  41. </el-button>
  42. <el-button
  43. type="primary"
  44. link
  45. icon="View"
  46. v-auth="['demo:DataAugmentation:query']"
  47. @click="viewLogRef.handleOpen(scope.row.id)"
  48. v-if="scope.row.status != '0' && scope.row.status != '1'"
  49. >
  50. 日志
  51. </el-button>
  52. <el-button type="primary" link icon="Delete" v-auth="['demo:DataAugmentation:remove']" @click="deleteDataAugmentation(scope.row)">
  53. 删除
  54. </el-button>
  55. </template>
  56. </ProTable>
  57. <DataAugmentationFormDialog ref="formDialogRef" />
  58. <ImportExcel ref="dialogRef" />
  59. <el-dialog v-model="dialogVisible" title="结果预览" width="80%">
  60. <div style="display: flex; text-align: center">
  61. <div style="width: 50%" v-if="inputVideoUrl">
  62. <video controls width="90%">
  63. <source :src="inputVideoUrl" />
  64. Your browser does not support the video tag.
  65. </video>
  66. <!-- <video-player ref="videoPlayer" :options="playerOptions" @play="onPlay" @pause="onPause" />
  67. <button @click="playVideo">开始播放</button>
  68. <button @click="pauseVideo">暂停播放</button> -->
  69. </div>
  70. <div style="width: 50%" v-if="outputVideoUrl">
  71. <video controls width="90%">
  72. <source :src="outputVideoUrl" />
  73. Your browser does not support the video tag.
  74. </video>
  75. </div>
  76. </div>
  77. </el-dialog>
  78. <!-- <VideoWindow ref="videoWindowRef" :video-url="url"></VideoWindow> -->
  79. <!-- <el-dialog v-model="logDialogVisible" title="日志" width="80%">
  80. <el-text class="mx-1">{{ logDialog }}</el-text>
  81. </el-dialog> -->
  82. <ViewLog ref="viewLogRef" :get-log-api="getDialogApi" />
  83. <el-dialog v-model="metricDialogVisible" title="算法结果指标" width="500px" style="text-align: center">
  84. <el-table :data="metricList" style="width: 100%; margin-left: 10%; text-align: left">
  85. <el-table-column prop="name" label="name" width="200px" />
  86. <el-table-column prop="value" label="value" width="200px" />
  87. </el-table>
  88. </el-dialog>
  89. </div>
  90. </template>
  91. <script setup lang="tsx" name="DataAugmentation">
  92. import { ref, reactive, onMounted, computed, onUnmounted, onBeforeMount } from 'vue'
  93. import { useHandleData } from '@/hooks/useHandleData'
  94. import { useDownload } from '@/hooks/useDownload'
  95. import { ElMessageBox, ElMessage } from 'element-plus'
  96. import ProTable from '@/components/ProTable/index.vue'
  97. import ImportExcel from '@/components/ImportExcel/index.vue'
  98. import ViewLog from '@/views/demo/components/ViewLog.vue'
  99. import DataAugmentationFormDialog from '@/components/DataAugmentationFormDialog/index.vue'
  100. import { ProTableInstance, ColumnProps, EnumProps } from '@/components/ProTable/interface'
  101. import {
  102. listDataAugmentationApi3,
  103. delDataAugmentationApi,
  104. addDataAugmentationApi,
  105. updateDataAugmentationApi,
  106. importTemplateApi,
  107. importDataAugmentationDataApi,
  108. exportDataAugmentationApi,
  109. getDataAugmentationApi,
  110. startDataAugmentationApi,
  111. stopDataAugmentationApi,
  112. getCompareImageApi,
  113. getCompareImageCountApi,
  114. getDialogApi,
  115. getTaskDictData,
  116. getMetricApi,
  117. getVideoUrl
  118. } from '@/api/modules/demo/dataAugmentation'
  119. // import { VideoPlayer } from 'vue-video-player'
  120. // import 'vue-video-player/dist/simple.css'
  121. // const playerOptions = ref({})
  122. // const videoPlayer = ref<InstanceType<typeof VideoPlayer> | null>(null);
  123. // const playVideo = () => {
  124. // if (videoPlayer.value) {
  125. // videoPlayer.value.player.play();
  126. // }
  127. // };
  128. // const pauseVideo = () => {
  129. // if (videoPlayer.value) {
  130. // videoPlayer.value.player.pause();
  131. // }
  132. // };
  133. // const onPlay = () => {
  134. // console.log('视频开始播放');
  135. // };
  136. // const onPause = () => {
  137. // console.log('视频暂停播放');
  138. // };
  139. // const setPlayerOptions = (url) => {
  140. // playerOptions.value = {
  141. // sources: [{
  142. // type: "video/mp4",
  143. // src: url
  144. // }],
  145. // poster: "", // 视频封面图
  146. // autoplay: false,
  147. // controls: true,
  148. // fluid: true // 响应式布局
  149. // }
  150. // }
  151. //打开指标窗口查看算法指标信息
  152. const metricDialogVisible = ref(false)
  153. const metricList = reactive([])
  154. const openMetricDialog = async (id: string | number) => {
  155. const res: any = await getMetricApi(id)
  156. console.log(res.data)
  157. metricList.splice(0, metricList.length, ...res.data)
  158. metricDialogVisible.value = true
  159. }
  160. const dialogVisible = ref(false)
  161. const inputVideoUrl = ref()
  162. const outputVideoUrl = ref()
  163. const openResultDialog = async (id: string | number) => {
  164. const res: any = await getVideoUrl(id)
  165. inputVideoUrl.value = res.data.input
  166. outputVideoUrl.value = res.data.output
  167. //setPlayerOptions(inputVideoUrl.value)
  168. // inputVideoUrl.value = '/api/public/video/input/' + id
  169. // outputVideoUrl.value = '/api/public/video/output/' + id
  170. console.log(inputVideoUrl)
  171. dialogVisible.value = true
  172. }
  173. const imageIdx = ref(0)
  174. const taskType = ref([])
  175. const modelType = ref([
  176. {
  177. label: 'yolov8_best',
  178. value: 'yolov8_best'
  179. },
  180. {
  181. label: 'yolov8_best_new',
  182. value: 'yolov8_best_new'
  183. },
  184. {
  185. label: 'yolov8__best',
  186. value: 'yolov8__best'
  187. },
  188. {
  189. label: 'yolov8__best_new',
  190. value: 'yolov8__best_new'
  191. }
  192. ])
  193. const trackingMethod = ref([
  194. {
  195. label: 'botsort',
  196. value: 'botsort'
  197. },
  198. {
  199. label: 'strongsort',
  200. value: 'strongsort'
  201. },
  202. {
  203. label: 'bytetrack',
  204. value: 'bytetrack'
  205. },
  206. {
  207. label: 'ocsort',
  208. value: 'ocsort'
  209. },
  210. {
  211. label: 'imprassoc',
  212. value: 'imprassoc'
  213. },
  214. {
  215. label: 'deepocsort',
  216. value: 'deepocsort'
  217. }
  218. ])
  219. const taskTypeEnums: EnumProps[] = []
  220. const hyperparameterConfiguration = []
  221. const viewLogRef = ref()
  222. const getTaskType = async () => {
  223. const res: any = await getTaskDictData()
  224. if (res.data.length != 0) {
  225. taskType.value = res.data
  226. .map(item => {
  227. if (item.taskType === '多目标跟踪') {
  228. return {
  229. label: item.taskType,
  230. value: item.taskType
  231. }
  232. } else {
  233. return null
  234. }
  235. })
  236. .filter(item => item !== null)
  237. res.data.forEach(item => {
  238. if (item.taskType === '多目标跟踪') {
  239. taskTypeEnums.push({
  240. label: item.taskType,
  241. value: item.taskType,
  242. disabled: false,
  243. tagType: 'default'
  244. })
  245. let obj = {}
  246. obj[item.taskType] = item.hyperparameterConfiguration
  247. hyperparameterConfiguration.push(obj)
  248. }
  249. })
  250. }
  251. }
  252. let intervalId: NodeJS.Timeout | null = null
  253. onUnmounted(() => {
  254. // 组件卸载时清除定时器
  255. if (intervalId !== null) {
  256. clearInterval(intervalId)
  257. }
  258. })
  259. onMounted(() => {
  260. intervalId = setInterval(async () => {
  261. proTable.value?.getTableList()
  262. }, 5000)
  263. getTaskType()
  264. })
  265. const startDataAugmentation = async (params: any) => {
  266. const res = await startDataAugmentationApi(params.id)
  267. if (res.code === 200) {
  268. ElMessage.success('任务已经开始,请等待')
  269. } else {
  270. ElMessage.error('任务开始失败,请检查!')
  271. }
  272. proTable.value?.getTableList()
  273. }
  274. const stopDataAugmentation = async (params: any) => {
  275. const res = await stopDataAugmentationApi(params.id)
  276. if (res.code === 200) {
  277. ElMessage.success('任务终止成功')
  278. } else {
  279. ElMessage.error('任务终止失败!')
  280. }
  281. proTable.value?.getTableList()
  282. }
  283. // ProTable 实例
  284. const proTable = ref<ProTableInstance>()
  285. // 删除视频去抖动信息
  286. const deleteDataAugmentation = async (params: any) => {
  287. await useHandleData(delDataAugmentationApi, params.id, '删除任务【' + params.name + '】?')
  288. proTable.value?.getTableList()
  289. }
  290. // 批量删除视频去抖动信息
  291. const batchDelete = async (ids: string[]) => {
  292. await useHandleData(delDataAugmentationApi, ids, '删除所选任务?')
  293. proTable.value?.clearSelection()
  294. proTable.value?.getTableList()
  295. }
  296. // 导出视频去抖动列表
  297. const downloadFile = async task => {
  298. ElMessageBox.confirm('确认导出任务数据?', '温馨提示', { type: 'warning' }).then(() =>
  299. useDownload(exportDataAugmentationApi, 'export', task.outputPath)
  300. )
  301. }
  302. // 批量添加视频去抖动
  303. const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
  304. const formDialogRef = ref<InstanceType<typeof DataAugmentationFormDialog> | null>(null)
  305. // 打开弹框的功能
  306. const openDialog = async (type: number, title: string, row?: any) => {
  307. // hyperparameter.value = ''
  308. let res = { data: {} }
  309. if (row?.id) {
  310. res = await getDataAugmentationApi(row?.id || null)
  311. // hyperparameter.value = res.data?.hyperparameterConfiguration
  312. }
  313. // console.log(itemsOptions[1].compOptions?.value)
  314. // 重置表单
  315. // setItemsOptions()
  316. const params = {
  317. title,
  318. width: 580,
  319. isEdit: type !== 3,
  320. itemsOptions: itemsOptions,
  321. model: type == 1 ? model : res.data,
  322. api: type == 1 ? addDataAugmentationApi : updateDataAugmentationApi,
  323. getTableList: proTable.value?.getTableList
  324. }
  325. formDialogRef.value?.openDialog(params)
  326. }
  327. const statusEnums: EnumProps[] = [
  328. {
  329. label: '未开始',
  330. value: '0',
  331. disabled: false,
  332. tagType: 'default'
  333. },
  334. {
  335. label: '进行中',
  336. value: '1',
  337. disabled: false,
  338. tagType: 'primary'
  339. },
  340. {
  341. label: '完成',
  342. value: '2',
  343. disabled: false,
  344. tagType: 'success'
  345. },
  346. {
  347. label: '失败',
  348. value: '3',
  349. disabled: false,
  350. tagType: 'danger'
  351. },
  352. {
  353. label: '已中断',
  354. value: '4',
  355. disabled: false,
  356. tagType: 'default'
  357. }
  358. ]
  359. // 表格配置项
  360. const columns = reactive<ColumnProps<any>[]>([
  361. { type: 'selection', fixed: 'left', width: 70 },
  362. { prop: 'id', label: '主键ID', width: 180 },
  363. {
  364. prop: 'name',
  365. label: '任务名称',
  366. search: {
  367. el: 'input'
  368. },
  369. width: 150
  370. },
  371. {
  372. prop: 'taskType',
  373. label: '任务类型',
  374. search: {
  375. el: 'select'
  376. },
  377. width: 100,
  378. tag: true,
  379. enum: taskTypeEnums
  380. },
  381. {
  382. prop: 'status',
  383. label: '任务状态',
  384. search: {
  385. el: 'select'
  386. },
  387. width: 100,
  388. tag: true,
  389. enum: statusEnums
  390. },
  391. {
  392. prop: 'startTime',
  393. label: '开始时间',
  394. width: 180
  395. },
  396. {
  397. prop: 'endTime',
  398. label: '结束时间',
  399. width: 180
  400. },
  401. {
  402. prop: 'costSecond',
  403. label: '耗时',
  404. search: {
  405. el: 'input'
  406. },
  407. width: 80
  408. },
  409. {
  410. prop: 'log',
  411. label: '日志',
  412. width: 120
  413. },
  414. {
  415. prop: 'algorithmPath',
  416. label: '日志路径',
  417. width: 120
  418. },
  419. {
  420. prop: 'hyperparameterConfiguration',
  421. label: '超参配置',
  422. width: 120
  423. },
  424. {
  425. prop: 'remarks',
  426. label: '备注',
  427. search: {
  428. el: 'input'
  429. },
  430. width: 120
  431. },
  432. {
  433. prop: 'operation',
  434. label: '操作',
  435. width: 230,
  436. fixed: 'right'
  437. }
  438. ])
  439. // 表单配置项
  440. let itemsOptions: ProForm.ItemsOptions[] = [
  441. {
  442. label: '任务名称',
  443. prop: 'name',
  444. rules: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
  445. compOptions: {
  446. placeholder: '请输入任务名称'
  447. }
  448. },
  449. {
  450. label: '任务类型',
  451. prop: 'taskType',
  452. rules: [{ required: true, message: '任务类型不能为空', trigger: 'change' }],
  453. compOptions: {
  454. elTagName: 'select', // 指定使用 el-select 组件
  455. placeholder: '请选择任务类型',
  456. enum: taskType,
  457. onChange: (value: string) => {
  458. model.value['taskType'] = value
  459. hyperparameterConfiguration.forEach(obj => {
  460. if (value in obj) {
  461. // console.log(obj[value])
  462. addParams(obj[value], value)
  463. openDialog(1, '任务新增')
  464. }
  465. })
  466. }
  467. }
  468. },
  469. {
  470. label: '图片集压缩包',
  471. prop: 'inputOssId',
  472. rules: [{ required: true, message: '数据压缩包不能为空', trigger: 'change' }],
  473. compOptions: {
  474. elTagName: 'file-upload',
  475. fileSize: 4096,
  476. fileType: ['zip'],
  477. placeholder: '请上传图片集压缩包'
  478. }
  479. },
  480. {
  481. label: '备注',
  482. prop: 'remarks',
  483. rules: [
  484. {
  485. required: false,
  486. trigger: 'blur'
  487. }
  488. ],
  489. compOptions: {
  490. placeholder: '请输入备注'
  491. }
  492. }
  493. ]
  494. const model = ref({})
  495. const setItemsOptions = () => {
  496. if (itemsOptions.length > 4) {
  497. itemsOptions.splice(4) // 如果里面有新增参数,删除,重新添加
  498. }
  499. }
  500. const addParams = (params, task_Type) => {
  501. setItemsOptions()
  502. if (params == 'null') {
  503. return
  504. }
  505. let validJsonString = params.replace(/'/g, '"')
  506. if (task_Type === '多目标跟踪') {
  507. itemsOptions.push({
  508. label: '模型',
  509. prop: 'yolo_model',
  510. rules: [{ trigger: 'blur' }],
  511. compOptions: {
  512. elTagName: 'select',
  513. enum: modelType,
  514. placeholder: '默认值为' + 'yolov8__best.pt'
  515. }
  516. })
  517. itemsOptions.push({
  518. label: '跟踪方法',
  519. prop: 'tracking_method',
  520. rules: [{ trigger: 'blur' }],
  521. compOptions: {
  522. elTagName: 'select',
  523. enum: trackingMethod,
  524. placeholder: '默认值为' + 'bytetrack'
  525. }
  526. })
  527. }
  528. model.value['tracking_method'] = 'bytetrack'
  529. model.value['yolo_model'] = 'yolov8__best.pt'
  530. try {
  531. const obj: { [key: string]: number } = JSON.parse(validJsonString)
  532. Object.keys(obj).forEach(key => {
  533. model.value[key] = obj[key]
  534. if (key === 'conf' || key === 'iou') {
  535. itemsOptions.push({
  536. label: key,
  537. prop: key,
  538. rules: [
  539. {
  540. trigger: 'change',
  541. validator: (rule, value, callback) => {
  542. if (value === '' || value === undefined) {
  543. return callback()
  544. }
  545. if (!value) {
  546. return callback(new Error('请输入一个0到1之间的浮点数'))
  547. }
  548. const regex = /^(0(\.\d+)?|1(\.0+)?)$/
  549. if (!regex.test(value)) {
  550. return callback(new Error('请输入一个0到1之间的浮点数'))
  551. }
  552. callback()
  553. }
  554. }
  555. ],
  556. compOptions: {
  557. type: 'input',
  558. clearable: true,
  559. placeholder: '默认值为' + obj[key]
  560. }
  561. })
  562. } else if (key === 'imgsz') {
  563. itemsOptions.push({
  564. label: key,
  565. prop: key,
  566. rules: [
  567. {
  568. trigger: 'change',
  569. validator: (rule, value, callback) => {
  570. //console.log(value)
  571. if (value === '' || value === undefined) {
  572. return callback()
  573. }
  574. if (!value) {
  575. return callback(new Error('请输入一个大于0的整数'))
  576. }
  577. const regex = /^[1-9]\d*$/
  578. if (!regex.test(value)) {
  579. return callback(new Error('请输入一个大于0的整数'))
  580. }
  581. callback()
  582. }
  583. }
  584. ],
  585. compOptions: {
  586. type: 'input',
  587. clearable: true,
  588. placeholder: '默认值为' + obj[key]
  589. }
  590. })
  591. }
  592. })
  593. } catch (error) {
  594. console.error('解析 JSON 字符串时出错:', error)
  595. }
  596. }
  597. </script>
  598. <style lang="scss" scoped>
  599. .image-dialog {
  600. display: flex;
  601. align-items: center;
  602. justify-content: center;
  603. .el-image {
  604. margin-right: 20px;
  605. margin-bottom: 20px;
  606. }
  607. }
  608. .image-dialog-btn {
  609. display: flex;
  610. justify-content: center;
  611. margin-top: 20px;
  612. }
  613. </style>