index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. <template>
  2. <div class="table-box">
  3. <ProTable ref="proTable" :columns="columns" row-key="id" :data="bizProcessList">
  4. <!-- 表格 header 按钮 -->
  5. <template #tableHeader="scope">
  6. <!-- <el-button type="primary" v-auth="['identification:identificationSubtaskDetails:add']" icon="CirclePlus" @click="openDialog(1, '算法业务处理新增')"> 新增 </el-button>
  7. <el-button type="primary" v-auth="['identification:identificationSubtaskDetails:import']" icon="Upload" plain @click="batchAdd"> 导入 </el-button> -->
  8. <el-button type="primary" v-auth="['identification:identificationSubtaskDetails:export']" icon="Download" plain @click="downloadFile"> 导出 </el-button>
  9. <el-button
  10. type="danger"
  11. v-auth="['identification:identificationSubtaskDetails:remove']"
  12. icon="Delete"
  13. plain
  14. :disabled="!scope.isSelected"
  15. @click="batchDelete(scope.selectedListIds)"
  16. >
  17. 批量删除
  18. </el-button>
  19. <el-button type="primary" v-auth="['identification:identificationSubtaskDetails:add']" icon="View" @click="contrastResults()"> 对比结果 </el-button>
  20. </template>
  21. <!-- 表格操作 -->
  22. <template #operation="scope">
  23. <el-button type="primary" link icon="View" v-auth="['identification:identificationSubtaskDetails:query']" @click="openDialog(3, '算法业务处理查看', scope.row)">
  24. 查看
  25. </el-button>
  26. <el-button type="primary" link icon="EditPen" v-auth="['identification:identificationSubtaskDetails:edit']" @click="openDialog(2, '算法业务处理编辑', scope.row)">
  27. 编辑
  28. </el-button>
  29. <el-button type="primary" link icon="Delete" v-auth="['identification:identificationSubtaskDetails:remove']" @click="deleteBizProcess(scope.row)"> 删除 </el-button>
  30. <el-button type="primary" link icon="View" v-auth="['identification:identificationSubtaskDetails:query']" @click="viewLog(scope.row)"> 查看日志 </el-button>
  31. </template>
  32. </ProTable>
  33. <FormDialog ref="formDialogRef" />
  34. <ImportExcel ref="dialogRef" />
  35. <el-dialog v-model="dialogVisible" title="日志" width="70%">
  36. <div class="log" ref="logRef">
  37. <div class="p" v-for="(item, index) in logInfo" :key="index">{{ item }}</div>
  38. </div>
  39. </el-dialog>
  40. <el-dialog v-model="resultVisible" title="对比结果" width="70%">
  41. <div v-if="!resultsFlag" class="resultShow">
  42. <el-row class="headerRow">
  43. <el-col class="col" :span="spanNum" v-for="(agloName, index) in resultsData['agNameList']" :key="index">
  44. <span>{{ agloName }}</span>
  45. </el-col>
  46. </el-row>
  47. <el-row class="row">
  48. <el-col class="col" :span="spanNum" v-for="(RCurveUrl, index) in resultsData['rcureList']" :key="index">
  49. <div v-if="index === 0" :span="4" class="oneCol">{{ RCurveUrl }}</div>
  50. <ImagePreview
  51. class="img"
  52. v-else
  53. :width="100"
  54. :height="100"
  55. :src="'/api/profile' + RCurveUrl"
  56. :preview-src-list="['/api/profile' + RCurveUrl]"
  57. />
  58. </el-col>
  59. </el-row>
  60. <el-row class="row">
  61. <el-col class="col" :span="spanNum" v-for="(PCurveUrl, index) in resultsData['pcureList']" :key="index">
  62. <div v-if="index === 0" class="oneCol">{{ PCurveUrl }}</div>
  63. <ImagePreview
  64. class="img"
  65. v-else
  66. :width="100"
  67. :height="100"
  68. :src="'/api/profile' + PCurveUrl"
  69. :preview-src-list="['/api/profile' + PCurveUrl]"
  70. />
  71. </el-col>
  72. </el-row>
  73. <el-row class="row">
  74. <el-col class="col" :span="spanNum" v-for="(F1CurveUrl, index) in resultsData['f1cureList']" :key="index">
  75. <div v-if="index === 0" class="oneCol">{{ F1CurveUrl }}</div>
  76. <ImagePreview
  77. class="img"
  78. v-else
  79. :width="100"
  80. :height="100"
  81. :src="'/api/profile' + F1CurveUrl"
  82. :preview-src-list="['/api/profile' + F1CurveUrl]"
  83. />
  84. </el-col>
  85. </el-row>
  86. </div>
  87. <div v-if="resultsFlag" class="resultShow">
  88. <el-row class="headerRow">
  89. <el-col class="col" :span="spanNum" v-for="(agloName, index) in testResultsData['agNameList']" :key="index">
  90. <span>{{ agloName }}</span>
  91. </el-col>
  92. </el-row>
  93. <el-row class="row" v-for="(item, index) in testResultsData['resultList']" :key="index">
  94. <el-col class="col" :span="spanNum" v-for="(url, index1) in item" :key="index1">
  95. <!-- <span>{{ url }}</span> -->
  96. <ImagePreview class="img" :width="100" :height="100" :src="'/api/profile' + url" :preview-src-list="['/api/profile' + url]" />
  97. </el-col>
  98. </el-row>
  99. </div>
  100. </el-dialog>
  101. </div>
  102. </template>
  103. <script setup lang="tsx" name="BizProcess">
  104. import { ref, reactive, onMounted, onUnmounted, watch, nextTick } from 'vue'
  105. import { useHandleData } from '@/hooks/useHandleData'
  106. import { useDownload } from '@/hooks/useDownload'
  107. import { ElMessageBox, ElMessage } from 'element-plus'
  108. import ProTable from '@/components/ProTable/index.vue'
  109. import ImportExcel from '@/components/ImportExcel/index.vue'
  110. import FormDialog from '@/components/FormDialog/index.vue'
  111. import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
  112. import {
  113. listBizProcessApi,
  114. delBizProcessApi,
  115. addBizProcessApi,
  116. updateBizProcessApi,
  117. exportBizProcessApi,
  118. getBizProcessApi,
  119. getTrainResultApi,
  120. getVerifyResultApi,
  121. getTestResultApi
  122. } from '@/api/modules/task/bizProcessNew'
  123. // getTrainResultApi,getVerifyResultApi,getTestResultApi
  124. import { getSubtaskApi } from '@/api/modules/task/subtask'
  125. import { getDictsApi } from '@/api/modules/system/dictData'
  126. import { useRoute } from 'vue-router'
  127. import ImagePreview from '@/components/ImagePreview/index.vue'
  128. const route = useRoute()
  129. const subTaskId = route.query.id as string
  130. // ProTable 实例
  131. const proTable = ref<ProTableInstance>()
  132. const dialogVisible = ref(false)
  133. const resultVisible = ref(false)
  134. let resultsData = ref({})
  135. let testResultsData = ref({})
  136. let resultsFlag = ref(false)
  137. let spanNum = ref<number>(4)
  138. // let logList = ref()
  139. const logRef = ref<HTMLElement | null>(null) // 显式声明 logRef 的类型;
  140. let logInfo = ref([] as any[])
  141. let taskType = ref()
  142. let taskStatus = ref()
  143. let bizProcessList = ref()
  144. // 每隔10秒请求一下列表
  145. const timer = ref() // 定时器
  146. const refreshList = () => {
  147. setTimeout(() => {
  148. listBizProcessApi({
  149. pageNum: 1,
  150. pageSize: 100,
  151. subtaskId: subTaskId
  152. }).then(res => {
  153. bizProcessList.value = res.data['list'].sort((a, b) => b.index - a.index)
  154. })
  155. }, 0)
  156. }
  157. onMounted(() => {
  158. getSubtaskApi(subTaskId).then(res => {
  159. taskType.value = res.data.type
  160. taskStatus.value = res.data.status
  161. })
  162. refreshList()
  163. timer.value = setInterval(() => {
  164. refreshList()
  165. }, 10000)
  166. // 组件挂载后,logRef 将指向实际的 DOM 元素
  167. if (logRef.value) {
  168. // 操作 logRef 对应的 DOM 元素
  169. logRef.value.scrollTop = logRef.value.scrollHeight
  170. }
  171. })
  172. // 删除算法业务处理信息
  173. const deleteBizProcess = async (params: any) => {
  174. await useHandleData(delBizProcessApi, params.id, '删除【' + params.id + '】算法业务处理')
  175. proTable.value?.getTableList()
  176. }
  177. // 批量删除算法业务处理信息
  178. const batchDelete = async (ids: string[]) => {
  179. await useHandleData(delBizProcessApi, ids, '删除所选算法业务处理信息')
  180. proTable.value?.clearSelection()
  181. proTable.value?.getTableList()
  182. }
  183. // 导出算法业务处理列表
  184. const downloadFile = async () => {
  185. ElMessageBox.confirm('确认导出算法业务处理数据?', '温馨提示', { type: 'warning' }).then(() =>
  186. useDownload(exportBizProcessApi, '算法业务处理列表', proTable.value?.searchParam)
  187. )
  188. }
  189. // 批量添加算法业务处理
  190. const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
  191. // const batchAdd = () => {
  192. // const params = {
  193. // title: '算法业务处理',
  194. // tempApi: importTemplateApi,
  195. // importApi: importBizProcessDataApi,
  196. // getTableList: proTable.value?.getTableList
  197. // }
  198. // dialogRef.value?.acceptParams(params)
  199. // }
  200. // 对比结果
  201. const contrastResults = () => {
  202. const statusFlag = bizProcessList.value.every(item => {
  203. return item.status == '2'
  204. })
  205. if (statusFlag) {
  206. switch (taskType.value) {
  207. case '1':
  208. getTrainResultApi(subTaskId).then(res => {
  209. resultsFlag.value = false
  210. handleResultData(res.data)
  211. resultVisible.value = true
  212. })
  213. break
  214. case '2':
  215. getVerifyResultApi(subTaskId).then(res => {
  216. resultsFlag.value = false
  217. handleResultData(res.data)
  218. resultVisible.value = true
  219. })
  220. break
  221. case '3':
  222. getTestResultApi(subTaskId).then(res => {
  223. console.log('333', res)
  224. resultsFlag.value = true
  225. testResultsData.value = res.data as any
  226. const num = testResultsData.value['agNameList'].length
  227. spanNum.value = 24 / num <= 4 ? 4 : Math.floor(24 / num)
  228. resultVisible.value = true
  229. })
  230. break
  231. default:
  232. break
  233. }
  234. } else {
  235. ElMessage.warning(`所有算法状态为‘已完成’,才可以对比`)
  236. }
  237. }
  238. const handleResultData = data => {
  239. resultsData.value = data
  240. resultsData.value['agNameList'].unshift('')
  241. const num = resultsData.value['agNameList'].length + 1
  242. spanNum.value = 24 / num <= 4 ? 4 : Math.floor(24 / num)
  243. resultsData.value['rcureList'].unshift('R_curve')
  244. resultsData.value['pcureList'].unshift('P_curve')
  245. resultsData.value['f1cureList'].unshift('F1_curve')
  246. }
  247. // 查看日志
  248. let timer2 = ref()
  249. const viewLog = row => {
  250. // if (row.status === '0') {
  251. // ElMessage.warning('算法状态为待处理,暂无日志')
  252. // return
  253. // }
  254. const url = `/api/profile${row.log}`
  255. logShow(url)
  256. dialogVisible.value = true
  257. timer2.value = setInterval(() => {
  258. if (dialogVisible.value) {
  259. logShow(url)
  260. }
  261. }, 5000)
  262. }
  263. // 日志读取
  264. const logShow = (url: any) => {
  265. fetchLogFile(url)
  266. .then(text => {
  267. logInfo.value = []
  268. logInfo.value = text.split('\n')
  269. nextTick(() => {
  270. const logContainer = logRef.value
  271. if (logContainer) {
  272. ;(logContainer as HTMLElement).scrollTop = (logContainer as HTMLElement).scrollHeight
  273. }
  274. })
  275. })
  276. .catch(error => {
  277. console.error('Failed to fetch the log file:', error)
  278. ElMessage.error('日志读取错误')
  279. })
  280. }
  281. const fetchLogFile = async url => {
  282. try {
  283. const response = await fetch(url, { method: 'GET' })
  284. if (!response.ok) {
  285. throw new Error(`HTTP error! status: ${response.status}`)
  286. }
  287. return await response.text()
  288. } catch (error) {
  289. throw error
  290. }
  291. }
  292. const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
  293. // 打开弹框的功能
  294. const openDialog = async (type: number, title: string, row?: any) => {
  295. let res = { data: {} }
  296. if (row?.id) {
  297. res = await getBizProcessApi(row?.id || null)
  298. }
  299. // 重置表单
  300. setItemsOptions()
  301. const params = {
  302. title,
  303. width: 580,
  304. isEdit: type !== 3,
  305. itemsOptions: itemsOptions,
  306. model: type == 1 ? {} : res.data,
  307. api: type == 1 ? addBizProcessApi : updateBizProcessApi,
  308. getTableList: proTable.value?.getTableList
  309. }
  310. formDialogRef.value?.openDialog(params)
  311. }
  312. onUnmounted(() => {
  313. clearInterval(timer.value)
  314. timer.value = null
  315. })
  316. watch(
  317. () => dialogVisible.value,
  318. val => {
  319. if (!val) {
  320. clearInterval(timer2.value)
  321. timer2.value = null
  322. }
  323. }
  324. )
  325. // 表格配置项
  326. const columns = reactive<ColumnProps<any>[]>([
  327. { type: 'selection', fixed: 'left', width: 70 },
  328. // { prop: 'id', label: '主键ID' },
  329. // {
  330. // prop: 'subTaskId',
  331. // label: '子任务id',
  332. // search: {
  333. // el: 'input'
  334. // },
  335. // width: 120
  336. // },
  337. {
  338. prop: 'name',
  339. label: '任务名称',
  340. search: {
  341. el: 'input'
  342. },
  343. width: 120
  344. },
  345. // {
  346. // prop: 'type',
  347. // label: '任务类型',
  348. // search: {
  349. // el: 'input'
  350. // },
  351. // width: 120
  352. // },
  353. {
  354. prop: 'status',
  355. label: '任务状态',
  356. tag: true,
  357. enum: () => getDictsApi('biz_task_status'),
  358. search: {
  359. el: 'tree-select'
  360. },
  361. width: 100,
  362. fieldNames: { label: 'dictLabel', value: 'dictValue' }
  363. },
  364. // {
  365. // prop: 'algorithmId',
  366. // label: '算法',
  367. // width: 120
  368. // },
  369. // {
  370. // prop: 'modelId',
  371. // label: '模型',
  372. // width: 120
  373. // },
  374. {
  375. prop: 'parameters',
  376. label: '调用算法时所用的参数'
  377. },
  378. {
  379. prop: 'preprocessPath',
  380. label: '预处理数据路径'
  381. },
  382. {
  383. prop: 'resultPath',
  384. label: '结果数据路径'
  385. },
  386. {
  387. prop: 'index',
  388. label: '序号',
  389. width: 120
  390. },
  391. {
  392. prop: 'startTime',
  393. label: '开始时间'
  394. },
  395. {
  396. prop: 'endTime',
  397. label: '结束时间'
  398. },
  399. {
  400. prop: 'costSecond',
  401. label: '耗时'
  402. },
  403. {
  404. prop: 'log',
  405. label: '日志'
  406. },
  407. { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
  408. ])
  409. // 结果表格配置项
  410. // const resultColumns = reactive<ColumnProps<any>[]>([])
  411. // 表单配置项
  412. let itemsOptions: ProForm.ItemsOptions[] = []
  413. const setItemsOptions = () => {
  414. itemsOptions = [
  415. {
  416. label: '子任务id',
  417. prop: 'subTaskId',
  418. compOptions: {
  419. placeholder: '请输入子任务id'
  420. }
  421. },
  422. {
  423. label: '任务名称',
  424. prop: 'name',
  425. compOptions: {
  426. placeholder: '请输入任务名称'
  427. }
  428. },
  429. {
  430. label: '任务类型',
  431. prop: 'type',
  432. compOptions: {
  433. placeholder: '请输入任务类型'
  434. }
  435. },
  436. {
  437. label: '任务状态',
  438. prop: 'status',
  439. compOptions: {
  440. elTagName: 'select',
  441. labelKey: 'dictLabel',
  442. valueKey: 'dictValue',
  443. enum: () => getDictsApi('biz_task_status'),
  444. placeholder: '请选择任务状态'
  445. }
  446. },
  447. {
  448. label: '算法',
  449. prop: 'algorithmId',
  450. compOptions: {
  451. placeholder: '请输入算法'
  452. }
  453. },
  454. {
  455. label: '模型',
  456. prop: 'modelId',
  457. compOptions: {
  458. placeholder: '请输入模型'
  459. }
  460. },
  461. {
  462. label: '调用算法时所用的参数',
  463. prop: 'parameters',
  464. compOptions: {
  465. type: 'textarea',
  466. clearable: true,
  467. placeholder: '请输入内容'
  468. }
  469. },
  470. {
  471. label: '预处理数据路径',
  472. prop: 'preprocessPath',
  473. compOptions: {
  474. placeholder: '请输入预处理数据路径'
  475. }
  476. },
  477. {
  478. label: '结果数据路径',
  479. prop: 'resultPath',
  480. compOptions: {
  481. placeholder: '请输入结果数据路径'
  482. }
  483. },
  484. {
  485. label: '序号',
  486. prop: 'index',
  487. compOptions: {
  488. placeholder: '请输入序号'
  489. }
  490. },
  491. {
  492. label: '开始时间',
  493. prop: 'startTime',
  494. compOptions: {
  495. elTagName: 'date-picker',
  496. type: 'date',
  497. placeholder: '请选择开始时间'
  498. }
  499. },
  500. {
  501. label: '结束时间',
  502. prop: 'endTime',
  503. compOptions: {
  504. elTagName: 'date-picker',
  505. type: 'date',
  506. placeholder: '请选择结束时间'
  507. }
  508. },
  509. {
  510. label: '耗时',
  511. prop: 'costSecond',
  512. compOptions: {
  513. placeholder: '请输入耗时'
  514. }
  515. },
  516. {
  517. label: '日志',
  518. prop: 'log',
  519. compOptions: {
  520. type: 'textarea',
  521. clearable: true,
  522. placeholder: '请输入内容'
  523. }
  524. }
  525. ]
  526. }
  527. </script>
  528. <style scoped lang="scss">
  529. .log {
  530. width: 90%;
  531. height: 60vh; /* 根据需要调整 */
  532. padding: 10px;
  533. // padding-bottom: 80px;
  534. margin-left: 50px;
  535. overflow-y: auto;
  536. font-family: 'Courier New', monospace;
  537. color: #4aff84;
  538. background-color: #1e1e1e;
  539. }
  540. .p {
  541. padding-left: 10px;
  542. margin-bottom: 5px;
  543. border-left: 3px solid #4aff84;
  544. }
  545. .resultShow {
  546. width: 100%;
  547. height: 60vh;
  548. overflow: hidden;
  549. overflow: scroll scroll;
  550. .headerRow {
  551. height: 50px;
  552. font-size: 1.2rem;
  553. line-height: 50px;
  554. text-align: center;
  555. }
  556. .row {
  557. .col {
  558. text-align: center;
  559. .oneCol {
  560. margin-top: 45px;
  561. font-size: 1.2rem;
  562. }
  563. .img {
  564. margin: 10px 0;
  565. }
  566. }
  567. }
  568. }
  569. </style>