Ver código fonte

统一地图标记与 API 的路口状态数据源,离线标记显示提示弹窗

  - 新增 getIntersectionCategory() 统一根据路口 ID 生成状态分类,地图和 API 共用同一数据源
  - classifyIntersectionsByStatus 从按数组位置硬编码改为按 ID 调用统一分类函数
  - 鼠标滑过离线/降级/故障标记显示小弹窗提示状态,移开自动关闭
  - 点击离线/降级/故障标记弹出提醒消息,拦截详情面板打开
  - 新增 OfflineTip 组件,状态监控/勤务管理/干线协调三个页面统一处理
画安 1 semana atrás
pai
commit
a376ced424

+ 13 - 19
src/components/TongzhouTrafficMap.vue

@@ -43,6 +43,7 @@
 
 <script>
 import AMapLoader from '@amap/amap-jsapi-loader';
+import { getIntersectionCategory } from '@/mock/api';
 
 export default {
   name: "TrafficMap",
@@ -225,26 +226,19 @@ export default {
      * 将真实路口数据按状态类型分类
      */
     classifyIntersectionsByStatus() {
-      const remainingData = this.intersectionData;
-      const maxAbnormalCount = 4;
-      const abnormalTotal = maxAbnormalCount * 3;
-      const normalData = remainingData.slice(0, remainingData.length - abnormalTotal);
-      const abnormalData = remainingData.slice(remainingData.length - abnormalTotal);
-      const normalChunk = Math.ceil(normalData.length / 6);
-
-      this.statusIntersections = {
-        "中心计划": normalData.slice(0, normalChunk),
-        "干线协调": [],
-        "勤务路线": [],
-        "定周期控制": normalData.slice(normalChunk, normalChunk * 2),
-        "感应控制": normalData.slice(normalChunk * 2, normalChunk * 3),
-        "自适应控制": normalData.slice(normalChunk * 3, normalChunk * 4),
-        "手动控制": normalData.slice(normalChunk * 4, normalChunk * 5),
-        "特殊控制": normalData.slice(normalChunk * 5),
-        "离线": abnormalData.slice(0, maxAbnormalCount),
-        "降级": abnormalData.slice(maxAbnormalCount, maxAbnormalCount * 2),
-        "故障": abnormalData.slice(maxAbnormalCount * 2, maxAbnormalCount * 3)
+      const categories = {
+        "中心计划": [], "干线协调": [], "勤务路线": [],
+        "定周期控制": [], "感应控制": [], "自适应控制": [],
+        "手动控制": [], "特殊控制": [],
+        "离线": [], "降级": [], "故障": []
       };
+      for (const item of this.intersectionData) {
+        const category = getIntersectionCategory(item["路口编号"]);
+        if (categories[category]) {
+          categories[category].push(item);
+        }
+      }
+      this.statusIntersections = categories;
     },
 
     /**

+ 38 - 0
src/components/ui/OfflineTip.vue

@@ -0,0 +1,38 @@
+<template>
+  <div class="offline-tip">
+    <span class="offline-dot" :style="{ background: dotColor }"></span>
+    <span class="offline-text">设备{{ status }}</span>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'OfflineTip',
+  props: {
+    status: { type: String, default: '离线' },
+    road: { type: String, default: '' },
+  },
+  computed: {
+    dotColor() {
+      const map = { '离线': '#7A7A7A', '降级': '#D9C13B', '故障': '#FF3938' };
+      return map[this.status] || '#7A7A7A';
+    }
+  }
+};
+</script>
+
+<style scoped>
+.offline-tip {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  color: #e0e8f0;
+  font-size: 14px;
+}
+.offline-dot {
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  flex-shrink: 0;
+}
+</style>

+ 3 - 1
src/layouts/DashboardLayout.vue

@@ -107,6 +107,7 @@ import SpecialTaskMonitorPanel from '@/components/ui/SpecialTaskMonitorPanel.vue
 import ChangePassword from '@/components/ui/ChangePassword.vue';
 import CrossingMultiView from '@/components/ui/CrossingMultiView.vue';
 import CrossingDetailHeader from '@/components/ui/CrossingDetailHeader.vue';
+import OfflineTip from '@/components/ui/OfflineTip.vue';
 
 export default {
     name: 'DashboardLayout',
@@ -134,7 +135,8 @@ export default {
         SpecialTaskMonitorPanel,
         ChangePassword,
         CrossingMultiView,
-        CrossingDetailHeader
+        CrossingDetailHeader,
+        OfflineTip
     },
     provide() {
         return {

+ 15 - 0
src/mock/api.js

@@ -44,6 +44,21 @@ function _getDeviceStatus(id) {
   return statusList[seed % statusList.length]
 }
 
+/**
+ * 根据路口ID生成稳定的地图分类(在线→控制方式,离线→异常类型)
+ * 地图标记和 API 共用,确保状态一致
+ */
+export function getIntersectionCategory(id) {
+  const status = _getDeviceStatus(id)
+  const seed = id ? Array.from(id).reduce((s, c, i) => s + c.charCodeAt(0) * (i + 1), 0) : 0
+  if (status === '离线') {
+    const abnormalTypes = ['离线', '降级', '故障']
+    return abnormalTypes[seed % abnormalTypes.length]
+  }
+  const normalTypes = ['中心计划', '定周期控制', '感应控制', '自适应控制', '手动控制', '特殊控制']
+  return normalTypes[seed % normalTypes.length]
+}
+
 /** 基于当前秒数产生稳定随机(同一秒内多次调用返回相同值) */
 function seededRand(seed) {
   const x = Math.sin(seed) * 10000

+ 35 - 8
src/views/SpecialSituationMonitoring.vue

@@ -170,10 +170,29 @@ export default {
             let nodeData = {
                 id: mapData.id || (mapData.position[0] + mapData.position[1]),
                 label: mapData.road,
-                // 反算为设计稿坐标(SmartDialog 内部会再乘 scale)
                 pixelX: pixel ? Math.round(pixel.x / scale) : 950,
                 pixelY: pixel ? Math.round(pixel.y / scale) : 430,
             }
+            // 离线/降级/故障:显示小弹窗提示状态
+            const abnormalNames = ['离线', '降级', '故障'];
+            if (abnormalNames.includes(mapData.statusLabel || mapData.name)) {
+                const statusText = mapData.statusLabel || mapData.name;
+                this.$refs.layout.openDialog({
+                    id: 'offline_tip_' + nodeData.id,
+                    title: nodeData.label,
+                    component: 'OfflineTip',
+                    width: 260,
+                    height: 100,
+                    center: false,
+                    showClose: false,
+                    noPadding: false,
+                    draggable: false,
+                    resizable: false,
+                    position: { x: (nodeData.pixelX || 950) + 10, y: nodeData.pixelY || 430 },
+                    data: { status: statusText, road: nodeData.label },
+                });
+                return;
+            }
             console.log(nodeData);
             if (this.activeLeftTab === 'overview') { // 总览
                 this.showOverviewDalogs(nodeData);
@@ -184,20 +203,28 @@ export default {
         // 处理地图鼠标滑出事件
         handleMapCrossingMouseout(mapData) {
             console.log('父组件接收到了地图路口鼠标滑出事件:', mapData);
-            if (this.activeLeftTab === 'overview' && mapData) { // 总览
-                const id = mapData.id || (mapData.position[0] + mapData.position[1]);
+            if (!mapData) return;
+            const id = mapData.id || (mapData.position[0] + mapData.position[1]);
+            this.$refs.layout.handleDialogClose('offline_tip_' + id);
+            if (this.activeLeftTab === 'overview') { // 总览
                 this.$refs.layout.handleDialogClose('crossing3_' + id);
-            } else if (this.activeLeftTab === 'crossing' && mapData) { // 路口
-                
-                const id = mapData.id || (mapData.position[0] + mapData.position[1]);
+            } else if (this.activeLeftTab === 'crossing') { // 路口
                 this.$refs.layout.handleDialogClose('crossing3_' + id);
             }
         },
         // 处理地图点击事件
         handleMapCrossingClick(mapData, lnglat, pixel) {
             console.log('父组件接收到了地图路口点击事件:', mapData);
-            console.log('父组件接收到了地图路口点击事件:', lnglat);
-            console.log('父组件接收到了地图路口点击事件:', pixel);
+            // 离线/降级/故障状态不弹详情
+            const abnormalNames = ['离线', '降级', '故障'];
+            if (abnormalNames.includes(mapData.statusLabel || mapData.name)) {
+                this.$msg({
+                    title: '提示',
+                    message: `路口「${mapData.road || mapData.id}」设备${mapData.statusLabel || mapData.name},无法查看详情`,
+                    duration: 3000,
+                });
+                return;
+            }
             // 组装模拟数据
             const scale = window.innerWidth / 1920;
             let nodeData = {

+ 36 - 8
src/views/StatusMonitoring.vue

@@ -209,10 +209,29 @@ export default {
             let nodeData = {
                 id: mapData.id || (mapData.position[0] + mapData.position[1]),
                 label: mapData.road,
-                // 反算为设计稿坐标(SmartDialog 内部会再乘 scale)
                 pixelX: pixel ? Math.round(pixel.x / scale) : 950,
                 pixelY: pixel ? Math.round(pixel.y / scale) : 430,
             }
+            // 离线/降级/故障:显示小弹窗提示状态
+            const abnormalNames = ['离线', '降级', '故障'];
+            if (abnormalNames.includes(mapData.statusLabel || mapData.name)) {
+                const statusText = mapData.statusLabel || mapData.name;
+                this.$refs.layout.openDialog({
+                    id: 'offline_tip_' + nodeData.id,
+                    title: nodeData.label,
+                    component: 'OfflineTip',
+                    width: 260,
+                    height: 100,
+                    center: false,
+                    showClose: false,
+                    noPadding: false,
+                    draggable: false,
+                    resizable: false,
+                    position: { x: (nodeData.pixelX || 950) + 10, y: nodeData.pixelY || 430 },
+                    data: { status: statusText, road: nodeData.label },
+                });
+                return;
+            }
             console.log(nodeData);
             if (this.activeLeftTab === 'overview') { // 总览
                 this.showOverviewDalogs(nodeData);
@@ -223,20 +242,29 @@ export default {
         // 处理地图鼠标滑出事件
         handleMapCrossingMouseout(mapData) {
             console.log('父组件接收到了地图路口鼠标滑出事件:', mapData);
-            if (this.activeLeftTab === 'overview' && mapData) { // 总览
-                const id = mapData.id || (mapData.position[0] + mapData.position[1]);
+            if (!mapData) return;
+            const id = mapData.id || (mapData.position[0] + mapData.position[1]);
+            // 关闭离线提示小弹窗
+            this.$refs.layout.handleDialogClose('offline_tip_' + id);
+            if (this.activeLeftTab === 'overview') { // 总览
                 this.$refs.layout.handleDialogClose('crossing3_' + id);
-            } else if (this.activeLeftTab === 'crossing' && mapData) { // 路口
-
-                const id = mapData.id || (mapData.position[0] + mapData.position[1]);
+            } else if (this.activeLeftTab === 'crossing') { // 路口
                 this.$refs.layout.handleDialogClose('crossing3_' + id);
             }
         },
         // 处理地图点击事件
         handleMapCrossingClick(mapData, lnglat, pixel) {
             console.log('父组件接收到了地图路口点击事件:', mapData);
-            console.log('父组件接收到了地图路口点击事件:', lnglat);
-            console.log('父组件接收到了地图路口点击事件:', pixel);
+            // 离线/降级/故障状态不弹详情
+            const abnormalNames = ['离线', '降级', '故障'];
+            if (abnormalNames.includes(mapData.statusLabel || mapData.name)) {
+                this.$msg({
+                    title: '提示',
+                    message: `路口「${mapData.road || mapData.id}」设备${mapData.statusLabel || mapData.name},无法查看详情`,
+                    duration: 3000,
+                });
+                return;
+            }
             // 组装模拟数据
             const scale = window.innerWidth / 1920;
             let nodeData = {

+ 35 - 8
src/views/TrunkCoordination.vue

@@ -174,10 +174,29 @@ export default {
             let nodeData = {
                 id: mapData.id || (mapData.position[0] + mapData.position[1]),
                 label: mapData.road,
-                // 反算为设计稿坐标(SmartDialog 内部会再乘 scale)
                 pixelX: pixel ? Math.round(pixel.x / scale) : 950,
                 pixelY: pixel ? Math.round(pixel.y / scale) : 430,
             }
+            // 离线/降级/故障:显示小弹窗提示状态
+            const abnormalNames = ['离线', '降级', '故障'];
+            if (abnormalNames.includes(mapData.statusLabel || mapData.name)) {
+                const statusText = mapData.statusLabel || mapData.name;
+                this.$refs.layout.openDialog({
+                    id: 'offline_tip_' + nodeData.id,
+                    title: nodeData.label,
+                    component: 'OfflineTip',
+                    width: 260,
+                    height: 100,
+                    center: false,
+                    showClose: false,
+                    noPadding: false,
+                    draggable: false,
+                    resizable: false,
+                    position: { x: (nodeData.pixelX || 950) + 10, y: nodeData.pixelY || 430 },
+                    data: { status: statusText, road: nodeData.label },
+                });
+                return;
+            }
             console.log(nodeData);
             if (this.activeLeftTab === 'overview') { // 总览
                 this.showOverviewDalogs(nodeData);
@@ -188,20 +207,28 @@ export default {
         // 处理地图鼠标滑出事件
         handleMapCrossingMouseout(mapData) {
             console.log('父组件接收到了地图路口鼠标滑出事件:', mapData);
-            if (this.activeLeftTab === 'overview' && mapData) { // 总览
-                const id = mapData.id || (mapData.position[0] + mapData.position[1]);
+            if (!mapData) return;
+            const id = mapData.id || (mapData.position[0] + mapData.position[1]);
+            this.$refs.layout.handleDialogClose('offline_tip_' + id);
+            if (this.activeLeftTab === 'overview') { // 总览
                 this.$refs.layout.handleDialogClose('crossing3_' + id);
-            } else if (this.activeLeftTab === 'crossing' && mapData) { // 路口
-                
-                const id = mapData.id || (mapData.position[0] + mapData.position[1]);
+            } else if (this.activeLeftTab === 'crossing') { // 路口
                 this.$refs.layout.handleDialogClose('crossing3_' + id);
             }
         },
         // 处理地图点击事件
         handleMapCrossingClick(mapData, lnglat, pixel) {
             console.log('父组件接收到了地图路口点击事件:', mapData);
-            console.log('父组件接收到了地图路口点击事件:', lnglat);
-            console.log('父组件接收到了地图路口点击事件:', pixel);
+            // 离线/降级/故障状态不弹详情
+            const abnormalNames = ['离线', '降级', '故障'];
+            if (abnormalNames.includes(mapData.statusLabel || mapData.name)) {
+                this.$msg({
+                    title: '提示',
+                    message: `路口「${mapData.road || mapData.id}」设备${mapData.statusLabel || mapData.name},无法查看详情`,
+                    duration: 3000,
+                });
+                return;
+            }
             // 组装模拟数据
             const scale = window.innerWidth / 1920;
             let nodeData = {