index.vue 15 KB

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