Преглед на файлове

修复勤务路线地图标记点击与任务列表弹窗数据不一致的问题

  - 为勤务路线 mock 数据添加 taskId,关联地图路线与任务列表中的具体任务
  - 地图 marker 点击时优先使用 taskId 查询任务详情,确保与列表"查看"弹窗一致
  - 新增任务4(重要会议)和任务5(演唱会特殊安保)的独立地图路线
  - 勤务路线使用完整驾车路径绘制,避免 extractMainStraightSegments 拆段导致重复绘制
  - 同步修复 StatusMonitoring 和 TrunkCoordination 页面的特勤 marker 点击逻辑
  - 勤务路线上所有类型 marker(passed/normal/start/end)统一在缩放时通过 setContent 重建,解决偏移问题
  - passed 标记点字体颜色改为白色,提升可读性
  - 状态监控页面路口 tab 左侧菜单点击子区支持与总览一样的区域标记效果
  - 子区标题随地图缩放自适应字号,增加宽高双约束防止文字超出区域范围
画安 преди 1 седмица
родител
ревизия
5dfdebbb45
променени са 2 файла, в които са добавени 57 реда и са изтрити 10 реда
  1. 56 9
      src/components/TongzhouTrafficMap.vue
  2. 1 1
      src/views/StatusMonitoring.vue

+ 56 - 9
src/components/TongzhouTrafficMap.vue

@@ -89,6 +89,7 @@ export default {
       markerById: {}, // ID → marker 索引,加速 focusById 查找
       startEndMarkers: [], // 起点/终点 marker 引用,缩放时需重建
       subAreaOverlays: [], // 子区蒙层覆盖物
+      _subAreaParams: null, // 子区绘制参数,缩放时重绘用
     };
   },
   mounted() {
@@ -319,6 +320,8 @@ export default {
           if (!this.isComponentDestroyed) {
             this.currentZoomSize = this.getDotSizeByZoom();
             this.rebuildStartEndMarkers();
+            this.rebuildDotMarkers();
+            this.rebuildSubAreaText();
           }
         });
       } catch (err) {
@@ -348,11 +351,11 @@ export default {
           { start: [116.66325, 39.9171], end: [116.6833, 39.9171], color: "#13C373", trunkId: 'trunk_6', trunkName: '张台路与湖亦路路口' },
         ],
         "勤务路线": [
-          { start: [116.6445, 39.8980], end: [116.6850, 39.8980], color: "#BC301D", dutyState: 'pending', taskId: 1 },
-          { start: [116.6850, 39.8980], end: [116.7250, 39.8980], color: "#BC301D", dutyState: 'active', progress: 0.45, taskId: 3 },
-          { start: [116.7250, 39.8980], end: [116.7650, 39.8980], color: "#BC301D", dutyState: 'done', taskId: 2 },
-          { start: [116.6445, 39.9080], end: [116.7050, 39.9080], color: "#BC301D", dutyState: 'done', taskId: 4 },
-          { start: [116.6445, 39.8880], end: [116.7050, 39.8880], color: "#BC301D", dutyState: 'pending', taskId: 5 }
+          { start: [116.6400, 39.9270], end: [116.7000, 39.9270], color: "#BC301D", dutyState: 'pending', taskId: 1 },
+          { start: [116.6400, 39.9120], end: [116.7000, 39.9120], color: "#BC301D", dutyState: 'active', progress: 0.45, taskId: 3 },
+          { start: [116.6700, 39.8970], end: [116.7300, 39.8970], color: "#BC301D", dutyState: 'done', taskId: 2 },
+          { start: [116.6400, 39.8820], end: [116.7000, 39.8820], color: "#BC301D", dutyState: 'done', taskId: 4 },
+          { start: [116.6400, 39.8670], end: [116.7000, 39.8670], color: "#BC301D", dutyState: 'pending', taskId: 5 }
         ]
       };
 
@@ -966,6 +969,31 @@ export default {
       }
     },
 
+    rebuildDotMarkers() {
+      if (!this.map || this.dotMarkers.length === 0) return;
+      const size = this.currentZoomSize;
+      const textDisplay = size >= 14 ? 'flex' : 'none';
+      const textSize = `${Math.max(10, size - 4)}px`;
+      for (const entry of this.dotMarkers) {
+        const { marker, config, isPassed } = entry;
+        if (isPassed) {
+          marker.setContent(`
+            <div class="pure-light-node" style="width: ${size}px; height: ${size}px; background: #5A5A5A; border: 1.5px solid rgba(255,255,255,0.25); box-sizing: border-box; display: flex; justify-content: center; align-items: center; color: #fff; border-radius: 50%; cursor: pointer; opacity: 0.55;">
+              <span style="display: ${textDisplay}; font-weight: bold; font-size: ${textSize};">勤</span>
+            </div>
+          `);
+        } else {
+          const displayText = config.name ? config.name.charAt(0) : '';
+          marker.setContent(`
+            <div class="pure-light-node route-node" style="width: ${size}px; height: ${size}px; background: ${config.color || '#999'}; box-shadow: none; border: none; box-sizing: border-box; display: flex; justify-content: center; align-items: center; color: #fff; border-radius: 50%; cursor: pointer;">
+              <span style="display: ${textDisplay}; font-weight: bold; font-size: ${textSize};">${displayText}</span>
+            </div>
+          `);
+        }
+      }
+    },
+
+
     /**
      * 创建交通信号灯标记
      * @param {Array<number>|Object} position - 位置坐标
@@ -1001,7 +1029,7 @@ export default {
           `;
         } else if (isPassed) {
           markerContent = `
-            <div class="pure-light-node" style="width: var(--dot-size); height: var(--dot-size); background: #5A5A5A; border: 1.5px solid rgba(255,255,255,0.25); box-sizing: content-box; display: flex; justify-content: center; align-items: center; color: #888; border-radius: 50%; cursor: pointer; padding: var(--dot-padding); opacity: 0.55;">
+            <div class="pure-light-node" style="width: var(--dot-size); height: var(--dot-size); background: #5A5A5A; border: 1.5px solid rgba(255,255,255,0.25); box-sizing: border-box; display: flex; justify-content: center; align-items: center; color: #fff; border-radius: 50%; cursor: pointer; opacity: 0.55;">
               <span style="display: var(--text-display); font-weight: bold; font-size: var(--text-size);">勤</span>
             </div>
           `;
@@ -1045,6 +1073,10 @@ export default {
         if (isStartEnd) {
           this.startEndMarkers.push({ marker, position: [lng, lat], config, type });
         }
+        // 记录勤务路线上的圆点 marker(passed + normal route),缩放时需重建
+        if (!isStartEnd && isRoute) {
+          this.dotMarkers.push({ marker, config, type, isPassed });
+        }
 
         marker.on('click', (e) => {
           if (this.isComponentDestroyed) return;
@@ -1395,6 +1427,7 @@ export default {
     drawSubAreaCircle(leaves, label) {
       if (!this.isMapReady() || !leaves || leaves.length === 0) return;
 
+      this._subAreaParams = { leaves, label };
       this.clearSubAreaOverlays();
 
       const pts = leaves.map(n => [Number(n.lng), Number(n.lat)]);
@@ -1436,10 +1469,16 @@ export default {
         const bottomRight = this.map.lngLatToContainer([Math.max(...lngs), Math.min(...lats)]);
         const pixelW = Math.abs(bottomRight.x - topLeft.x);
         const pixelH = Math.abs(bottomRight.y - topLeft.y);
-        // 字号以横向宽度为基准,按文字长度均分,确保不超出区域
+        // 字号同时受宽度和高度约束,取较小值确保不超出区域
         const charCount = label.length || 1;
-        const fontSize = Math.min(Math.max(12, Math.round(pixelW / (charCount + 1))), 48);
+        const sizeByW = Math.round(pixelW * 0.8 / (charCount + 1));
+        const sizeByH = Math.round(pixelH * 0.6);
+        const fontSize = Math.min(Math.max(10, Math.min(sizeByW, sizeByH)), 48);
 
+        // 缩放过小时隐藏文字
+        if (fontSize < 10) {
+          // 不绘制文字
+        } else {
         const text = new this.AMap.Text({
           text: label,
           position: [cx, cy],
@@ -1452,13 +1491,14 @@ export default {
             'font-size': `${fontSize}px`,
             'font-weight': 'bold',
             'padding': '0',
-            'letter-spacing': '2px',
+            'letter-spacing': fontSize >= 16 ? '2px' : '0px',
             'white-space': 'nowrap',
             'pointer-events': 'none',
           },
         });
         this.subAreaOverlays.push(text);
         this.map.add(text);
+        }
       }
     },
 
@@ -1501,6 +1541,13 @@ export default {
     /**
      * 清除子区圆形蒙层
      */
+    rebuildSubAreaText() {
+      if (!this._subAreaParams) return;
+      const { leaves, label } = this._subAreaParams;
+      // 保留参数,重绘整个子区(包含字号重新计算)
+      this.drawSubAreaCircle(leaves, label);
+    },
+
     clearSubAreaOverlays() {
       if (this.map && this.subAreaOverlays.length > 0) {
         try { this.map.remove(this.subAreaOverlays); } catch (e) { void e; }

+ 1 - 1
src/views/StatusMonitoring.vue

@@ -36,7 +36,7 @@
                     </TechTabPane>
                     <TechTabPane label="路口" name="crossing" class="menu-scroll-view" :loading="menuData.length === 0">
                         <MenuItem theme="tech" v-for="item in menuData" :key="item.id" :node="item" :level="0"
-                            @node-click="handleMenuClick" />
+                            @node-click="handleMenuClick" @folder-click="handleFolderClick" />
                     </TechTabPane>
                     <TechTabPane label="干线" name="trunkLine" class="menu-scroll-view" :loading="trunkLineMenuData.length === 0">
                         <MenuItem v-for="item in trunkLineMenuData" :key="item.id" :node="item" :level="0"