ソースを参照

fix(cockpit): 事件定位高亮的解除——空目标也下发引擎,点高亮点位即还原

根因:highlightMapObject() 用 resolved.length 把空目标整段跳过,而空目标正是
highlightEntities(viewer, []) 的唯一还原入口(emphasisBase 记原值)。于是收起事件详情只
改写了 data-map-emphasis 读数与 DOM 点位尺寸,管线线宽与设备 billboard 一直停在强调值。
票 14 的 F6/F7 只守了引擎侧,没守住服务层有没有把空目标发下去。

- 空目标照发引擎(还原路径走通);
- 新增 releaseEmphasisIfPicked 挂在 dispatchPickResult:点到正被强调的对象(点位码 /
  pointCode / 标记 deviceCodes,判定复用 domain/mapEmphasis.shouldEmphasize)即解除,
  真实点击与 DEV 按坐标点选共用同一条下游;点别的对象或空白不解除;
- 引擎还原只写回记过的初值(restoreEmphasisBase 没记过回 null),不再给从未强调过的
  实体猜默认值(设备引线 width=2 曾被写成 undefined 变 1px)。

闸门 t69-current-warning 17/17、变异 t69-mutations 12/12;回归 t14 44/44。
按 AGENTS.md 未跑 build/dev/browser。
ChenJZ 14 時間 前
親
コミット
0767a5fbb0

+ 20 - 7
src/modules/cockpit/services/cesiumEngine.js

@@ -2348,13 +2348,24 @@ export function highlightEntities(viewer, codes) {
                 read('deviceCode'),
                 ...(Array.isArray(list) ? list : []),
             ]
-            // 每个实体都过判定:命中与**未命中**都要写回值,后者即"还原"。
+            // 每个实体都过判定:命中记初值再写强调值,未命中即"还原"。
             const hit = shouldEmphasize(candidates, wanted)
-            if (hit) matched += 1
-            const base = hit ? rememberEmphasisBase(entity) : restoreEmphasisBase(entity)
-
-            if (entity.billboard) entity.billboard.scale = hit ? EMPHASIS_STYLE.markerScale : base.billboardScale
-            if (entity.polyline) entity.polyline.width = hit ? EMPHASIS_STYLE.pipelineWidth : base.polylineWidth
+            if (hit) {
+                matched += 1
+                rememberEmphasisBase(entity)
+                if (entity.billboard) entity.billboard.scale = EMPHASIS_STYLE.markerScale
+                if (entity.polyline) entity.polyline.width = EMPHASIS_STYLE.pipelineWidth
+                continue
+            }
+            // 未命中 = 还原:**只写回记过的初值**。没记过说明这个实体从未被强调过,
+            // 此时猜一个默认值写回去会改到不该改的实体 —— 票 69 实证:设备引线
+            // (billboard 数据源里的 polyline,width=2)会被写成 undefined 而变 1px。
+            // 票 69 之前空目标整段被服务层跳过,这条"猜默认值"的路径几乎没被执行到;
+            // 修好空目标还原后它每次解除都会跑一遍,所以顺手收紧。
+            const base = restoreEmphasisBase(entity)
+            if (!base) continue
+            if (entity.billboard) entity.billboard.scale = base.billboardScale
+            if (entity.polyline) entity.polyline.width = base.polylineWidth
         }
     }
     return matched
@@ -2373,10 +2384,12 @@ function rememberEmphasisBase(entity) {
     return emphasisBase.get(entity)
 }
 
+/** 取出并清掉强调前的原值;**没记过就回 null**(调用方据此跳过写回,不猜默认值)。 */
 function restoreEmphasisBase(entity) {
     const base = emphasisBase.get(entity)
+    if (!base) return null
     emphasisBase.delete(entity)
-    return base || { billboardScale: 1, polylineWidth: undefined }
+    return base
 }
 
 /** 管线数据源(票 22 交付时才存在;没有就回 null,不当成错误)。 */

+ 38 - 3
src/modules/cockpit/services/mapService.js

@@ -3717,11 +3717,41 @@ export function pickDeviceOrPointAt(clientX, clientY) {
     return pickMarkerByIdentity(target.kind, target.code)
 }
 
+/**
+ * 票 69:点到**正被强调**的对象时解除整轮强调。
+ *
+ * 为什么放在派发链上:用户点击走 `overlay 节点 → pickDeviceOrPointAt → dispatchPickResult`,
+ * 解除挂在同一条下游,两条入口(真实点击 / DEV 按坐标点选)不会有一条漏掉。
+ * 判定只认「点的是高亮对象本身」:点位码、`pointCode`、标记的 `deviceCodes`
+ * (与引擎候选编码同一份集合,走 `domain/mapEmphasis.shouldEmphasize`);
+ * 点别的对象或空白**不**替用户取消上一轮强调(事件详情可能还开着)。
+ * 还原复用 `highlightMapObject([])` 的同一条空目标路径(引擎 `emphasisBase` 写回原值),
+ * 不在这里另写一套尺寸还原。
+ * @returns {boolean} 这一次点击是否真的解除了强调
+ */
+function releaseEmphasisIfPicked(resolved) {
+    const pick = resolved && resolved.pick
+    if (!pick || !emphasisCodes.length) return false
+    const marker = (markerPlan.markers || []).find((item) => item.code === pick.code) || null
+    const candidates = [
+        pick.code,
+        pick.pointCode,
+        ...(marker && Array.isArray(marker.deviceCodes) ? marker.deviceCodes : []),
+    ]
+    if (!shouldEmphasize(candidates, emphasisCodes)) return false
+    highlightMapObject([])
+    return true
+}
+
 /**
  * 派发一次点选结论(票 76 / C6 抽出):GPU 拾取与「按 DOM 身份点选」共用同一段下游 ——
  * 选中/呈现/收起/订阅派发只有这一处,两条入口不会各写一套行为。
  */
 function dispatchPickResult(resolved) {
+    // 票 69:点到的正是**当前被强调**的对象 —— 注意力已经落到地图上,这一轮强调到此为止。
+    // 用户真正的点击走这条(overlay 节点 → `pickDeviceOrPointAt` → 本函数),
+    // 与 DEV 把手同一条下游,所以解除也只需挂在这里一处。
+    releaseEmphasisIfPicked(resolved)
     if (resolved.target === 'device') {
         // 统一选择入口负责选中与呈现;不提前写 selectedDeviceCode,避免 store 的幂等检查吞掉更新。
         lastPicked = resolved.pick
@@ -3974,8 +4004,13 @@ export function clearPointMarkers() {
  * 所以「高亮关联管线」只能按设备码匹配;点位码则用于强调那个点位标记。
  * 引擎只调已存在实体的显示参数 —— 事件不新开图层;还没落地时(票 22 之前)返回 0,
  * 调用方如实说「尚未出现在地图上」,不伪造成成功。
+ *
+ * **空目标必须下发给引擎**(票 69 修):票 14 的契约是 `highlightEntities(viewer, [])` = 全部还原,
+ * 但这里曾用 `resolved.length` 把空目标整段跳过 —— DOM 覆盖层只承担点位视觉,于是收起详情只改写了
+ * `data-map-emphasis` 这个读数,管线线宽与设备 billboard 一直停在强调值(「高亮解除不了」的真根因)。
+ * 还原本来就是**空目标那条路径**,不能因为「没有目标」就不发。
  * @param {string|Array<string>} codes
- * @returns {number} 实际被强调的实体数(0 = 目标还不在图上 / 引擎没就绪)
+ * @returns {number} 实际被强调的实体数(0 = 目标还不在图上 / 引擎没就绪 / 刚还原完)
  */
 export function highlightMapObject(codes) {
     const wanted = (Array.isArray(codes) ? codes : [codes]).map((item) => String(item || '').trim()).filter(Boolean)
@@ -3994,8 +4029,8 @@ export function highlightMapObject(codes) {
     emphasisCodes = resolved
 
     const matched =
-        instance && instance.engine && instance.viewer && resolved.length
-            ? instance.engine.highlightEntities(instance.viewer, resolved)
+        instance && instance.engine && instance.viewer
+            ? instance.engine.highlightEntities(instance.viewer, resolved) || 0
             : 0
 
     // 强调结果写进宿主 dataset(`data-map-emphasis` = 当前被强调的实体数):