Quellcode durchsuchen

路口设备状态统一化,离线路口拦截提示及列表字段屏蔽

  - mock/api.js: 提取统一函数_getDeviceStatus(id)基于路口ID生成稳定设备状态,
    所有API(列表、面板、详情)共用,解决同一路口在不同视图状态不一致的问题;
    _makeIntersectionConfig及各API补齐status字段
  - StatusMonitoring: showCrossingDetailDialogs和showCrossingDalogs增加离线检查,
    设备离线时弹出提示不打开详情弹窗;多窗口入口预加载数据传递给CrossingMultiView
  - CrossingMultiView: rebuildSlots优先使用预加载数据,避免header短暂显示错误状态
  - CrossingListPanel: 离线设备的时间偏差、周期、相位状态、版本信息显示"--"
画安 vor 2 Wochen
Ursprung
Commit
4a4365cdda

+ 16 - 2
src/components/ui/CrossingListPanel.vue

@@ -14,7 +14,8 @@
 
             <TechTable :columns="tableColumns" :data="tableList" height="100%">
                 <template #phaseStatus="{ row }">
-                    <div class="mini-chart-wrapper">
+                    <div v-if="row._offline" class="offline-placeholder">--</div>
+                    <div v-else class="mini-chart-wrapper">
                         <SignalTimingChart :phaseData="row.phaseData" :cycleLength="row.cycle" :currentTime="row.currentTime || 0" :showAxis="false" :showScanLine="true" :showScanLineLabel="false" :autoScan="true" />
                     </div>
                 </template>
@@ -152,7 +153,14 @@ export default {
                 console.log('发起请求,参数:', params);
 
                 const data = await apiGetCrossingList(params);
-                this.tableList = data?.list || data || [];
+                const list = data?.list || data || [];
+                // 离线设备:时间偏差、周期、版本信息显示"--"
+                this.tableList = list.map(row => {
+                    if (row.status === '离线') {
+                        return { ...row, timeOffset: '--', cycle: '--', version: '--', _offline: true };
+                    }
+                    return { ...row, _offline: false };
+                });
                 this.pagination.total = data?.total || 0;
             } catch (error) {
                 console.error('获取列表失败', error);
@@ -330,6 +338,12 @@ export default {
 
 /* ================= 针对插槽内容的样式 ================= */
 
+/* 离线设备占位符 */
+.offline-placeholder {
+    color: rgba(255, 255, 255, 0.3);
+    text-align: center;
+}
+
 /* 控制相位图表在表格内的高度 */
 .mini-chart-wrapper {
     height: 30px;

+ 1 - 1
src/components/ui/CrossingMultiView.vue

@@ -139,7 +139,7 @@ export default {
                 if (s.data && s.headerData) oldMap[s.data.id] = s.headerData;
             });
             this.localSlots = this.crossings.map(c => ({
-                type: 'panel', data: c, headerData: oldMap[c.id] || null
+                type: 'panel', data: c, headerData: oldMap[c.id] || c._preloadedData || null
             }));
             // 如果展开的路口被外部移除了,退出展开
             if (this.expandedId) {

+ 19 - 1
src/mock/api.js

@@ -37,6 +37,13 @@ function delay(base = 200) { return sleep(base + Math.floor(Math.random() * 200)
 function ok(data) { return { code: 200, message: 'success', data } }
 function fail(msg, code = 400) { return { code, message: msg, data: null } }
 
+/** 根据路口ID生成稳定的设备状态(所有API共用,确保一致) */
+function _getDeviceStatus(id) {
+  const seed = id ? Array.from(id).reduce((s, c, i) => s + c.charCodeAt(0) * (i + 1), 0) : 0
+  const statusList = ['在线', '在线', '在线', '在线', '在线', '在线', '在线', '离线']
+  return statusList[seed % statusList.length]
+}
+
 /** 基于当前秒数产生稳定随机(同一秒内多次调用返回相同值) */
 function seededRand(seed) {
   const x = Math.sin(seed) * 10000
@@ -118,6 +125,7 @@ function _makeIntersectionConfig(id, name, { fixedNsGreen } = {}) {
   ]
 
   return {
+    status: _getDeviceStatus(id),
     signals: {
       ns: { phaseName: phases[0], time: countdown, isGreen: nsGreen },
       ew: { phaseName: phases[1], time: countdown, isGreen: !nsGreen },
@@ -330,6 +338,7 @@ export async function apiGetIntersectionData(id, { fixedNsGreen } = {}) {
   const nsGreen = nsGreenVal
 
   const config = base ? {
+    status: base.status || _getDeviceStatus(id),
     signals: {
       ns: { ...base.signals.ns, time: Math.max(1, cycle - elapsed), isGreen: nsGreen },
       ew: { ...base.signals.ew, time: elapsed || 1, isGreen: !nsGreen },
@@ -606,7 +615,7 @@ export async function apiGetCrossingList(params = {}) {
     const phaseData = _makePhaseData(cycleLength, false)
     return {
       ...r,
-      status: statuses[Math.floor(seededRand(i + 42) * statuses.length)],
+      status: _getDeviceStatus(r.id),
       cycle: cycleLength,
       phaseData,
       currentTime: Math.floor(seededRand(i * 31 + page * 97) * cycleLength),
@@ -788,6 +797,10 @@ export async function apiGetCrossingPanelData(id) {
   const point = DB.points.find(p => p.id === id)
   const config = DB.intersectionConfigs[id] || _makeIntersectionConfig(id, null, { fixedNsGreen: false })
   const seed = id ? id.charCodeAt(id.length - 1) : 0
+  // 确保 config 有 status
+  if (!config.status) {
+    config.status = _getDeviceStatus(id)
+  }
 
   const preset = DB.signalTimings[id]
   const cycleLength = preset ? preset.data.cycleLength : [100, 120, 130, 140, 150, 160][Math.abs(seed) % 6]
@@ -818,6 +831,11 @@ export async function apiGetCrossingDetailData(id) {
   // 用 id 的全部字符生成稳定 seed(加权位置避免 charCode 总和碰撞)
   const seed = id ? Array.from(id).reduce((s, c, i) => s + c.charCodeAt(0) * (i + 1), 0) : 0
 
+  // 确保 config 有 status 字段(预存配置可能缺失)
+  if (!config.status) {
+    config.status = _getDeviceStatus(id)
+  }
+
   // 从真实阶段数据推导周期和相位
   const preset = DB.signalTimings[id]
   const cycleLength = preset ? preset.data.cycleLength : [100, 120, 130, 140, 150, 160][seed % 6]

+ 22 - 3
src/views/StatusMonitoring.vue

@@ -388,9 +388,20 @@ export default {
             } catch (e) { /* ignore */ }
         },
         // 显示路口弹窗组(多选分屏)
-        showCrossingDalogs(nodeData) {
+        async showCrossingDalogs(nodeData) {
             console.log('路口多选', nodeData.id, nodeData.label);
 
+            // 0. 离线检查
+            const detailData = await apiGetCrossingDetailData(nodeData.id);
+            if (detailData?.intersectionData?.status !== '在线') {
+                this.$msg({
+                    title: '提示',
+                    message: `路口「${nodeData.label || nodeData.id}」设备离线,无法查看详情`,
+                    duration: 3000,
+                });
+                return;
+            }
+
             // 1. 已选中 → 不重复操作
             const existIndex = this.crossingSelections.findIndex(c => c.id === nodeData.id);
             if (existIndex !== -1) {
@@ -402,8 +413,8 @@ export default {
                 this.crossingSelections.shift();
             }
 
-            // 3. 追加选中
-            this.crossingSelections.push({ ...nodeData });
+            // 3. 追加选中,带上预加载数据避免重复请求
+            this.crossingSelections.push({ ...nodeData, _preloadedData: detailData });
 
             // 4. 打开或更新弹窗
             this.openCrossingMultiView();
@@ -449,6 +460,14 @@ export default {
         async showCrossingDetailDialogs(nodeData) {
             console.log('显示路口详情弹窗组', nodeData.id, nodeData.label);
             const detailData = await apiGetCrossingDetailData(nodeData.id);
+            if (detailData?.intersectionData?.status !== '在线') {
+                this.$msg({
+                    title: '提示',
+                    message: `路口「${nodeData.label || nodeData.id}」设备离线,无法查看详情`,
+                    duration: 3000,
+                });
+                return;
+            }
             const dialogId = 'crossing_detail' + nodeData.id;
             this.$refs.layout.openDialog({
                 id: dialogId,