Forráskód Böngészése

地图图例:移除特殊控制、加入黄闪/全红/关灯,全选改为勾选框

  调整 TongzhouTrafficMap 的图例与底层数据分类:
  - 删除"特殊控制"(图例 + 数据分类一并移除)
  - 新增"黄闪/全红/关灯",与原"特殊控制"同等级,参与 statusConfig
    与 getIntersectionCategory 的在线类目分配
  - "降级"加 hideInLegend 标记,仅图例隐藏,数据与地图绘制保留
  - 图例只展示有路口数据的控制模式(路线类不受此过滤影响);
    全选范围、勾选状态都跟随可见列表,避免"全选了但图例上看不到"的不一致
  - "全选"改成方框 + CSS 边框拼成的对勾,不依赖 ✓ 字符在不同字体下
    的基线表现

  修复 getIntersectionCategory 的模数撞车 bug:_getDeviceStatus 用
  seed%8 划分在/离线,原本的在线分支再用 seed%len 取 normalTypes
  (长度同为 8)就永远取不到最后一个 index——之前把"特殊控制"排在
  末位时被这个 bug 隐藏了,本次扩到 8 项后"关灯"暴露空数据。改成
  floor(seed/8)%len 解耦两侧模数,718 路口分布回到均衡。
画安 1 napja%!(EXTRA string=óta)
szülő
commit
1b05e2ee75
2 módosított fájl, 79 hozzáadás és 19 törlés
  1. 75 17
      src/components/TongzhouTrafficMap.vue
  2. 4 2
      src/mock/api.js

+ 75 - 17
src/components/TongzhouTrafficMap.vue

@@ -10,8 +10,9 @@
       </div>
       <div class="legend-list">
         <div class="legend-item all-select" @click="toggleAll">
-          <div class="legend-dot"
-            :style="{ backgroundColor: isAllSelected ? '#fff' : 'transparent', border: '1px solid #fff' }"></div>
+          <div class="legend-dot all-select-dot" :class="{ 'is-checked': isAllSelected }">
+            <i class="check-icon" v-if="isAllSelected"></i>
+          </div>
           <div class="legend-label" style="font-weight: bold;">全选</div>
         </div>
 
@@ -63,7 +64,7 @@ export default {
         legend: {}
       },
       legendVisible: true,
-      activeLegends: ["中心计划", "干线协调", "勤务路线", "定周期控制", "感应控制", "自适应控制", "手动控制", "特殊控制", "离线", "降级", "故障"],
+      activeLegends: ["中心计划", "干线协调", "勤务路线", "定周期控制", "感应控制", "自适应控制", "手动控制", "黄闪", "全红", "关灯", "离线", "降级", "故障"],
       isComponentDestroyed: false,
       drawSeq: 0,
       driving: null,
@@ -78,9 +79,12 @@ export default {
         { name: "感应控制", color: "#FF864C", type: "normal" },
         { name: "自适应控制", color: "#9F6EFE", type: "normal" },
         { name: "手动控制", color: "#EB9F36", type: "normal" },
-        { name: "特殊控制", color: "#A26218", type: "normal" },
+        { name: "黄闪", color: "#F4D03F", type: "normal" },
+        { name: "全红", color: "#E74C3C", type: "normal" },
+        { name: "关灯", color: "#5B6B7A", type: "normal" },
         { name: "离线", color: "#7A7A7A", type: "abnormal" },
-        { name: "降级", color: "#D9C13B", type: "abnormal" },
+        // 降级 仍参与数据分类与地图绘制,但不出现在图例(hideInLegend: true)
+        { name: "降级", color: "#D9C13B", type: "abnormal", hideInLegend: true },
         { name: "故障", color: "#FF3938", type: "abnormal" }
       ],
       intersectionData: [],
@@ -170,8 +174,11 @@ export default {
     this.isInfoWindowHovered = false;
   },
   computed: {
+    // 全选状态:仅判断"图例上可见的项"是否全部启用,不受常隐项(如降级)影响
     isAllSelected() {
-      return this.activeLegends.length === this.statusConfig.length;
+      const visible = this.legendStatusConfig.map(i => i.name);
+      if (visible.length === 0) return false;
+      return visible.every(n => this.activeLegends.includes(n));
     },
     isHomePage() {
       return this.$route && (this.$route.path === '/home' || this.$route.path === '/surve');
@@ -182,11 +189,24 @@ export default {
       return { right, borderRadius: '6px' };
     },
     legendStatusConfig() {
-      if (!this.mode) return this.statusConfig;
+      // 1. 按 mode 过滤
+      let list = this.statusConfig;
       if (this.mode === '路口') {
-        return this.statusConfig.filter(item => !['干线协调', '勤务路线'].includes(item.name));
+        list = list.filter(item => !['干线协调', '勤务路线'].includes(item.name));
+      } else if (this.mode && this.mode !== '路口') {
+        return [];
       }
-      return [];
+      // 2. hideInLegend 项一律不进图例(数据与地图绘制不受影响)
+      list = list.filter(item => !item.hideInLegend);
+      // 3. 仅展示路口/路线实际存在的控制模式:
+      //    - 路线类(type: route)固定有线段配置,始终显示
+      //    - 其余按 statusIntersections[name] 是否非空过滤
+      list = list.filter(item => {
+        if (item.type === 'route') return true;
+        const ints = this.statusIntersections && this.statusIntersections[item.name];
+        return ints && ints.length > 0;
+      });
+      return list;
     },
     // 自动计算并下发给所有 CSS 的变量字典
     mapCssVars() {
@@ -232,7 +252,8 @@ export default {
       const categories = {
         "中心计划": [], "干线协调": [], "勤务路线": [],
         "定周期控制": [], "感应控制": [], "自适应控制": [],
-        "手动控制": [], "特殊控制": [],
+        "手动控制": [],
+        "黄闪": [], "全红": [], "关灯": [],
         "离线": [], "降级": [], "故障": []
       };
       for (const item of this.intersectionData) {
@@ -250,7 +271,7 @@ export default {
     updateMapByMode() {
       switch (this.mode) {
         case '路口':
-          this.activeLegends = ["中心计划", "定周期控制", "感应控制", "自适应控制", "手动控制", "特殊控制", "离线", "降级", "故障"];
+          this.activeLegends = ["中心计划", "定周期控制", "感应控制", "自适应控制", "手动控制", "黄闪", "全红", "关灯", "离线", "降级", "故障"];
           break;
         case '干线':
           this.activeLegends = ["干线协调"];
@@ -1257,20 +1278,28 @@ export default {
     },
 
     /**
-     * 切换所有图例的可见性
+     * 切换"所有图例可见项"的开关。常隐项(hideInLegend,如 降级)始终保留显示。
      */
     toggleAll() {
-      const targetState = !this.isAllSelected;
       if (!this.isMapReady()) return;
+      const visibleNames = this.legendStatusConfig.map(i => i.name);
+      const alwaysOnNames = this.statusConfig
+        .filter(i => i.hideInLegend)
+        .map(i => i.name);
+      const targetState = !this.isAllSelected;
 
       if (targetState) {
-        this.activeLegends = this.statusConfig.map(item => item.name);
-        Object.values(this.routeGroups).forEach(overlays => {
+        // 全选可见项 + 保留常隐项
+        this.activeLegends = Array.from(new Set([...alwaysOnNames, ...visibleNames]));
+        visibleNames.forEach(name => {
+          const overlays = this.routeGroups[name];
           if (overlays && overlays.length > 0) this.map.add(overlays);
         });
       } else {
-        this.activeLegends = [];
-        Object.values(this.routeGroups).forEach(overlays => {
+        // 取消所有可见项,常隐项保留在 activeLegends,对应图层不动
+        this.activeLegends = [...alwaysOnNames];
+        visibleNames.forEach(name => {
+          const overlays = this.routeGroups[name];
           if (overlays && overlays.length > 0) this.map.remove(overlays);
         });
         if (this.infoWindow) this.infoWindow.close();
@@ -1876,6 +1905,35 @@ export default {
   height: 10px;
 }
 
+/* 全选勾选框:未选时为透明带白边的方框,选中时填充蓝色并显示对勾 */
+.all-select .legend-dot.all-select-dot {
+  width: 14px;
+  height: 14px;
+  border-radius: 3px;
+  border: 1px solid #fff;
+  background: transparent;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  box-shadow: none;
+}
+
+.all-select .legend-dot.all-select-dot.is-checked {
+  background: #1890ff;
+  border-color: #1890ff;
+}
+
+/* 用两条边框拼一个真正居中的对勾,避免 ✓ 字符在不同字体下基线偏移 */
+.all-select .check-icon {
+  display: block;
+  width: 6px;
+  height: 9px;
+  border: solid #fff;
+  border-width: 0 2px 2px 0;
+  transform: translateY(-1px) rotate(45deg);
+  transform-origin: center;
+}
+
 .legend-dot.is-status-wrapper {
   width: 28px;
   height: 28px;

+ 4 - 2
src/mock/api.js

@@ -55,8 +55,10 @@ export function getIntersectionCategory(id) {
     const abnormalTypes = ['离线', '降级', '故障']
     return abnormalTypes[seed % abnormalTypes.length]
   }
-  const normalTypes = ['中心计划', '定周期控制', '感应控制', '自适应控制', '手动控制', '特殊控制']
-  return normalTypes[seed % normalTypes.length]
+  // 用 floor(seed / 8) 解耦:_getDeviceStatus 已用 seed%8 决定在/离线,
+  // 这里若再用同一模数,最后一个 index 永远拿不到数据(被离线分支吃掉了)
+  const normalTypes = ['中心计划', '定周期控制', '感应控制', '自适应控制', '手动控制', '黄闪', '全红', '关灯']
+  return normalTypes[Math.floor(seed / 8) % normalTypes.length]
 }
 
 /** 基于当前秒数产生稳定随机(同一秒内多次调用返回相同值) */