ソースを参照

feat(cockpit): 补齐设施点与无位置统计

ChenJZ 13 時間 前
親
コミット
37ae0b9992

+ 71 - 3
src/modules/cockpit/mocks/contractMocks.js

@@ -1810,7 +1810,9 @@ function operatingInformationPayload(scenario, query = {}) {
     // 对象序列 → 运行信息面板不产定位按钮(票 50)。样例按行业用真实同形的设施码
     // (TCRQ/CNRQ 燃气、TCGC/CNGC 供水、TCWS/TCYS 排水),**不得**用 `MAP_POINTS` 的 MP-*——
     // 否则 mock 下按钮能点、真实下全 MISSING,同构纪律(票 08/18/38)被打破。
-    const metric = (code, name, category, unit, value, capacity, ratio, load, pointCodes) => ({
+    // 票 68:设施点图层从运行信息行上枚举「可上图的设施实体」(facilityId/facilityKind/facilityStatus),
+    // 所以 mock 的对应行也带这三个字段(同构纪律:不带上就永远演示不出设施点图层)。
+    const metric = (code, name, category, unit, value, capacity, ratio, load, pointCodes, facility = null) => ({
         metricCode: code,
         metricName: name,
         category,
@@ -1825,6 +1827,7 @@ function operatingInformationPayload(scenario, query = {}) {
         trend: [{ bucket: '2026-09-01', value }, { bucket: '2026-09-02', value }],
         abnormal: load === 'OVERLOAD',
         pointCodes,
+        ...(facility || {}),
     })
 
     const industries = blank
@@ -1848,7 +1851,10 @@ function operatingInformationPayload(scenario, query = {}) {
                   abnormalMetricCount: 1,
                   metrics: [
                       // 边界:真 0 是有效读数;超载项走 lv1 色键。
-                      metric('water_supply_flow', '供水流量', 'SUPPLY', 'm3/h', '0', '4000', '0.0000', 'NORMAL', ['TCGC202609020001']),
+                      // 票 68:带设施实体身份(水厂),设施点图层据此取实体详情坐标上图。
+                      metric('water_supply_flow', '供水流量', 'SUPPLY', 'm3/h', '0', '4000', '0.0000', 'NORMAL', ['TCGC202609020001'], {
+                          facilityId: '1', facilityKind: 'WATER_PLANT', facilityStatus: '0',
+                      }),
                       metric('water_supply_load', '供水负荷', 'CAPACITY', 'm3/h', '5000', '4000', '1.2500', 'OVERLOAD', ['CNGC202609020001']),
                       // 边界:能力基准缺失 → 负荷率也算不出来(界面显示占位符,不显示 0)。
                       metric('water_pressure', '供水压力', 'PRESSURE', 'MPa', '0.45', null, null, 'UNKNOWN', ['TCGC202609020002']),
@@ -1861,7 +1867,10 @@ function operatingInformationPayload(scenario, query = {}) {
                   abnormalMetricCount: 0,
                   metrics: [
                       metric('sewage_cod', '污水 COD', 'QUALITY', 'mg/L', '18.2', '50', '0.3640', 'NORMAL', ['TCWS202609020001']),
-                      metric('rainwater_flow', '雨水流量', 'FLOW', 'm3/h', '880', '1000', '0.8800', 'WATCH', ['TCYS202609020001']),
+                      // 票 68:带设施实体身份(泵站),与上面那条水厂一起覆盖「可上图 + 状态色」两条路径。
+                      metric('rainwater_flow', '雨水流量', 'FLOW', 'm3/h', '880', '1000', '0.8800', 'WATCH', ['TCYS202609020001'], {
+                          facilityId: '2', facilityKind: 'PUMP_STATION', facilityStatus: '2',
+                      }),
                   ],
               },
           ]
@@ -1893,6 +1902,63 @@ function operatingInformationPayload(scenario, query = {}) {
     }
 }
 
+// ---------------------------------------------------------------- 设施点实体详情(票 68)
+
+/**
+ * 票 68:设施点图层补坐标用的实体详情样例(水厂 / 泵站各一条)。
+ *
+ * 形状照抄真实 `/api/cockpit/v1/entities/{id}/detail`:坐标在 `coordinates` 上
+ * (缺坐标即 null,前端据此把该设施进「无地图位置」清单,不补默认值)。
+ * 两条样例与运行信息 mock 里带 `facilityId/facilityKind` 的指标行一一对应。
+ */
+const FACILITY_ENTITY_FIXTURES = {
+    'WATER_PLANT|1': {
+        category: 'WATER_PLANT',
+        id: '1',
+        code: 'WP001',
+        name: 'XX自来水厂',
+        status: '0',
+        location: '沅陵县城东',
+        coordinates: { longitude: 110.392, latitude: 28.437, endLongitude: null, endLatitude: null },
+    },
+    'PUMP_STATION|2': {
+        category: 'PUMP_STATION',
+        id: '2',
+        code: 'PS002',
+        name: '城东泵站2',
+        status: '2',
+        location: '沅陵县城东',
+        coordinates: { longitude: 110.385, latitude: 28.436, endLongitude: null, endLatitude: null },
+    },
+}
+
+/**
+ * 实体详情样例:查询串里的 `category` 决定查哪个设施表(与真接口同形)。
+ * 查不到就**抛错**(真接口是 404/业务错误,不是"返回一个空详情")。
+ */
+function facilityEntityDetailPayload(scenario, requested, query = {}) {
+    if (scenario === 'empty') throw new Error('mock: 实体不存在')
+    const category = String(query.category || '').toUpperCase()
+    const fixture = FACILITY_ENTITY_FIXTURES[`${category}|${requested}`]
+    if (!fixture) throw new Error(`mock: 实体 ${requested || '(空)'} 不存在`)
+    return {
+        requestId: 'mock-entity-detail',
+        apiVersion: 'v1',
+        readOnly: true,
+        category: fixture.category,
+        id: fixture.id,
+        code: fixture.code,
+        name: fixture.name,
+        status: fixture.status,
+        location: fixture.location,
+        district: null,
+        districtName: '未分区',
+        coordinates: fixture.coordinates,
+        attributes: {},
+        updatedAt: '2026-09-02T09:59:50+08:00',
+    }
+}
+
 /** 管理信息样例:四个「片区 × 行业」组合(含「未分区」与「有设备、没有设施」的城北)。 */
 function managementInformationPayload(scenario, query = {}) {
     const status = infraStatus(scenario)
@@ -3118,6 +3184,8 @@ const PAYLOADS = {
     '/overview/warning-settings': (s, _requested, context) => overviewWarningSettingsPayload(s, context.query),
     // 票 24:设施点位与管线几何读模型(票 30 的真实端点 `/infrastructure/geometry`,免鉴权)。
     '/infrastructure/geometry': (s, _requested, context) => pipeNetworkPayload(s, context.query),
+    // 票 68:四类设施点的实体详情(设施点图层补坐标;查询串 `category` 与真接口同形)。
+    '/entities/*/detail': (s, requested, context) => facilityEntityDetailPayload(s, requested, context.query),
 }
 
 /**

+ 12 - 4
src/modules/cockpit/stores/mapStore.js

@@ -31,8 +31,10 @@ import {
     emptyMapSummary,
     summaryView,
 } from '../domain/mapSummary.js'
-import { locationVerdict, coordinateOverview, facilityCoordinateExtra } from '../domain/coordinate.js'
+import { locationVerdict, coordinateOverview, facilityCoordinateExtra, mergeCoordinateExtras } from '../domain/coordinate.js'
+import { facilityPointCoordinateExtra } from '../domain/facilityPoints.js'
 import { usePipeNetworkStore } from './pipeNetworkStore.js'
+import { useFacilityPointStore } from './facilityPointStore.js'
 import { applyPointStates as applyStatusToObjects } from '../domain/statusPush.js'
 import { fetchMapSnapshot } from '../services/mapApi.js'
 
@@ -288,15 +290,21 @@ export const useMapStore = defineStore('mapStore', () => {
      * 票 07:坐标准入总览(后端全量口径 + 前端已加载口径 + 不上图清单)。
      * 组件只读这一个对象,不自己算坐标 —— 判定归 `domain/coordinate.js`。
      *
-     * 票 24:设施与管线也并进同一份总览。它们不是监测点、**不进**本 store 的对象序列
-     * (两套统计口径不得混算),但「哪些东西没上图」是跨类的,所以这里**只读**管线图层
+     * 票 24/68:设施与管线也并进同一份总览。它们不是监测点、**不进**本 store 的对象序列
+     * (两套统计口径不得混算),但「哪些东西没上图」是跨类的,所以这里**只读**两个图层
      * 折算出的准入结果并进总览;`blocked` 与 `blockedEntries` 仍由同一个对象产出,
      * 徽标与清单因此保持自洽。
+     *
+     * 设施点(票 68)与管线/构件是两份附加项(各自有独立的筛选条件),合并只做加法:
+     * 管线一份照旧、设施点一份新增,两边都按各自当前筛选裁剪。
      */
     const coordinate = computed(() => coordinateOverview(
         summary.value.objects,
         summary.value.coordinateSummary,
-        facilityCoordinateExtra(usePipeNetworkStore().filtered),
+        mergeCoordinateExtras(
+            facilityCoordinateExtra(usePipeNetworkStore().filtered),
+            facilityPointCoordinateExtra(useFacilityPointStore().filtered),
+        ),
     ))
 
     /**