فهرست منبع

新增坐标系标定界面优化

yawuga 8 ماه پیش
والد
کامیت
c3b60f785b

+ 7 - 0
src/components/OlMap/index.vue

@@ -82,6 +82,11 @@ export default {
       type: Number,
       default: 2
     },
+    // 是否显示默认控件(缩放按钮等)
+    showDefaultControls: {
+      type: Boolean,
+      default: true
+    },
     // 最小缩放等级
     minZoom: {
       type: Number,
@@ -384,6 +389,8 @@ export default {
         // interactions: InteractionDefaults().extend([new DragRotateAndZoom()]),
         // 关闭点击放大放小交互
         interactions: new InteractionDefaults({ doubleClickZoom: false }),
+        // 根据prop决定是否显示默认控件
+        controls: this.showDefaultControls ? undefined : [],
       });
       // 初始化位姿初始化所需的变量
       let startPoint = null; // 起点坐标

+ 4 - 4
src/views/devops/individuation/index.vue

@@ -6,7 +6,7 @@
         <div class="header-main">
           <h1 class="page-title">个性配置</h1>
           <p class="page-desc">配置主题颜色与登录资源,修改后即时生效</p>
-        </div>
+    </div>
       </div>
     </div>
 
@@ -119,7 +119,7 @@
                   </el-button>
                 </div>
               </div>
-            </el-form-item>
+          </el-form-item>
 
             <!-- 登录页 Logo -->
             <el-form-item label="登录页 Logo">
@@ -160,7 +160,7 @@
                   </el-button>
                 </div>
               </div>
-            </el-form-item>
+          </el-form-item>
 
             <!-- 登录页背景图 -->
             <el-form-item label="登录页背景图">
@@ -201,7 +201,7 @@
                   </el-button>
                 </div>
               </div>
-            </el-form-item>
+          </el-form-item>
         </el-form>
 
           <!-- 吸底操作条 -->

+ 746 - 351
src/views/map/maplist/calibration.vue

@@ -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>

+ 294 - 0
src/views/map/maplist/components/MapToolbar.vue

@@ -0,0 +1,294 @@
+<template>
+  <div class="map-toolbar">
+    <!-- 地图控制按钮组 -->
+    <el-tooltip content="放大" placement="right">
+      <el-button 
+        size="small" 
+        icon="el-icon-plus" 
+        circle
+        @click="zoomIn"
+        class="toolbar-btn"
+      />
+    </el-tooltip>
+    
+    <el-tooltip content="缩小" placement="right">
+      <el-button 
+        size="small" 
+        icon="el-icon-minus" 
+        circle
+        @click="zoomOut"
+        class="toolbar-btn"
+      />
+    </el-tooltip>
+    
+    <el-tooltip content="居中到机器人" placement="right">
+      <el-button 
+        size="small" 
+        icon="el-icon-location" 
+        circle
+        @click="centerToRobot"
+        :disabled="!hasRobotPosition"
+        class="toolbar-btn"
+      />
+    </el-tooltip>
+    
+    <el-tooltip :content="isFullscreen ? '退出全屏' : '全屏'" placement="right">
+      <el-button 
+        size="small" 
+        :icon="isFullscreen ? 'el-icon-aim' : 'el-icon-full-screen'"
+        circle
+        @click="toggleFullscreen"
+        class="toolbar-btn"
+      />
+    </el-tooltip>
+
+    <!-- 分割线 -->
+    <div class="toolbar-divider"></div>
+
+    <!-- 主要操作按钮 -->
+    <el-tooltip content="添加标定点" placement="right">
+      <el-button 
+        type="primary"
+        size="small" 
+        icon="el-icon-circle-plus"
+        circle
+        @click="addCalibrationPoint"
+        class="toolbar-btn primary-btn"
+        :disabled="!canAddCalibration"
+      />
+    </el-tooltip>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'MapToolbar',
+  props: {
+    canAddCalibration: {
+      type: Boolean,
+      default: true
+    },
+    hasRobotPosition: {
+      type: Boolean,
+      default: false
+    },
+    isFullscreen: {
+      type: Boolean,
+      default: false
+    },
+    showAdvanced: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      measureToolActive: false
+    }
+  },
+  methods: {
+    zoomIn() {
+      this.$emit('zoom-in')
+    },
+    
+    zoomOut() {
+      this.$emit('zoom-out')
+    },
+    
+    centerToRobot() {
+      if (!this.hasRobotPosition) {
+        return
+      }
+      this.$emit('center-to-robot')
+    },
+    
+    toggleFullscreen() {
+      this.$emit('toggle-fullscreen')
+    },
+    
+    addCalibrationPoint() {
+      this.$emit('add-calibration-point')
+    },
+    
+    resetView() {
+      this.$emit('reset-view')
+      this.showToast('视图已重置')
+    },
+    
+    toggleMeasureTool() {
+      this.measureToolActive = !this.measureToolActive
+      this.$emit('toggle-measure-tool', this.measureToolActive)
+      this.showToast(this.measureToolActive ? '测量工具已启用' : '测量工具已关闭')
+    },
+    
+    showToast(message) {
+      // 简单的提示信息,避免过多弹窗
+      console.log('地图操作:', message)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.map-toolbar {
+  /* 位置由父组件控制 */
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px; /* 固定间距确保一致性 */
+  background: var(--color-bg-card);
+  border-radius: var(--radius-lg);
+  box-shadow: var(--shadow-lg);
+  padding: 12px; /* 统一内边距 */
+  border: 1px solid var(--color-border-primary);
+  backdrop-filter: blur(8px);
+  pointer-events: auto;
+  width: 60px; /* 固定宽度确保对齐 */
+
+  .toolbar-btn {
+    width: 36px !important;
+    height: 36px !important;
+    min-width: 36px !important;
+    min-height: 36px !important;
+    border-radius: 50% !important;
+    border: 1px solid var(--color-border-secondary);
+    background: var(--color-bg-card);
+    color: var(--color-text-secondary);
+    transition: all var(--duration-200) var(--ease-out);
+    display: flex !important;
+    align-items: center !important;
+    justify-content: center !important;
+    padding: 0 !important;
+    margin: 0 !important;
+    flex-shrink: 0;
+
+    &:hover {
+      background: var(--color-bg-tertiary);
+      color: var(--color-text-primary);
+      border-color: var(--color-border-primary);
+      transform: translateY(-1px);
+      box-shadow: var(--shadow-md);
+    }
+
+    &:active {
+      transform: translateY(0);
+      box-shadow: var(--shadow-sm);
+    }
+
+    &.primary-btn {
+      background: var(--color-primary) !important;
+      color: var(--color-text-inverse) !important;
+      border-color: var(--color-primary) !important;
+
+      &:hover {
+        background: var(--color-primary-light) !important;
+        border-color: var(--color-primary-light) !important;
+      }
+
+      &:disabled {
+        background: var(--color-text-quaternary) !important;
+        border-color: var(--color-text-quaternary) !important;
+        color: var(--color-text-inverse) !important;
+        opacity: 0.6;
+        cursor: not-allowed;
+        transform: none;
+        box-shadow: none;
+      }
+    }
+
+    &.active {
+      background: var(--color-primary);
+      color: var(--color-text-inverse);
+      border-color: var(--color-primary);
+    }
+
+    &:disabled {
+      opacity: 0.5;
+      cursor: not-allowed;
+      transform: none;
+      
+      &:hover {
+        background: var(--color-bg-card);
+        color: var(--color-text-secondary);
+        border-color: var(--color-border-secondary);
+        transform: none;
+        box-shadow: none;
+      }
+    }
+
+    ::v-deep .el-icon {
+      font-size: var(--font-size-base) !important;
+      line-height: 1 !important;
+    }
+
+    ::v-deep i {
+      font-size: var(--font-size-base) !important;
+      line-height: 1 !important;
+    }
+  }
+
+  .toolbar-divider {
+    width: 24px;
+    height: 1px;
+    background: var(--color-border-secondary);
+    margin: 4px 0;
+    flex-shrink: 0;
+  }
+}
+
+// 暗色主题适配
+html.dark {
+  .map-toolbar {
+    background: rgba(31, 41, 55, 0.9);
+    border-color: var(--color-border-tertiary);
+
+    .toolbar-group {
+      .toolbar-btn {
+        background: var(--color-bg-tertiary);
+        border-color: var(--color-border-tertiary);
+
+        &:hover {
+          background: var(--color-bg-quaternary);
+          border-color: var(--color-border-secondary);
+        }
+      }
+    }
+
+    .toolbar-divider {
+      background: var(--color-border-tertiary);
+    }
+  }
+}
+
+// 响应式适配
+@media (max-width: 768px) {
+  .map-toolbar {
+    .toolbar-group {
+      .toolbar-btn {
+        width: 32px;
+        height: 32px;
+
+        ::v-deep .el-icon {
+          font-size: var(--font-size-sm);
+        }
+      }
+    }
+  }
+}
+
+// 动画效果
+@keyframes toolbarSlideIn {
+  from {
+    opacity: 0;
+    transform: translateX(-20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateX(0);
+  }
+}
+
+.map-toolbar {
+  animation: toolbarSlideIn 0.3s ease-out;
+}
+</style>

+ 571 - 0
src/views/map/maplist/components/RightPanel.vue

@@ -0,0 +1,571 @@
+<template>
+  <div class="right-panel">
+    <!-- 面板头部 -->
+    <div class="panel-header">
+      <div class="panel-title">
+        <h3>实时标定信息</h3>
+        <span class="map-name">(当前地图: {{ currentMap.name }})</span>
+      </div>
+      <el-button 
+        @click="toggleCollapse" 
+        type="text" 
+        size="mini" 
+        class="collapse-btn"
+        icon="el-icon-arrow-right"
+      />
+    </div>
+
+    <!-- 面板内容 -->
+    <div class="panel-content">
+      <!-- 实时位姿卡片 -->
+      <div class="info-card">
+        <div class="card-header">
+          <h4 class="card-title">实时位姿</h4>
+        </div>
+        <div class="card-content">
+          <!-- 激光定位 -->
+          <div class="pose-section">
+            <div class="section-title">激光定位</div>
+            <div class="pose-item">
+              <span class="pose-label">X坐标</span>
+              <span class="pose-value">{{ laserPositionData.x }}</span>
+            </div>
+            <div class="pose-item">
+              <span class="pose-label">Y坐标</span>
+              <span class="pose-value">{{ laserPositionData.y }}</span>
+            </div>
+            <div class="pose-item">
+              <span class="pose-label">航向角</span>
+              <span class="pose-value">{{ laserPositionData.angle }}</span>
+            </div>
+          </div>
+
+          <!-- GNSS定位 -->
+          <div class="pose-section">
+            <div class="section-title">GNSS定位</div>
+            <div class="pose-item">
+              <span class="pose-label">状态</span>
+              <el-tag 
+                :type="getGnssStatusType(gnssPositionData.status)" 
+                size="mini"
+                class="pose-value"
+              >
+                {{ gnssPositionData.status }}
+              </el-tag>
+            </div>
+            <div class="pose-item">
+              <span class="pose-label">经度</span>
+              <span class="pose-value">{{ gnssPositionData.longitude }}</span>
+            </div>
+            <div class="pose-item">
+              <span class="pose-label">纬度</span>
+              <span class="pose-value">{{ gnssPositionData.latitude }}</span>
+            </div>
+            <div class="pose-item">
+              <span class="pose-label">方向角</span>
+              <span class="pose-value">{{ gnssPositionData.angle }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 当前标定点卡片 -->
+      <div class="info-card">
+        <div class="card-header">
+          <h4 class="card-title">当前标定点</h4>
+        </div>
+        <div class="card-content">
+          <div class="calibration-table" v-if="calibrationList.length > 0">
+            <div class="table-header">
+              <div class="col-id">序号</div>
+              <div class="col-coord">坐标(X,Y)</div>
+              <div class="col-action">操作</div>
+            </div>
+            <div class="table-body">
+              <div 
+                v-for="item in calibrationList" 
+                :key="item.id" 
+                class="table-row"
+              >
+                <div class="col-id">{{ item.id }}</div>
+                <div class="col-coord">{{ item.coordinate }}</div>
+                <div class="col-action">
+                  <el-popconfirm
+                    title="确定要删除这个标定点吗?"
+                    @confirm="removeCalibration(item.id)"
+                    placement="left"
+                  >
+                    <el-button 
+                      slot="reference"
+                      type="text" 
+                      size="mini" 
+                      icon="el-icon-delete"
+                      class="delete-btn"
+                    />
+                  </el-popconfirm>
+                </div>
+              </div>
+            </div>
+          </div>
+          
+          <!-- 空状态 -->
+          <div v-else class="empty-state">
+            <i class="el-icon-position"></i>
+            <p>暂无标定点</p>
+          </div>
+
+          <!-- 操作按钮组 -->
+          <div class="action-buttons">
+            <el-button 
+              type="primary" 
+              size="small" 
+              icon="el-icon-plus"
+              @click="addCalibration"
+              :disabled="!canAddCalibration"
+            >
+              添加标定点
+            </el-button>
+            <el-button 
+              size="small" 
+              icon="el-icon-check"
+              @click="executeCalibration"
+              :disabled="calibrationList.length === 0"
+            >
+              一键标定
+            </el-button>
+          </div>
+        </div>
+      </div>
+
+    </div>
+
+    <!-- 拖拽调宽手柄 -->
+    <div class="resize-handle" @mousedown="startResize" v-show="!isDrawerMode"></div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'RightPanel',
+  props: {
+    laserPositionData: {
+      type: Object,
+      default: () => ({ x: 0, y: 0, angle: 0 })
+    },
+    gnssPositionData: {
+      type: Object,
+      default: () => ({ status: '0/0', longitude: 0, latitude: 0, angle: 0 })
+    },
+    calibrationList: {
+      type: Array,
+      default: () => []
+    },
+    currentMap: {
+      type: Object,
+      default: () => ({ name: 'sh02' })
+    },
+    isDrawerMode: {
+      type: Boolean,
+      default: false
+    },
+    panelWidth: {
+      type: Number,
+      default: 360
+    }
+  },
+  data() {
+    return {
+      panelWidth: 360,
+      isResizing: false
+    }
+  },
+  computed: {
+    canAddCalibration() {
+      return this.laserPositionData.x !== 0 || this.laserPositionData.y !== 0
+    }
+  },
+  mounted() {
+    // 在浮层模式下,宽度由父组件控制
+    if (!this.isDrawerMode) {
+      this.panelWidth = this.panelWidth || 360
+    }
+    
+    // 添加全局鼠标事件监听
+    document.addEventListener('mousemove', this.handleResize)
+    document.addEventListener('mouseup', this.stopResize)
+  },
+  beforeDestroy() {
+    document.removeEventListener('mousemove', this.handleResize)
+    document.removeEventListener('mouseup', this.stopResize)
+  },
+  methods: {
+    toggleCollapse() {
+      // 直接通知父组件切换状态,不维护内部状态
+      this.$emit('panel-toggle', true) // 总是发送折叠信号
+    },
+    
+    updatePanelWidth() {
+      // 在浮层模式下,宽度由CSS和父组件控制
+      if (!this.isDrawerMode) {
+        this.$nextTick(() => {
+          if (this.$el) {
+            this.$el.style.width = this.panelWidth + 'px'
+          }
+        })
+      }
+    },
+    
+    startResize(event) {
+      this.isResizing = true
+      this.startX = event.clientX
+      this.startWidth = this.panelWidth
+      document.body.style.cursor = 'col-resize'
+      document.body.style.userSelect = 'none'
+    },
+    
+    handleResize(event) {
+      if (!this.isResizing || this.isDrawerMode) return
+      
+      const diff = this.startX - event.clientX
+      const newWidth = Math.max(320, Math.min(420, this.startWidth + diff))
+      
+      if (newWidth !== this.panelWidth) {
+        this.panelWidth = newWidth
+        this.updatePanelWidth()
+        this.$emit('panel-resize', newWidth)
+      }
+    },
+    
+    stopResize() {
+      if (this.isResizing) {
+        this.isResizing = false
+        document.body.style.cursor = ''
+        document.body.style.userSelect = ''
+        localStorage.setItem('calibration-panel-width', this.panelWidth)
+      }
+    },
+    
+    getGnssStatusType(status) {
+      if (status === '0/0') return 'info'
+      if (status.includes('锁定')) return 'success'
+      if (status.includes('异常')) return 'warning'
+      return 'info'
+    },
+    
+    addCalibration() {
+      this.$emit('add-calibration')
+    },
+    
+    removeCalibration(id) {
+      this.$emit('remove-calibration', id)
+    },
+    
+    executeCalibration() {
+      this.$emit('execute-calibration')
+    }
+  },
+
+  watch: {
+    panelWidth(newWidth) {
+      if (!this.isDrawerMode) {
+        this.updatePanelWidth()
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.right-panel {
+  /* 浮层模式下的基础样式由父组件控制 */
+  height: 100%;
+  background: var(--color-bg-card);
+  border: 1px solid var(--color-border-primary);
+  border-radius: var(--radius-lg);
+  transition: all var(--duration-200) var(--ease-out);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  box-shadow: var(--shadow-xl);
+
+  &.collapsed {
+    opacity: 0;
+    pointer-events: none;
+  }
+
+  .panel-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: var(--spacing-4) var(--spacing-4) var(--spacing-3) var(--spacing-4);
+    border-bottom: 1px solid var(--color-border-secondary);
+    background: var(--color-bg-tertiary);
+
+    .panel-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);
+      }
+    }
+
+    .collapse-btn {
+      color: var(--color-text-tertiary);
+      
+      &:hover {
+        color: var(--color-primary);
+      }
+    }
+  }
+
+  .panel-content {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    padding: var(--spacing-4);
+    opacity: 1;
+    transition: opacity var(--duration-200) var(--ease-out);
+    overflow: hidden;
+
+    .info-card {
+      background: var(--color-bg-card);
+      border-radius: var(--radius-lg);
+      box-shadow: var(--shadow-card);
+      margin-bottom: 16px; /* 统一卡片间距 */
+      overflow: hidden;
+
+      &:last-child {
+        margin-bottom: 0;
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        min-height: 0;
+      }
+
+      .card-header {
+        padding: var(--spacing-3) var(--spacing-4);
+        border-bottom: 1px solid var(--color-border-secondary);
+        background: var(--color-bg-secondary);
+
+        .card-title {
+          margin: 0;
+          font-size: var(--font-size-base);
+          font-weight: var(--font-weight-semibold);
+          color: var(--color-text-primary);
+        }
+      }
+
+      .card-content {
+        padding: var(--spacing-4);
+      }
+      
+      /* 为最后一个卡片(标定点卡片)设置可滚动 */
+      &:last-child {
+        .card-content {
+          flex: 1;
+          overflow-y: auto;
+          
+          /* 自定义滚动条样式 */
+          &::-webkit-scrollbar {
+            width: 6px;
+          }
+
+          &::-webkit-scrollbar-track {
+            background: var(--color-bg-tertiary);
+            border-radius: var(--radius-full);
+          }
+
+          &::-webkit-scrollbar-thumb {
+            background: var(--color-border-primary);
+            border-radius: var(--radius-full);
+            
+            &:hover {
+              background: var(--color-text-quaternary);
+            }
+          }
+        }
+      }
+
+      .pose-section {
+        margin-bottom: var(--spacing-4);
+
+        &:last-child {
+          margin-bottom: 0;
+        }
+
+        .section-title {
+          font-size: var(--font-size-sm);
+          font-weight: var(--font-weight-medium);
+          color: var(--color-text-tertiary);
+          margin-bottom: var(--spacing-2);
+          padding-bottom: var(--spacing-1);
+          border-bottom: 1px solid var(--color-border-tertiary);
+        }
+
+        .pose-item {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          padding: var(--spacing-2) 0;
+          border-bottom: 1px solid var(--color-border-tertiary);
+
+          &:last-child {
+            border-bottom: none;
+          }
+
+          .pose-label {
+            font-size: var(--font-size-sm);
+            color: var(--color-text-secondary);
+            font-weight: var(--font-weight-medium);
+          }
+
+          .pose-value {
+            font-size: var(--font-size-sm);
+            color: var(--color-text-primary);
+            font-weight: var(--font-weight-normal);
+            text-align: right;
+          }
+        }
+      }
+
+      .calibration-table {
+        .table-header,
+        .table-row {
+          display: grid;
+          grid-template-columns: 48px 1fr 48px;
+          gap: var(--spacing-2);
+          align-items: center;
+        }
+
+        .table-header {
+          padding: var(--spacing-2) 0;
+          border-bottom: 1px solid var(--color-border-secondary);
+          font-size: var(--font-size-xs);
+          font-weight: var(--font-weight-semibold);
+          color: var(--color-text-tertiary);
+          text-align: center;
+        }
+
+        .table-row {
+          padding: var(--spacing-2) 0;
+          border-bottom: 1px solid var(--color-border-tertiary);
+          font-size: var(--font-size-sm);
+
+          &:last-child {
+            border-bottom: none;
+          }
+
+          &:hover {
+            background: var(--color-bg-secondary);
+          }
+
+          .col-id {
+            text-align: center;
+            font-weight: var(--font-weight-medium);
+            color: var(--color-primary);
+          }
+
+          .col-coord {
+            font-family: var(--font-family-mono);
+            font-size: var(--font-size-xs);
+            color: var(--color-text-secondary);
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+          }
+
+          .col-action {
+            text-align: center;
+
+            .delete-btn {
+              color: var(--color-danger);
+              
+              &:hover {
+                color: var(--color-danger-dark);
+              }
+            }
+          }
+        }
+      }
+
+      .action-buttons {
+        display: flex;
+        gap: var(--spacing-2);
+        margin-top: var(--spacing-4);
+
+        .el-button {
+          flex: 1;
+        }
+      }
+
+      .empty-state {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        padding: var(--spacing-6) var(--spacing-4);
+        color: var(--color-text-quaternary);
+
+        i {
+          font-size: var(--font-size-2xl);
+          margin-bottom: var(--spacing-2);
+          opacity: 0.5;
+        }
+
+        p {
+          margin: 0;
+          font-size: var(--font-size-sm);
+          opacity: 0.8;
+        }
+      }
+    }
+  }
+
+  .resize-handle {
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    width: 8px;
+    cursor: col-resize;
+    background: transparent;
+    z-index: 10;
+
+    &:hover {
+      background: var(--color-primary);
+      opacity: 0.3;
+    }
+
+    &:active {
+      background: var(--color-primary);
+      opacity: 0.5;
+    }
+  }
+}
+
+// 暗色主题适配
+html.dark {
+  .right-panel {
+    .panel-header {
+      background: var(--color-bg-quaternary);
+    }
+
+    .info-card {
+      background: var(--color-bg-tertiary);
+      box-shadow: var(--shadow-card);
+
+      .card-header {
+        background: var(--color-bg-quaternary);
+      }
+    }
+  }
+}
+</style>