AboutView.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. <template>
  2. <el-form :model="form" label-width="auto" style="max-width: 700px">
  3. <!-- <el-form-item label="发布终点 :">-->
  4. <!-- <el-button type="primary" @click="onSubmit">默认终点</el-button>-->
  5. <!-- <el-button type="primary" @click="onSubmit">自定义终点</el-button>-->
  6. <!-- </el-form-item>-->
  7. <el-dialog v-model="evalTableVisible" title="算法评价结果" width="70%">
  8. <div class="topbar">
  9. <el-form style=" background-color: rgba(0,0,0,0%);" :inline="true" :model="queryLine"
  10. class="demo-form-inline">
  11. <el-form-item label="算法评价等级">
  12. <!-- <el-input v-model="queryLine.algorithmLevel" placeholder="请输入算法评价等级" clearable/>-->
  13. <el-select
  14. v-model="selectLevel"
  15. placeholder="请选择"
  16. size="default"
  17. style="width: 180px; "
  18. clearable
  19. >
  20. <el-option
  21. v-for="item in algoLevels"
  22. :key="item"
  23. :label="item"
  24. :value="item"
  25. />
  26. </el-select>
  27. </el-form-item>
  28. <el-form-item>
  29. <el-button type="danger" @click="queryRecordByCondition">查询</el-button>
  30. </el-form-item>
  31. <el-form-item>
  32. <el-button type="danger" @click="resetRecord">重置</el-button>
  33. </el-form-item>
  34. <div style="float: right">
  35. <el-form-item>
  36. <el-button type="danger" @click="downloadFile('bag')" :disabled="multipleSelection.length == 0">下载测试bag</el-button>
  37. </el-form-item>
  38. <el-form-item>
  39. <el-button type="danger" @click="downloadFile('report')" :disabled="multipleSelection.length == 0">下载算法评价报告</el-button>
  40. </el-form-item>
  41. </div>
  42. </el-form>
  43. </div>
  44. <el-table stripe style="background-color: rgba(255,0,0,99%);width: 100%" border :data="evalTableData"
  45. @selection-change="handleSelectionChange"
  46. fixed ref="multipleTableRef" :cell-style="{ textAlign: 'center'}" :header-cell-style="{ textAlign: 'center'}">
  47. <el-table-column type="selection" width="55"/>
  48. <el-table-column prop="test_time" :formatter="formatDate" label="测试时间"/>
  49. <el-table-column prop="round" label="轮次"/>
  50. <el-table-column prop="test_bag_path" :formatter="(row) => formatPath(row, 'test_bag_path')" label="测试bag"/>
  51. <el-table-column prop="test_duration" label="测试时长"/>
  52. <el-table-column prop="test_distance" label="测试里程"/>
  53. <el-table-column prop="algo_evaluation_level" label="算法评价等级"/>
  54. <el-table-column prop="algo_evaluation_report" :formatter="(row) => formatPath(row, 'algo_evaluation_report')" label="算法评价报告"/>
  55. </el-table>
  56. </el-dialog>
  57. <el-form-item label="随机起终点 :">
  58. <el-switch
  59. v-model="form.isRandom"
  60. inline-prompt
  61. active-text="是"
  62. inactive-text="否"
  63. />
  64. <div v-if="form.isRandom">
  65. <el-input-number class="mx-4" v-model="form.randomCount" :min="1" :max="5" style="margin-left: 15px; width: 100px;"/>
  66. <span style="margin-left: 10px;">次</span>
  67. </div>
  68. </el-form-item>
  69. <el-form-item label="起点设置 :" v-if="!form.isRandom">
  70. <el-radio-group v-model="form.origin" @change="originChange">
  71. <el-radio :value="true">默认起点</el-radio>
  72. <el-radio :value="false">自定义起点</el-radio>
  73. </el-radio-group>
  74. <span v-show="form.origin==false" @click="startDialogVisible=true" style="margin-left: 20px; font-size: 14px; color: gray; cursor: pointer">
  75. 当前值:X: {{startForm.X}} Y: {{startForm.Y}} Z: {{startForm.Z}} R: {{startForm.R}} P: {{startForm.P}} H: {{startForm.H}}
  76. </span>
  77. </el-form-item>
  78. <el-form-item label="终点设置 :" v-if="!form.isRandom">
  79. <el-radio-group v-model="form.destination" @change="destinationChange">
  80. <el-radio :value="true">默认终点</el-radio>
  81. <el-radio :value="false">自定义终点</el-radio>
  82. </el-radio-group>
  83. <span v-show="form.destination==false" @click="endDialogVisible=true" style="margin-left: 20px; font-size: 14px; color: gray; cursor: pointer">
  84. 当前值:X: {{endForm.X}} Y: {{endForm.Y}} Z: {{endForm.Z}} R: {{endForm.R}} P: {{endForm.P}} H: {{endForm.H}}
  85. </span>
  86. </el-form-item>
  87. <el-form-item label="加载默认障碍物:">
  88. <el-radio-group v-model="form.resource">
  89. <el-radio :value="true">是</el-radio>
  90. <el-radio :value="false">否</el-radio>
  91. </el-radio-group>
  92. </el-form-item>
  93. <el-form-item label=" ">
  94. <!-- <el-button type="primary" @click="goToMain">开始执行</el-button>-->
  95. <el-button type="primary" @click="runSimulation">开始执行</el-button>
  96. <el-dialog
  97. v-model="startDialogVisible"
  98. title="请输入自定义起点参数"
  99. width="300"
  100. :before-close="handleClose"
  101. draggable
  102. >
  103. <template #footer>
  104. <div class="dialog-footer">
  105. <el-form :model="startForm" label-width="auto">
  106. <el-form-item style="margin-bottom: 10px" label="位置 X :" required >
  107. <el-input-number style="width: 100%;" v-model="startForm.X" :controls="false" :precision="2" />
  108. </el-form-item>
  109. <el-form-item style="margin-bottom: 10px" label="位置 Y :" required>
  110. <el-input-number style="width: 100%;" v-model="startForm.Y" :controls="false" :precision="2" />
  111. </el-form-item>
  112. <el-form-item style="margin-bottom: 10px" label="位置 Z :">
  113. <el-input-number style="width: 100%;" v-model="startForm.Z" :controls="false" :precision="2" disabled/>
  114. </el-form-item>
  115. <el-form-item style="margin-bottom: 10px" label="角度 R :">
  116. <el-input-number style="width: 100%;" v-model="startForm.R" :controls="false" :precision="2" disabled/>
  117. </el-form-item>
  118. <el-form-item style="margin-bottom: 10px" label="角度 P :">
  119. <el-input-number style="width: 100%;" v-model="startForm.P" :controls="false" :precision="2" disabled/>
  120. </el-form-item>
  121. <el-form-item style="margin-bottom: 10px" label="角度 H :" required>
  122. <el-input-number style="width: 100%;" v-model="startForm.H" :controls="false" :precision="2" />
  123. </el-form-item>
  124. </el-form>
  125. <el-button type="primary" @click="startDialogVisible = false">确认</el-button>
  126. </div>
  127. </template>
  128. </el-dialog>
  129. <el-dialog
  130. v-model="endDialogVisible"
  131. title="请输入自定义终点参数"
  132. width="300"
  133. :before-close="handleClose"
  134. draggable
  135. >
  136. <template #footer>
  137. <div class="dialog-footer">
  138. <el-form :model="endForm" label-width="auto">
  139. <el-form-item style="margin-bottom: 10px" label="位置 X :">
  140. <el-input-number style="width: 100%;" v-model="endForm.X" :controls="false" :precision="2" />
  141. </el-form-item>
  142. <el-form-item style="margin-bottom: 10px" label="位置 Y :">
  143. <el-input-number style="width: 100%;" v-model="endForm.Y" :controls="false" :precision="2"/>
  144. </el-form-item>
  145. <el-form-item style="margin-bottom: 10px" label="位置 Z :">
  146. <el-input-number style="width: 100%;" v-model="endForm.Z" :controls="false" :precision="2" disabled/>
  147. </el-form-item>
  148. <el-form-item style="margin-bottom: 10px" label="角度 R :">
  149. <el-input-number style="width: 100%;" v-model="endForm.R" :controls="false" :precision="2" disabled/>
  150. </el-form-item>
  151. <el-form-item style="margin-bottom: 10px" label="角度 P :">
  152. <el-input-number style="width: 100%;" v-model="endForm.P" :controls="false" :precision="2" disabled/>
  153. </el-form-item>
  154. <el-form-item style="margin-bottom: 10px" label="角度 H :">
  155. <el-input-number style="width: 100%;" v-model="endForm.H" :controls="false" :precision="2"/>
  156. </el-form-item>
  157. </el-form>
  158. <el-button type="primary" @click="endDialogVisible = false">确认</el-button>
  159. </div>
  160. </template>
  161. </el-dialog>
  162. <!-- <el-button style="margin-left: 10px;" type="primary" @click="goToMain">更换设置并执行</el-button> -->
  163. <el-button style="margin-left: 10px;" type="primary" @click="showEvalResult" :disabled="evalResult.length === 0">算法评价结果</el-button>
  164. </el-form-item>
  165. <!-- <el-form-item label=" ">-->
  166. <!-- <el-button type="primary" @click="goToAlgorithmEval">算法评价结果</el-button>-->
  167. <!-- </el-form-item>-->
  168. <el-form-item label=" ">
  169. <el-button type="primary" @click="goToMain">返回</el-button>
  170. </el-form-item>
  171. </el-form>
  172. </template>
  173. <script lang="ts" setup>
  174. import {useRoute, useRouter} from 'vue-router'; // 导入 Vue Router 的 useRouter 钩子
  175. import {nextTick, reactive} from 'vue'
  176. import {ref} from 'vue'
  177. import {ElTable, ElLoading, ElMessage} from "element-plus";
  178. import {getCurrentInstance} from "vue";
  179. import axios from "axios";
  180. import path from 'path-browserify';
  181. import moment from "moment";
  182. const startDialogVisible = ref(false)
  183. const endDialogVisible = ref(false)
  184. const {proxy} = getCurrentInstance();
  185. const evalResult = ref([])
  186. const evalTableVisible = ref(false)
  187. const evalTableData = ref([])
  188. const queryLine = reactive({
  189. algorithmLevel: ''
  190. })
  191. const multipleSelection = ref<[]>([])
  192. const onSubmit = () => {
  193. // page()
  194. }
  195. const router = useRouter();
  196. const route = useRoute();
  197. console.log("route.query", route.query)
  198. const sceneId = route.query.scene_id;
  199. const deviceType = route.query.device_type;
  200. const deviceId = route.query.device_id;
  201. const deviceNo = route.query.device_no;
  202. const deviceName = route.query.device_name;
  203. const algoImageName = route.query.algo_image_name;
  204. const worldPath = route.query.world_path;
  205. const PJI_SCRIPT_PATH_PREFIX = import.meta.env.VITE_PJI_SCRIPT_PATH_PREFIX
  206. console.log("sceneId", sceneId)
  207. console.log("deviceType", deviceType)
  208. console.log("deviceId", deviceId)
  209. console.log("deviceNo", deviceNo)
  210. console.log("deviceName", deviceName)
  211. console.log("algoImageName", algoImageName)
  212. console.log("worldPath", worldPath)
  213. const selectLevel = ref("")
  214. const algoLevels = ['较差', '一般', '良好', '优秀']
  215. // const testTime = ref("1726113464184")
  216. const testTime = ref("")
  217. const resetEvalTableData = ref([])
  218. const handleClose = (done: () => void) => {
  219. done()
  220. }
  221. // do not use same name with ref
  222. const form = reactive({
  223. name: '',
  224. region: '',
  225. date1: '',
  226. date2: '',
  227. delivery: false,
  228. type: [],
  229. resource: true,
  230. desc: '',
  231. isRandom: false,
  232. origin: true,
  233. destination: true,
  234. randomCount: ref(1),
  235. })
  236. const startForm = reactive({
  237. X: 0,
  238. Y: 0,
  239. Z: 0,
  240. R: 0,
  241. P: 0,
  242. H: 0,
  243. })
  244. const endForm = reactive({
  245. X: 0,
  246. Y: 0,
  247. Z: 0,
  248. R: 0,
  249. P: 0,
  250. H: 0,
  251. })
  252. const originChange = (value: string) => {
  253. if (!value) {
  254. startDialogVisible.value = true
  255. }
  256. }
  257. const destinationChange = (value: string) => {
  258. if (!value) {
  259. endDialogVisible.value = true
  260. }
  261. }
  262. const handleSelectionChange = (rows: []) => {
  263. multipleSelection.value = rows
  264. console.log("multipleSelection", multipleSelection.value)
  265. }
  266. const downloadFile = async (typeName) => {
  267. // object key
  268. let keys
  269. // 文件名(区分1个文件/多个文件)
  270. let fileName
  271. if (typeName === "bag") {
  272. keys = multipleSelection.value.map(item => item["test_bag_path"])
  273. fileName = multipleSelection.value.length > 1 ? sceneId + "_" + typeName + ".zip" : "test.bag"
  274. } else if(typeName === "report") {
  275. keys = multipleSelection.value.map(item => item["algo_evaluation_report"])
  276. fileName = multipleSelection.value.length > 1 ? sceneId + "_" + typeName + ".zip" : "report.pdf"
  277. }
  278. console.log("keys", keys)
  279. console.log("typeName", typeName)
  280. console.log("fileName", fileName)
  281. // 下载记录
  282. const url = "http://127.0.0.1:8888/simulation/download/oss/key?sceneId=" + sceneId + "&typeName=" + typeName
  283. const result = await window.electronAPI.downloadFile(url, fileName, "", true, true, "post", keys);
  284. if (!result.success) { // 下载失败
  285. console.error('File download failed:', result.error);
  286. ElMessage.error("文件下载失败!");
  287. } else { // 下载成功
  288. console.log('File downloaded successfully:', result.filePath);
  289. ElMessage.success("文件下载成功!");
  290. }
  291. }
  292. // 表格 - 格式化路径
  293. const formatPath = (row, name) => {
  294. return path.basename(row[name])
  295. }
  296. const formatDate = (row) => {
  297. return moment(+row.test_time).format("YYYY-MM-DD HH:mm:ss")
  298. }
  299. // 弹窗显示算法评价记录
  300. const showEvalResult = async (value: string) => {
  301. console.log("testTime", testTime.value)
  302. console.log("sceneId", sceneId)
  303. if (testTime.value != "") {
  304. // 查询算法评价记录
  305. let response = await axios.post('/local/simulation/query/test/record', {
  306. 'scene_id': sceneId,
  307. 'test_time': testTime.value,
  308. })
  309. if (!response.data.status){ // 不存在
  310. proxy.$message.error("算法评价结果查询失败!");
  311. return
  312. }
  313. // 解析结果
  314. evalTableData.value = JSON.parse(response.data.data)
  315. resetEvalTableData.value = evalTableData.value // for reset purpose
  316. await nextTick() // 避免vue warning: onMounted is called when there is no active component instance...
  317. // 开启弹窗
  318. evalTableVisible.value = true
  319. console.log("evalTableData.value", evalTableData.value)
  320. }else {
  321. proxy.$message.error("暂无算法评价结果!");
  322. }
  323. }
  324. // 根据条件筛选结果
  325. // 正常情况下包含分页需要发送请求以根据条件查询数据,但本页面不包含分页且查询条件较少,因此直接通过filter筛选数据以节省网络资源
  326. const queryRecordByCondition = async () => {
  327. // 含分页 - 发送请求
  328. // let response = await axios.post('/local/simulation/query/test/record', {
  329. // 'scene_id': sceneId,
  330. // 'test_time': testTime.value,
  331. // "algo_evaluation_level": selectLevel.value
  332. // })
  333. //
  334. // if (!response.data.status){ // 不存在
  335. // proxy.$message.error("算法评价结果查询失败!");
  336. // return
  337. // }
  338. // // 解析结果
  339. // evalTableData.value = JSON.parse(response.data.details)
  340. console.log("selectLevel.value", selectLevel.value)
  341. // 不含分页 - filter
  342. if (selectLevel.value !== "" && selectLevel.value != undefined) { // 查询条件不为空
  343. evalTableData.value = resetEvalTableData.value.filter(d => d["algo_evaluation_level"] === selectLevel.value)
  344. } else { // 查询条件为空,重置结果
  345. resetRecord()
  346. }
  347. }
  348. // 重置结果
  349. const resetRecord = () => {
  350. evalTableData.value = resetEvalTableData.value
  351. }
  352. const runSimulation = () => {
  353. // 是否加载默认障碍物
  354. let random_flag = form.isRandom
  355. let count = form.randomCount
  356. let obstacle_flag = form.resource
  357. let default_start_flag = form.origin
  358. let default_end_flag = form.destination
  359. let start_point = form.origin ? '"0 0 0 0 0 0"' : '"' + startForm.X + ' ' + startForm.Y + ' ' + startForm.Z + ' ' + startForm.R + ' ' + startForm.P + ' ' + startForm.H + '"'
  360. let end_point = form.destination ? '"0 0 0 0 0 0"' : '"' + endForm.X + ' ' + endForm.Y + ' ' + endForm.Z + ' ' + endForm.R + ' ' + endForm.P + ' ' + endForm.H + '"'
  361. let N = random_flag ? count : 1
  362. console.log("end_point=" + end_point)
  363. console.log("count", count)
  364. // 异步
  365. window.electronAPI.runSimulation(random_flag, count, obstacle_flag, default_start_flag, default_end_flag, start_point, end_point)
  366. evalResult.value = []
  367. // 监听脚本执行状态
  368. window.electronAPI.onRunSimulationResponse( async (event, result) => {
  369. if (!result.success) { // 脚本执行过程中发生错误
  370. console.error('Script execution failed.');
  371. proxy.$message.error("仿真测试发生错误!");
  372. console.log()
  373. } else { // 脚本执行成功
  374. console.log('Script execution completed successfully.')
  375. // 上传并收集算法评价结果
  376. const response = await window.electronAPI.processEvaluationFiles(N, deviceNo, sceneId)
  377. if (!response.success) { // 算法评价结果收集失败
  378. console.log("Error collecting evaluation results")
  379. proxy.$message.error("仿真测试发生错误!");
  380. return
  381. }
  382. console.log("response.data", response.data)
  383. evalResult.value = response.data
  384. // 上传pgm文件并获取存储url
  385. // 执行脚本 - 上传文件
  386. const filePath = PJI_SCRIPT_PATH_PREFIX + "simulation/data/mapBuf/map.pgm"
  387. const pgmUploadUrl = "http://127.0.0.1:8888/simulation/upload/pgm?equipmentNo=" + deviceNo + "&sceneNo=" + sceneId
  388. window.electronAPI.uploadFile(filePath, pgmUploadUrl);
  389. // 监听脚本执行状态
  390. window.electronAPI.onUploadFileResponse( async (event, result) => {
  391. if (result.success) { // 脚本执行成功
  392. console.log('File uploaded successfully.')
  393. // 上传路径 - map.pgm
  394. const uploadPgmKey = result.details
  395. console.log('uploadPgmKey', uploadPgmKey)
  396. let simulation_data = []
  397. evalResult.value.forEach((d, i) => {
  398. if (i === 0) testTime.value = String(d["timeStamp"])
  399. simulation_data.push({
  400. 'device_id': deviceId,
  401. 'device_name': deviceName,
  402. 'device_type': deviceType,
  403. 'algo_image_name': algoImageName,
  404. 'pgm_path': uploadPgmKey,
  405. 'world_path': worldPath,
  406. 'test_bag_path': d["uploadBagKey"],
  407. 'test_duration': d["jsonData"]["testDuration"],
  408. 'test_distance': d["jsonData"]["testMileage"],
  409. 'algo_evaluation_level': d["jsonData"]["algorithmLevel"],
  410. 'algo_evaluation_report': d["uploadPdfKey"],
  411. 'test_time': String(d["timeStamp"]),
  412. 'round': d["round"],
  413. 'scene_id': sceneId,
  414. 'device_no': deviceNo,
  415. })
  416. })
  417. console.log("simulation_data", simulation_data)
  418. // 上传数据
  419. let response = await axios.post('/local/simulation/add/record', simulation_data)
  420. if (!response.data.status){ // 不存在
  421. proxy.$message.error("仿真测试发生错误!");
  422. return
  423. }
  424. // ElMessage.success("仿真测试成功!");
  425. // 清除历史窗口
  426. ElMessage.closeAll()
  427. proxy.$message.success("仿真测试执行成功!");
  428. } else { // 脚本执行失败
  429. console.error('Uploading file failed.');
  430. proxy.$message.error("仿真测试发生错误!");
  431. }
  432. })
  433. }
  434. })
  435. }
  436. const goToMain = () => {
  437. // router.push('/')
  438. router.back()
  439. }
  440. const goToAlgorithmEval = () => {
  441. router.push('/algorithm_eval')
  442. }
  443. </script>