|
|
@@ -1,352 +1,747 @@
|
|
|
-<template>
|
|
|
- <div class="app-container">
|
|
|
- <div class="calib-dra-class-all">
|
|
|
- <OlMap ref="olmap" :width="olWidth + 'px'" :height="olHeight + 'px'" backgroundColor="#F5F5F5"
|
|
|
- :robotPoseData="laserPositionData" :poseCalibrationIndex="nowCalibId"></OlMap>
|
|
|
- <el-drawer :visible.sync="calibrationDra" :direction="'rtl'" :modal="false" :size="'100%'" :with-header="false"
|
|
|
- custom-class="calibration-dra-class" :wrapperClosable="false">
|
|
|
- <div class="calibration-dra-class_content">
|
|
|
- <p style="color: #404040;font-weight: bold;margin-bottom: 5px;">实时位姿<i class="el-icon-close"
|
|
|
- style="float: right;cursor: pointer;" @click="calibrationDra = false"></i></p>
|
|
|
- <span style="color: #FC523B;font-size: 13px;">(当前地图:{{ currentMap.name }})</span>
|
|
|
- <div class="current-pose">
|
|
|
- <div style="width: 100%;padding: 12px 0;text-align: left;"><span style="color: #797979;">激光定位</span></div>
|
|
|
- <el-row style="margin-bottom: 5px;">
|
|
|
- <el-col :span="6">
|
|
|
- <div class="grid-content dra-content-pose-left">x坐标</div>
|
|
|
- </el-col>
|
|
|
- <el-col :span="18">
|
|
|
- <div class="grid-content dra-content-pose-right">{{ laserPositionData.x }}</div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- <el-row style="margin-bottom: 5px;">
|
|
|
- <el-col :span="6">
|
|
|
- <div class="grid-content dra-content-pose-left">y坐标</div>
|
|
|
- </el-col>
|
|
|
- <el-col :span="18">
|
|
|
- <div class="grid-content dra-content-pose-right">{{ laserPositionData.y }}</div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- <el-row style="margin-bottom: 5px;">
|
|
|
- <el-col :span="6">
|
|
|
- <div class="grid-content dra-content-pose-left">航向角</div>
|
|
|
- </el-col>
|
|
|
- <el-col :span="18">
|
|
|
- <div class="grid-content dra-content-pose-right">{{ laserPositionData.angle }}</div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- <div style="width: 100%;padding: 12px 0;text-align: left;"><span style="color: #797979;">GNSS定位</span></div>
|
|
|
- <el-row style="margin-bottom: 5px;">
|
|
|
- <el-col :span="6">
|
|
|
- <div class="grid-content dra-content-pose-left">状态</div>
|
|
|
- </el-col>
|
|
|
- <el-col :span="18">
|
|
|
- <div class="grid-content dra-content-pose-right">{{ gnssPositionData.status }}</div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- <el-row style="margin-bottom: 5px;">
|
|
|
- <el-col :span="6">
|
|
|
- <div class="grid-content dra-content-pose-left">经度</div>
|
|
|
- </el-col>
|
|
|
- <el-col :span="18">
|
|
|
- <div class="grid-content dra-content-pose-right">{{ gnssPositionData.longitude }}</div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- <el-row style="margin-bottom: 5px;">
|
|
|
- <el-col :span="6">
|
|
|
- <div class="grid-content dra-content-pose-left">纬度</div>
|
|
|
- </el-col>
|
|
|
- <el-col :span="18">
|
|
|
- <div class="grid-content dra-content-pose-right">{{ gnssPositionData.latitude }}</div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- <el-row style="margin-bottom: 5px;">
|
|
|
- <el-col :span="6">
|
|
|
- <div class="grid-content dra-content-pose-left">方向角</div>
|
|
|
- </el-col>
|
|
|
- <el-col :span="18">
|
|
|
- <div class="grid-content dra-content-pose-right">{{ gnssPositionData.angle }}</div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- <el-button round style="width: 100%;margin-top: 10px;border: none;background-color: #F2F2F2;" icon="el-icon-circle-plus"
|
|
|
- @click="addCalibration()">添加标定点</el-button>
|
|
|
- </div>
|
|
|
- <div class="point-data">
|
|
|
- <div>
|
|
|
- <p style="color: #404040;font-weight: bold;">当前标定点</p>
|
|
|
- <el-card class="box-card" shadow="hover">
|
|
|
- <div class="add-calibration-list" v-if="calibrationList.length == 0">
|
|
|
- <el-row style="padding: 8px 0;">
|
|
|
- <el-col :span="3">
|
|
|
- <div class="add-calibration-list_grid-content">无</div>
|
|
|
- </el-col>
|
|
|
- <el-col :span="18">
|
|
|
- <div class="add-calibration-list_grid-content"></div>
|
|
|
- </el-col>
|
|
|
- <el-col :span="3">
|
|
|
- <div class="add-calibration-list_grid-content"></div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- </div>
|
|
|
- <div class="add-calibration-list" v-else>
|
|
|
- <el-row style="padding: 8px 0;" v-for="item in calibrationList" :key="item.id">
|
|
|
- <el-col :span="3">
|
|
|
- <div class="add-calibration-list_grid-content">{{ item.id }}</div>
|
|
|
- </el-col>
|
|
|
- <el-col :span="18">
|
|
|
- <div class="add-calibration-list_grid-content">{{ item.coordinate }}</div>
|
|
|
- </el-col>
|
|
|
- <el-col :span="3" style="cursor: pointer;">
|
|
|
- <div class="add-calibration-list_grid-content" @click="removeCalibration(item.id)"><i
|
|
|
- class="el-icon-delete"></i></div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- </div>
|
|
|
- </el-card>
|
|
|
- </div>
|
|
|
- <div class="calibration-key">
|
|
|
- <el-button style="width: 100%;margin-top: 10px;border: none;background-color: #F2F2F2;" icon="el-icon-success"
|
|
|
- @click="executeCalibration()">一键标定</el-button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </el-drawer>
|
|
|
- </div>
|
|
|
- <div class="fixed-right-center" @click="showInfoDra" v-if="!calibrationDra">
|
|
|
- <i class="el-icon-arrow-left"></i>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script>
|
|
|
-import OlMap from "@/components/OlMap";
|
|
|
-
|
|
|
-export default {
|
|
|
- name: "Calibration",
|
|
|
- components: {
|
|
|
- OlMap
|
|
|
- },
|
|
|
- data() {
|
|
|
- return {
|
|
|
- // 弹出层标题
|
|
|
- title: "",
|
|
|
- calibrationDra: true,
|
|
|
- calibrationList: [],
|
|
|
- // 激光定位数据
|
|
|
- laserPositionData: {
|
|
|
- x: 0,
|
|
|
- y: 0,
|
|
|
- angle: 0
|
|
|
- },
|
|
|
- // gnss定位数据
|
|
|
- gnssPositionData: {
|
|
|
- status: '0/0',
|
|
|
- longitude: 0,
|
|
|
- latitude: 0,
|
|
|
- angle: 0.000
|
|
|
- },
|
|
|
- // 当前地图
|
|
|
- currentMap: {
|
|
|
- id: 1,
|
|
|
- name: 'sh02'
|
|
|
- },
|
|
|
- olWidth: 0, // 用于存储宽度的变量
|
|
|
- olHeight: 0,
|
|
|
- nowCalibId: 0 // 当前标定点id
|
|
|
- };
|
|
|
- },
|
|
|
- created() {
|
|
|
-
|
|
|
- },
|
|
|
- mounted() {
|
|
|
- const mapId = this.$route.params.mapId;
|
|
|
- this.updateOlCss();
|
|
|
- window.addEventListener('resize', this.updateOlCss);
|
|
|
- },
|
|
|
- methods: {
|
|
|
- updateOlCss() {
|
|
|
- const element = this.$el.querySelector('.calib-dra-class-all');
|
|
|
- this.olWidth = element.offsetWidth;
|
|
|
- this.olHeight = element.offsetHeight;
|
|
|
- },
|
|
|
- // 展开右侧实时信息
|
|
|
- showInfoDra() {
|
|
|
- this.calibrationDra = true;
|
|
|
- },
|
|
|
- // 添加标定
|
|
|
- addCalibration() {
|
|
|
- // 需要获取无人车实时坐标, 现在先写死
|
|
|
- this.nowCalibId = this.calibrationList.length > 0 ? this.calibrationList[this.calibrationList.length - 1].id + 1 : 1;
|
|
|
- let coordinate = `${this.laserPositionData.x},${this.laserPositionData.y}`
|
|
|
- let data = { id: this.nowCalibId, coordinate: coordinate, angle: this.laserPositionData.angle }
|
|
|
- this.calibrationList.push(data)
|
|
|
- },
|
|
|
- // 移除标定
|
|
|
- removeCalibration(id) {
|
|
|
- if (this.calibrationList.length < 1) {
|
|
|
- return;
|
|
|
- }
|
|
|
- this.calibrationList = this.calibrationList.filter(item => item.id !== id);
|
|
|
- // 删除图标
|
|
|
- this.nowCalibId = - id;
|
|
|
- },
|
|
|
- // 一键标定
|
|
|
- executeCalibration() {
|
|
|
- if (this.calibrationList.length < 1) {
|
|
|
- this.$message('请添加标定点!');
|
|
|
- return;
|
|
|
- }
|
|
|
- this.$modal.msgSuccess("已添加标定点");
|
|
|
- }
|
|
|
- }
|
|
|
-};
|
|
|
-</script>
|
|
|
-
|
|
|
-<style scoped>
|
|
|
-.current-pose {
|
|
|
- margin-top: 10px;
|
|
|
-}
|
|
|
-
|
|
|
-.add-calibration-list_grid-content {
|
|
|
- text-align: center;
|
|
|
- font-size: 14px;
|
|
|
-}
|
|
|
-
|
|
|
-.point-data {
|
|
|
- margin-top: 50px;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
-}
|
|
|
-
|
|
|
-::v-deep .point-data .el-card__body,
|
|
|
-.el-main {
|
|
|
- padding: 5px;
|
|
|
-}
|
|
|
-
|
|
|
-.grid-content {
|
|
|
- text-align: center;
|
|
|
- color: #fff;
|
|
|
- padding: 6px 4px;
|
|
|
- font-weight: bold;
|
|
|
- font-size: 15px;
|
|
|
-}
|
|
|
-
|
|
|
-.dra-content-pose-left {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- background: linear-gradient(to right, #24A7E0, #27D8E0);
|
|
|
- border-radius: 5px 0 0 5px;
|
|
|
-}
|
|
|
-
|
|
|
-.dra-content-pose-right {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- background-color: #fff;
|
|
|
- color: #676767;
|
|
|
- padding: 7px 7px;
|
|
|
- border-radius: 0 5px 5px 0;
|
|
|
-}
|
|
|
-
|
|
|
-.dra-title {
|
|
|
- text-align: center;
|
|
|
-}
|
|
|
-
|
|
|
-.calibration-dra-class_content {
|
|
|
- padding: 0 15px 15px 15px;
|
|
|
-}
|
|
|
-
|
|
|
-.fixed-right-center {
|
|
|
- position: fixed;
|
|
|
- top: 50%;
|
|
|
- right: 0;
|
|
|
- transform: translateY(-50%);
|
|
|
- background-color: rgba(0, 0, 255, 0.6);
|
|
|
- padding: 4px;
|
|
|
- color: white;
|
|
|
- border-radius: 6px 0 0 6px;
|
|
|
- cursor: pointer;
|
|
|
-}
|
|
|
-
|
|
|
-.explore-unit {
|
|
|
- margin-left: 8px;
|
|
|
-}
|
|
|
-
|
|
|
-::v-deep .el-table__header-wrapper {
|
|
|
- border-radius: 10px 10px 0 0;
|
|
|
-}
|
|
|
-
|
|
|
-::v-deep .el-table .el-table__header-wrapper th,
|
|
|
-.el-table .el-table__fixed-header-wrapper th {
|
|
|
- background-color: #f6f6f6;
|
|
|
-}
|
|
|
-
|
|
|
-::v-deep .el-table--medium .el-table__cell {
|
|
|
- padding: 8px 0;
|
|
|
-}
|
|
|
-
|
|
|
-::v-deep .el-dialog__body {
|
|
|
- padding: 20px 20px 0 20px;
|
|
|
-}
|
|
|
-
|
|
|
-::v-deep .download-map .el-dialog__body {
|
|
|
- padding: 10px 20px 0 20px !important;
|
|
|
-}
|
|
|
-
|
|
|
-::v-deep .el-message-box {
|
|
|
- border-radius: 10px;
|
|
|
-}
|
|
|
-
|
|
|
-::v-deep .calib-dra-class-all .el-drawer__wrapper {
|
|
|
- width: 15%;
|
|
|
- left: 85%;
|
|
|
-}
|
|
|
-
|
|
|
-@media (max-width: 1599px) {
|
|
|
- ::v-deep .calib-dra-class-all .el-drawer__wrapper {
|
|
|
- width: 18%;
|
|
|
- left: 82%;
|
|
|
-}
|
|
|
-}
|
|
|
-
|
|
|
-@media (max-width: 1269px) {
|
|
|
- ::v-deep .calib-dra-class-all .el-drawer__wrapper {
|
|
|
- width: 21%;
|
|
|
- left: 79%;
|
|
|
-}
|
|
|
-}
|
|
|
-
|
|
|
-@media (max-width: 1000px) {
|
|
|
- ::v-deep .calib-dra-class-all .el-drawer__wrapper {
|
|
|
- width: 24%;
|
|
|
- left: 76%;
|
|
|
-}
|
|
|
-}
|
|
|
-
|
|
|
-.calib-dra-class-all {
|
|
|
- width: 100%;
|
|
|
- min-height: calc(100vh - 84px);
|
|
|
-}
|
|
|
-
|
|
|
-.app-container {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- padding: 10px;
|
|
|
- min-width: 1100px;
|
|
|
- overflow-x: auto;
|
|
|
-}
|
|
|
-</style>
|
|
|
-
|
|
|
-<style>
|
|
|
-.calibration-dra-class {
|
|
|
- background-color: #FAFAFA;
|
|
|
- box-shadow: none;
|
|
|
- border-radius: 5px 0 0 5px;
|
|
|
-}
|
|
|
-
|
|
|
-.calibration-dra-class .el-drawer__header {
|
|
|
- margin-bottom: 20px;
|
|
|
- color: #404040;
|
|
|
- padding: 15px 15px 0;
|
|
|
- font-weight: bold;
|
|
|
-}
|
|
|
+<template>
|
|
|
+ <div class="calibration-container">
|
|
|
+ <!-- 地图舞台容器 -->
|
|
|
+ <div class="map-stage" ref="mapStage">
|
|
|
+ <!-- 地图底层 -->
|
|
|
+ <div class="map-canvas-wrapper" ref="mapWrapper">
|
|
|
+ <OlMap
|
|
|
+ ref="olmap"
|
|
|
+ :width="olWidth + 'px'"
|
|
|
+ :height="olHeight + 'px'"
|
|
|
+ backgroundColor="#F5F5F5"
|
|
|
+ :robotPoseData="laserPositionData"
|
|
|
+ :poseCalibrationIndex="nowCalibId"
|
|
|
+ :showDefaultControls="false"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 地图工具条 -->
|
|
|
+ <MapToolbar
|
|
|
+ class="map-toolbar"
|
|
|
+ :canAddCalibration="canAddCalibration"
|
|
|
+ :hasRobotPosition="hasRobotPosition"
|
|
|
+ :isFullscreen="isFullscreen"
|
|
|
+ @zoom-in="handleZoomIn"
|
|
|
+ @zoom-out="handleZoomOut"
|
|
|
+ @center-to-robot="handleCenterToRobot"
|
|
|
+ @toggle-fullscreen="handleToggleFullscreen"
|
|
|
+ @add-calibration-point="addCalibration"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 右侧信息面板 (宽屏模式,浮层) -->
|
|
|
+ <RightPanel
|
|
|
+ v-if="!isMobileMode"
|
|
|
+ class="right-panel"
|
|
|
+ :class="{ 'panel-collapsed': isPanelCollapsed }"
|
|
|
+ :style="{ width: isPanelCollapsed ? '0px' : rightPanelWidth + 'px' }"
|
|
|
+ :laserPositionData="laserPositionData"
|
|
|
+ :gnssPositionData="gnssPositionData"
|
|
|
+ :calibrationList="calibrationList"
|
|
|
+ :currentMap="currentMap"
|
|
|
+ :panelWidth="rightPanelWidth"
|
|
|
+ @panel-toggle="handlePanelToggle"
|
|
|
+ @panel-resize="handlePanelResize"
|
|
|
+ @add-calibration="addCalibration"
|
|
|
+ @remove-calibration="removeCalibration"
|
|
|
+ @execute-calibration="executeCalibration"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 面板展开按钮 (面板收起时显示) -->
|
|
|
+ <div
|
|
|
+ class="panel-reopen-btn"
|
|
|
+ v-if="shouldShowExpandButton"
|
|
|
+ @click="expandPanel"
|
|
|
+ title="展开面板"
|
|
|
+ >
|
|
|
+ <i class="el-icon-arrow-left"></i>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 右上角信息按钮 (窄屏模式) -->
|
|
|
+ <div class="info-toggle-btn" v-if="isMobileMode" @click="showInfoDrawer">
|
|
|
+ <el-button type="primary" icon="el-icon-info" circle size="small" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 抽屉模式 (窄屏) -->
|
|
|
+ <el-drawer
|
|
|
+ :visible.sync="drawerVisible"
|
|
|
+ :direction="'rtl'"
|
|
|
+ :modal="false"
|
|
|
+ :size="'360px'"
|
|
|
+ :with-header="false"
|
|
|
+ custom-class="info-drawer"
|
|
|
+ :wrapperClosable="false"
|
|
|
+ v-if="isMobileMode"
|
|
|
+ >
|
|
|
+ <div class="drawer-content">
|
|
|
+ <div class="drawer-header">
|
|
|
+ <div class="drawer-title">
|
|
|
+ <h3>实时标定信息</h3>
|
|
|
+ <span class="map-name">(当前地图: {{ currentMap.name }})</span>
|
|
|
+ </div>
|
|
|
+ <el-button
|
|
|
+ @click="drawerVisible = false"
|
|
|
+ type="text"
|
|
|
+ size="mini"
|
|
|
+ icon="el-icon-close"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="drawer-body">
|
|
|
+ <RightPanel
|
|
|
+ :laserPositionData="laserPositionData"
|
|
|
+ :gnssPositionData="gnssPositionData"
|
|
|
+ :calibrationList="calibrationList"
|
|
|
+ :currentMap="currentMap"
|
|
|
+ :isDrawerMode="true"
|
|
|
+ @add-calibration="addCalibration"
|
|
|
+ @remove-calibration="removeCalibration"
|
|
|
+ @execute-calibration="executeCalibration"
|
|
|
+ class="drawer-panel"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-drawer>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import OlMap from "@/components/OlMap";
|
|
|
+import RightPanel from "./components/RightPanel.vue";
|
|
|
+import MapToolbar from "./components/MapToolbar.vue";
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: "Calibration",
|
|
|
+ components: {
|
|
|
+ OlMap,
|
|
|
+ RightPanel,
|
|
|
+ MapToolbar
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ // 弹出层标题
|
|
|
+ title: "",
|
|
|
+ calibrationList: [],
|
|
|
+ // 激光定位数据
|
|
|
+ laserPositionData: {
|
|
|
+ x: 123.45,
|
|
|
+ y: 678.90,
|
|
|
+ angle: 45.2
|
|
|
+ },
|
|
|
+ // gnss定位数据
|
|
|
+ gnssPositionData: {
|
|
|
+ status: '卫星锁定',
|
|
|
+ longitude: 121.473701,
|
|
|
+ latitude: 31.230416,
|
|
|
+ angle: 45.5
|
|
|
+ },
|
|
|
+ // 当前地图
|
|
|
+ currentMap: {
|
|
|
+ id: 1,
|
|
|
+ name: 'sh02'
|
|
|
+ },
|
|
|
+ olWidth: 0, // 用于存储宽度的变量
|
|
|
+ olHeight: 0,
|
|
|
+ nowCalibId: 0, // 当前标定点id
|
|
|
+
|
|
|
+ // 布局相关状态
|
|
|
+ rightPanelWidth: 360, // 右侧面板宽度
|
|
|
+ isPanelCollapsed: false, // 面板是否折叠
|
|
|
+ windowWidth: window.innerWidth, // 窗口宽度
|
|
|
+ drawerVisible: false, // 抽屉是否可见
|
|
|
+ isFullscreen: false, // 是否全屏状态
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ // 是否为移动模式(窄屏)
|
|
|
+ isMobileMode() {
|
|
|
+ return this.windowWidth < 1440;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 是否可以添加标定点
|
|
|
+ canAddCalibration() {
|
|
|
+ return this.laserPositionData.x !== 0 || this.laserPositionData.y !== 0;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 是否有机器人位置数据
|
|
|
+ hasRobotPosition() {
|
|
|
+ return this.laserPositionData.x !== 0 || this.laserPositionData.y !== 0;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 是否显示展开按钮
|
|
|
+ shouldShowExpandButton() {
|
|
|
+ return !this.isMobileMode && this.isPanelCollapsed;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ // 从 localStorage 恢复状态
|
|
|
+ const savedPanelWidth = localStorage.getItem('calibration-panel-width');
|
|
|
+ const savedPanelCollapsed = localStorage.getItem('calibration-panel-collapsed');
|
|
|
+
|
|
|
+ if (savedPanelWidth) {
|
|
|
+ this.rightPanelWidth = Math.max(320, Math.min(420, parseInt(savedPanelWidth)));
|
|
|
+ }
|
|
|
+ if (savedPanelCollapsed) {
|
|
|
+ this.isPanelCollapsed = savedPanelCollapsed === 'true';
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ const mapId = this.$route.params.mapId;
|
|
|
+ this.updateOlCss();
|
|
|
+
|
|
|
+ // 监听窗口大小变化
|
|
|
+ window.addEventListener('resize', this.handleWindowResize);
|
|
|
+
|
|
|
+ // 监听全屏状态变化
|
|
|
+ document.addEventListener('fullscreenchange', this.handleFullscreenChange);
|
|
|
+ document.addEventListener('webkitfullscreenchange', this.handleFullscreenChange);
|
|
|
+ document.addEventListener('mozfullscreenchange', this.handleFullscreenChange);
|
|
|
+ document.addEventListener('MSFullscreenChange', this.handleFullscreenChange);
|
|
|
+
|
|
|
+ // 初始设置面板宽度
|
|
|
+ this.updateRightPanelWidth();
|
|
|
+
|
|
|
+ // 页面初始化完成
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ window.removeEventListener('resize', this.handleWindowResize);
|
|
|
+ document.removeEventListener('fullscreenchange', this.handleFullscreenChange);
|
|
|
+ document.removeEventListener('webkitfullscreenchange', this.handleFullscreenChange);
|
|
|
+ document.removeEventListener('mozfullscreenchange', this.handleFullscreenChange);
|
|
|
+ document.removeEventListener('MSFullscreenChange', this.handleFullscreenChange);
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ updateOlCss() {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ const mapStage = this.$refs.mapStage;
|
|
|
+ if (mapStage) {
|
|
|
+ this.olWidth = mapStage.offsetWidth;
|
|
|
+ this.olHeight = mapStage.offsetHeight;
|
|
|
+
|
|
|
+ // 触发地图重绘
|
|
|
+ if (this.$refs.olmap && this.$refs.olmap.map) {
|
|
|
+ this.$refs.olmap.map.updateSize();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理窗口大小变化
|
|
|
+ handleWindowResize() {
|
|
|
+ this.windowWidth = window.innerWidth;
|
|
|
+ this.updateOlCss();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理全屏状态变化
|
|
|
+ handleFullscreenChange() {
|
|
|
+ this.isFullscreen = !!(
|
|
|
+ document.fullscreenElement ||
|
|
|
+ document.webkitFullscreenElement ||
|
|
|
+ document.mozFullScreenElement ||
|
|
|
+ document.msFullscreenElement
|
|
|
+ );
|
|
|
+
|
|
|
+ // 全屏状态变化后触发地图重绘
|
|
|
+ setTimeout(() => {
|
|
|
+ this.updateOlCss();
|
|
|
+ }, 100);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更新右侧面板宽度
|
|
|
+ updateRightPanelWidth() {
|
|
|
+ if (this.isMobileMode) {
|
|
|
+ this.rightPanelWidth = 0;
|
|
|
+ } else {
|
|
|
+ const savedWidth = localStorage.getItem('calibration-panel-width');
|
|
|
+ const savedCollapsed = localStorage.getItem('calibration-panel-collapsed');
|
|
|
+
|
|
|
+ if (savedCollapsed === 'true') {
|
|
|
+ this.isPanelCollapsed = true;
|
|
|
+ this.rightPanelWidth = 360; // 保持原宽度,但面板是折叠状态
|
|
|
+ } else if (savedWidth) {
|
|
|
+ this.rightPanelWidth = Math.max(320, Math.min(420, parseInt(savedWidth)));
|
|
|
+ this.isPanelCollapsed = false;
|
|
|
+ } else {
|
|
|
+ this.rightPanelWidth = 360;
|
|
|
+ this.isPanelCollapsed = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.updateOlCss();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理面板切换
|
|
|
+ handlePanelToggle(isCollapsed) {
|
|
|
+ this.isPanelCollapsed = isCollapsed;
|
|
|
+ localStorage.setItem('calibration-panel-collapsed', String(isCollapsed));
|
|
|
+ // 面板宽度不变,只是改变显示状态
|
|
|
+ this.updateOlCss();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 展开面板
|
|
|
+ expandPanel() {
|
|
|
+ this.isPanelCollapsed = false;
|
|
|
+ localStorage.setItem('calibration-panel-collapsed', 'false');
|
|
|
+ this.updateOlCss();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理面板调整大小
|
|
|
+ handlePanelResize(newWidth) {
|
|
|
+ this.rightPanelWidth = newWidth;
|
|
|
+ // 浮层模式下不需要重算地图尺寸
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示信息抽屉
|
|
|
+ showInfoDrawer() {
|
|
|
+ this.drawerVisible = true;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 地图API适配器
|
|
|
+ getMapInstance() {
|
|
|
+ return this.$refs.olmap && this.$refs.olmap.map ? this.$refs.olmap.map : null;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 地图工具条事件处理
|
|
|
+ handleZoomIn() {
|
|
|
+ try {
|
|
|
+ const map = this.getMapInstance();
|
|
|
+ if (map) {
|
|
|
+ // OpenLayers API
|
|
|
+ const view = map.getView();
|
|
|
+ const currentZoom = view.getZoom();
|
|
|
+ view.animate({
|
|
|
+ zoom: currentZoom + 1,
|
|
|
+ duration: 250
|
|
|
+ });
|
|
|
+ } else if (this.$refs.olmap && this.$refs.olmap.zoomIn) {
|
|
|
+ // 备用方法
|
|
|
+ this.$refs.olmap.zoomIn();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('地图放大失败:', error);
|
|
|
+ this.$message.warning('地图放大失败');
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ handleZoomOut() {
|
|
|
+ try {
|
|
|
+ const map = this.getMapInstance();
|
|
|
+ if (map) {
|
|
|
+ // OpenLayers API
|
|
|
+ const view = map.getView();
|
|
|
+ const currentZoom = view.getZoom();
|
|
|
+ view.animate({
|
|
|
+ zoom: Math.max(currentZoom - 1, 1),
|
|
|
+ duration: 250
|
|
|
+ });
|
|
|
+ } else if (this.$refs.olmap && this.$refs.olmap.zoomOut) {
|
|
|
+ // 备用方法
|
|
|
+ this.$refs.olmap.zoomOut();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('地图缩小失败:', error);
|
|
|
+ this.$message.warning('地图缩小失败');
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ handleCenterToRobot() {
|
|
|
+ try {
|
|
|
+ if (!this.hasRobotPosition) {
|
|
|
+ this.$message.warning('暂无机器人定位数据');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const map = this.getMapInstance();
|
|
|
+ if (map) {
|
|
|
+ // OpenLayers API
|
|
|
+ const view = map.getView();
|
|
|
+ view.animate({
|
|
|
+ center: [this.laserPositionData.x, this.laserPositionData.y],
|
|
|
+ zoom: Math.max(view.getZoom(), 15),
|
|
|
+ duration: 500
|
|
|
+ });
|
|
|
+ this.$message.success('已定位到机器人位置');
|
|
|
+ } else if (this.$refs.olmap && this.$refs.olmap.centerToRobot) {
|
|
|
+ // 备用方法
|
|
|
+ this.$refs.olmap.centerToRobot();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('定位机器人失败:', error);
|
|
|
+ this.$message.warning('定位机器人失败');
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ handleToggleFullscreen() {
|
|
|
+ try {
|
|
|
+ const mapStage = this.$refs.mapStage;
|
|
|
+ if (!mapStage) return;
|
|
|
+
|
|
|
+ if (!this.isFullscreen) {
|
|
|
+ // 进入全屏
|
|
|
+ if (mapStage.requestFullscreen) {
|
|
|
+ mapStage.requestFullscreen();
|
|
|
+ } else if (mapStage.webkitRequestFullscreen) {
|
|
|
+ mapStage.webkitRequestFullscreen();
|
|
|
+ } else if (mapStage.mozRequestFullScreen) {
|
|
|
+ mapStage.mozRequestFullScreen();
|
|
|
+ } else if (mapStage.msRequestFullscreen) {
|
|
|
+ mapStage.msRequestFullscreen();
|
|
|
+ }
|
|
|
+ this.isFullscreen = true;
|
|
|
+ } else {
|
|
|
+ // 退出全屏
|
|
|
+ if (document.exitFullscreen) {
|
|
|
+ document.exitFullscreen();
|
|
|
+ } else if (document.webkitExitFullscreen) {
|
|
|
+ document.webkitExitFullscreen();
|
|
|
+ } else if (document.mozCancelFullScreen) {
|
|
|
+ document.mozCancelFullScreen();
|
|
|
+ } else if (document.msExitFullscreen) {
|
|
|
+ document.msExitFullscreen();
|
|
|
+ }
|
|
|
+ this.isFullscreen = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 延迟触发地图重绘
|
|
|
+ setTimeout(() => {
|
|
|
+ this.updateOlCss();
|
|
|
+ }, 100);
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('全屏切换失败:', error);
|
|
|
+ this.$message.warning('全屏功能不可用');
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 添加标定
|
|
|
+ addCalibration() {
|
|
|
+ // 需要获取无人车实时坐标, 现在先写死
|
|
|
+ this.nowCalibId = this.calibrationList.length > 0 ? this.calibrationList[this.calibrationList.length - 1].id + 1 : 1;
|
|
|
+ let coordinate = `${this.laserPositionData.x},${this.laserPositionData.y}`
|
|
|
+ let data = { id: this.nowCalibId, coordinate: coordinate, angle: this.laserPositionData.angle }
|
|
|
+ this.calibrationList.push(data)
|
|
|
+ },
|
|
|
+
|
|
|
+ // 移除标定
|
|
|
+ removeCalibration(id) {
|
|
|
+ if (this.calibrationList.length < 1) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.calibrationList = this.calibrationList.filter(item => item.id !== id);
|
|
|
+ // 删除图标
|
|
|
+ this.nowCalibId = - id;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 一键标定
|
|
|
+ executeCalibration() {
|
|
|
+ if (this.calibrationList.length < 1) {
|
|
|
+ this.$message('请添加标定点!');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.$modal.msgSuccess("标定执行成功");
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ watch: {
|
|
|
+ // 监听窗口宽度变化,自动切换模式
|
|
|
+ windowWidth(newWidth) {
|
|
|
+ if (newWidth < 1440 && !this.isMobileMode) {
|
|
|
+ // 切换到移动模式时关闭抽屉
|
|
|
+ this.drawerVisible = false;
|
|
|
+ }
|
|
|
+ this.updateRightPanelWidth();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 监听抽屉开关状态,确保地图重绘
|
|
|
+ drawerVisible() {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.updateOlCss();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.calibration-container {
|
|
|
+ width: 100%;
|
|
|
+ height: calc(100vh - 84px);
|
|
|
+ min-height: 600px;
|
|
|
+ background: var(--color-bg-secondary);
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .map-stage {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: var(--color-bg-card);
|
|
|
+ border-radius: var(--radius-lg);
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: var(--shadow-card);
|
|
|
+
|
|
|
+ .map-canvas-wrapper {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ z-index: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-toolbar {
|
|
|
+ position: absolute;
|
|
|
+ left: 16px;
|
|
|
+ top: 16px;
|
|
|
+ z-index: 11;
|
|
|
+ pointer-events: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .right-panel {
|
|
|
+ position: absolute;
|
|
|
+ right: 16px;
|
|
|
+ top: 16px;
|
|
|
+ height: calc(100% - 32px);
|
|
|
+ z-index: 10;
|
|
|
+ border-radius: var(--radius-lg);
|
|
|
+ box-shadow: var(--shadow-xl);
|
|
|
+ background: var(--color-bg-card);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ pointer-events: auto;
|
|
|
+ transition: all var(--duration-200) var(--ease-out);
|
|
|
+
|
|
|
+ &.panel-collapsed {
|
|
|
+ width: 0 !important;
|
|
|
+ opacity: 0;
|
|
|
+ pointer-events: none;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .panel-reopen-btn {
|
|
|
+ position: absolute;
|
|
|
+ right: 0;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ z-index: 12;
|
|
|
+ width: 36px;
|
|
|
+ height: 72px;
|
|
|
+ background: var(--color-bg-card);
|
|
|
+ border: 1px solid var(--color-border-primary);
|
|
|
+ border-radius: 8px 0 0 8px;
|
|
|
+ box-shadow: var(--shadow-lg);
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: var(--color-text-secondary);
|
|
|
+ transition: all var(--duration-200) var(--ease-out);
|
|
|
+ pointer-events: auto;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: var(--color-primary);
|
|
|
+ color: var(--color-text-inverse);
|
|
|
+ border-color: var(--color-primary);
|
|
|
+ transform: translateY(-50%) translateX(-2px);
|
|
|
+ box-shadow: var(--shadow-xl);
|
|
|
+ }
|
|
|
+
|
|
|
+ i {
|
|
|
+ font-size: var(--font-size-lg);
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-toggle-btn {
|
|
|
+ position: absolute;
|
|
|
+ top: 16px;
|
|
|
+ right: 16px;
|
|
|
+ z-index: 10;
|
|
|
+ pointer-events: auto;
|
|
|
+
|
|
|
+ .el-button {
|
|
|
+ box-shadow: var(--shadow-lg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 抽屉样式 */
|
|
|
+.drawer-content {
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ background: var(--color-bg-card);
|
|
|
+
|
|
|
+ .drawer-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: var(--spacing-4);
|
|
|
+ border-bottom: 1px solid var(--color-border-secondary);
|
|
|
+ background: var(--color-bg-tertiary);
|
|
|
+
|
|
|
+ .drawer-title {
|
|
|
+ h3 {
|
|
|
+ margin: 0;
|
|
|
+ font-size: var(--font-size-lg);
|
|
|
+ font-weight: var(--font-weight-semibold);
|
|
|
+ color: var(--color-text-primary);
|
|
|
+ line-height: var(--line-height-tight);
|
|
|
+ }
|
|
|
+
|
|
|
+ .map-name {
|
|
|
+ font-size: var(--font-size-xs);
|
|
|
+ color: var(--color-danger);
|
|
|
+ margin-left: var(--spacing-2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .drawer-body {
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .drawer-panel {
|
|
|
+ height: 100%;
|
|
|
+
|
|
|
+ ::v-deep .panel-header {
|
|
|
+ display: none; /* 隐藏面板头部,使用抽屉头部 */
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .panel-content {
|
|
|
+ height: 100%;
|
|
|
+ padding: var(--spacing-4);
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .resize-handle {
|
|
|
+ display: none; /* 抽屉模式下隐藏调整手柄 */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式设计 */
|
|
|
+@media (max-width: 1439px) {
|
|
|
+ .calibration-container {
|
|
|
+ .map-stage {
|
|
|
+ .right-panel {
|
|
|
+ display: none; /* 窄屏时隐藏固定面板 */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .calibration-container {
|
|
|
+ height: calc(100vh - 60px);
|
|
|
+
|
|
|
+ .map-stage {
|
|
|
+ border-radius: 0;
|
|
|
+
|
|
|
+ .map-toolbar {
|
|
|
+ left: 12px;
|
|
|
+ top: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .panel-reopen-btn {
|
|
|
+ width: 32px;
|
|
|
+ height: 60px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-toggle-btn {
|
|
|
+ top: 12px;
|
|
|
+ right: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 动画效果 */
|
|
|
+@keyframes slideInRight {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateX(20px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateX(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes slideInUp {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(20px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.calibration-container {
|
|
|
+ animation: slideInUp 0.3s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+.info-toggle-btn {
|
|
|
+ animation: slideInRight 0.3s ease-out 0.2s both;
|
|
|
+}
|
|
|
+
|
|
|
+/* 暗色主题适配 */
|
|
|
+html.dark {
|
|
|
+ .calibration-container {
|
|
|
+ .map-stage {
|
|
|
+ background: var(--color-bg-tertiary);
|
|
|
+ box-shadow: var(--shadow-card);
|
|
|
+
|
|
|
+ .right-panel {
|
|
|
+ background: var(--color-bg-tertiary);
|
|
|
+ border-color: var(--color-border-tertiary);
|
|
|
+ box-shadow: var(--shadow-xl);
|
|
|
+ }
|
|
|
+
|
|
|
+ .panel-reopen-btn {
|
|
|
+ background: var(--color-bg-tertiary);
|
|
|
+ border-color: var(--color-border-tertiary);
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: var(--color-primary);
|
|
|
+ border-color: var(--color-primary);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .drawer-content {
|
|
|
+ background: var(--color-bg-tertiary);
|
|
|
+
|
|
|
+ .drawer-header {
|
|
|
+ background: var(--color-bg-quaternary);
|
|
|
+ border-bottom-color: var(--color-border-tertiary);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|
|
|
+<style>
|
|
|
+/* 全局抽屉样式 */
|
|
|
+.info-drawer {
|
|
|
+ background-color: var(--color-bg-card);
|
|
|
+ box-shadow: var(--shadow-xl);
|
|
|
+ border-radius: var(--radius-lg) 0 0 var(--radius-lg);
|
|
|
+}
|
|
|
+
|
|
|
+.info-drawer .el-drawer__body {
|
|
|
+ padding: 0;
|
|
|
+ height: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+/* 暗色主题抽屉样式 */
|
|
|
+html.dark .info-drawer {
|
|
|
+ background-color: var(--color-bg-tertiary);
|
|
|
+ box-shadow: var(--shadow-xl);
|
|
|
+}
|
|
|
</style>
|