Browse Source

feat: 添加仿真测试上传

HeWang 8 months ago
parent
commit
1de18e9b41
6 changed files with 130 additions and 60 deletions
  1. 29 11
      electron/main.js
  2. 2 2
      electron/preload.js
  3. 9 2
      package-lock.json
  4. 2 0
      package.json
  5. 85 15
      src/views/AboutView.vue
  6. 3 30
      src/views/ReportView.vue

+ 29 - 11
electron/main.js

@@ -4,9 +4,6 @@ import path from 'path';
 import {fileURLToPath} from 'url';
 import {fileURLToPath} from 'url';
 import fs from 'fs';
 import fs from 'fs';
 import axios from 'axios';
 import axios from 'axios';
-import {resolve} from "node:dns";
-import {execSync} from "node:child_process";
-import * as url from "node:url";
 
 
 
 
 const __filename = fileURLToPath(import.meta.url);
 const __filename = fileURLToPath(import.meta.url);
@@ -38,10 +35,6 @@ app.on('window-all-closed', () => {
     }
     }
 });
 });
 
 
-// app.on('ready', () => {
-//     // 异步
-//
-// })
 
 
 app.on('activate', () => {
 app.on('activate', () => {
     if (BrowserWindow.getAllWindows().length === 0) {
     if (BrowserWindow.getAllWindows().length === 0) {
@@ -58,19 +51,43 @@ ipcMain.handle('dialog:open', async (event, options = {}) => {
 
 
 
 
 // 下载文件
 // 下载文件
-ipcMain.handle('download-file', async (event, { url, fileName, savePath, overwriteFlag }) => {
+ipcMain.handle('download-file', async (event, { url, fileName, savePath, overwriteFlag, dialogFlag, requestType, data }) => {
     try {
     try {
+        let filePath
+        let getPath
         // console.log("url", url)
         // console.log("url", url)
         // console.log("response", response)
         // console.log("response", response)
+        if (dialogFlag) { // 弹出保存文件对话框
+            console.log("savePath", savePath)
+            // 获取保存路径
+            getPath = await dialog.showSaveDialog({
+                defaultPath: path.join(app.getPath('downloads'), fileName)
+            })
+            filePath = getPath.filePath
+            if (getPath.canceled || filePath === "") {
+                return { success: false, message: 'File path does not exist' }
+            }
+        } else {
+            filePath = path.join(savePath, fileName);
+        }
 
 
-        const filePath = path.join(savePath, fileName);
+        console.log("filePath", filePath)
 
 
         // 查找文件是否存在
         // 查找文件是否存在
         const fileExists = fs.existsSync(filePath)
         const fileExists = fs.existsSync(filePath)
         if (fileExists && !overwriteFlag) { // 已存在且不覆盖
         if (fileExists && !overwriteFlag) { // 已存在且不覆盖
             return {success: true, filePath}
             return {success: true, filePath}
         } else { // 不存在
         } else { // 不存在
-            const response = await axios.get(url, {responseType: 'stream'});
+            console.log("url", url)
+            let response
+            if (requestType === "get") {
+                console.log("get...")
+                response = await axios.get(url, {responseType: 'stream'});
+            } else {
+                response = await axios.post(url, data, {responseType: 'stream'});
+            }
+            console.log("response", response)
+
             // 写入文件
             // 写入文件
             const writer = fs.createWriteStream(filePath);
             const writer = fs.createWriteStream(filePath);
 
 
@@ -94,11 +111,12 @@ ipcMain.handle('download-file', async (event, { url, fileName, savePath, overwri
             });
             });
         }
         }
     } catch (error) {
     } catch (error) {
-        // console.error('下载文件出错:', error);
+        console.error('下载文件出错:', error);
         throw error;
         throw error;
     }
     }
 });
 });
 
 
+
 // 删除文件
 // 删除文件
 ipcMain.on('delete-file', (event, {fileName, savePath}) => {
 ipcMain.on('delete-file', (event, {fileName, savePath}) => {
     const filePath = path.join(savePath, fileName);
     const filePath = path.join(savePath, fileName);

+ 2 - 2
electron/preload.js

@@ -12,9 +12,9 @@ contextBridge.exposeInMainWorld('electronAPI', {
             throw error;
             throw error;
         }
         }
     },
     },
-    downloadFile: async (url, fileName, savePath, overwriteFlag) => {
+    downloadFile: async (url, fileName, savePath, overwriteFlag, dialogFlag, requestType, data) => {
         try {
         try {
-            return await ipcRenderer.invoke('download-file', { url, fileName, savePath, overwriteFlag });
+            return await ipcRenderer.invoke('download-file', { url, fileName, savePath, overwriteFlag, dialogFlag, requestType, data });
         } catch (error) {
         } catch (error) {
             console.error('下载文件时出错:', error);
             console.error('下载文件时出错:', error);
             return { success: false, error: error.message };
             return { success: false, error: error.message };

+ 9 - 2
package-lock.json

@@ -10,6 +10,7 @@
       "dependencies": {
       "dependencies": {
         "axios": "^1.7.2",
         "axios": "^1.7.2",
         "element-plus": "^2.7.5",
         "element-plus": "^2.7.5",
+        "path-browserify": "^1.0.1",
         "pinia": "^2.1.7",
         "pinia": "^2.1.7",
         "vue": "^3.4.21",
         "vue": "^3.4.21",
         "vue-router": "^4.3.0"
         "vue-router": "^4.3.0"
@@ -18,6 +19,7 @@
         "@tsconfig/node20": "^20.1.4",
         "@tsconfig/node20": "^20.1.4",
         "@types/jsdom": "^21.1.6",
         "@types/jsdom": "^21.1.6",
         "@types/node": "^20.12.5",
         "@types/node": "^20.12.5",
+        "@types/path-browserify": "^1.0.3",
         "@vitejs/plugin-vue": "^5.0.4",
         "@vitejs/plugin-vue": "^5.0.4",
         "@vitejs/plugin-vue-jsx": "^3.1.0",
         "@vitejs/plugin-vue-jsx": "^3.1.0",
         "@vue/test-utils": "^2.4.5",
         "@vue/test-utils": "^2.4.5",
@@ -1581,6 +1583,12 @@
         "undici-types": "~5.26.4"
         "undici-types": "~5.26.4"
       }
       }
     },
     },
+    "node_modules/@types/path-browserify": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/@types/path-browserify/-/path-browserify-1.0.3.tgz",
+      "integrity": "sha512-ZmHivEbNCBtAfcrFeBCiTjdIc2dey0l7oCGNGpSuRTy8jP6UVND7oUowlvDujBy8r2Hoa8bfFUOCiPWfmtkfxw==",
+      "dev": true
+    },
     "node_modules/@types/responselike": {
     "node_modules/@types/responselike": {
       "version": "1.0.3",
       "version": "1.0.3",
       "resolved": "https://registry.npmmirror.com/@types/responselike/-/responselike-1.0.3.tgz",
       "resolved": "https://registry.npmmirror.com/@types/responselike/-/responselike-1.0.3.tgz",
@@ -4516,8 +4524,7 @@
     "node_modules/path-browserify": {
     "node_modules/path-browserify": {
       "version": "1.0.1",
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
       "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
-      "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
-      "dev": true
+      "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="
     },
     },
     "node_modules/path-exists": {
     "node_modules/path-exists": {
       "version": "3.0.0",
       "version": "3.0.0",

+ 2 - 0
package.json

@@ -18,6 +18,7 @@
   "dependencies": {
   "dependencies": {
     "axios": "^1.7.2",
     "axios": "^1.7.2",
     "element-plus": "^2.7.5",
     "element-plus": "^2.7.5",
+    "path-browserify": "^1.0.1",
     "pinia": "^2.1.7",
     "pinia": "^2.1.7",
     "vue": "^3.4.21",
     "vue": "^3.4.21",
     "vue-router": "^4.3.0"
     "vue-router": "^4.3.0"
@@ -26,6 +27,7 @@
     "@tsconfig/node20": "^20.1.4",
     "@tsconfig/node20": "^20.1.4",
     "@types/jsdom": "^21.1.6",
     "@types/jsdom": "^21.1.6",
     "@types/node": "^20.12.5",
     "@types/node": "^20.12.5",
+    "@types/path-browserify": "^1.0.3",
     "@vitejs/plugin-vue": "^5.0.4",
     "@vitejs/plugin-vue": "^5.0.4",
     "@vitejs/plugin-vue-jsx": "^3.1.0",
     "@vitejs/plugin-vue-jsx": "^3.1.0",
     "@vue/test-utils": "^2.4.5",
     "@vue/test-utils": "^2.4.5",

+ 85 - 15
src/views/AboutView.vue

@@ -20,25 +20,27 @@
           </el-form-item>
           </el-form-item>
           <div style="float: right">
           <div style="float: right">
             <el-form-item>
             <el-form-item>
-              <el-button type="danger" @click="onSubmit">下载测试bag</el-button>
+              <el-button type="danger" @click="downloadFile('bag')" :disabled="multipleSelection.length == 0">下载测试bag</el-button>
             </el-form-item>
             </el-form-item>
             <el-form-item>
             <el-form-item>
-              <el-button type="danger" @click="onSubmit">下载算法评价报告</el-button>
+              <el-button type="danger" @click="downloadFile('report')" :disabled="multipleSelection.length == 0">下载算法评价报告</el-button>
             </el-form-item>
             </el-form-item>
           </div>
           </div>
 
 
         </el-form>
         </el-form>
 
 
       </div>
       </div>
-      <el-table stripe style="background-color: rgba(255,0,0,99%);width: 100%" border :data="evalData"
+      <el-table stripe style="background-color: rgba(255,0,0,99%);width: 100%" border :data="evalTableData"
+                @selection-change="handleSelectionChange"
                 fixed ref="multipleTableRef" :cell-style="{ textAlign: 'center'}" :header-cell-style="{ textAlign: 'center'}">
                 fixed ref="multipleTableRef" :cell-style="{ textAlign: 'center'}" :header-cell-style="{ textAlign: 'center'}">
         <el-table-column type="selection" width="55"/>
         <el-table-column type="selection" width="55"/>
-        <el-table-column prop="testTime" label="测试时间"/>
-        <el-table-column prop="testBag" label="测试bag"/>
-        <el-table-column prop="testDuration" label="测试时长"/>
-        <el-table-column prop="testDistance" label="测试里程"/>
-        <el-table-column prop="algorithmLevel" label="算法评价等级"/>
-        <el-table-column prop="algorithmReport" label="算法评价报告"/>
+        <el-table-column prop="test_time" label="测试时间"/>
+        <el-table-column prop="round" label="轮次"/>
+        <el-table-column prop="test_bag_path" :formatter="(row) => formatPath(row, 'test_bag_path')"  label="测试bag"/>
+        <el-table-column prop="test_duration" label="测试时长"/>
+        <el-table-column prop="test_distance" label="测试里程"/>
+        <el-table-column prop="algo_evaluation_level" label="算法评价等级"/>
+        <el-table-column prop="algo_evaluation_report" :formatter="(row) => formatPath(row, 'algo_evaluation_report')" label="算法评价报告"/>
       </el-table>
       </el-table>
 
 
     </el-dialog>
     </el-dialog>
@@ -151,7 +153,8 @@
       </el-dialog>
       </el-dialog>
 
 
 <!--      <el-button style="margin-left: 10px;" type="primary" @click="goToMain">更换设置并执行</el-button>-->
 <!--      <el-button style="margin-left: 10px;" type="primary" @click="goToMain">更换设置并执行</el-button>-->
-      <el-button style="margin-left: 10px;" type="primary" @click="evalTableVisible = true" >算法评价结果</el-button>
+<!--      :disabled="evalResult.length === 0"-->
+      <el-button style="margin-left: 10px;" type="primary" @click="showEvalResult" >算法评价结果</el-button>
     </el-form-item>
     </el-form-item>
 <!--    <el-form-item label="            ">-->
 <!--    <el-form-item label="            ">-->
 <!--      <el-button type="primary" @click="goToAlgorithmEval">算法评价结果</el-button>-->
 <!--      <el-button type="primary" @click="goToAlgorithmEval">算法评价结果</el-button>-->
@@ -165,24 +168,27 @@
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
 import {useRoute, useRouter} from 'vue-router'; // 导入 Vue Router 的 useRouter 钩子
 import {useRoute, useRouter} from 'vue-router'; // 导入 Vue Router 的 useRouter 钩子
-import {reactive} from 'vue'
+import {nextTick, reactive} from 'vue'
 import {ref} from 'vue'
 import {ref} from 'vue'
 import {ElMessageBox} from 'element-plus'
 import {ElMessageBox} from 'element-plus'
 import {ElTable, ElLoading, ElMessage} from "element-plus";
 import {ElTable, ElLoading, ElMessage} from "element-plus";
 import {getCurrentInstance} from "vue";
 import {getCurrentInstance} from "vue";
 import axios from "axios";
 import axios from "axios";
+import path from 'path-browserify';
+import * as net from "node:net";
 
 
 const startDialogVisible = ref(false)
 const startDialogVisible = ref(false)
 const endDialogVisible = ref(false)
 const endDialogVisible = ref(false)
 const {proxy} = getCurrentInstance();
 const {proxy} = getCurrentInstance();
 const evalResult = ref([])
 const evalResult = ref([])
-const evalTableVisible = ref(true)
-const evalData = ref([])
+const evalTableVisible = ref(false)
+const evalTableData = ref([])
 const queryLine = reactive({
 const queryLine = reactive({
   algorithmLevel: ''
   algorithmLevel: ''
 })
 })
+const multipleSelection = ref<[]>([])
 const onSubmit = () => {
 const onSubmit = () => {
-  page()
+  // page()
 }
 }
 
 
 const router = useRouter();
 const router = useRouter();
@@ -203,6 +209,8 @@ console.log("deviceName", deviceName)
 console.log("algoImageName", algoImageName)
 console.log("algoImageName", algoImageName)
 console.log("worldPath", worldPath)
 console.log("worldPath", worldPath)
 
 
+const testTime = ref("1726033307148")
+
 const handleClose = (done: () => void) => {
 const handleClose = (done: () => void) => {
   done()
   done()
 }
 }
@@ -253,6 +261,68 @@ const destinationChange = (value: string) => {
   }
   }
 }
 }
 
 
+const handleSelectionChange = (rows: []) => {
+  multipleSelection.value = rows
+  console.log("multipleSelection", multipleSelection.value)
+}
+
+const downloadFile = async (typeName) => {
+  // object key
+  let keys
+  // 文件名(区分1个文件/多个文件)
+  let fileName
+
+  if (typeName === "bag") {
+    keys = multipleSelection.value.map(item => item["test_bag_path"])
+    fileName = multipleSelection.value.length > 1 ? sceneId + "_" + typeName + ".zip" : "test.bag"
+  } else if(typeName === "report") {
+    keys = multipleSelection.value.map(item => item["algo_evaluation_report"])
+    fileName = multipleSelection.value.length > 1 ? sceneId + "_" + typeName + ".zip" : "report.pdf"
+  }
+  console.log("keys", keys)
+  console.log("typeName", typeName)
+  console.log("fileName", fileName)
+
+  // 下载记录
+  const url = "http://127.0.0.1:8888/simulation/download/oss/key?sceneId=" + sceneId + "&typeName=" + typeName
+  const result = await window.electronAPI.downloadFile(url, fileName, "", true, true, "post", keys);
+  if (!result.success) { // 下载失败
+    console.error('File download failed:', result.error);
+    ElMessage.error("文件下载失败!");
+  } else { // 下载成功
+    console.log('File downloaded successfully:', result.filePath);
+    ElMessage.success("文件下载成功!");
+  }
+}
+
+// 表格 - 格式化路径
+const formatPath = (row, name) => {
+  return path.basename(row[name])
+}
+
+// 弹窗显示算法评价记录
+const showEvalResult = async (value: string) => {
+  console.log("testTime", testTime.value)
+  console.log("sceneId", sceneId)
+  if (testTime.value != "") {
+    // 查询算法评价记录
+    let response = await axios.get('/local/simulation/query/eval/record?sceneId=' + sceneId + "&testTime=" + testTime.value)
+
+    if (!response.data.status){ // 不存在
+      proxy.$message.error("算法评价结果查询失败!");
+      return
+    }
+    // 解析结果
+    evalTableData.value = JSON.parse(response.data.details)
+    await nextTick() // 避免vue warning: onMounted is called when there is no active component instance...
+    // 开启弹窗
+    evalTableVisible.value = true
+    console.log("evalTableData.value",  evalTableData.value)
+  }else {
+    proxy.$message.error("暂无算法评价结果!");
+  }
+}
+
 const runSimulation = () => {
 const runSimulation = () => {
   // 是否加载默认障碍物
   // 是否加载默认障碍物
   let random_flag = form.isRandom
   let random_flag = form.isRandom
@@ -300,6 +370,7 @@ const runSimulation = () => {
           console.log('uploadPgmKey', uploadPgmKey)
           console.log('uploadPgmKey', uploadPgmKey)
           let simulation_data = []
           let simulation_data = []
           evalResult.value.forEach((d, i) => {
           evalResult.value.forEach((d, i) => {
+            if (i === 0) testTime.value = String(d["timeStamp"])
             simulation_data.push({
             simulation_data.push({
               'device_id': deviceId,
               'device_id': deviceId,
               'device_name': deviceName,
               'device_name': deviceName,
@@ -320,7 +391,6 @@ const runSimulation = () => {
           })
           })
           console.log("simulation_data", simulation_data)
           console.log("simulation_data", simulation_data)
           // 上传数据
           // 上传数据
-          // 检查world文件是否存在
           let response = await axios.post('/local/simulation/add/record', simulation_data)
           let response = await axios.post('/local/simulation/add/record', simulation_data)
 
 
           if (!response.data.status){ // 不存在
           if (!response.data.status){ // 不存在

+ 3 - 30
src/views/ReportView.vue

@@ -128,33 +128,6 @@
             </el-dialog>
             </el-dialog>
 <!--            <el-button style="margin-left: 10px;"  type="primary" @click="getDockerImages('pji_nav')">算法镜像选择</el-button>-->
 <!--            <el-button style="margin-left: 10px;"  type="primary" @click="getDockerImages('pji_nav')">算法镜像选择</el-button>-->
 
 
-
-            <!--        <el-switch-->
-            <!--            v-model="algorithmContainerState"-->
-            <!--            class="ml-2"-->
-            <!--            inline-prompt-->
-            <!--            style="margin-left: 100px;&#45;&#45;el-switch-on-color: #13ce66; &#45;&#45;el-switch-off-color: #ff4949"-->
-            <!--            active-text="算法容器已开启"-->
-            <!--            inactive-text="算法容器已关闭"-->
-            <!--        />-->
-            <!--        <el-switch-->
-            <!--            v-model="gazeboState"-->
-            <!--            class="ml-2"-->
-            <!--            inline-prompt-->
-            <!--            style="margin-left: 100px; &#45;&#45;el-switch-on-color: #13ce66; &#45;&#45;el-switch-off-color: #ff4949"-->
-            <!--            active-text="Gazebo 已开启"-->
-            <!--            inactive-text="Gazebo 已关闭"-->
-            <!--            @click="gazebo"-->
-            <!--        />-->
-            <!--        <el-switch-->
-            <!--            v-model="rvizState"-->
-            <!--            class="ml-2"-->
-            <!--            inline-prompt-->
-            <!--            style="margin-left: 100px;&#45;&#45;el-switch-on-color: #13ce66; &#45;&#45;el-switch-off-color: #ff4949"-->
-            <!--            active-text="Rviz 已开启"-->
-            <!--            inactive-text="Rviz 已关闭"-->
-            <!--            @click="rviz"-->
-            <!--        />-->
             <el-button style="margin-left: 10px;" type="primary" @click="goToTestRecord">仿真测试记录</el-button>
             <el-button style="margin-left: 10px;" type="primary" @click="goToTestRecord">仿真测试记录</el-button>
           </div>
           </div>
         </div>
         </div>
@@ -502,7 +475,7 @@ const updateMap = async () => {
 
 
   // 开启loading
   // 开启loading
   const loadingInstance = ElLoading.service({fullscreen: false, target: '.el-dialog'})
   const loadingInstance = ElLoading.service({fullscreen: false, target: '.el-dialog'})
-  const result = await window.electronAPI.downloadFile(url, fileName, savePath, false);
+  const result = await window.electronAPI.downloadFile(url, fileName, savePath, false, false, "get", {});
   if (!result.success) { // 下载失败
   if (!result.success) { // 下载失败
     console.error('File download failed:', result.error);
     console.error('File download failed:', result.error);
     ElMessage.error("地图更新数据拉取失败!");
     ElMessage.error("地图更新数据拉取失败!");
@@ -560,7 +533,7 @@ const generateWorld = async (row) => {
   worldDialogVisible.value = true
   worldDialogVisible.value = true
 
 
   // 下载map.bag
   // 下载map.bag
-  const result = await window.electronAPI.downloadFile(url, fileName, savePath, false);
+  const result = await window.electronAPI.downloadFile(url, fileName, savePath, false, false, "get", {});
   if (!result.success) { // 下载失败
   if (!result.success) { // 下载失败
     console.error('File download failed:', result.error);
     console.error('File download failed:', result.error);
     ElMessage.error("地图bag数据拉取失败!");
     ElMessage.error("地图bag数据拉取失败!");
@@ -639,7 +612,7 @@ const startSimulation = async (row) => {
   console.log("Starting download files...")
   console.log("Starting download files...")
   // 开启loading
   // 开启loading
   let loadingInstance = ElLoading.service({fullscreen: false, target: '.el-dialog'})
   let loadingInstance = ElLoading.service({fullscreen: false, target: '.el-dialog'})
-  const result = await window.electronAPI.downloadFile(url, fileName, savePath, false);
+  const result = await window.electronAPI.downloadFile(url, fileName, savePath, false, false, "get", {});
   if (!result.success) { // 下载失败
   if (!result.success) { // 下载失败
     console.error('File download failed:', result.error);
     console.error('File download failed:', result.error);
     ElMessage.error("仿真测试数据拉取失败!");
     ElMessage.error("仿真测试数据拉取失败!");