1
0

2 Commits 26f99c5bf1 ... 825c041077

Autor SHA1 Nachricht Datum
  HeWang 825c041077 部署-外网(已测试) vor 7 Monaten
  HeWang 2207fb4da6 部署测试版-朴津办公室 vor 7 Monaten

+ 1 - 0
.env

@@ -0,0 +1 @@
+VITE_PJI_SCRIPT_PATH_PREFIX=/home/cicv/work/pji_desktop/

+ 1 - 0
App-linux-x64/resources/app/.env

@@ -0,0 +1 @@
+VITE_PJI_SCRIPT_PATH_PREFIX=/home/cicv/work/pji_desktop/

BIN
App-linux-x64/resources/app/App-linux-x64.tar.xz


+ 45 - 0
App-linux-x64/resources/app/electron/processManager.js

@@ -0,0 +1,45 @@
+import {exec, spawn} from 'child_process';
+
+class ProcessManager {
+  constructor() {
+    this.processes = new Map(); // 存储所有子进程的引用
+  }
+
+  // 启动新的子进程
+  startProcess(key, command, callback) {
+    // 如果相同 key 的进程已经在运行,先终止它
+    if (this.processes.has(key)) {
+      this.killProcess(key);
+    }
+
+    // 启动新进程
+    const child = exec(command, (error, stdout, stderr) => {
+      if (callback) callback(error, stdout, stderr); // 执行回调
+
+      // 执行完成后,清理存储的进程
+      this.processes.delete(key);
+    });
+
+    // 存储进程引用
+    this.processes.set(key, child);
+  }
+
+  // 终止指定 key 的子进程
+  killProcess(key) {
+    const child = this.processes.get(key);
+    if (child) {
+      child.kill();
+      this.processes.delete(key);
+    }
+  }
+
+  // 终止所有子进程
+  killAll() {
+    for (const [key, child] of this.processes.entries()) {
+      child.kill();
+      this.processes.delete(key);
+    }
+  }
+}
+
+export default ProcessManager;

+ 17 - 0
App-linux-x64/resources/app/src/utils/resetMessage.js

@@ -0,0 +1,17 @@
+
+import { ElMessage } from 'element-plus' //引入message弹出框
+
+let messageDom = null
+const resetMessage = (options) => {
+    if (messageDom) messageDom.close() // 判断弹窗是否已存在,若存在则关闭
+    messageDom = ElMessage(options)
+}
+const typeArr = ['success', 'error', 'warning', 'info']
+typeArr.forEach(type => {
+    resetMessage[type] = options => {
+        if (typeof options === 'string') options = { message: options }
+        options.type = type
+        return resetMessage(options)
+    }
+})
+export const message = resetMessage

+ 186 - 0
App-linux-x64/resources/app/src/views/AlgorithmEvalView.vue

@@ -0,0 +1,186 @@
+<!--suppress ALL -->
+<template>
+  <div class="common-layout">
+    <el-container>
+      <el-header
+          style="height: 250px; background-color: rgba(255,0,0,50%); display: flex; flex-direction: column; justify-content: center; align-items: center;">
+        <div class="demo-image" style="text-align: center;"> <!-- 注意这里添加了 text-align: center; 但对于块级元素这不是必需的 -->
+          <img src="../assets/pji-logo.jpg" alt="" style="width:506.5px;height: 146px">
+          <!-- 推荐使用 max-width 保持图片比例 -->
+        </div>
+      </el-header>
+      <el-main style="background-color:white;">
+        <el-breadcrumb separator="/" style="margin-bottom: 10px;">
+          <el-breadcrumb-item :to="{ path: '/' }">主页</el-breadcrumb-item>
+          <el-breadcrumb-item>算法评价</el-breadcrumb-item>
+        </el-breadcrumb>
+        <div class="topbar">
+          <el-form style=" background-color: rgba(0,0,0,0%);" :inline="true" :model="queryLine"
+                   class="demo-form-inline">
+
+            <el-form-item label="算法评价等级">
+              <el-input v-model="queryLine.algorithmLevel" placeholder="请输入算法评价等级" clearable/>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="danger" @click="onSubmit">查询</el-button>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="danger" @click="onSubmit">重置</el-button>
+            </el-form-item>
+            <div style="float: right">
+              <el-form-item>
+                <el-button type="danger" @click="onSubmit">下载测试bag</el-button>
+              </el-form-item>
+              <el-form-item>
+                <el-button type="danger" @click="onSubmit">下载算法评价报告</el-button>
+              </el-form-item>
+            </div>
+
+          </el-form>
+
+        </div>
+        <el-table stripe style="background-color: rgba(255,0,0,99%);width: 100%" border :data="tableData"
+                  fixed ref="multipleTableRef" :cell-style="{ textAlign: 'center'}" :header-cell-style="{ textAlign: 'center'}">
+          <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>
+
+        <p></p> <!--空行-->
+        <el-pagination
+            v-model:current-page="currentPage"
+            v-model:page-size="pageSize"
+            :page-sizes="[10]"
+            :small="small"
+            :disabled="disabled"
+            :background="true"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="total"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+        />
+        <!--        <el-pagination background layout="prev, pager, next" :total="1000"/>-->
+      </el-main>
+      <!--      <el-footer style="background-color: rgba(255,0,0,99%)">国家智能网联汽车创新中心</el-footer>-->
+    </el-container>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {onBeforeMount, reactive, ref} from "vue";
+import axios from "axios";
+import {ElTable} from "element-plus";
+
+// 数据容量单位从B转成MB
+const queryLine = reactive({
+  algorithmLevel: ''
+})
+
+const onSubmit = () => {
+  page()
+}
+
+let total = ref(0)
+let tableData = ref([]);
+onBeforeMount(() => {
+  page()
+})
+
+function page() {
+  axios.get('http://1.202.169.139:8081/open/scene/list?equipmentType=YI_DAO_JI_QI_REN&page=' + currentPage.value + '&size=' + pageSize.value,
+      {
+        headers: {
+          "Authorization": "4773hd92ysj54paflw2jem3onyhywxt2"
+        }
+      }
+  ).then(function (response) {
+    tableData.value = response.data.data.content
+    total.value = response.data.data.totalElements
+    // total.value = tableData.value.length
+    // console.log(tableData);
+    // console.log(response.data.data);
+  }).catch(function (error) {
+    console.log(error);
+  });
+}
+
+const currentPage = ref(1)
+const pageSize = ref(10)
+const small = ref(false)
+const disabled = ref(false)
+
+const handleSizeChange = (val: number) => {
+  pageSize.value = val
+  page()
+  if (tableData.value.length == 0) {
+    page()
+  }
+}
+const handleCurrentChange = (val: number) => {
+  currentPage.value = val
+  page()
+}
+
+
+</script>
+<style scoped>
+
+.demo-pagination-block + .demo-pagination-block {
+  margin-top: 10px;
+}
+
+.demo-pagination-block .demonstration {
+  margin-bottom: 16px;
+}
+
+
+.demo-form-inline .el-input {
+  --el-input-width: 200px;
+}
+
+.demo-form-inline .el-select {
+  --el-select-width: 400px;
+}
+
+.demo-image .block {
+  padding: 30px 0;
+  text-align: center;
+  border-right: solid 1px var(--el-border-color);
+  display: inline-block;
+  width: 20%;
+  box-sizing: border-box;
+  vertical-align: top;
+}
+
+.demo-image .block:last-child {
+  border-right: none;
+}
+
+.demo-image .demonstration {
+  display: block;
+  color: var(--el-text-color-secondary);
+  font-size: 14px;
+  margin-bottom: 20px;
+}
+
+.topbar {
+  margin-bottom: 15px;
+}
+
+/* 假设 .el-pager__item 是分页按钮的类名,这可能需要你根据实际的 Element UI 版本和源码进行调整 */
+.el-pager__item {
+  background-color: rgba(255, 0, 0, 50%) !important; /* 修改背景色 */
+  color: #fff; /* 可能需要修改文本颜色以在红色背景上可见 */
+  border-color: transparent; /* 如果需要,修改边框颜色 */
+}
+
+/* 修改当前选中页码的按钮样式 */
+.el-pager__item.is-active {
+  background-color: rgba(255, 0, 0, 70%) !important; /* 选中时的背景色 */
+  color: #fff; /* 选中时的文本颜色 */
+}
+</style>

+ 300 - 0
App-linux-x64/resources/app/src/views/MapRescanView.vue

@@ -0,0 +1,300 @@
+<!--suppress ALL -->
+<template>
+  <div class="common-layout">
+    <el-container>
+      <el-header
+          style="height: 250px; background-color: rgba(255,0,0,50%); display: flex; flex-direction: column; justify-content: center; align-items: center;">
+        <div class="demo-image" style="text-align: center;"> <!-- 注意这里添加了 text-align: center; 但对于块级元素这不是必需的 -->
+          <img src="../assets/pji-logo.jpg" alt="" style="width:506.5px;height: 146px">
+          <!-- 推荐使用 max-width 保持图片比例 -->
+        </div>
+      </el-header>
+      <el-main style="background-color:white;">
+        <el-breadcrumb separator="/" style="margin-bottom: 10px;">
+          <el-breadcrumb-item :to="{ path: '/' }">主页</el-breadcrumb-item>
+          <el-breadcrumb-item>地图续扫提醒</el-breadcrumb-item>
+        </el-breadcrumb>
+        <div class="topbar">
+          <el-form style=" background-color: rgba(0,0,0,0%);" :inline="true" :model="queryLine"
+                   class="demo-form-inline">
+            <el-form-item label="设备名称">
+              <el-input v-model="queryLine.equipmentName" placeholder="请输入设备名称" clearable/>
+            </el-form-item>
+            <el-form-item label="设备类型">
+              <!--              <el-input v-model="queryLine.equipmentType" placeholder="请输入设备类型" clearable/>-->
+              <el-select
+                  v-model="queryLine.equipmentType"
+                  placeholder="请选择"
+                  size="default"
+                  style="width: 180px; "
+                  clearable
+              >
+                <el-option
+                    v-for="item in equipmentTypes"
+                    :key="item"
+                    :label="item"
+                    :value="item"
+                />
+              </el-select>
+            </el-form-item>
+<!--            <el-form-item label="更新阈值设置" label-width="100">-->
+<!--              <el-input v-model="queryLine.threshold" min="0" max="100" step="1" style="width: 65px">-->
+<!--                <template #suffix>-->
+<!--                  <i slot="suffix" style="color: #181818">%</i>-->
+<!--                </template>-->
+<!--              </el-input>-->
+
+<!--&lt;!&ndash;              <el-input-number&ndash;&gt;-->
+<!--&lt;!&ndash;                  v-model="queryLine.threshold"&ndash;&gt;-->
+<!--&lt;!&ndash;                  :min="1"&ndash;&gt;-->
+<!--&lt;!&ndash;                  :max="10"&ndash;&gt;-->
+<!--&lt;!&ndash;                  size="large"&ndash;&gt;-->
+<!--&lt;!&ndash;                  style="width: 100px"&ndash;&gt;-->
+<!--&lt;!&ndash;              />&ndash;&gt;-->
+<!--            </el-form-item>-->
+            <el-form-item>
+              <el-button type="danger" @click="queryRecordByCondition">查询</el-button>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="danger" @click="onSubmit">重置</el-button>
+            </el-form-item>
+<!--            <div style="float: right">-->
+<!--              <el-form-item>-->
+<!--                <el-button type="danger" @click="onSubmit">查询</el-button>-->
+<!--              </el-form-item>-->
+<!--              <el-form-item>-->
+<!--                <el-button type="danger" @click="onSubmit">重置</el-button>-->
+<!--              </el-form-item>-->
+<!--            </div>-->
+          </el-form>
+        </div>
+<!--        <el-table stripe style="background-color: rgba(255,0,0,99%);width: 100%" border :data="tableData"-->
+<!--                  fixed ref="multipleTableRef" :cell-style="{ textAlign: 'center'}" :header-cell-style="{ textAlign: 'center'}">-->
+<!--          <el-table-column type="selection" width="55"/>-->
+<!--          <el-table-column prop="equipmentName" label="设备名称"/>-->
+<!--          <el-table-column prop="equipmentTypeName" label="设备类型"/>-->
+<!--          <el-table-column prop="mapId" label="地图id"/>-->
+<!--          <el-table-column prop="originalPgm" label="原始pgm"/>-->
+<!--          <el-table-column prop="updateTime" label="地图更新时间"/>-->
+<!--          <el-table-column prop="cumulativeUpdateRate" label="累积地图更新率"/>-->
+<!--          <el-table-column prop="currentPgm" label="当前pgm"/>-->
+<!--        </el-table>-->
+        <el-image-viewer @close="closeImgViewer" :url-list="imageUrl" v-if="showImageViewer"></el-image-viewer>
+        <el-table stripe style="background-color: rgba(255,0,0,99%);width: 100%" border :data="tableData"
+                  fixed ref="multipleTableRef" :cell-style="{ textAlign: 'center'}" :header-cell-style="{ textAlign: 'center'}">
+          <el-table-column type="selection" width="55"/>
+          <el-table-column prop="device_name" label="设备名称"/>
+          <el-table-column prop="device_type" label="设备类型"/>
+          <el-table-column prop="map_id" label="地图id"/>
+          <el-table-column prop="update_time" label="地图更新时间" :formatter="formatDate"/>
+<!--          <el-table-column prop="update_rate" label="地图更新率"/>-->
+          <el-table-column prop="cumulative_update_rate" label="累积地图更新率"/>
+          <el-table-column prop="rescan_notify_threshold" label="地图续扫阈值"/>
+<!--          <el-table-column prop="update_flag" label="地图更新状态" :formatter="formatStatus"/>-->
+          <el-table-column prop="update_flag" label="地图续扫提醒" :formatter="formatStatus"/>
+          <el-table-column prop="pre_pgm_url" label="原始pgm" width="400px">
+            <template v-slot="scope">
+              <img :src="'http://oss-cn-beijing-gqzl-d01-a.ops.gqzl-cloud.com/pji-bucket1/' + scope.row.pre_png_url" @click="viewImg" alt="" style="width:300px; height: 100%; cursor: pointer">
+            </template>
+
+          </el-table-column>
+          <el-table-column prop="current_pgm_url" label="当前pgm" width="400px">
+            <template v-slot="scope">
+              <img :src="'http://oss-cn-beijing-gqzl-d01-a.ops.gqzl-cloud.com/pji-bucket1/' + scope.row.current_png_url" @click="viewImg" alt="" style="width:300px; height: 100%; cursor: pointer">
+            </template>
+          </el-table-column>
+
+        </el-table>
+        <p></p> <!--空行-->
+        <el-pagination
+            v-model:current-page="currentPage"
+            v-model:page-size="pageSize"
+            :page-sizes="[10]"
+            :small="small"
+            :disabled="disabled"
+            :background="true"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="total"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+        />
+        <!--        <el-pagination background layout="prev, pager, next" :total="1000"/>-->
+      </el-main>
+      <!--      <el-footer style="background-color: rgba(255,0,0,99%)">国家智能网联汽车创新中心</el-footer>-->
+    </el-container>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {onBeforeMount, reactive, ref} from "vue";
+import axios from "axios";
+import {ElLoading, ElMessage, ElTable} from "element-plus";
+import moment from "moment/moment";
+
+const showImageViewer = ref(false);
+const imageUrl = ref([])
+
+const formatDate =  (row) => {
+  return moment(+row.update_time).format("YYYY-MM-DD HH:mm:ss")
+}
+
+const formatStatus =  (row) => {
+  if (row.rescan_notify_flag == 0) return "未发送续扫提醒";
+  if (row.rescan_notify_flag == 1) return "已发送续扫提醒";
+  return
+}
+
+const viewImg = (event) => {
+  if (event.target.nodeName == "IMG") {
+    let src = event.target.currentSrc;
+    imageUrl.value = [src]
+    showImageViewer.value = true
+  }
+}
+
+const closeImgViewer = () => {
+  showImageViewer.value = false;
+}
+
+// 数据容量单位从B转成MB
+const queryLine = reactive({
+  equipmentName: '',
+  equipmentTypeName: '',
+  threshold:'',
+  equipmentType: '',
+})
+const equipmentTypes =ref(["引导服务机器人"])
+const resetTableData = ref([]);
+const onSubmit = () => {
+  page()
+}
+
+let total = ref(0)
+let tableData = ref([]);
+onBeforeMount(() => {
+  page()
+})
+
+// 根据条件筛选结果
+const queryRecordByCondition = async () => {
+  let response = await axios.post('/local/map/rescan/notify/list?page=' + currentPage.value + '&pageSize=' + pageSize.value, {
+    "device_name": queryLine.equipmentName,
+    "device_type": queryLine.equipmentType
+  })
+
+  if (!response.data.status){ // 不存在
+    ElMessage.error("地图续扫提醒记录查询失败!");
+    return
+  }
+  // 解析结果
+  tableData.value = JSON.parse(response.data.data)
+  total.value = response.data.total
+}
+
+async function page() {
+  // axios.get('http://1.202.169.139:8081/open/scene/list?equipmentType=YI_DAO_JI_QI_REN&page=' + currentPage.value + '&size=' + pageSize.value,
+  //     {
+  //       headers: {
+  //         "Authorization": "4773hd92ysj54paflw2jem3onyhywxt2"
+  //       }
+  //     }
+  // ).then(function (response) {
+  //   tableData.value = response.data.data.content
+  //   total.value = response.data.data.totalElements
+  //   // total.value = tableData.value.length
+  //   // console.log(tableData);
+  //   // console.log(response.data.data);
+  // }).catch(function (error) {
+  //   console.log(error);
+  // });
+
+  // 查询地图更新记录
+  let response = await axios.get('/local/map/rescan/notify/list?page=' + currentPage.value + '&pageSize=' + pageSize.value)
+
+  if (!response.data.status){ // 不存在
+    ElMessage.error("地图续扫提醒记录查询失败!");
+    return
+  }
+  // 解析结果
+  tableData.value = JSON.parse(response.data.data)
+  console.log("tableData.value", tableData.value)
+  resetTableData.value = tableData.value // for reset purpose
+  total.value = response.data.total
+}
+
+const currentPage = ref(1)
+const pageSize = ref(10)
+const small = ref(false)
+const disabled = ref(false)
+
+const handleSizeChange = (val: number) => {
+  pageSize.value = val
+  page()
+  if (tableData.value.length == 0) {
+    page()
+  }
+}
+const handleCurrentChange = (val: number) => {
+  currentPage.value = val
+  page()
+}
+
+
+</script>
+<style scoped>
+
+.demo-pagination-block + .demo-pagination-block {
+  margin-top: 10px;
+}
+
+.demo-pagination-block .demonstration {
+  margin-bottom: 16px;
+}
+
+
+.demo-form-inline .el-input {
+  --el-input-width: 200px;
+}
+
+.demo-form-inline .el-select {
+  --el-select-width: 400px;
+}
+
+.demo-image .block {
+  padding: 30px 0;
+  text-align: center;
+  border-right: solid 1px var(--el-border-color);
+  display: inline-block;
+  width: 20%;
+  box-sizing: border-box;
+  vertical-align: top;
+}
+
+.demo-image .block:last-child {
+  border-right: none;
+}
+
+.demo-image .demonstration {
+  display: block;
+  color: var(--el-text-color-secondary);
+  font-size: 14px;
+  margin-bottom: 20px;
+}
+
+.topbar {
+  margin-bottom: 15px;
+}
+
+/* 假设 .el-pager__item 是分页按钮的类名,这可能需要你根据实际的 Element UI 版本和源码进行调整 */
+.el-pager__item {
+  background-color: rgba(255, 0, 0, 50%) !important; /* 修改背景色 */
+  color: #fff; /* 可能需要修改文本颜色以在红色背景上可见 */
+  border-color: transparent; /* 如果需要,修改边框颜色 */
+}
+
+/* 修改当前选中页码的按钮样式 */
+.el-pager__item.is-active {
+  background-color: rgba(255, 0, 0, 70%) !important; /* 选中时的背景色 */
+  color: #fff; /* 选中时的文本颜色 */
+}
+</style>

+ 206 - 0
App-linux-x64/resources/app/src/views/MapUpdateView.vue

@@ -0,0 +1,206 @@
+<template>
+  <div class="common-layout">
+    <el-container>
+      <el-header
+          style="height: 250px; background-color: rgba(255,0,0,50%); display: flex; flex-direction: column; justify-content: center; align-items: center;">
+        <div class="demo-image" style="text-align: center;"> <!-- 注意这里添加了 text-align: center; 但对于块级元素这不是必需的 -->
+          <img src="../assets/pji-logo.jpg" alt="" style="width:506.5px;height: 146px">
+          <!-- 推荐使用 max-width 保持图片比例 -->
+        </div>
+      </el-header>
+      <el-main style="background-color:white;">
+        <el-breadcrumb separator="/" style="margin-bottom: 10px;">
+          <el-breadcrumb-item :to="{ path: '/' }">主页</el-breadcrumb-item>
+          <el-breadcrumb-item>地图更新</el-breadcrumb-item>
+        </el-breadcrumb>
+        <div class="topbar">
+          <el-button type="primary" @click="dialogVisible = true" style="float: right; margin-bottom: 20px;">将地图更新至机器人端</el-button>
+          <el-dialog
+              v-model="dialogVisible"
+              title="提示"
+              width="300"
+              :before-close="handleClose"
+          >
+            <span>请确认是否进行地图更新</span>
+            <template #footer>
+              <div class="dialog-footer">
+                <el-button @click="dialogVisible = false">取消</el-button>
+                <el-button type="primary" @click="dialogVisible = false">
+                  确认
+                </el-button>
+              </div>
+            </template>
+          </el-dialog>
+        </div>
+
+        <el-table stripe style="background-color: rgba(255,0,0,99%);width: 100%" border :data="tableData"
+                  fixed ref="multipleTableRef" :cell-style="{ textAlign: 'center'}" :header-cell-style="{ textAlign: 'center'}">
+          <el-table-column type="selection" width="55"/>
+          <el-table-column prop="mapId" label="地图id"/>
+          <el-table-column prop="beforePgm" label="更新前pgm"/>
+          <el-table-column prop="afterPgm" label="更新后pgm"/>
+          <el-table-column prop="mapUpdateRate" label="地图更新率" :formatter="mapUpdateRateFormatter"/>
+        </el-table>
+
+<!--        <p></p> &lt;!&ndash;空行&ndash;&gt;-->
+<!--        <el-pagination-->
+<!--            v-model:current-page="currentPage"-->
+<!--            v-model:page-size="pageSize"-->
+<!--            :page-sizes="[10]"-->
+<!--            :small="small"-->
+<!--            :disabled="disabled"-->
+<!--            :background="true"-->
+<!--            layout="total, sizes, prev, pager, next, jumper"-->
+<!--            :total="total"-->
+<!--            @size-change="handleSizeChange"-->
+<!--            @current-change="handleCurrentChange"-->
+<!--        />-->
+<!--        &lt;!&ndash;        <el-pagination background layout="prev, pager, next" :total="1000"/>&ndash;&gt;-->
+      </el-main>
+      <!--      <el-footer style="background-color: rgba(255,0,0,99%)">国家智能网联汽车创新中心</el-footer>-->
+    </el-container>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {onBeforeMount, ref} from "vue";
+import axios from "axios";
+import {ElTable} from "element-plus";
+
+
+const multipleTableRef = ref<InstanceType<typeof ElTable>>
+
+const dialogVisible = ref(false)
+
+const handleClose = (done: () => void) => {
+  done()
+}
+
+
+const mapUpdateRateFormatter = (row, column, cellValue, index) => {
+  // 假设 1MB = 1024 * 1024 字节
+  if (cellValue) {
+    const percentValue = (cellValue * 100).toFixed(1); // 保留两位小数
+    return `${percentValue}%`;
+  }
+  return cellValue; // 如果cellValue为null或undefined,则返回原值或你想要的默认值
+}
+
+
+let total = ref(0)
+let tableData = ref([{
+  'mapId': 1,
+  'beforePgm': 'url1',
+  'afterPgm': 'url1_1',
+  'mapUpdateRate': 0.201
+},
+{
+  'mapId': 2,
+  'beforePgm': 'url2',
+  'afterPgm': 'url2_1',
+  'mapUpdateRate': 0.21
+}
+]);
+
+onBeforeMount(() => {
+  page()
+})
+
+function page() {
+  axios.get('http://1.202.169.139:8081/open/scene/list?equipmentType=YI_DAO_JI_QI_REN&page=' + currentPage.value + '&size=' + pageSize.value,
+      {
+        headers: {
+          "Authorization": "4773hd92ysj54paflw2jem3onyhywxt2"
+        }
+      }
+  ).then(function (response) {
+    tableData.value = response.data.data.content
+    total.value = response.data.data.totalElements
+    // total.value = tableData.value.length
+    // console.log(tableData);
+    // console.log(response.data.data);
+  }).catch(function (error) {
+    console.log(error);
+  });
+}
+
+const currentPage = ref(1)
+const pageSize = ref(10)
+const small = ref(false)
+const disabled = ref(false)
+
+const handleSizeChange = (val: number) => {
+  pageSize.value = val
+  page()
+  if (tableData.value.length == 0) {
+    page()
+  }
+}
+const handleCurrentChange = (val: number) => {
+  currentPage.value = val
+  page()
+}
+const handleClick = () => {
+  console.log('click')
+}
+
+
+</script>
+<style scoped>
+
+
+.demo-pagination-block + .demo-pagination-block {
+  margin-top: 10px;
+}
+
+.demo-pagination-block .demonstration {
+  margin-bottom: 16px;
+}
+
+
+.demo-form-inline .el-input {
+  --el-input-width: 200px;
+}
+
+.demo-form-inline .el-select {
+  --el-select-width: 400px;
+}
+
+.demo-image .block {
+  padding: 30px 0;
+  text-align: center;
+  border-right: solid 1px var(--el-border-color);
+  display: inline-block;
+  width: 20%;
+  box-sizing: border-box;
+  vertical-align: top;
+}
+
+.demo-image .block:last-child {
+  border-right: none;
+}
+
+.demo-image .demonstration {
+  display: block;
+  color: var(--el-text-color-secondary);
+  font-size: 14px;
+  margin-bottom: 20px;
+}
+
+.topbar {
+  margin-bottom: 15px;
+}
+
+/* 假设 .el-pager__item 是分页按钮的类名,这可能需要你根据实际的 Element UI 版本和源码进行调整 */
+.el-pager__item {
+  background-color: rgba(255, 0, 0, 50%) !important; /* 修改背景色 */
+  color: #fff; /* 可能需要修改文本颜色以在红色背景上可见 */
+  border-color: transparent; /* 如果需要,修改边框颜色 */
+}
+
+/* 修改当前选中页码的按钮样式 */
+.el-pager__item.is-active {
+  background-color: rgba(255, 0, 0, 70%) !important; /* 选中时的背景色 */
+  color: #fff; /* 选中时的文本颜色 */
+}
+</style>

+ 347 - 0
App-linux-x64/resources/app/src/views/TestRecordView.vue

@@ -0,0 +1,347 @@
+<!--suppress ALL -->
+<template>
+  <div class="common-layout">
+    <el-container>
+      <el-header
+          style="height: 250px; background-color: rgba(255,0,0,50%); display: flex; flex-direction: column; justify-content: center; align-items: center;">
+        <div class="demo-image" style="text-align: center;"> <!-- 注意这里添加了 text-align: center; 但对于块级元素这不是必需的 -->
+          <img src="../assets/pji-logo.jpg" alt="" style="width:506.5px;height: 146px">
+          <!-- 推荐使用 max-width 保持图片比例 -->
+        </div>
+      </el-header>
+      <el-main style="background-color:white;">
+        <el-breadcrumb separator="/" style="margin-bottom: 10px;">
+          <el-breadcrumb-item :to="{ path: '/' }">主页</el-breadcrumb-item>
+          <el-breadcrumb-item>测试仿真记录</el-breadcrumb-item>
+        </el-breadcrumb>
+        <div class="topbar">
+          <el-form style=" background-color: rgba(0,0,0,0%);" :inline="true" :model="queryLine"
+                   class="demo-form-inline">
+            <el-form-item label="设备名称">
+              <el-input v-model="queryLine.equipmentName" placeholder="请输入设备名称" clearable/>
+            </el-form-item>
+            <el-form-item label="设备类型">
+<!--              <el-input v-model="queryLine.equipmentType" placeholder="请输入设备类型" clearable/>-->
+              <el-select
+                  v-model="queryLine.equipmentType"
+                  placeholder="请选择"
+                  size="default"
+                  style="width: 180px; "
+                  clearable
+              >
+                <el-option
+                    v-for="item in equipmentTypes"
+                    :key="item"
+                    :label="item"
+                    :value="item"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="算法版本">
+              <el-input v-model="queryLine.algorithmVersion" placeholder="请输入算法版本" clearable/>
+<!--              <el-select-->
+<!--                  v-model="queryLine.algorithmVersion"-->
+<!--                  placeholder="请选择"-->
+<!--                  size="default"-->
+<!--                  @click.native="getKeywordsByField('algo_image_name')"-->
+<!--                  style="width: 180px; "-->
+<!--                  clearable-->
+<!--              >-->
+<!--                <el-option-->
+<!--                    v-for="item in algorithmVersions"-->
+<!--                    :key="item"-->
+<!--                    :label="item"-->
+<!--                    :value="item"-->
+<!--                />-->
+<!--              </el-select>-->
+            </el-form-item>
+
+            <el-form-item label="算法评价等级">
+              <!--            <el-input v-model="queryLine.algorithmLevel" placeholder="请输入算法评价等级" clearable/>-->
+              <el-select
+                  v-model="queryLine.algorithmLevel"
+                  placeholder="请选择"
+                  size="default"
+                  style="width: 180px; "
+                  clearable
+              >
+                <el-option
+                    v-for="item in algoLevels"
+                    :key="item"
+                    :label="item"
+                    :value="item"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="danger" @click="queryRecordByCondition">查询</el-button>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="danger" @click="onSubmit">重置</el-button>
+            </el-form-item>
+            <div style="float: right">
+              <el-form-item>
+                <el-button type="danger" @click="downloadFile('bag')" :disabled="multipleSelection.length == 0">下载测试bag</el-button>
+              </el-form-item>
+              <el-form-item>
+                <el-button type="danger" @click="downloadFile('report')" :disabled="multipleSelection.length == 0">下载算法评价报告</el-button>
+              </el-form-item>
+            </div>
+
+          </el-form>
+
+        </div>
+        <el-table stripe style="background-color: rgba(255,0,0,99%);width: 100%" border :data="tableData"
+                  @selection-change="handleSelectionChange"
+                  fixed ref="multipleTableRef" :cell-style="{ textAlign: 'center'}" :header-cell-style="{ textAlign: 'center'}">
+          <el-table-column type="selection" width="55"/>
+          <el-table-column prop="device_name" label="设备名称" width="300"/>
+          <el-table-column prop="device_type" label="设备类型"/>
+          <el-table-column prop="algo_image_name" label="算法版本"/>
+          <el-table-column prop="test_time" :formatter="formatDate" label="测试时间"/>
+          <el-table-column prop="round" label="轮次"/>
+          <el-table-column prop="pgm_path" :formatter="(row) => formatPath(row, 'pgm_path')" label="pgm文件"/>
+          <el-table-column prop="world_path" :formatter="(row) => formatPath(row, 'world_path')" label="world文件"/>
+          <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>
+
+        <p></p> <!--空行-->
+        <el-pagination
+            v-model:current-page="currentPage"
+            v-model:page-size="pageSize"
+            :page-sizes="[10, 15, 20, 50]"
+            :small="small"
+            :disabled="disabled"
+            :background="true"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="total"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+        />
+        <!--        <el-pagination background layout="prev, pager, next" :total="1000"/>-->
+      </el-main>
+      <!--      <el-footer style="background-color: rgba(255,0,0,99%)">国家智能网联汽车创新中心</el-footer>-->
+    </el-container>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {onBeforeMount, reactive, ref} from "vue";
+import axios from "axios";
+import {ElMessage, ElTable} from "element-plus";
+import path from 'path-browserify';
+import moment from "moment";
+
+const queryLine = reactive({
+  equipmentName: '',
+  equipmentType: '',
+  algorithmVersion: '',
+  algorithmLevel: ''
+})
+
+const onSubmit = () => {
+  page()
+}
+
+const total = ref(0)
+const tableData = ref([]);
+const resetTableData = ref([]);
+const algorithmVersions =ref()
+const equipmentTypes =ref(["引导服务机器人"])
+const equipmentNames =ref()
+onBeforeMount(() => {
+  page()
+})
+const multipleTableRef = ref<InstanceType<typeof ElTable>>
+const multipleSelection = ref<[]>([])
+
+const algoLevels = ['较差', '一般', '良好', '优秀']
+
+const handleSelectionChange = (rows: []) => {
+  multipleSelection.value = rows
+  console.log("multipleSelection", multipleSelection.value)
+}
+
+// 表格 - 格式化路径
+const formatPath = (row, name) => {
+  return path.basename(row[name])
+}
+
+const formatDate =  (row) => {
+  return moment(+row.test_time).format("YYYY-MM-DD HH:mm:ss")
+}
+
+const getKeywordsByField = (field) => {
+  if (resetTableData.value.length > 0) {
+    let keywords = new Set(resetTableData.value.map((d) => d[field]))
+    if (field === "algo_image_name") {
+      algorithmVersions.value = keywords
+    }
+    // else if (field === "device_type") {
+    //   equipmentTypes.value = keywords
+    // } else if (field === "device_name") {
+    //   equipmentNames.value = keywords
+    // }
+
+  }
+}
+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 ? typeName + ".zip" : "test.bag"
+  } else if(typeName === "report") {
+    keys = multipleSelection.value.map(item => item["algo_evaluation_report"])
+    fileName = multipleSelection.value.length > 1 ?  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?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 queryRecordByCondition = async () => {
+  let response = await axios.post('/local/simulation/query/test/record?page=' + currentPage.value + '&pageSize=' + pageSize.value, {
+    "device_name": queryLine.equipmentName,
+    "device_type": queryLine.equipmentType,
+    "algo_image_name": queryLine.algorithmVersion,
+    "algo_evaluation_level": queryLine.algorithmLevel
+  })
+
+  if (!response.data.status){ // 不存在
+    ElMessage.error("仿真测试记录查询失败!");
+    return
+  }
+  // 解析结果
+  tableData.value = JSON.parse(response.data.data)
+  total.value = response.data.total
+}
+
+async function page() {
+  // axios.get('http://1.202.169.139:8081/open/scene/list?equipmentType=YI_DAO_JI_QI_REN&page=' + currentPage.value + '&size=' + pageSize.value,
+  //     {
+  //       headers: {
+  //         "Authorization": "4773hd92ysj54paflw2jem3onyhywxt2"
+  //       }
+  //     }
+  // ).then(function (response) {
+  //   tableData.value = response.data.data.content
+  //   total.value = response.data.data.totalElements
+  //   // total.value = tableData.value.length
+  //   // console.log(tableData);
+  //   // console.log(response.data.data);
+  // }).catch(function (error) {
+  //   console.log(error);
+  // });
+
+  // 查询算法评价记录
+  let response = await axios.post('/local/simulation/query/test/record?page=' + currentPage.value + '&pageSize=' + pageSize.value)
+
+  if (!response.data.status){ // 不存在
+    ElMessage.error("仿真测试记录查询失败!");
+    return
+  }
+  // 解析结果
+  tableData.value = JSON.parse(response.data.data)
+  console.log("tableData.value", tableData.value)
+  resetTableData.value = tableData.value // for reset purpose
+  total.value = response.data.total
+}
+
+const currentPage = ref(1)
+const pageSize = ref(10)
+const small = ref(false)
+const disabled = ref(false)
+
+const handleSizeChange = (val: number) => {
+  pageSize.value = val
+  // page()
+  queryRecordByCondition()
+  if (tableData.value.length == 0) {
+    // page()
+    queryRecordByCondition()
+  }
+}
+const handleCurrentChange = (val: number) => {
+  currentPage.value = val
+  // page()
+  queryRecordByCondition()
+}
+
+</script>
+<style scoped>
+
+.demo-pagination-block + .demo-pagination-block {
+  margin-top: 10px;
+}
+
+.demo-pagination-block .demonstration {
+  margin-bottom: 16px;
+}
+
+
+.demo-form-inline .el-input {
+  --el-input-width: 200px;
+}
+
+.demo-form-inline .el-select {
+  --el-select-width: 400px;
+}
+
+.demo-image .block {
+  padding: 30px 0;
+  text-align: center;
+  border-right: solid 1px var(--el-border-color);
+  display: inline-block;
+  width: 20%;
+  box-sizing: border-box;
+  vertical-align: top;
+}
+
+.demo-image .block:last-child {
+  border-right: none;
+}
+
+.demo-image .demonstration {
+  display: block;
+  color: var(--el-text-color-secondary);
+  font-size: 14px;
+  margin-bottom: 20px;
+}
+
+.topbar {
+  margin-bottom: 15px;
+}
+
+/* 假设 .el-pager__item 是分页按钮的类名,这可能需要你根据实际的 Element UI 版本和源码进行调整 */
+.el-pager__item {
+  background-color: rgba(255, 0, 0, 50%) !important; /* 修改背景色 */
+  color: #fff; /* 可能需要修改文本颜色以在红色背景上可见 */
+  border-color: transparent; /* 如果需要,修改边框颜色 */
+}
+
+/* 修改当前选中页码的按钮样式 */
+.el-pager__item.is-active {
+  background-color: rgba(255, 0, 0, 70%) !important; /* 选中时的背景色 */
+  color: #fff; /* 选中时的文本颜色 */
+}
+</style>

+ 310 - 0
App-linux-x64/resources/app/src/views/UpdateStatView.vue

@@ -0,0 +1,310 @@
+<template>
+  <div class="common-layout">
+    <el-container>
+      <el-header
+          style="height: 250px; background-color: rgba(255,0,0,50%); display: flex; flex-direction: column; justify-content: center; align-items: center;">
+        <div class="demo-image" style="text-align: center;"> <!-- 注意这里添加了 text-align: center; 但对于块级元素这不是必需的 -->
+          <img src="../assets/pji-logo.jpg" alt="" style="width:506.5px;height: 146px">
+          <!-- 推荐使用 max-width 保持图片比例 -->
+        </div>
+      </el-header>
+      <el-main style="background-color:white;">
+        <el-breadcrumb separator="/" style="margin-bottom: 10px;">
+          <el-breadcrumb-item :to="{ path: '/' }">主页</el-breadcrumb-item>
+          <el-breadcrumb-item>地图更新统计</el-breadcrumb-item>
+        </el-breadcrumb>
+        <div class="topbar">
+          <el-form style=" background-color: rgba(0,0,0,0%);" :inline="true" :model="queryLine"
+                   class="demo-form-inline">
+            <el-form-item label="设备名称">
+              <el-input v-model="queryLine.equipmentName" placeholder="请输入设备名称" clearable/>
+            </el-form-item>
+<!--            <el-form-item label="设备类型">-->
+<!--              <el-input v-model="queryLine.equipmentTypeName" placeholder="请输入设备类型" clearable/>-->
+<!--            </el-form-item>-->
+
+            <el-form-item label="设备类型">
+              <!--              <el-input v-model="queryLine.equipmentType" placeholder="请输入设备类型" clearable/>-->
+              <el-select
+                  v-model="queryLine.equipmentType"
+                  placeholder="请选择"
+                  size="default"
+                  style="width: 180px; "
+                  clearable
+              >
+                <el-option
+                    v-for="item in equipmentTypes"
+                    :key="item"
+                    :label="item"
+                    :value="item"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item >
+              <el-button type="danger" @click="queryRecordByCondition">查询</el-button>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="danger" @click="onSubmit">重置</el-button>
+            </el-form-item>
+<!--            <div style="float: right">-->
+<!--              <el-form-item >-->
+<!--                <el-button type="danger" @click="onSubmit">查询</el-button>-->
+<!--              </el-form-item>-->
+<!--              <el-form-item>-->
+<!--                <el-button type="danger" @click="onSubmit">重置</el-button>-->
+<!--              </el-form-item>-->
+<!--            </div>-->
+
+          </el-form>
+        </div>
+
+        <el-image-viewer @close="closeImgViewer" :url-list="imageUrl" v-if="showImageViewer"></el-image-viewer>
+        <el-table stripe style="background-color: rgba(255,0,0,99%);width: 100%" border :data="tableData"
+                  fixed ref="multipleTableRef" :cell-style="{ textAlign: 'center'}" :header-cell-style="{ textAlign: 'center'}">
+          <el-table-column type="selection" width="55"/>
+          <el-table-column prop="device_name" label="设备名称"/>
+          <el-table-column prop="device_type" label="设备类型"/>
+          <el-table-column prop="map_id" label="地图id"/>
+          <el-table-column prop="update_time" label="地图更新时间" :formatter="formatDate"/>
+          <el-table-column prop="update_rate" label="地图更新率"/>
+          <el-table-column prop="cumulative_update_rate" label="累积地图更新率"/>
+          <el-table-column prop="update_flag" label="地图更新状态" :formatter="formatStatus"/>
+          <el-table-column prop="pre_pgm_url" label="原始pgm" width="400px">
+            <template v-slot="scope">
+              <img :src="'http://oss-cn-beijing-gqzl-d01-a.ops.gqzl-cloud.com/pji-bucket1/' + scope.row.pre_png_url" @click="viewImg" alt="" style="width:300px; height: 100%; cursor: pointer">
+            </template>
+
+          </el-table-column>
+          <el-table-column prop="current_pgm_url" label="当前pgm" width="400px">
+            <template v-slot="scope">
+              <img :src="'http://oss-cn-beijing-gqzl-d01-a.ops.gqzl-cloud.com/pji-bucket1/' + scope.row.current_png_url" @click="viewImg" alt="" style="width:300px; height: 100%; cursor: pointer">
+            </template>
+          </el-table-column>
+
+          <el-table-column width="200" fixed="right" label="操作">
+            <template v-slot="scope">
+              <el-button :id="'button'+scope.row.id" size="small" type="danger" @click="uploadMap(scope.row)" :disabled="scope.row.update_flag!= 0">上传地图</el-button>
+            </template>
+
+          </el-table-column>
+        </el-table>
+
+        <p></p> <!--空行-->
+        <el-pagination
+            v-model:current-page="currentPage"
+            v-model:page-size="pageSize"
+            :page-sizes="[5,10]"
+            :small="small"
+            :disabled="disabled"
+            :background="true"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="total"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+        />
+        <!--        <el-pagination background layout="prev, pager, next" :total="1000"/>-->
+      </el-main>
+      <!--      <el-footer style="background-color: rgba(255,0,0,99%)">国家智能网联汽车创新中心</el-footer>-->
+    </el-container>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import {onBeforeMount, reactive, ref} from "vue";
+import axios from "axios";
+import {ElLoading, ElMessage, ElTable} from "element-plus";
+import moment from "moment/moment";
+
+
+const multipleTableRef = ref<InstanceType<typeof ElTable>>
+const showImageViewer = ref(false);
+const imageUrl = ref([])
+const resetTableData = ref([]);
+
+const queryLine = reactive({
+  dataName: '',
+  equipmentName: '',
+  equipmentTypeName: '',
+  equipmentType: '',
+})
+const equipmentTypes =ref(["引导服务机器人"])
+
+const onSubmit = () => {
+  page()
+}
+
+const uploadMap = async (row) => {
+  let id = row.id
+  console.log("id", id)
+  try {
+    // 开启loading
+    let loadingInstance = ElLoading.service({fullscreen: false, target:'.el-table', text: '地图上传中...'})
+    const response = await axios.get('/local/map/update/pji/upload?id=' + id)
+    console.log(response.data)
+    if (!response.data.status) {
+      loadingInstance.close()
+      if (response.data.code != "") {
+        ElMessage.error(response.data.message)
+      }
+      return
+    }
+    loadingInstance.close()
+    ElMessage.success(response.data.message)
+    await page()
+  } catch (error) {
+    console.log("Error uploading the pji map:", error)
+  }
+}
+
+// 根据条件筛选结果
+const queryRecordByCondition = async () => {
+  let response = await axios.post('/local/map/query/update/record?page=' + currentPage.value + '&pageSize=' + pageSize.value, {
+    "device_name": queryLine.equipmentName,
+    "device_type": queryLine.equipmentType
+  })
+
+  if (!response.data.status){ // 不存在
+    ElMessage.error("仿真测试记录查询失败!");
+    return
+  }
+  // 解析结果
+  tableData.value = JSON.parse(response.data.data)
+  total.value = response.data.total
+}
+
+let total = ref(0)
+let tableData = ref([]);
+onBeforeMount(() => {
+  page()
+})
+
+const formatDate =  (row) => {
+  return moment(+row.update_time).format("YYYY-MM-DD HH:mm:ss")
+}
+
+const formatStatus =  (row) => {
+  if (row.update_flag == 0) return "未上传";
+  if (row.update_flag == 1) return "已上传,未部署";
+  if (row.update_flag == 2) return "已部署";
+  return
+}
+
+const viewImg = (event) => {
+  if (event.target.nodeName == "IMG") {
+    let src = event.target.currentSrc;
+    imageUrl.value = [src]
+    showImageViewer.value = true
+  }
+}
+
+const closeImgViewer = () => {
+  showImageViewer.value = false;
+}
+
+async function page() {
+  // axios.get('http://1.202.169.139:8081/open/scene/list?equipmentType=YI_DAO_JI_QI_REN&page=' + currentPage.value + '&size=' + pageSize.value,
+  //     {
+  //       headers: {
+  //         "Authorization": "4773hd92ysj54paflw2jem3onyhywxt2"
+  //       }
+  //     }
+  // ).then(function (response) {
+  //   tableData.value = response.data.data.content
+  //   total.value = response.data.data.totalElements
+  //   // total.value = tableData.value.length
+  //   // console.log(tableData);
+  //   // console.log(response.data.data);
+  // }).catch(function (error) {
+  //   console.log(error);
+  // });
+
+  // 查询地图更新记录
+  let response = await axios.post('/local/map/query/update/record?page=' + currentPage.value + '&pageSize=' + pageSize.value)
+
+  if (!response.data.status){ // 不存在
+    ElMessage.error("地图更新记录查询失败!");
+    return
+  }
+  // 解析结果
+  tableData.value = JSON.parse(response.data.data)
+  console.log("tableData.value", tableData.value)
+  resetTableData.value = tableData.value // for reset purpose
+  total.value = response.data.total
+}
+
+const currentPage = ref(1)
+const pageSize = ref(5)
+const small = ref(false)
+const disabled = ref(false)
+
+const handleSizeChange = (val: number) => {
+  pageSize.value = val
+  page()
+  if (tableData.value.length == 0) {
+    page()
+  }
+}
+const handleCurrentChange = (val: number) => {
+  currentPage.value = val
+  page()
+}
+
+
+</script>
+<style scoped>
+
+.demo-pagination-block + .demo-pagination-block {
+  margin-top: 10px;
+}
+
+.demo-pagination-block .demonstration {
+  margin-bottom: 16px;
+}
+
+
+.demo-form-inline .el-input {
+  --el-input-width: 200px;
+}
+
+.demo-form-inline .el-select {
+  --el-select-width: 400px;
+}
+
+.demo-image .block {
+  padding: 30px 0;
+  text-align: center;
+  border-right: solid 1px var(--el-border-color);
+  display: inline-block;
+  width: 20%;
+  box-sizing: border-box;
+  vertical-align: top;
+}
+
+.demo-image .block:last-child {
+  border-right: none;
+}
+
+.demo-image .demonstration {
+  display: block;
+  color: var(--el-text-color-secondary);
+  font-size: 14px;
+  margin-bottom: 20px;
+}
+
+.topbar {
+  margin-bottom: 15px;
+}
+
+/* 假设 .el-pager__item 是分页按钮的类名,这可能需要你根据实际的 Element UI 版本和源码进行调整 */
+.el-pager__item {
+  background-color: rgba(255, 0, 0, 50%) !important; /* 修改背景色 */
+  color: #fff; /* 可能需要修改文本颜色以在红色背景上可见 */
+  border-color: transparent; /* 如果需要,修改边框颜色 */
+}
+
+/* 修改当前选中页码的按钮样式 */
+.el-pager__item.is-active {
+  background-color: rgba(255, 0, 0, 70%) !important; /* 选中时的背景色 */
+  color: #fff; /* 选中时的文本颜色 */
+}
+</style>

+ 571 - 0
App-linux-x64/resources/app/typescript

@@ -0,0 +1,571 @@
+Script started on 2024-10-11 11:16:15+08:00 [<not executed on terminal>]
+[INFO] [1728616581.024300, 39.385000]: Goal received, starting rosbag recording...
+[ INFO] [1728616581.216027626]: Subscribing to /amcl_pose
+[ INFO] [1728616581.217067452]: Subscribing to /imu
+[ INFO] [1728616581.218209602]: Subscribing to /obstacle_detection
+[ INFO] [1728616581.218993581]: Subscribing to /odom
+[ INFO] [1728616581.220243837]: Subscribing to /sys_info
+[ INFO] [1728616581.221516481]: Recording to '/home/cicv/work/pji_desktop/simulation/data/record_bag/test-1.bag'.
+[INFO] [1728616610.172074, 66.285000]: Stopping rosbag recording...
+[INFO] [1728616611.975764, 67.893000]: Goal received, starting rosbag recording...
+[ INFO] [1728616612.178647017]: Subscribing to /amcl_pose
+[ INFO] [1728616612.180807530]: Subscribing to /imu
+[ INFO] [1728616612.182218238]: Subscribing to /obstacle_detection
+[ INFO] [1728616612.184152883]: Subscribing to /odom
+[ INFO] [1728616612.185843282]: Subscribing to /sys_info
+[ INFO] [1728616612.186892322]: Recording to '/home/cicv/work/pji_desktop/simulation/data/record_bag/test-2.bag'.
+[INFO] [1728616637.283010, 90.592000]: Stopping rosbag recording...
+[INFO] [1728616637.417460, 90.685000]: Stopping rosbag recording...
+[INFO] [1728616637.582412, 90.827000]: All recordings completed. Exiting...
+[INFO] [1728616639.314068, 92.401000]: Recording limit reached. Exiting...
+[INFO] [1728616639.316357, 92.402000]: Shutting down ROS node and killing the process.
+[INFO] [1728616639.796093, 92.828000]: Shutting down ROS node and killing the process.
+
+Script done on 2024-10-11 11:17:21+08:00 [COMMAND_EXIT_CODE="0"]
+urrent.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.89975
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.89975
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.89975
+current.linear.x: 0.5
+current.linear.y: -0.00124739
+Checking if goal is reached...
+Distance to goal: 5.74122
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.74122
+current.linear.x: 0.3
+current.linear.y: 6.02594e-05
+Checking if goal is reached...
+Distance to goal: 5.55078
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.55078
+current.linear.x: 0.3
+current.linear.y: 0.000142122
+Checking if goal is reached...
+Distance to goal: 5.32603
+current.linear.x: 0.3
+current.linear.y: 0.000170943
+Checking if goal is reached...
+Distance to goal: 5.32603
+current.linear.x: 0.3
+current.linear.y: 0.000240446
+Checking if goal is reached...
+Distance to goal: 5.10807
+current.linear.x: 0.4
+current.linear.y: 0.00174979
+Checking if goal is reached...
+Distance to goal: 4.89888
+current.linear.x: 0.4
+current.linear.y: 0.000629316
+Checking if goal is reached...
+Distance to goal: 4.69718
+current.linear.x: 0.5
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 4.41036
+current.linear.x: 0.7
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 4.15644
+current.linear.x: 0.5
+current.linear.y: -0.00103255
+Checking if goal is reached...
+Distance to goal: 3.91931
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 3.70009
+current.linear.x: 0.5
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 3.45375
+current.linear.x: 0.444444
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 3.21538
+current.linear.x: 0.444444
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.95304
+current.linear.x: 0.5
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.95304
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.73925
+current.linear.x: 0.3
+current.linear.y: 0.000165471
+Checking if goal is reached...
+Distance to goal: 2.51282
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.51282
+current.linear.x: 0.2
+current.linear.y: 0.000188372
+Checking if goal is reached...
+Distance to goal: 2.51282
+current.linear.x: 0.2
+current.linear.y: 7.04614e-05
+Checking if goal is reached...
+Distance to goal: 2.27595
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.27595
+current.linear.x: 0.2
+current.linear.y: 0.00091407
+Checking if goal is reached...
+Distance to goal: 2.05103
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.05103
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.84199
+current.linear.x: 0.177778
+current.linear.y: 0.000249047
+Checking if goal is reached...
+Distance to goal: 1.84199
+current.linear.x: 0.2
+current.linear.y: 0.000267907
+Checking if goal is reached...
+Distance to goal: 1.63738
+current.linear.x: 0.233333
+current.linear.y: 0.000129806
+Checking if goal is reached...
+Distance to goal: 1.63738
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.63738
+current.linear.x: 0.133333
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.43501
+current.linear.x: 0.177778
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.43501
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.43501
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.26372
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.26372
+current.linear.x: 0.2
+current.linear.y: 0.000256419
+Checking if goal is reached...
+Distance to goal: 1.10692
+current.linear.x: 0.2
+current.linear.y: 0.00174867
+Checking if goal is reached...
+Distance to goal: 1.10692
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.952484
+current.linear.x: 0.177778
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.952484
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.952484
+current.linear.x: 0.2
+current.linear.y: 0.00109587
+Checking if goal is reached...
+Distance to goal: 0.751948
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.751948
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.491513
+current.linear.x: 0.266667
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.491513
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.491513
+current.linear.x: 0.166667
+current.linear.y: 0.00138019
+Checking if goal is reached...
+Distance to goal: 0.224612
+current.linear.x: 0.133333
+current.linear.y: 0.000446061
+Checking if goal is reached...
+Distance to goal: 0.224612
+current.linear.x: 0.1
+current.linear.y: 0.00181511
+Checking if goal is reached...
+Distance to goal: 0.224612
+current.linear.x: 0
+current.linear.y: 0
+[ INFO] [1728616610.859334173, 66.893000000]: Goal 1 reached, proceeding to the next goal after 2 seconds...
+End Point set to X: -8.2228 Y: 0.3353
+Distance to goal: 5.75793
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.83065
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.83101
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.83101
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.82787
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.82364
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.82364
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.82926
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.82926
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.82926
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.75513
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.75513
+current.linear.x: 0.266667
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.6322
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.6322
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.42462
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.42462
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.20623
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 4.98486
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 4.78848
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 4.78848
+current.linear.x: 0.5
+current.linear.y: -0.00101535
+Checking if goal is reached...
+Distance to goal: 4.57387
+current.linear.x: 0.7
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 4.02386
+current.linear.x: 0.5
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 3.74852
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 3.74852
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 3.54375
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 3.34906
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 3.14366
+current.linear.x: 0.5
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.88886
+current.linear.x: 0.5
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.61388
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.40833
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.21847
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.02557
+current.linear.x: 0.5
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.79434
+current.linear.x: 0.7
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.5912
+current.linear.x: 0.5
+current.linear.y: -0.000908178
+Checking if goal is reached...
+Distance to goal: 1.32263
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.32263
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.07991
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.850306
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.850306
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.850306
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.62768
+current.linear.x: 0.2
+current.linear.y: 0.000587972
+Checking if goal is reached...
+Distance to goal: 0.62768
+current.linear.x: 0.2
+current.linear.y: 0.000486369
+Checking if goal is reached...
+Distance to goal: 0.62768
+current.linear.x: 0.2
+current.linear.y: 0.000320695
+Checking if goal is reached...
+Distance to goal: 0.346434
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.346434
+current.linear.x: 0.133333
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.346434
+current.linear.x: 0.0888889
+current.linear.y: 0.000493234
+Checking if goal is reached...
+Distance to goal: 0.346434
+current.linear.x: 0.0888889
+current.linear.y: 0.000365244
+Checking if goal is reached...
+Distance to goal: 0.0807508
+current.linear.x: 0
+current.linear.y: 0
+[ INFO] [1728616638.201756737, 91.400000000]: Goal 2 reached, proceeding to the next goal after 2 seconds...
+End Point set to X: -2.3728 Y: 0.2853
+Distance to goal: 5.80416
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.81762
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.81928
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.82484
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.82484
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.81799
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.81799
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.81799
+current.linear.x: 0
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.81799
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.81799
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.68547
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.68547
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.49241
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.49241
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.29178
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 5.0694
+current.linear.x: 0.4
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 4.87633
+current.linear.x: 0.5
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 4.87633
+current.linear.x: 0.6
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 4.41021
+current.linear.x: 0.7
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 4.19387
+current.linear.x: 0.5
+current.linear.y: 0.000859475
+Checking if goal is reached...
+Distance to goal: 3.91224
+current.linear.x: 0.5
+current.linear.y: 0.00314842
+Checking if goal is reached...
+Distance to goal: 3.65036
+current.linear.x: 0.5
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 3.37485
+current.linear.x: 0.6
+current.linear.y: 2.18187e-05
+Checking if goal is reached...
+Distance to goal: 3.15059
+current.linear.x: 0.5
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.9389
+current.linear.x: 0.5
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.68632
+current.linear.x: 0.5
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.45377
+current.linear.x: 0.5
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 2.21385
+current.linear.x: 0.6
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.94299
+current.linear.x: 0.7
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.72702
+current.linear.x: 0.5
+current.linear.y: -0.000687903
+Checking if goal is reached...
+Distance to goal: 1.55254
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.35505
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.17794
+current.linear.x: 0.3
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 1.17794
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.958262
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.958262
+current.linear.x: 0.2
+current.linear.y: 0
+Checking if goal is reached...
+Distance to goal: 0.958262
+current.lin

+ 27 - 101
electron/main.js

@@ -6,10 +6,13 @@ import fs from 'fs';
 import axios from 'axios';
 import ProcessManager from './processManager.js'
 
+
 const processManager = new ProcessManager(); // 创建进程管理器实例
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = path.dirname(__filename);
 
+const PJI_SCRIPT_PATH_PREFIX = "/home/cicv/work/pji_desktop/"
+// console.log("PJI_SCRIPT_PATH_PREFIX", PJI_SCRIPT_PATH_PREFIX)
 
 function createWindow() {
     const win = new BrowserWindow({
@@ -44,6 +47,9 @@ app.on('activate', () => {
     }
 });
 
+// // 禁用GPU加速
+// app.disableHardwareAcceleration()
+
 
 // ------------- 进程通信 -------------
 ipcMain.handle('dialog:open', async (event, options = {}) => {
@@ -206,13 +212,12 @@ async function uploadFileWithRetry(filePath, uploadUrl, maxRetries = 3) {
 
 // 导入算法镜像
 ipcMain.on('docker-import', (event, filePath, tag) => {
-    const command = 'bash /home/cicv/work/pji_desktop/docker_import/run_docker_import.sh ' + filePath + ' pji_nav ' + tag
+    const command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + 'docker_import/run_docker_import.sh ' + filePath + ' pji_nav ' + tag
     console.log('Docker import command:', command);
     exec(command, (error, stdout, stderr) => {
         if (error) {
             console.error(`exec error: ${error}`);
             event.reply('docker-import-response', { success: false, message: error.message });
-            return;
         } else {
             console.log(`stdout: ${stdout}`);
             console.error(`stderr: ${stderr}`);
@@ -223,7 +228,7 @@ ipcMain.on('docker-import', (event, filePath, tag) => {
 
 // 更新地图
 ipcMain.on('update-map', (event, {container_name, file_path}) => {
-    const command = 'bash /home/cicv/work/pji_desktop/map_update/run_map_update.sh ' + container_name + ' ' + file_path;
+    const command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + '/map_update/run_map_update.sh ' + container_name + ' ' + file_path;
     console.log('command:', command);
 
     processManager.startProcess('update-map', command, (error, stdout, stderr) => {
@@ -244,7 +249,7 @@ ipcMain.on('update-map', (event, {container_name, file_path}) => {
 
 // 生成world
 ipcMain.on('generate-world', (event, {rosbag_path}) => {
-    const command = 'bash /home/cicv/work/pji_desktop/simulation/generate_world.sh ' + rosbag_path
+    const command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + 'simulation/generate_world.sh ' + rosbag_path
     console.log('World generation command:', command);
     exec(command, (error, stdout, stderr) => {
         if (error) {
@@ -262,7 +267,7 @@ ipcMain.on('generate-world', (event, {rosbag_path}) => {
 ipcMain.on('start-container', (event, {zip_file_path, image_name, container_name}) => {
 
     // 使用 spawn 启动脚本
-    const serviceProcess = spawn('bash', ["/home/cicv/work/pji_desktop/simulation/data_preparation.sh", zip_file_path], { detached: true });
+    const serviceProcess = spawn('bash', [PJI_SCRIPT_PATH_PREFIX + "simulation/data_preparation.sh", zip_file_path], { detached: true });
 
     // 设置为后台进程
     serviceProcess.unref();
@@ -287,7 +292,7 @@ ipcMain.on('start-container', (event, {zip_file_path, image_name, container_name
     });
 
     function startSecondScript() {
-        let command = "bash /home/cicv/work/pji_desktop/simulation/start_container.sh " + image_name + " " + container_name
+        let command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + "/simulation/start_container.sh " + image_name + " " + container_name
         // 启动第二个脚本
         const script2 = exec(command, (error, stdout, stderr) => {
             if (error) {
@@ -309,28 +314,13 @@ ipcMain.on('start-container', (event, {zip_file_path, image_name, container_name
 // 执行仿真
 ipcMain.on('run-simulation', (event, {random_flag, count, obstacle_flag, default_start_flag, default_end_flag, start_point, end_point}) => {
 
-    const command = 'bash /home/cicv/work/pji_desktop/simulation/run_simulation.sh ' + random_flag + ' ' + count + ' ' + obstacle_flag + ' ' +
+    const command = 'bash ' + PJI_SCRIPT_PATH_PREFIX + 'simulation/run_simulation.sh ' + random_flag + ' ' + count + ' ' + obstacle_flag + ' ' +
         default_start_flag + ' ' + default_end_flag + ' ' + start_point + ' ' + end_point;
     console.log('command:', command);
-    // exec(command, (error, stdout, stderr) => {
-    //     if (error) {
-    //         console.error(`exec error: ${error}`);
-    //         event.reply('run-simulation-response', { success: false, message: error.message });
-    //     } else {
-    //         console.log(`stdout: ${stdout}`);
-    //         console.error(`stderr: ${stderr}`);
-    //         if (stdout.toString().includes('Evaluation finished')) { // 判断结束标志
-    //             event.reply('run-simulation-response', { success: true, message: 'Run simulation successfully' });
-    //         } else {
-    //             event.reply('run-simulation-response', { success: false, message: 'Error running simulation' });
-    //         }
-    //     }
-    // });
-
     processManager.startProcess('run-simulation', command, (error, stdout, stderr) => {
         if (error) {
             console.error(`exec error: ${error}`);
-            event.reply('run-simulation-response', { success: false, message: error.message });
+            // event.reply('run-simulation-response', { success: false, message: error.message });
         } else {
             console.log(`stdout: ${stdout}`);
             console.error(`stderr: ${stderr}`);
@@ -343,17 +333,21 @@ ipcMain.on('run-simulation', (event, {random_flag, count, obstacle_flag, default
     });
 
     // 设置超时机制(
-    setTimeout(() => {
-        processManager.killProcess('run-simulation');
-        event.reply('run-simulation-response', { success: false, message: 'Script running timeout' });
-    }, 3*60*1000); // 3 分钟
+    const timeoutId = setTimeout(() => {
+        if (!event.sender.isDestroyed()) {
+            // 如果渲染进程仍然存在
+            processManager.killProcess('run-simulation');
+            event.reply('run-simulation-response', { success: false, message: 'Script running timeout' });
+        }
+        clearTimeout(timeoutId)
+    }, 10*60*1000); // 10 分钟
 });
 
 // 读取并上传算法评价结果
 ipcMain.handle('process-evaluation-files', async (event, {N, equipmentNo, sceneNo}) => {
     const result = []
-    const evalDir = "/home/cicv/work/pji_desktop/simulation/data/evaluation";
-    const bagDir = "/home/cicv/work/pji_desktop/simulation/data/record_bag";
+    const evalDir = PJI_SCRIPT_PATH_PREFIX + "simulation/data/evaluation";
+    const bagDir = PJI_SCRIPT_PATH_PREFIX + "simulation/data/record_bag";
     console.log('N', N)
     // 记录时间戳
     const timeStamp = Date.now()
@@ -445,10 +439,10 @@ ipcMain.handle('process-map-update-files', async (event, {equipmentNo, timeStamp
     timeStamp = Date.now()
     // 记录时间戳
     console.log("timeStamp", timeStamp);
-    const dataDir = "/home/cicv/work/pji_desktop/map_update/data";
-    const mapBufDir = "/home/cicv/work/pji_desktop/map_update/data/bag_folder/mapBuf";
-    const resultDir = "/home/cicv/work/pji_desktop/map_update/data/bag_folder/result";
-    const updateMapDir = "/home/cicv/work/pji_desktop/map_update/data/bag_folder/update_map";
+    const dataDir = PJI_SCRIPT_PATH_PREFIX + "map_update/data";
+    const mapBufDir = PJI_SCRIPT_PATH_PREFIX + "map_update/data/bag_folder/mapBuf";
+    const resultDir = PJI_SCRIPT_PATH_PREFIX + "map_update/data/bag_folder/result";
+    const updateMapDir = PJI_SCRIPT_PATH_PREFIX + "map_update/data/bag_folder/update_map";
 
     // 读取result*.json
     // 文件路径 - result*.json
@@ -552,72 +546,4 @@ ipcMain.handle('process-map-update-files', async (event, {equipmentNo, timeStamp
     } catch (error) {
         console.log("error", error);
     }
-
-
-
-    // // 遍历评价结果文件夹,读取并上传相关数据
-    // for (let i = 1; i <= N; i++) {
-    //     const subDir = path.join(evalDir, `${i}/result`);
-    //     console.log("subDir", subDir);
-    //     // 文件路径 - report.json
-    //     const jsonFilePath = path.join(subDir, "report.json");
-    //     console.log("jsonFilePath", jsonFilePath);
-    //     // 文件路径 - report.pdf
-    //     const pdfFilePath = path.join(subDir, "report.pdf");
-    //     console.log("pdfFilePath", pdfFilePath);
-    //     // 文件路径 - test-${i}.bag
-    //     const bagFilePath = path.join(bagDir, `test-${i}.bag`);
-    //     console.log("bagFilePath", bagFilePath);
-    //
-    //     const uploadPdfUrl = "http://127.0.0.1:8888/simulation/upload/pdf?equipmentNo=" + equipmentNo + "&sceneNo=" + sceneNo +
-    //         "&timeStamp=" + timeStamp + "&round=" + i;
-    //
-    //     const uploadBagUrl = "http://127.0.0.1:8888/simulation/upload/bag?equipmentNo=" + equipmentNo + "&sceneNo=" + sceneNo +
-    //         "&timeStamp=" + timeStamp + "&round=" + i;
-    //
-    //     try {
-    //         // 读取json文件
-    //         const jsonData = fs.readFileSync(jsonFilePath, "utf-8");
-    //         const parsedData = JSON.parse(jsonData);
-    //         console.log("parsedData", parsedData);
-    //         // 上传pdf文件
-    //         // 查找pdf文件是否存在
-    //         let pdfFileExists = fs.existsSync(pdfFilePath)
-    //         // pdf文件不存在
-    //         if (!pdfFileExists) break
-    //         // pdf文件存在
-    //         const uploadPdfResult = await uploadFileWithRetry(pdfFilePath, uploadPdfUrl);
-    //         console.log("uploadPdfResult", uploadPdfResult);
-    //         if (!uploadPdfResult.status) {
-    //             break
-    //         }
-    //
-    //         // 上传bag文件
-    //         // 查找bag文件是否存在
-    //         let bagFileExists = fs.existsSync(bagFilePath)
-    //         // bag文件不存在
-    //         if (!bagFileExists) break
-    //         // bag文件存在
-    //         const uploadBagResult = await uploadFile(bagFilePath, uploadBagUrl);
-    //         console.log("uploadBagResult", uploadBagResult);
-    //         if (!uploadBagResult.status) { break}
-    //         // 整合结果
-    //         result.push({
-    //             "round": i,
-    //             "jsonData": parsedData,
-    //             "uploadPdfKey": uploadPdfResult.details,
-    //             "uploadBagKey": uploadBagResult.details,
-    //             "timeStamp": timeStamp
-    //         })
-    //     } catch (error) {
-    //         console.log("error", error);
-    //         return { success: false, message: "Error uploading evaluation results", data: [] }
-    //     }
-    // }
-    // // 判断数据上传是否完整
-    // if (result.length === N) {
-    //     return { success: true, message: "Evaluation results uploaded successfully", data: result }
-    // } else {
-    //     return { success: false, message: "Error uploading evaluation results", data: [] }
-    // }
 })

+ 3 - 3
package.json

@@ -5,11 +5,11 @@
   "private": true,
   "type": "module",
   "scripts": {
-    "packager:windows": "electron-packager . /App --platform=win32 --arch=x64 --overwrite",
-    "packager:linux": "electron-packager ./App --platform=linux -arch=x64 --overwrite",
+    "packager:windows": "electron-packager ./ App --platform=win32 --arch=x64 --overwrite",
+    "packager:linux": "electron-packager ./ App --platform=linux -arch=x64 --overwrite",
     "dev:vue": "vite",
     "dev:electron": "electron .",
-    "build": "run-p type-check \"build-only {@}\" --",
+    "build": "run-p  \"build-only {@}\" --",
     "preview": "vite preview",
     "test:unit": "vitest",
     "build-only": "vite build",

+ 5 - 1
src/views/AboutView.vue

@@ -212,6 +212,9 @@ const deviceNo = route.query.device_no;
 const deviceName = route.query.device_name;
 const algoImageName = route.query.algo_image_name;
 const worldPath = route.query.world_path;
+
+const PJI_SCRIPT_PATH_PREFIX = import.meta.env.VITE_PJI_SCRIPT_PATH_PREFIX
+
 console.log("sceneId", sceneId)
 console.log("deviceType", deviceType)
 console.log("deviceId", deviceId)
@@ -401,6 +404,7 @@ const runSimulation = () => {
     if (!result.success) { // 脚本执行过程中发生错误
       console.error('Script execution failed.');
       proxy.$message.error("仿真测试发生错误!");
+      console.log()
     } else { // 脚本执行成功
       console.log('Script execution completed successfully.')
 
@@ -415,7 +419,7 @@ const runSimulation = () => {
       evalResult.value = response.data
       // 上传pgm文件并获取存储url
       // 执行脚本 - 上传文件
-      const filePath = "/home/cicv/work/pji_desktop/simulation/data/mapBuf/map.pgm"
+      const filePath = PJI_SCRIPT_PATH_PREFIX + "simulation/data/mapBuf/map.pgm"
       const pgmUploadUrl = "http://127.0.0.1:8888/simulation/upload/pgm?equipmentNo=" + deviceNo + "&sceneNo=" + sceneId
       window.electronAPI.uploadFile(filePath, pgmUploadUrl);
       // 监听脚本执行状态

+ 2 - 2
src/views/MapRescanView.vue

@@ -94,13 +94,13 @@
           <el-table-column prop="update_flag" label="地图续扫提醒" :formatter="formatStatus"/>
           <el-table-column prop="pre_pgm_url" label="原始pgm" width="400px">
             <template v-slot="scope">
-              <img :src="'http://oss-cn-beijing-gqzl-d01-a.ops.gqzl-cloud.com/pji-bucket1/' + scope.row.pre_png_url" @click="viewImg" alt="" style="width:300px; height: 100%; cursor: pointer">
+              <img :src="'http://pji-bucket1.oss.icvdc.com/pji-bucket1/' + scope.row.pre_png_url" @click="viewImg" alt="" style="width:300px; height: 100%; cursor: pointer">
             </template>
 
           </el-table-column>
           <el-table-column prop="current_pgm_url" label="当前pgm" width="400px">
             <template v-slot="scope">
-              <img :src="'http://oss-cn-beijing-gqzl-d01-a.ops.gqzl-cloud.com/pji-bucket1/' + scope.row.current_png_url" @click="viewImg" alt="" style="width:300px; height: 100%; cursor: pointer">
+              <img :src="'http://pji-bucket1.oss.icvdc.com/pji-bucket1/' + scope.row.current_png_url" @click="viewImg" alt="" style="width:300px; height: 100%; cursor: pointer">
             </template>
           </el-table-column>
 

+ 76 - 12
src/views/ReportView.vue

@@ -227,7 +227,7 @@
         <el-pagination
             v-model:current-page="currentPage"
             v-model:page-size="pageSize"
-            :page-sizes="[10]"
+            :page-sizes="[10, 20, 50]"
             :small="small"
             :disabled="disabled"
             :background="true"
@@ -276,6 +276,8 @@ const imageNames = ref([])
 const currentRow = ref()
 const worldUrl = ref("")
 
+const PJI_SCRIPT_PATH_PREFIX = import.meta.env.VITE_PJI_SCRIPT_PATH_PREFIX
+
 const goToMapUpdate = () => {
   router.push('/map_update')
 }
@@ -300,6 +302,8 @@ const setThreshold = async () =>{
   }
 }
 
+// console.log("path:", import.meta.env.VITE_PJI_SCRIPT_PATH_PREFIX)
+
 const getThreshold = async () => {
   try {
     const response =  await axios.get('/local/config/query/rescanReminderThreshold')
@@ -342,6 +346,7 @@ const goToTestRecord = () => {
 }
 
 const handleClose = (done: () => void) => {
+  algorithmForm.tag = ""
   done()
 }
 const handleWorldClose = (done: () => void) => {
@@ -401,7 +406,7 @@ const uploadSuccess = (response, file, fileList) => { // world 文件上传成
 
 const uploadStlFile = (callback) => {
   // stl文件路径
-  const filePath =  "/home/cicv/work/pji_desktop/simulation/catkin_map2gazebo/src/models/map/meshes/map.stl"
+  const filePath =  PJI_SCRIPT_PATH_PREFIX + "simulation/catkin_map2gazebo/src/models/map/meshes/map.stl"
   if(stlUploadUrl.value !== "") { // 上传路径不为空
     // 执行脚本 - 上传文件
     window.electronAPI.uploadFile(filePath, stlUploadUrl.value);
@@ -450,17 +455,26 @@ const mapRescanForm = reactive({
 })
 
 const algorithmImport = async () => {
-  algorithmDialogVisible.value = false
   try {
     const result = await window.electronAPI.openFileManager();
+    if (result == null) {
+      ElMessage.info("未选择文件")
+      return
+    }
     console.log('用户选择的文件路径为:', result, ',版本为:', algorithmForm.tag);
+    // 开启loading
+    let loadingInstance = ElLoading.service({fullscreen: false, target: '.el-dialog', text: '算法镜像导入中...'})
+
     // 导入镜像
     window.electronAPI.dockerImport(result, algorithmForm.tag)
     // 监听脚本执行状态
     window.electronAPI.onDockerImportResponse( (event, result) => {
+      loadingInstance.close()
+
+      algorithmDialogVisible.value = false
       if (result.success) { // 脚本执行成功
         console.log('Script execution completed successfully.')
-        ElMessage.success("镜像导入发生成功!");
+        ElMessage.success("镜像导入成功!");
       } else { // 脚本执行过程中发生错误
         console.error('Script execution failed.');
         ElMessage.error("镜像导入发生错误!");
@@ -497,6 +511,32 @@ const isPropertySame= (array, propertyName) => {
   return true; // 所有值都是相同的
 }
 
+const getTimeByDataName = (dataName) => {
+  const parts = dataName.split("-");
+
+  const year = parseInt(parts[0], 10);
+  const month = parseInt(parts[1], 10) - 1;
+  const day = parseInt(parts[2], 10);
+  const hours = parseInt(parts[3], 10) + 8;
+  const minutes = parseInt(parts[4], 10);
+  const seconds = parseInt(parts[5], 10);
+  return new Date(year, month, day, hours, minutes, seconds).getTime()
+}
+
+const getLatestTime = (reocrds) => {
+
+  let latestTime = getTimeByDataName(reocrds[0].dataName)
+  console.log("1", latestTime)
+  for (let i = 1; i < reocrds.length; i++) {
+    let time = getTimeByDataName(reocrds[i].dataName)
+    if ( time > latestTime) {
+      latestTime = time
+    }
+  }
+  return latestTime.toString()
+}
+
+
 // 地图更新
 const updateMap = async () => {
   // 检查选择的记录数量
@@ -540,12 +580,15 @@ const updateMap = async () => {
   const updateTime = Date.now()
   const updateFlag = 0
 
+  const recordTime = getLatestTime(multipleSelection.value)
+  console.log("recordTime", recordTime)
+
   console.log(device_id, device_no, device_name, device_type, device_no, updateTime, updateFlag)
   // 检查地图状态(是否可以更新)
-  response = await checkDeviceMapStatus(id, removePerfix(device_no, "pjibot-"), device_name, device_type)
+  response = await checkDeviceMapStatus(id, removePerfix(device_no, "pjibot-"), device_name, device_type, recordTime)
 
   if (!response.status) {
-    if (response.code === "1001") {
+    if (response.code != "") {
       ElMessage.error(response.msg)
     } else {
       ElMessage.error("地图状态查询失败")
@@ -565,12 +608,22 @@ const updateMap = async () => {
   const url = "http://localhost:8888/map/download/zip?id=" + ids.flat() + "&deviceNo=" + removePerfix(device_no, "pjibot-")  // 此处url不受配置的代理规则影响,应传递后端完整url地址
 
   const fileName = "data" + idList + ".zip"
-  const savePath = "/home/cicv/work/pji_desktop/tmp_download/map_zip/"
+  const savePath = PJI_SCRIPT_PATH_PREFIX + "tmp_download/map_zip/"
   const filePath = savePath + fileName
   const containerName = " map_update_20240923"
 
   // 开启loading
   let loadingInstance = ElLoading.service({fullscreen: false, target: '.el-dialog', text: '依赖数据拉取中...'})
+
+  // 保证朴津地图压缩包及map.pgm已上传到oss,从而正确拉取地图更新所需压缩包
+  response = await uploadOriginalMapById(id, removePerfix(device_no, "pjibot-"))
+  console.log("response", response)
+  if(!response.status) {
+    ElMessage.error("数据更新失败,请重试!")
+    return
+  }
+
+
   const result = await window.electronAPI.downloadFile(url, fileName, savePath, false, false, "get", {});
   if (!result.success) { // 下载失败
     console.error('File download failed:', result.error);
@@ -614,7 +667,7 @@ const updateMap = async () => {
           let map_update_data = {
             "map_id": data["jsonData"]["origin_pgm_id"],
             "device_id": device_id,
-            "device_sn": device_no,
+            "device_sn": removePerfix(device_no, "pjibot-"),
             "device_name": device_name,
             "device_type": device_type,
             "pre_pgm_url": data["prePgmUrl"],
@@ -671,10 +724,21 @@ const updateDeviceMap = async () => {
   }
 }
 
-const checkDeviceMapStatus = async (sceneId, deviceNo, deviceName, deviceType) => {
+const uploadOriginalMapById = async (sceneId, deviceNo) => {
+  try {
+    const response =  await axios.get('/local/map/upload/originalMap/oneRecord?sceneId=' + sceneId + '&deviceNo=' + deviceNo)
+    console.log(response.data)
+
+    return response.data
+  } catch (error) {
+    console.log("Error uploading the original map:", error)
+  }
+}
+
+const checkDeviceMapStatus = async (sceneId, deviceNo, deviceName, deviceType, recordTime) => {
   try {
     const response =  await axios.get('/local/map/check/deviceMap/status?sceneId=' + sceneId + '&deviceNo='
-        + deviceNo + '&deviceType=' + deviceType + '&deviceName=' + deviceName)
+        + deviceNo + '&deviceType=' + deviceType + '&deviceName=' + deviceName + '&recordTime=' + recordTime)
     console.log(response.data)
 
     return response.data
@@ -701,7 +765,7 @@ const generateWorld = async (row) => {
   const sceneNo = id
   const url = "http://127.0.0.1:8888/map/download/mapbag?id=" + id
   const fileName = "map-" + id + ".bag"
-  const savePath = "/home/cicv/work/pji_desktop/tmp_download/map_bag"
+  const savePath = PJI_SCRIPT_PATH_PREFIX + "tmp_download/map_bag"
   console.log("id", id)
 
   // 拼接world文件上传url
@@ -798,7 +862,7 @@ const startSimulation = async (row) => {
   const id = row.id
   const url = "http://localhost:8888/simulation/download/zip?id=" + id // 此处url不受配置的代理规则影响,应传递后端完整url地址
   const fileName = "data-" + id + ".zip"
-  const savePath = "/home/cicv/work/pji_desktop/tmp_download/simulation_zip"
+  const savePath = PJI_SCRIPT_PATH_PREFIX + "tmp_download/simulation_zip"
 
   console.log("Starting download files...")
   // 开启loading

+ 3 - 4
src/views/UpdateStatView.vue

@@ -71,13 +71,13 @@
           <el-table-column prop="update_flag" label="地图更新状态" :formatter="formatStatus"/>
           <el-table-column prop="pre_pgm_url" label="原始pgm" width="400px">
             <template v-slot="scope">
-              <img :src="'http://oss-cn-beijing-gqzl-d01-a.ops.gqzl-cloud.com/pji-bucket1/' + scope.row.pre_png_url" @click="viewImg" alt="" style="width:300px; height: 100%; cursor: pointer">
+              <img :src="'http://pji-bucket1.oss.icvdc.com/pji-bucket1/' + scope.row.pre_png_url" @click="viewImg" alt="" style="width:300px; height: 100%; cursor: pointer">
             </template>
 
           </el-table-column>
           <el-table-column prop="current_pgm_url" label="当前pgm" width="400px">
             <template v-slot="scope">
-              <img :src="'http://oss-cn-beijing-gqzl-d01-a.ops.gqzl-cloud.com/pji-bucket1/' + scope.row.current_png_url" @click="viewImg" alt="" style="width:300px; height: 100%; cursor: pointer">
+              <img :src="'http://pji-bucket1.oss.icvdc.com/pji-bucket1/' + scope.row.current_png_url" @click="viewImg" alt="" style="width:300px; height: 100%; cursor: pointer">
             </template>
           </el-table-column>
 
@@ -114,7 +114,6 @@ import {onBeforeMount, reactive, ref} from "vue";
 import axios from "axios";
 import {ElLoading, ElMessage, ElTable} from "element-plus";
 import moment from "moment/moment";
-import {fa} from "element-plus/es/locale";
 
 
 const multipleTableRef = ref<InstanceType<typeof ElTable>>
@@ -144,7 +143,7 @@ const uploadMap = async (row) => {
     console.log(response.data)
     if (!response.data.status) {
       loadingInstance.close()
-      if (response.data.code === "1002") {
+      if (response.data.code != "") {
         ElMessage.error(response.data.message)
       }
       return

+ 2 - 2
vite.config.ts

@@ -12,8 +12,8 @@ export default defineConfig({
         rewrite: (path)  => path.replace(/^\/local/,''),
       },
       '/pji': {
-        // target:'http://36.110.106.156:11121', // 外网地址
-        target:'http://10.14.86.147:9081', // 内网地址
+        target:'http://36.110.106.156:11121', // 外网地址
+        // target:'http://10.14.86.147:9081', // 内网地址
         changeOrigin: true,
         rewrite: (path)  => path.replace(/^\/pji/,''),
       },