modeLayerPlan.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /**
  2. * 一级模式图层计划(票 46;规格 18 / 决策票 13)。
  3. *
  4. * 这是「一张图什么时候显示什么」的唯一领域接缝:不碰 Vue/Cesium/接口,不在组件或引擎
  5. * 二次过滤。设施点、管线和异常设备数据尚未全部具备时,计划如实输出 degradedNotes,
  6. * 不把缺数据伪装成空数据,也不伪造设施/管线标记。
  7. */
  8. import { MAP_OBJECT_KIND } from './mapSummary.js'
  9. import { planPointMarkers, pointLegend } from './pointStatus.js'
  10. export const MODE_LAYER = {
  11. OVERVIEW: 'overview',
  12. INFRASTRUCTURE: 'infrastructure',
  13. MONITORING: 'monitoring',
  14. WARNING_RESPONSE: 'warningResponse',
  15. }
  16. export const MODE_SELECTION = {
  17. PRESERVE: 'preserve',
  18. CLEAR: 'clear',
  19. }
  20. const ATTENTION_STATUSES = new Set(['ALARM', 'WARNING'])
  21. const MODE_KEYS = new Set(Object.values(MODE_LAYER))
  22. function hasArray(value) {
  23. return Array.isArray(value)
  24. }
  25. function note(key, text, mode) {
  26. return { key, text, mode }
  27. }
  28. /** 关联设备的模式过滤唯一判据;数据可用性仍由 deviceLayer.js 先判。 */
  29. export function isAssociatedDeviceVisible(mode, status) {
  30. if (mode === MODE_LAYER.INFRASTRUCTURE) return false
  31. if (mode === MODE_LAYER.WARNING_RESPONSE) return ATTENTION_STATUSES.has(status)
  32. return true
  33. }
  34. /** 总览↔分项保留选择;分项↔分项清空选择。 */
  35. export function planModeSelectionTransition({ fromMode, toMode, selection } = {}) {
  36. const current = selection || { level: 'none', pointCode: '', deviceCode: '' }
  37. if (!fromMode || fromMode === toMode) return { action: MODE_SELECTION.PRESERVE, selection: current }
  38. if (fromMode === MODE_LAYER.OVERVIEW || toMode === MODE_LAYER.OVERVIEW) {
  39. return { action: MODE_SELECTION.PRESERVE, selection: current }
  40. }
  41. return {
  42. action: MODE_SELECTION.CLEAR,
  43. selection: { level: 'none', pointCode: '', deviceCode: '' },
  44. }
  45. }
  46. function abnormalCodeSet(abnormalDevices) {
  47. return new Set(
  48. (Array.isArray(abnormalDevices) ? abnormalDevices : [])
  49. .filter((item) => item && ATTENTION_STATUSES.has(item.status))
  50. .map((item) => item.code)
  51. .filter(Boolean),
  52. )
  53. }
  54. function abnormalStatusMap(abnormalDevices) {
  55. return new Map(
  56. (Array.isArray(abnormalDevices) ? abnormalDevices : [])
  57. .filter((item) => item && item.code)
  58. .map((item) => [item.code, item.status || '']),
  59. )
  60. }
  61. function isAbnormalPoint(object, abnormalCodes) {
  62. return object && object.kind === MAP_OBJECT_KIND.MONITORING_POINT &&
  63. Array.isArray(object.deviceCodes) && object.deviceCodes.some((code) => abnormalCodes.has(code))
  64. }
  65. function visibleMapObjects(mode, objects, abnormalDevices) {
  66. const abnormalCodes = abnormalCodeSet(abnormalDevices)
  67. const source = Array.isArray(objects) ? objects : []
  68. if (mode === MODE_LAYER.INFRASTRUCTURE) return []
  69. if (mode === MODE_LAYER.WARNING_RESPONSE) {
  70. return source.filter((object) => {
  71. if (!object) return false
  72. if (object.kind === MAP_OBJECT_KIND.MONITORING_POINT) return isAbnormalPoint(object, abnormalCodes)
  73. if (object.kind === MAP_OBJECT_KIND.UNASSOCIATED_EQUIPMENT) return abnormalCodes.has(object.code)
  74. return false
  75. })
  76. }
  77. return source.filter((object) => object && (
  78. object.kind === MAP_OBJECT_KIND.MONITORING_POINT ||
  79. object.kind === MAP_OBJECT_KIND.UNASSOCIATED_EQUIPMENT
  80. ))
  81. }
  82. function planNonPointLayers({ mode, entered, facilities, pipelines, degradedNotes }) {
  83. const facilityVisible = mode === MODE_LAYER.OVERVIEW || mode === MODE_LAYER.INFRASTRUCTURE
  84. const pipelineVisible = entered && MODE_KEYS.has(mode)
  85. const facilityReady = hasArray(facilities)
  86. const pipelineReady = hasArray(pipelines)
  87. if (facilityVisible && !facilityReady) degradedNotes.push(note('facilities-unready', '设施数据未就绪', mode))
  88. if (pipelineVisible && !pipelineReady) degradedNotes.push(note('pipelines-unready', '管线数据未就绪', mode))
  89. // 票 33:**原样引用,不 slice()**。每 30s freshness tick 会让计划整体重算——
  90. // 以前这里每次 `slice()` 出新数组,`MapCanvas` 的 `deep:false` watcher 靠引用判变化,
  91. // 于是「数据没变」也被判成变了,批批触发管线全量重同步(票 26 诊断的放大器之一)。
  92. // 计划的消费者(mapService/MapCanvas)只读不写这两个数组,别名是安全的。
  93. return {
  94. facilities: facilityVisible && facilityReady ? facilities : [],
  95. pipelines: pipelineVisible && pipelineReady ? pipelines : [],
  96. layerStates: {
  97. facilities: facilityVisible ? (facilityReady ? 'ready' : 'degraded') : 'hidden',
  98. pipelines: pipelineVisible ? (pipelineReady ? 'ready' : 'degraded') : 'hidden',
  99. },
  100. }
  101. }
  102. function selectionVisible({ mode, selection, objects, markers, associatedDevices, abnormalCodes }) {
  103. const current = selection || {}
  104. if (!current.level || current.level === 'none') return false
  105. // 设备级选择**优先看设备**(设备级选择仍保留 pointCode 供"返回点位"用,
  106. // 先查 pointCode 会把"设备被本模式隐藏"误判成"点位可见 ⇒ 显示")。
  107. if (current.deviceCode) {
  108. // 该设备是**当前点位的关联设备**(在 `associatedDevices` 里找得到):
  109. // 详情面板与该设备能否上图无关 —— 一台 `no-location`(后端判不可上图)的设备
  110. // 仍然要看它的档案/指标/预警等分区,所以这里**不要求 placement === 'drawn'**,
  111. // 只按模式可见性(预警处置只显示报警/预警)判定。
  112. // (本票真机人工走查抓到:默认现网边界设备统一 `UNVERIFIED_COORDINATE/no-location`,
  113. // 点设备卡片后 `selectionVisible=false` ⇒ 外壳 `<aside v-if>` 卸载 ⇒ 摘要面板消失。)
  114. const row = (Array.isArray(associatedDevices) ? associatedDevices : [])
  115. .find((item) => item && item.code === current.deviceCode)
  116. if (row) {
  117. const parent = (Array.isArray(objects) ? objects : []).find((item) =>
  118. item && item.kind === MAP_OBJECT_KIND.MONITORING_POINT && item.code === row.pointCode)
  119. return Boolean(parent && isAssociatedDeviceVisible(mode, row.status) &&
  120. (mode !== MODE_LAYER.WARNING_RESPONSE || isAbnormalPoint(parent, abnormalCodes)))
  121. }
  122. // 未关联设备:以地图对象身份判(基础设施整体隐藏;预警处置只认异常设备)。
  123. const object = (Array.isArray(objects) ? objects : [])
  124. .find((item) => item && item.kind === MAP_OBJECT_KIND.UNASSOCIATED_EQUIPMENT && item.code === current.deviceCode)
  125. return Boolean(object && (mode !== MODE_LAYER.INFRASTRUCTURE) &&
  126. (mode !== MODE_LAYER.WARNING_RESPONSE || abnormalCodes.has(object.code)))
  127. }
  128. if (current.pointCode) {
  129. const point = (Array.isArray(objects) ? objects : []).find((item) =>
  130. item && item.kind === MAP_OBJECT_KIND.MONITORING_POINT && item.code === current.pointCode)
  131. return Boolean(point && mode !== MODE_LAYER.INFRASTRUCTURE &&
  132. (mode !== MODE_LAYER.WARNING_RESPONSE || isAbnormalPoint(point, abnormalCodes)))
  133. }
  134. return false
  135. }
  136. /**
  137. * 统一输出当前模式的图层计划。
  138. *
  139. * @returns {{mode:string, entered:boolean, markers:Array, blocked:Array, legend:Array,
  140. * locateTargets:Array, degradedNotes:Array, facilities:Array, pipelines:Array,
  141. * associatedDevices:Array, layerStates:object, selection:object}}
  142. */
  143. export function planModeLayers({
  144. mode = MODE_LAYER.OVERVIEW,
  145. objects = [],
  146. abnormalDevices,
  147. facilities,
  148. pipelines,
  149. associatedDevices = [],
  150. selection,
  151. entered = true,
  152. } = {}) {
  153. const normalizedMode = MODE_KEYS.has(mode) ? mode : MODE_LAYER.OVERVIEW
  154. const degradedNotes = []
  155. if (entered !== true) {
  156. return {
  157. mode: normalizedMode,
  158. entered: false,
  159. markers: [],
  160. blocked: [],
  161. legend: [],
  162. locateTargets: [],
  163. degradedNotes: [],
  164. facilities: [],
  165. pipelines: [],
  166. associatedDevices: [],
  167. layerStates: { facilities: 'hidden', pipelines: 'hidden', associatedDevices: 'hidden' },
  168. selection: { visible: false, action: MODE_SELECTION.PRESERVE },
  169. }
  170. }
  171. const source = visibleMapObjects(normalizedMode, objects, abnormalDevices)
  172. const pointPlan = planPointMarkers(source)
  173. const abnormalCodes = abnormalCodeSet(abnormalDevices)
  174. const abnormalDataReady = hasArray(abnormalDevices)
  175. if (normalizedMode === MODE_LAYER.WARNING_RESPONSE && !abnormalDataReady) {
  176. degradedNotes.push(note('abnormal-devices-unready', '异常设备数据未就绪,预警处置不猜测异常点', normalizedMode))
  177. }
  178. const visiblePointCodes = new Set(source.filter((item) => item.kind === MAP_OBJECT_KIND.MONITORING_POINT).map((item) => item.code))
  179. const visibleDevices = (Array.isArray(associatedDevices) ? associatedDevices : [])
  180. .filter((row) => row && visiblePointCodes.has(row.pointCode) && row.placement === 'drawn' && isAssociatedDeviceVisible(normalizedMode, row.status))
  181. const nonPoint = planNonPointLayers({
  182. mode: normalizedMode,
  183. entered: true,
  184. facilities,
  185. pipelines,
  186. degradedNotes,
  187. })
  188. const locateTargets = pointPlan.markers.map((marker) => ({
  189. kind: marker.kind,
  190. code: marker.code,
  191. name: marker.name,
  192. longitude: marker.longitude,
  193. latitude: marker.latitude,
  194. }))
  195. return {
  196. mode: normalizedMode,
  197. entered: true,
  198. // `markers` 与 `legend` 都来自同一份 pointPlan,地图服务只消费它们,不重判模式。
  199. markers: pointPlan.markers,
  200. blocked: pointPlan.blocked,
  201. legend: pointLegend(pointPlan.byStatus),
  202. byStatus: pointPlan.byStatus,
  203. locateTargets,
  204. degradedNotes,
  205. facilities: nonPoint.facilities,
  206. pipelines: nonPoint.pipelines,
  207. associatedDevices: visibleDevices,
  208. blockedAssociatedDevices: (Array.isArray(associatedDevices) ? associatedDevices : [])
  209. .filter((row) => row && row.placement !== 'drawn'),
  210. layerStates: {
  211. ...nonPoint.layerStates,
  212. associatedDevices: normalizedMode === MODE_LAYER.INFRASTRUCTURE ? 'hidden' : 'ready',
  213. },
  214. selection: {
  215. visible: selectionVisible({
  216. mode: normalizedMode,
  217. selection,
  218. objects,
  219. markers: pointPlan.markers,
  220. associatedDevices,
  221. abnormalCodes,
  222. }),
  223. action: MODE_SELECTION.PRESERVE,
  224. },
  225. }
  226. }
  227. /** 供测试和外壳使用的状态判断,不泄漏内部 Set。 */
  228. export function isAttentionDeviceStatus(status) {
  229. return ATTENTION_STATUSES.has(status)
  230. }
  231. /** 仅供验证说明使用:异常设备状态来源是设备事实,不是点位聚合状态。 */
  232. export function abnormalStatusesFromDevices(abnormalDevices) {
  233. return [...abnormalStatusMap(abnormalDevices).entries()].map(([code, status]) => ({ code, status }))
  234. }