topo-engine 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -1
- package/dist/index.d.ts +72 -44
- package/dist/style.css +11 -11
- package/dist/topo-engine.cjs +1177 -152
- package/dist/topo-engine.cjs.map +1 -1
- package/dist/topo-engine.js +1177 -152
- package/dist/topo-engine.js.map +1 -1
- package/dist/topo-engine.umd.cjs +1177 -152
- package/dist/topo-engine.umd.cjs.map +1 -1
- package/docs/API.md +148 -31
- package/package.json +1 -1
package/dist/topo-engine.js
CHANGED
|
@@ -6658,11 +6658,11 @@ var parseGradient$1 = /* @__PURE__ */ function() {
|
|
|
6658
6658
|
}
|
|
6659
6659
|
}
|
|
6660
6660
|
function matchPositioning() {
|
|
6661
|
-
var
|
|
6662
|
-
if (
|
|
6661
|
+
var location2 = matchCoordinates();
|
|
6662
|
+
if (location2.x || location2.y) {
|
|
6663
6663
|
return {
|
|
6664
6664
|
type: "position",
|
|
6665
|
-
value:
|
|
6665
|
+
value: location2
|
|
6666
6666
|
};
|
|
6667
6667
|
}
|
|
6668
6668
|
}
|
|
@@ -25095,11 +25095,14 @@ class TopoApi {
|
|
|
25095
25095
|
this._topoLines = /* @__PURE__ */ new Map();
|
|
25096
25096
|
this._cards = /* @__PURE__ */ new Map();
|
|
25097
25097
|
this._animations = /* @__PURE__ */ new Map();
|
|
25098
|
+
this._glowNodes = /* @__PURE__ */ new Map();
|
|
25099
|
+
this._glowRaf = null;
|
|
25098
25100
|
this._nodeLabels = /* @__PURE__ */ new Map();
|
|
25099
25101
|
this._originalNodeStyles = /* @__PURE__ */ new Map();
|
|
25100
25102
|
this._originalEdgeStyles = /* @__PURE__ */ new Map();
|
|
25101
25103
|
this._nodeColorOverrides = /* @__PURE__ */ new Map();
|
|
25102
25104
|
this._custColorOverrides = /* @__PURE__ */ new Map();
|
|
25105
|
+
this._cellFx = /* @__PURE__ */ new Map();
|
|
25103
25106
|
this._bindConnGraphEvents();
|
|
25104
25107
|
}
|
|
25105
25108
|
/** 数据刷新(图重新渲染后调用) */
|
|
@@ -25124,6 +25127,7 @@ class TopoApi {
|
|
|
25124
25127
|
this._removeAllConnectionsInternal();
|
|
25125
25128
|
this._unbindConnGraphEvents();
|
|
25126
25129
|
this._removeAllAnimationsInternal();
|
|
25130
|
+
this._removeAllGlowsInternal();
|
|
25127
25131
|
this._removeAllTopoLinesInternal();
|
|
25128
25132
|
this._hideAllCardsInternal();
|
|
25129
25133
|
this._listeners.clear();
|
|
@@ -25132,6 +25136,7 @@ class TopoApi {
|
|
|
25132
25136
|
this._nodeLabels.clear();
|
|
25133
25137
|
this._nodeColorOverrides.clear();
|
|
25134
25138
|
this._custColorOverrides.clear();
|
|
25139
|
+
this._cellFx.clear();
|
|
25135
25140
|
this._graph = null;
|
|
25136
25141
|
this._model = null;
|
|
25137
25142
|
this._layout = null;
|
|
@@ -25249,11 +25254,163 @@ class TopoApi {
|
|
|
25249
25254
|
}
|
|
25250
25255
|
}
|
|
25251
25256
|
// ============================================================
|
|
25257
|
+
// 统一目标解析(id / psrId / assetNo)
|
|
25258
|
+
//
|
|
25259
|
+
// 全库约定:凡入参语义为「节点/设备/用户」的方法,一律接受三种引用:
|
|
25260
|
+
// - 节点内部 id 或 psrId(设备编码) → 普通节点(变压器/开关/导线点/计量箱…)
|
|
25261
|
+
// - 客户 assetNo(资产编号,兼容 consNo/consId)→ 计量箱内该户「电表户」
|
|
25262
|
+
// 解析结果:
|
|
25263
|
+
// { kind:'node', id, node } 普通节点
|
|
25264
|
+
// { kind:'customer', boxId, index, boxNode, customer } 箱内电表户
|
|
25265
|
+
// ============================================================
|
|
25266
|
+
/** 统一解析 ref(内部实现;对外用 resolveRef) */
|
|
25267
|
+
_resolveTarget(ref2) {
|
|
25268
|
+
if (ref2 == null) return null;
|
|
25269
|
+
const e2 = this._parseEndpoint(ref2);
|
|
25270
|
+
if (!e2) return null;
|
|
25271
|
+
if (e2.kind === "node") {
|
|
25272
|
+
return { kind: "node", id: e2.id, node: this._nodeByIdMap.get(e2.id) || null };
|
|
25273
|
+
}
|
|
25274
|
+
const boxNode = this._nodeByIdMap.get(e2.boxId) || null;
|
|
25275
|
+
const customers = boxNode && Array.isArray(boxNode._customers) ? boxNode._customers : [];
|
|
25276
|
+
return {
|
|
25277
|
+
kind: "customer",
|
|
25278
|
+
boxId: e2.boxId,
|
|
25279
|
+
index: e2.index,
|
|
25280
|
+
boxNode,
|
|
25281
|
+
customer: customers[e2.index] || null,
|
|
25282
|
+
customers
|
|
25283
|
+
};
|
|
25284
|
+
}
|
|
25285
|
+
/** 解析到“节点 id”层:customer 统一落到其所在计量箱节点 */
|
|
25286
|
+
_targetNodeId(ref2) {
|
|
25287
|
+
const t = this._resolveTarget(ref2);
|
|
25288
|
+
return t ? t.kind === "node" ? t.id : t.boxId : null;
|
|
25289
|
+
}
|
|
25290
|
+
/** 解析到“图 id”层:普通节点=节点 id;电表户=`${boxId}#cust${index}`(唯一键) */
|
|
25291
|
+
_targetGraphKey(ref2) {
|
|
25292
|
+
const t = this._resolveTarget(ref2);
|
|
25293
|
+
if (!t) return null;
|
|
25294
|
+
return t.kind === "node" ? t.id : `${t.boxId}#cust${t.index}`;
|
|
25295
|
+
}
|
|
25296
|
+
/** 目标中心的世界坐标(节点中心 / 户表位中心) */
|
|
25297
|
+
_targetWorld(ref2) {
|
|
25298
|
+
const e2 = this._parseEndpoint(ref2);
|
|
25299
|
+
if (!e2) return null;
|
|
25300
|
+
if (e2.kind === "customer") return this._endpointWorld(e2);
|
|
25301
|
+
const p = this._layout.pos.get(e2.id);
|
|
25302
|
+
return p ? { x: p.x, y: p.y } : null;
|
|
25303
|
+
}
|
|
25304
|
+
// ---- 箱内电表户特效(_cellFx)读写 ----
|
|
25305
|
+
_cellFxKey(boxId, index) {
|
|
25306
|
+
return `${boxId}#cust${index}`;
|
|
25307
|
+
}
|
|
25308
|
+
/** 取某户特效 descriptor(无则新建空对象,不落库) */
|
|
25309
|
+
_cellFxGet(boxId, index) {
|
|
25310
|
+
const key = this._cellFxKey(boxId, index);
|
|
25311
|
+
let d2 = this._cellFx.get(key);
|
|
25312
|
+
if (!d2) {
|
|
25313
|
+
d2 = {};
|
|
25314
|
+
this._cellFx.set(key, d2);
|
|
25315
|
+
}
|
|
25316
|
+
return d2;
|
|
25317
|
+
}
|
|
25318
|
+
/** 删除某户特效键(若因此无任何特效则一并清除该箱 style 上的 _cellFx) */
|
|
25319
|
+
_cellFxDrop(boxId, index) {
|
|
25320
|
+
const key = this._cellFxKey(boxId, index);
|
|
25321
|
+
this._cellFx.delete(key);
|
|
25322
|
+
}
|
|
25323
|
+
/** 把某只计量箱的 _cellFx 映射刷成 style(无特效则移除 style 字段) */
|
|
25324
|
+
_flushCellFx(boxId) {
|
|
25325
|
+
if (!this._graph) return;
|
|
25326
|
+
const out = {};
|
|
25327
|
+
let any = false;
|
|
25328
|
+
for (const [key, d2] of this._cellFx) {
|
|
25329
|
+
if (!key.startsWith(`${boxId}#cust`)) continue;
|
|
25330
|
+
const idx = Number(key.slice(key.indexOf("#cust") + 5));
|
|
25331
|
+
if (!d2 || !d2.hl && !d2.note && !d2.glow && !d2.pulse) {
|
|
25332
|
+
this._cellFx.delete(key);
|
|
25333
|
+
continue;
|
|
25334
|
+
}
|
|
25335
|
+
out[idx] = d2;
|
|
25336
|
+
any = true;
|
|
25337
|
+
}
|
|
25338
|
+
const cur = this._safeNodeStyle(boxId, {});
|
|
25339
|
+
this._graph.updateNodeData([{ id: boxId, style: { ...cur, _cellFx: any ? out : void 0 } }]);
|
|
25340
|
+
this._graph.render();
|
|
25341
|
+
}
|
|
25342
|
+
/** 清理失效的户特效记录(箱/户不存在时自动丢弃),返回受影响箱列表 */
|
|
25343
|
+
_pruneCellFx() {
|
|
25344
|
+
const touched = /* @__PURE__ */ new Set();
|
|
25345
|
+
for (const [key] of this._cellFx) {
|
|
25346
|
+
const sep = key.indexOf("#cust");
|
|
25347
|
+
if (sep < 0) {
|
|
25348
|
+
this._cellFx.delete(key);
|
|
25349
|
+
continue;
|
|
25350
|
+
}
|
|
25351
|
+
const boxId = key.slice(0, sep);
|
|
25352
|
+
const idx = Number(key.slice(sep + 5));
|
|
25353
|
+
const box2 = this._nodeByIdMap.get(boxId);
|
|
25354
|
+
if (!box2 || !Array.isArray(box2._customers) || idx < 0 || idx >= box2._customers.length) {
|
|
25355
|
+
this._cellFx.delete(key);
|
|
25356
|
+
touched.add(boxId);
|
|
25357
|
+
}
|
|
25358
|
+
}
|
|
25359
|
+
return touched;
|
|
25360
|
+
}
|
|
25361
|
+
// ============================================================
|
|
25252
25362
|
// 一、查询 API
|
|
25253
25363
|
// ============================================================
|
|
25254
|
-
/**
|
|
25364
|
+
/** 获取单个节点(ref = 节点 id/psrId/资产编号 assetNo)
|
|
25365
|
+
* 命中电表户时返回其所在计量箱节点,并附带 customer/customerIndex 等客户信息 */
|
|
25255
25366
|
getNode(nodeId) {
|
|
25256
|
-
|
|
25367
|
+
const t = this._resolveTarget(nodeId);
|
|
25368
|
+
if (!t) return null;
|
|
25369
|
+
if (t.kind === "node") return t.node || null;
|
|
25370
|
+
if (!t.boxNode) return null;
|
|
25371
|
+
const box2 = t.boxNode;
|
|
25372
|
+
return {
|
|
25373
|
+
...box2,
|
|
25374
|
+
targetKind: "customer",
|
|
25375
|
+
meterBoxId: t.boxId,
|
|
25376
|
+
customer: t.customer,
|
|
25377
|
+
customerIndex: t.index,
|
|
25378
|
+
customerCount: t.customers.length
|
|
25379
|
+
};
|
|
25380
|
+
}
|
|
25381
|
+
/**
|
|
25382
|
+
* 解析任意引用 → 标准目标描述(调试 / 业务分支判断用)。
|
|
25383
|
+
* @param {string} ref 节点 id / psrId / 资产编号(assetNo,兼容 consNo/consId)
|
|
25384
|
+
* @returns {object|null}
|
|
25385
|
+
* kind='node' → { kind, id, node }
|
|
25386
|
+
* kind='customer' → { kind, boxId, boxNode, index, customer, key, assetNo, name }
|
|
25387
|
+
*/
|
|
25388
|
+
resolveRef(ref2) {
|
|
25389
|
+
const t = this._resolveTarget(ref2);
|
|
25390
|
+
if (!t) return null;
|
|
25391
|
+
if (t.kind === "node") {
|
|
25392
|
+
return { kind: "node", id: t.id, node: t.node };
|
|
25393
|
+
}
|
|
25394
|
+
const c = t.customer || {};
|
|
25395
|
+
return {
|
|
25396
|
+
kind: "customer",
|
|
25397
|
+
boxId: t.boxId,
|
|
25398
|
+
boxNode: t.boxNode,
|
|
25399
|
+
index: t.index,
|
|
25400
|
+
customer: t.customer,
|
|
25401
|
+
key: this._cellFxKey(t.boxId, t.index),
|
|
25402
|
+
assetNo: c.assetNo != null ? c.assetNo : c.consNo != null ? c.consNo : c.consId,
|
|
25403
|
+
name: c.realConsName || c.consName || ""
|
|
25404
|
+
};
|
|
25405
|
+
}
|
|
25406
|
+
/**
|
|
25407
|
+
* 按资产编号 / 客户编号取客户档案包装(也可传所在箱的 psrId 时返回 null)。
|
|
25408
|
+
* @returns {{ node, customer, index, count, boxId } | null}
|
|
25409
|
+
*/
|
|
25410
|
+
getCustomer(ref2) {
|
|
25411
|
+
const t = this._resolveTarget(ref2);
|
|
25412
|
+
if (!t || t.kind !== "customer") return null;
|
|
25413
|
+
return { node: t.boxNode, customer: t.customer, index: t.index, count: t.customers.length, boxId: t.boxId };
|
|
25257
25414
|
}
|
|
25258
25415
|
/** 获取所有节点(只读副本) */
|
|
25259
25416
|
getAllNodes() {
|
|
@@ -25285,15 +25442,17 @@ class TopoApi {
|
|
|
25285
25442
|
getSelectedId() {
|
|
25286
25443
|
return this._selectedId;
|
|
25287
25444
|
}
|
|
25288
|
-
/**
|
|
25445
|
+
/** 获取上下游邻居(ref 可传 节点id/psrId/资产编号;电表户按所在计量箱参与拓扑) */
|
|
25289
25446
|
getNeighbors(nodeId) {
|
|
25447
|
+
const id2 = this._targetNodeId(nodeId);
|
|
25448
|
+
if (id2 == null) return { upstream: [], downstream: [] };
|
|
25290
25449
|
const upstream = [];
|
|
25291
25450
|
const downstream = [];
|
|
25292
25451
|
for (const e2 of this._model.edges) {
|
|
25293
|
-
if (e2.target ===
|
|
25452
|
+
if (e2.target === id2) {
|
|
25294
25453
|
const p = this._nodeByIdMap.get(e2.source);
|
|
25295
25454
|
if (p) upstream.push(p);
|
|
25296
|
-
} else if (e2.source ===
|
|
25455
|
+
} else if (e2.source === id2) {
|
|
25297
25456
|
const c = this._nodeByIdMap.get(e2.target);
|
|
25298
25457
|
if (c) downstream.push(c);
|
|
25299
25458
|
}
|
|
@@ -25302,11 +25461,13 @@ class TopoApi {
|
|
|
25302
25461
|
}
|
|
25303
25462
|
/**
|
|
25304
25463
|
* 沿拓扑流向取节点链
|
|
25305
|
-
* @param {string}
|
|
25464
|
+
* @param {string} ref - 节点 id / psrId / 资产编号(电表户按所在箱)
|
|
25306
25465
|
* @param {'upstream'|'downstream'} direction
|
|
25307
25466
|
*/
|
|
25308
|
-
getStreamNodes(
|
|
25309
|
-
|
|
25467
|
+
getStreamNodes(ref2, direction2) {
|
|
25468
|
+
const id2 = this._targetNodeId(ref2);
|
|
25469
|
+
if (id2 == null) return [];
|
|
25470
|
+
return this._bfs(id2, direction2).nodes;
|
|
25310
25471
|
}
|
|
25311
25472
|
/** 获取主干链节点 */
|
|
25312
25473
|
getTrunkNodes() {
|
|
@@ -25344,11 +25505,13 @@ class TopoApi {
|
|
|
25344
25505
|
for (const k of kids) w += this._subtreeWeight(k, childrenMap, visited);
|
|
25345
25506
|
return w;
|
|
25346
25507
|
}
|
|
25347
|
-
/**
|
|
25348
|
-
getSubtreeNodes(
|
|
25508
|
+
/** 获取子树所有节点(ref 可传 节点id/psrId/资产编号) */
|
|
25509
|
+
getSubtreeNodes(ref2) {
|
|
25510
|
+
const id2 = this._targetNodeId(ref2);
|
|
25511
|
+
if (id2 == null) return [];
|
|
25349
25512
|
const result = [];
|
|
25350
25513
|
const visited = /* @__PURE__ */ new Set();
|
|
25351
|
-
const queue = [
|
|
25514
|
+
const queue = [id2];
|
|
25352
25515
|
while (queue.length) {
|
|
25353
25516
|
const cur = queue.shift();
|
|
25354
25517
|
if (visited.has(cur)) continue;
|
|
@@ -25361,8 +25524,11 @@ class TopoApi {
|
|
|
25361
25524
|
}
|
|
25362
25525
|
return result;
|
|
25363
25526
|
}
|
|
25364
|
-
/** 获取两节点间路径(BFS
|
|
25365
|
-
getPath(
|
|
25527
|
+
/** 获取两节点间路径(BFS;端点可传 id/psrId/assetNo,电表户按所在箱) */
|
|
25528
|
+
getPath(fromRef, toRef) {
|
|
25529
|
+
const fromId = this._targetNodeId(fromRef);
|
|
25530
|
+
const toId = this._targetNodeId(toRef);
|
|
25531
|
+
if (fromId == null || toId == null) return null;
|
|
25366
25532
|
const parent = /* @__PURE__ */ new Map([[fromId, null]]);
|
|
25367
25533
|
const queue = [fromId];
|
|
25368
25534
|
while (queue.length) {
|
|
@@ -25389,17 +25555,19 @@ class TopoApi {
|
|
|
25389
25555
|
}
|
|
25390
25556
|
return path;
|
|
25391
25557
|
}
|
|
25392
|
-
/**
|
|
25393
|
-
getEdge(
|
|
25394
|
-
return this._findEdgeData(
|
|
25558
|
+
/** 获取指定边(端点可传 id/psrId/assetNo,电表户按所在箱) */
|
|
25559
|
+
getEdge(srcRef, tgtRef) {
|
|
25560
|
+
return this._findEdgeData(this._targetNodeId(srcRef), this._targetNodeId(tgtRef));
|
|
25395
25561
|
}
|
|
25396
25562
|
/** 获取所有边 */
|
|
25397
25563
|
getEdges() {
|
|
25398
25564
|
return [...this._model.edges];
|
|
25399
25565
|
}
|
|
25400
|
-
/**
|
|
25401
|
-
getStreamEdges(
|
|
25402
|
-
|
|
25566
|
+
/** 沿流向取边(ref 可传 节点id/psrId/资产编号) */
|
|
25567
|
+
getStreamEdges(ref2, direction2) {
|
|
25568
|
+
const id2 = this._targetNodeId(ref2);
|
|
25569
|
+
if (id2 == null) return [];
|
|
25570
|
+
return this._bfs(id2, direction2).edges;
|
|
25403
25571
|
}
|
|
25404
25572
|
/** 获取主干边 */
|
|
25405
25573
|
getMainEdge() {
|
|
@@ -25413,14 +25581,15 @@ class TopoApi {
|
|
|
25413
25581
|
return true;
|
|
25414
25582
|
});
|
|
25415
25583
|
}
|
|
25416
|
-
/**
|
|
25417
|
-
getNodePosition(
|
|
25418
|
-
const
|
|
25419
|
-
return
|
|
25584
|
+
/** 获取节点 / 户表位中心坐标(assetNo → 该户表位中心;节点 id/psrId → 节点中心) */
|
|
25585
|
+
getNodePosition(ref2) {
|
|
25586
|
+
const world = this._targetWorld(ref2);
|
|
25587
|
+
return world ? { x: world.x, y: world.y } : null;
|
|
25420
25588
|
}
|
|
25421
|
-
/**
|
|
25422
|
-
getEdgePath(
|
|
25423
|
-
const
|
|
25589
|
+
/** 获取边折线点序列(端点可传 id/psrId/assetNo) */
|
|
25590
|
+
getEdgePath(srcRef, tgtRef) {
|
|
25591
|
+
const key = this._edgeKey(this._targetNodeId(srcRef), this._targetNodeId(tgtRef));
|
|
25592
|
+
const cps = this._layout.edgeCP.get(key);
|
|
25424
25593
|
return cps ? cps.map(([x, y]) => [x, y]) : null;
|
|
25425
25594
|
}
|
|
25426
25595
|
/** 获取图整体边界 */
|
|
@@ -25460,17 +25629,29 @@ class TopoApi {
|
|
|
25460
25629
|
const current = this._graph.getZoom();
|
|
25461
25630
|
this._graph.zoomTo(current / (1 + step2));
|
|
25462
25631
|
}
|
|
25463
|
-
/**
|
|
25464
|
-
locateNode(
|
|
25632
|
+
/** 定位节点到视口中央(ref 可传 id/psrId/资产编号;电表户聚焦其表位) */
|
|
25633
|
+
locateNode(ref2, zoom) {
|
|
25465
25634
|
this._assertReady();
|
|
25466
|
-
const
|
|
25467
|
-
if (!
|
|
25635
|
+
const t = this._resolveTarget(ref2);
|
|
25636
|
+
if (!t) return;
|
|
25637
|
+
const world = this._targetWorld(ref2);
|
|
25638
|
+
if (!world) return;
|
|
25468
25639
|
if (zoom) this._graph.zoomTo(zoom);
|
|
25640
|
+
if (t.kind === "node") {
|
|
25641
|
+
try {
|
|
25642
|
+
this._graph.focusElement(t.id);
|
|
25643
|
+
return;
|
|
25644
|
+
} catch {
|
|
25645
|
+
}
|
|
25646
|
+
}
|
|
25469
25647
|
try {
|
|
25470
|
-
this._graph.
|
|
25471
|
-
|
|
25472
|
-
const
|
|
25473
|
-
|
|
25648
|
+
const z2 = zoom || this._graph.getZoom();
|
|
25649
|
+
const [vx, vy] = this._graph.getViewportCenter();
|
|
25650
|
+
const dx = (vx - world.x) * z2;
|
|
25651
|
+
const dy = (vy - world.y) * z2;
|
|
25652
|
+
this._graph.translateBy([dx, dy]);
|
|
25653
|
+
} catch (e2) {
|
|
25654
|
+
console.warn("[TopoApi] locateNode 平移失败:", e2 && e2.message);
|
|
25474
25655
|
}
|
|
25475
25656
|
}
|
|
25476
25657
|
/** 获取当前视口状态 */
|
|
@@ -25483,25 +25664,34 @@ class TopoApi {
|
|
|
25483
25664
|
// ============================================================
|
|
25484
25665
|
// 三、节点样式
|
|
25485
25666
|
// ============================================================
|
|
25486
|
-
/**
|
|
25487
|
-
setNodeStyle(
|
|
25667
|
+
/** 设置单个节点样式(ref = 节点id/psrId;传 assetNo 时作用于其所在计量箱节点) */
|
|
25668
|
+
setNodeStyle(ref2, style) {
|
|
25488
25669
|
this._assertReady();
|
|
25489
|
-
this.
|
|
25490
|
-
|
|
25670
|
+
const id2 = this._targetNodeId(ref2);
|
|
25671
|
+
if (id2 == null) {
|
|
25672
|
+
console.warn(`[TopoApi] setNodeStyle: 无法解析目标 ${ref2}`);
|
|
25673
|
+
return;
|
|
25674
|
+
}
|
|
25675
|
+
this._snapshotNodeStyle(id2);
|
|
25676
|
+
this._updateNodes([{ id: id2, style }]);
|
|
25491
25677
|
}
|
|
25492
|
-
/**
|
|
25493
|
-
batchSetNodeStyle(
|
|
25678
|
+
/** 批量设置节点样式(每一项可为 id/psrId/assetNo) */
|
|
25679
|
+
batchSetNodeStyle(refs, style) {
|
|
25494
25680
|
this._assertReady();
|
|
25495
|
-
|
|
25496
|
-
|
|
25681
|
+
const ids = refs.map((r) => this._targetNodeId(r)).filter((v) => v != null);
|
|
25682
|
+
if (!ids.length) return;
|
|
25683
|
+
for (const id2 of ids) this._snapshotNodeStyle(id2);
|
|
25684
|
+
this._updateNodes(ids.map((id2) => ({ id: id2, style })));
|
|
25497
25685
|
}
|
|
25498
|
-
/**
|
|
25499
|
-
resetNodeStyle(
|
|
25686
|
+
/** 重置单个节点样式(ref = 节点id/psrId/assetNo) */
|
|
25687
|
+
resetNodeStyle(ref2) {
|
|
25500
25688
|
this._assertReady();
|
|
25501
|
-
const
|
|
25689
|
+
const id2 = this._targetNodeId(ref2);
|
|
25690
|
+
if (id2 == null) return;
|
|
25691
|
+
const orig = this._originalNodeStyles.get(id2);
|
|
25502
25692
|
if (orig) {
|
|
25503
|
-
this._updateNodes([{ id:
|
|
25504
|
-
this._originalNodeStyles.delete(
|
|
25693
|
+
this._updateNodes([{ id: id2, style: orig }]);
|
|
25694
|
+
this._originalNodeStyles.delete(id2);
|
|
25505
25695
|
}
|
|
25506
25696
|
}
|
|
25507
25697
|
/** 重置所有节点样式 */
|
|
@@ -25741,28 +25931,43 @@ class TopoApi {
|
|
|
25741
25931
|
// ============================================================
|
|
25742
25932
|
// 四、边样式
|
|
25743
25933
|
// ============================================================
|
|
25744
|
-
/**
|
|
25745
|
-
|
|
25934
|
+
/** 解析边端点 → 节点 id(customer → 所在计量箱;失败返回 null) */
|
|
25935
|
+
_edgeEndpoints(srcRef, tgtRef) {
|
|
25936
|
+
const a2 = this._targetNodeId(srcRef);
|
|
25937
|
+
const b = this._targetNodeId(tgtRef);
|
|
25938
|
+
return a2 != null && b != null ? [a2, b] : null;
|
|
25939
|
+
}
|
|
25940
|
+
/** 设置边样式(端点可为节点 id/psrId/资产编号 assetNo) */
|
|
25941
|
+
setEdgeStyle(srcRef, tgtRef, style) {
|
|
25746
25942
|
this._assertReady();
|
|
25747
|
-
const
|
|
25943
|
+
const ep = this._edgeEndpoints(srcRef, tgtRef);
|
|
25944
|
+
if (!ep) {
|
|
25945
|
+
console.warn(`[TopoApi] setEdgeStyle: 无法解析端点 ${srcRef} / ${tgtRef}`);
|
|
25946
|
+
return;
|
|
25947
|
+
}
|
|
25948
|
+
const edgeId = this._edgeKey(ep[0], ep[1]);
|
|
25748
25949
|
this._snapshotEdgeStyle(edgeId);
|
|
25749
25950
|
this._updateEdges([{ id: edgeId, style }]);
|
|
25750
25951
|
}
|
|
25751
|
-
/**
|
|
25752
|
-
batchSetEdgeStyle(
|
|
25952
|
+
/** 批量设置边样式(每个端对可为 id/psrId/assetNo) */
|
|
25953
|
+
batchSetEdgeStyle(edgeRefs, style) {
|
|
25753
25954
|
this._assertReady();
|
|
25754
25955
|
const updates = [];
|
|
25755
|
-
for (const [src, tgt] of
|
|
25756
|
-
const
|
|
25956
|
+
for (const [src, tgt] of edgeRefs) {
|
|
25957
|
+
const ep = this._edgeEndpoints(src, tgt);
|
|
25958
|
+
if (!ep) continue;
|
|
25959
|
+
const eid = this._edgeKey(ep[0], ep[1]);
|
|
25757
25960
|
this._snapshotEdgeStyle(eid);
|
|
25758
25961
|
updates.push({ id: eid, style });
|
|
25759
25962
|
}
|
|
25760
25963
|
if (updates.length) this._updateEdges(updates);
|
|
25761
25964
|
}
|
|
25762
|
-
/**
|
|
25763
|
-
resetEdgeStyle(
|
|
25965
|
+
/** 重置边样式(端点可为节点 id/psrId/资产编号 assetNo) */
|
|
25966
|
+
resetEdgeStyle(srcRef, tgtRef) {
|
|
25764
25967
|
this._assertReady();
|
|
25765
|
-
const
|
|
25968
|
+
const ep = this._edgeEndpoints(srcRef, tgtRef);
|
|
25969
|
+
if (!ep) return;
|
|
25970
|
+
const edgeId = this._edgeKey(ep[0], ep[1]);
|
|
25766
25971
|
const orig = this._originalEdgeStyles.get(edgeId);
|
|
25767
25972
|
if (orig) {
|
|
25768
25973
|
this._updateEdges([{ id: edgeId, style: orig }]);
|
|
@@ -25773,54 +25978,92 @@ class TopoApi {
|
|
|
25773
25978
|
// 五、高亮与标注
|
|
25774
25979
|
// ============================================================
|
|
25775
25980
|
/**
|
|
25776
|
-
* 高亮节点
|
|
25777
|
-
* @param {string}
|
|
25981
|
+
* 高亮节点 / 箱内电表户
|
|
25982
|
+
* @param {string} ref - 节点 id / psrId / 资产编号(电表户 → 该户表位高亮环 + 可选文字)
|
|
25778
25983
|
* @param {object} [opts] - { color, label, labelColor, labelSize, borderColor, borderStyle, borderWidth }
|
|
25779
25984
|
*/
|
|
25780
|
-
highlightNode(
|
|
25985
|
+
highlightNode(ref2, opts = {}) {
|
|
25781
25986
|
this._assertReady();
|
|
25782
|
-
this.
|
|
25783
|
-
|
|
25784
|
-
|
|
25987
|
+
const t = this._resolveTarget(ref2);
|
|
25988
|
+
if (!t) {
|
|
25989
|
+
console.warn(`[TopoApi] highlightNode: 无法解析目标 ${ref2}`);
|
|
25990
|
+
return;
|
|
25991
|
+
}
|
|
25992
|
+
if (t.kind === "node") {
|
|
25993
|
+
this._snapshotNodeStyle(t.id);
|
|
25994
|
+
const style = {
|
|
25995
|
+
highlight: 1
|
|
25996
|
+
};
|
|
25997
|
+
if (opts.borderColor) style.highlightColor = opts.borderColor;
|
|
25998
|
+
if (opts.borderWidth) style.highlightWidth = opts.borderWidth;
|
|
25999
|
+
if (opts.borderStyle === "solid") style.highlightDash = [];
|
|
26000
|
+
if (opts.label) {
|
|
26001
|
+
style.annotationText = opts.label;
|
|
26002
|
+
style.annotationColor = opts.labelColor || "#FFFFFF";
|
|
26003
|
+
style.annotationSize = opts.labelSize || 12;
|
|
26004
|
+
}
|
|
26005
|
+
this._updateNodes([{ id: t.id, style }]);
|
|
26006
|
+
return;
|
|
26007
|
+
}
|
|
26008
|
+
const d2 = this._cellFxGet(t.boxId, t.index);
|
|
26009
|
+
d2.hl = {
|
|
26010
|
+
color: opts.borderColor || opts.color || "#FFD700",
|
|
26011
|
+
width: opts.borderWidth || 2,
|
|
26012
|
+
dash: opts.borderStyle === "solid" ? [] : [4, 3]
|
|
25785
26013
|
};
|
|
25786
|
-
if (opts.borderColor) style.highlightColor = opts.borderColor;
|
|
25787
|
-
if (opts.borderWidth) style.highlightWidth = opts.borderWidth;
|
|
25788
|
-
if (opts.borderStyle === "solid") style.highlightDash = [];
|
|
25789
26014
|
if (opts.label) {
|
|
25790
|
-
|
|
25791
|
-
|
|
25792
|
-
|
|
26015
|
+
d2.note = {
|
|
26016
|
+
text: String(opts.label),
|
|
26017
|
+
color: opts.labelColor || "#FFD700",
|
|
26018
|
+
size: Math.min(opts.labelSize || 8, 9),
|
|
26019
|
+
position: "top",
|
|
26020
|
+
mark: "hl"
|
|
26021
|
+
};
|
|
25793
26022
|
}
|
|
25794
|
-
this.
|
|
26023
|
+
this._flushCellFx(t.boxId);
|
|
25795
26024
|
}
|
|
25796
|
-
/**
|
|
25797
|
-
unhighlightNode(
|
|
26025
|
+
/** 取消节点 / 箱内电表户高亮(ref 可传 id/psrId/assetNo) */
|
|
26026
|
+
unhighlightNode(ref2) {
|
|
25798
26027
|
this._assertReady();
|
|
25799
|
-
this.
|
|
25800
|
-
|
|
25801
|
-
|
|
25802
|
-
|
|
25803
|
-
|
|
25804
|
-
|
|
25805
|
-
|
|
25806
|
-
|
|
25807
|
-
|
|
25808
|
-
|
|
26028
|
+
const t = this._resolveTarget(ref2);
|
|
26029
|
+
if (!t) return;
|
|
26030
|
+
if (t.kind === "node") {
|
|
26031
|
+
this._updateNodes([{
|
|
26032
|
+
id: t.id,
|
|
26033
|
+
style: {
|
|
26034
|
+
highlight: 0,
|
|
26035
|
+
annotationText: "",
|
|
26036
|
+
annotationColor: "",
|
|
26037
|
+
annotationSize: 0
|
|
26038
|
+
}
|
|
26039
|
+
}]);
|
|
26040
|
+
this._originalNodeStyles.delete(t.id);
|
|
26041
|
+
return;
|
|
26042
|
+
}
|
|
26043
|
+
const d2 = this._cellFx.get(this._cellFxKey(t.boxId, t.index));
|
|
26044
|
+
if (!d2) return;
|
|
26045
|
+
delete d2.hl;
|
|
26046
|
+
if (d2.note && d2.note.mark === "hl") delete d2.note;
|
|
26047
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) this._cellFx.delete(this._cellFxKey(t.boxId, t.index));
|
|
26048
|
+
this._flushCellFx(t.boxId);
|
|
25809
26049
|
}
|
|
25810
|
-
/**
|
|
25811
|
-
highlightPath(
|
|
26050
|
+
/** 高亮路径(节点与边;节点可为 id/psrId,箱内户可用 assetNo) */
|
|
26051
|
+
highlightPath(refs, opts = {}) {
|
|
25812
26052
|
this._assertReady();
|
|
25813
|
-
for (const id2 of
|
|
26053
|
+
for (const id2 of refs) {
|
|
25814
26054
|
this.highlightNode(id2, opts);
|
|
25815
26055
|
}
|
|
25816
26056
|
const edgeColor = opts.edgeColor || opts.color || "#00C8FF";
|
|
25817
|
-
for (let i = 0; i + 1 <
|
|
25818
|
-
const
|
|
26057
|
+
for (let i = 0; i + 1 < refs.length; i++) {
|
|
26058
|
+
const a2 = this._targetNodeId(refs[i]);
|
|
26059
|
+
const b = this._targetNodeId(refs[i + 1]);
|
|
26060
|
+
if (a2 == null || b == null || a2 === b) continue;
|
|
26061
|
+
const eid = this._edgeKey(a2, b);
|
|
25819
26062
|
this._snapshotEdgeStyle(eid);
|
|
25820
26063
|
this._updateEdges([{ id: eid, style: { overrideStroke: edgeColor, overrideLineWidth: 2.5 } }]);
|
|
25821
26064
|
}
|
|
25822
26065
|
}
|
|
25823
|
-
/**
|
|
26066
|
+
/** 取消所有高亮(含箱内电表户高亮环) */
|
|
25824
26067
|
unhighlightAll() {
|
|
25825
26068
|
this._assertReady();
|
|
25826
26069
|
const nodeUpdates = [];
|
|
@@ -25838,11 +26081,26 @@ class TopoApi {
|
|
|
25838
26081
|
}
|
|
25839
26082
|
if (edgeUpdates.length) this._updateEdges(edgeUpdates);
|
|
25840
26083
|
this._originalEdgeStyles.clear();
|
|
26084
|
+
const boxes = /* @__PURE__ */ new Set();
|
|
26085
|
+
for (const [key, d2] of this._cellFx) {
|
|
26086
|
+
const hasHl = !!d2.hl;
|
|
26087
|
+
if (d2.note && d2.note.mark === "hl") delete d2.note;
|
|
26088
|
+
delete d2.hl;
|
|
26089
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) {
|
|
26090
|
+
this._cellFx.delete(key);
|
|
26091
|
+
} else if (hasHl) {
|
|
26092
|
+
const sep = key.indexOf("#cust");
|
|
26093
|
+
boxes.add(sep > 0 ? key.slice(0, sep) : null);
|
|
26094
|
+
}
|
|
26095
|
+
}
|
|
26096
|
+
for (const boxId of boxes) {
|
|
26097
|
+
if (boxId != null) this._flushCellFx(boxId);
|
|
26098
|
+
}
|
|
25841
26099
|
}
|
|
25842
|
-
/**
|
|
25843
|
-
dimOthers(
|
|
26100
|
+
/** 非指定节点变暗(keepIds 内每一项可为 节点id/psrId/assetNo) */
|
|
26101
|
+
dimOthers(keepRefs, opacity2 = 0.15) {
|
|
25844
26102
|
this._assertReady();
|
|
25845
|
-
const keepSet = new Set(
|
|
26103
|
+
const keepSet = new Set(keepRefs.map((r) => this._targetNodeId(r)).filter((v) => v != null));
|
|
25846
26104
|
const updates = [];
|
|
25847
26105
|
for (const n of this._model.nodes) {
|
|
25848
26106
|
if (!keepSet.has(n.id)) {
|
|
@@ -25856,13 +26114,34 @@ class TopoApi {
|
|
|
25856
26114
|
// 六、动画
|
|
25857
26115
|
// ============================================================
|
|
25858
26116
|
/**
|
|
25859
|
-
* 节点脉动动画(G6 v5: updateNodeData + draw
|
|
26117
|
+
* 节点脉动动画(G6 v5: updateNodeData + draw 方式)。
|
|
26118
|
+
* ref = 节点 id/psrId → 图元透明度呼吸;assetNo → 该户电表格淡入淡出。
|
|
26119
|
+
* opts: { duration, min, max }(min/max 为透明度下限/上限,默认 0.3/1)
|
|
25860
26120
|
*/
|
|
25861
|
-
setNodePulse(
|
|
26121
|
+
setNodePulse(ref2, opts = {}) {
|
|
25862
26122
|
this._assertReady();
|
|
26123
|
+
const t = this._resolveTarget(ref2);
|
|
26124
|
+
if (!t) {
|
|
26125
|
+
console.warn(`[TopoApi] setNodePulse: 无法解析目标 ${ref2}(节点用 id/psrId,电表户用 assetNo)`);
|
|
26126
|
+
return;
|
|
26127
|
+
}
|
|
26128
|
+
if (t.kind === "customer") {
|
|
26129
|
+
this.removeAnimation(ref2);
|
|
26130
|
+
const d2 = this._cellFxGet(t.boxId, t.index);
|
|
26131
|
+
d2.pulse = {
|
|
26132
|
+
duration: opts.duration || 1500,
|
|
26133
|
+
min: opts.min != null ? opts.min : 0.3,
|
|
26134
|
+
max: opts.max != null ? opts.max : 1,
|
|
26135
|
+
_start: performance.now(),
|
|
26136
|
+
opacity: 1
|
|
26137
|
+
};
|
|
26138
|
+
this._flushCellFx(t.boxId);
|
|
26139
|
+
this._ensureFxLoop();
|
|
26140
|
+
return;
|
|
26141
|
+
}
|
|
26142
|
+
const nodeId = t.id;
|
|
25863
26143
|
this.removeAnimation(nodeId);
|
|
25864
26144
|
const duration2 = opts.duration || 1500;
|
|
25865
|
-
this._safeNodeStyle(nodeId, {});
|
|
25866
26145
|
let start = null;
|
|
25867
26146
|
const animate = (ts) => {
|
|
25868
26147
|
if (!this._graph || !this._animations.has(nodeId)) return;
|
|
@@ -25946,8 +26225,19 @@ class TopoApi {
|
|
|
25946
26225
|
const raf2 = requestAnimationFrame(animate);
|
|
25947
26226
|
this._animations.set(edgeId, raf2);
|
|
25948
26227
|
}
|
|
25949
|
-
/**
|
|
26228
|
+
/** 移除指定元素动画(ref = 节点 id/psrId → 脉动/边流动;assetNo → 该户脉动+光晕) */
|
|
25950
26229
|
removeAnimation(targetId) {
|
|
26230
|
+
const t = targetId != null ? this._resolveTarget(String(targetId)) : null;
|
|
26231
|
+
if (t && t.kind === "customer") {
|
|
26232
|
+
const key = this._cellFxKey(t.boxId, t.index);
|
|
26233
|
+
const d2 = this._cellFx.get(key);
|
|
26234
|
+
if (!d2) return;
|
|
26235
|
+
delete d2.pulse;
|
|
26236
|
+
delete d2.glow;
|
|
26237
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) this._cellFx.delete(key);
|
|
26238
|
+
this._flushCellFx(t.boxId);
|
|
26239
|
+
return;
|
|
26240
|
+
}
|
|
25951
26241
|
const raf2 = this._animations.get(targetId);
|
|
25952
26242
|
if (raf2) {
|
|
25953
26243
|
cancelAnimationFrame(raf2);
|
|
@@ -25970,9 +26260,11 @@ class TopoApi {
|
|
|
25970
26260
|
console.error("[TopoApi] removeAnimation update error:", e2);
|
|
25971
26261
|
}
|
|
25972
26262
|
}
|
|
25973
|
-
/**
|
|
26263
|
+
/** 移除所有动画(脉动/流动/光晕/户脉动户光晕) */
|
|
25974
26264
|
removeAllAnimations() {
|
|
25975
26265
|
this._removeAllAnimationsInternal();
|
|
26266
|
+
this._removeAllGlowsInternal();
|
|
26267
|
+
this._removeAllCellPulses();
|
|
25976
26268
|
}
|
|
25977
26269
|
_removeAllAnimationsInternal() {
|
|
25978
26270
|
for (const [id2, raf2] of this._animations) {
|
|
@@ -25996,12 +26288,310 @@ class TopoApi {
|
|
|
25996
26288
|
}
|
|
25997
26289
|
this._animations.clear();
|
|
25998
26290
|
}
|
|
26291
|
+
/** 清除全部箱内电表户的脉动(保留光晕/高亮/文字) */
|
|
26292
|
+
_removeAllCellPulses() {
|
|
26293
|
+
if (this._glowRaf) {
|
|
26294
|
+
cancelAnimationFrame(this._glowRaf);
|
|
26295
|
+
this._glowRaf = null;
|
|
26296
|
+
}
|
|
26297
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
26298
|
+
for (const [key, d2] of this._cellFx) {
|
|
26299
|
+
if (!d2.pulse) continue;
|
|
26300
|
+
delete d2.pulse;
|
|
26301
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) {
|
|
26302
|
+
this._cellFx.delete(key);
|
|
26303
|
+
} else {
|
|
26304
|
+
const sep = key.indexOf("#cust");
|
|
26305
|
+
if (sep > 0) dirty.add(key.slice(0, sep));
|
|
26306
|
+
}
|
|
26307
|
+
}
|
|
26308
|
+
if (!this._graph) return;
|
|
26309
|
+
for (const boxId of dirty) this._flushCellFx(boxId);
|
|
26310
|
+
if (this._hasFramedFx()) this._ensureFxLoop();
|
|
26311
|
+
}
|
|
26312
|
+
// ------------------------------------------------------------
|
|
26313
|
+
// 光晕 / 脉动 动画 —— 节点 & 箱内电表户
|
|
26314
|
+
// setNodeGlow(ref, { color, spread/radius, duration, opacity, width, intensity, blink })
|
|
26315
|
+
// ref = 节点 id/psrId → 图元光环;assetNo → 该户表位光环
|
|
26316
|
+
// 强度栈:blur 柔光底衬 + 实心光块 + 外扩散环×2 + 霓虹主环(shadowBlur) + 亮芯环
|
|
26317
|
+
// intensity = 强度倍率(默认 1);blink = true/0~1 → 图元「整体闪烁」
|
|
26318
|
+
// setNodePulse(ref, opts) → 节点透明度呼吸;assetNo → 该户电表格淡入淡出
|
|
26319
|
+
// ------------------------------------------------------------
|
|
26320
|
+
/** 是否有需要逐帧推进的动画(节点光晕 / 户光晕 / 户脉动) */
|
|
26321
|
+
_hasFramedFx() {
|
|
26322
|
+
if (this._glowNodes.size) return true;
|
|
26323
|
+
for (const d2 of this._cellFx.values()) {
|
|
26324
|
+
if (d2.glow && !d2.glow.done || d2.pulse && !d2.pulse.done) return true;
|
|
26325
|
+
}
|
|
26326
|
+
return false;
|
|
26327
|
+
}
|
|
26328
|
+
/** 启动统一动画循环(节点光晕 + 箱内户光晕/脉动,每帧一次批量写入) */
|
|
26329
|
+
_ensureFxLoop() {
|
|
26330
|
+
if (this._glowRaf) return;
|
|
26331
|
+
const loop = (ts) => {
|
|
26332
|
+
if (!this._graph) {
|
|
26333
|
+
this._glowRaf = null;
|
|
26334
|
+
return;
|
|
26335
|
+
}
|
|
26336
|
+
const updates = [];
|
|
26337
|
+
if (this._glowNodes.size) {
|
|
26338
|
+
for (const [id2, s2] of this._glowNodes) {
|
|
26339
|
+
if (!this._graph.getNodeData(id2)) {
|
|
26340
|
+
this._glowNodes.delete(id2);
|
|
26341
|
+
continue;
|
|
26342
|
+
}
|
|
26343
|
+
s2.phase = (ts - s2.start) % s2.duration / s2.duration;
|
|
26344
|
+
updates.push({ id: id2, style: { _glowT: s2.phase } });
|
|
26345
|
+
}
|
|
26346
|
+
}
|
|
26347
|
+
const dirtyBoxes = /* @__PURE__ */ new Set();
|
|
26348
|
+
if (this._cellFx.size) {
|
|
26349
|
+
const stale = [];
|
|
26350
|
+
for (const [key, d2] of this._cellFx) {
|
|
26351
|
+
if (!d2.glow && !d2.pulse) continue;
|
|
26352
|
+
const sep = key.indexOf("#cust");
|
|
26353
|
+
if (sep < 0) {
|
|
26354
|
+
stale.push(key);
|
|
26355
|
+
continue;
|
|
26356
|
+
}
|
|
26357
|
+
const boxId = key.slice(0, sep);
|
|
26358
|
+
const idx = Number(key.slice(sep + 5));
|
|
26359
|
+
const box2 = this._nodeByIdMap.get(boxId);
|
|
26360
|
+
if (!box2 || !Array.isArray(box2._customers) || idx < 0 || idx >= box2._customers.length) {
|
|
26361
|
+
stale.push(key);
|
|
26362
|
+
continue;
|
|
26363
|
+
}
|
|
26364
|
+
if (d2.glow) d2.glow.t = (ts - d2.glow._start) % d2.glow.duration / d2.glow.duration;
|
|
26365
|
+
if (d2.pulse) {
|
|
26366
|
+
d2.pulse.opacity = d2.pulse.min + (d2.pulse.max - d2.pulse.min) * (0.5 + 0.5 * Math.sin((ts - d2.pulse._start) % d2.pulse.duration / d2.pulse.duration * Math.PI * 2));
|
|
26367
|
+
}
|
|
26368
|
+
dirtyBoxes.add(boxId);
|
|
26369
|
+
}
|
|
26370
|
+
for (const k of stale) this._cellFx.delete(k);
|
|
26371
|
+
}
|
|
26372
|
+
if (dirtyBoxes.size) {
|
|
26373
|
+
const perBox = /* @__PURE__ */ new Map();
|
|
26374
|
+
for (const [key, d2] of this._cellFx) {
|
|
26375
|
+
const sep = key.indexOf("#cust");
|
|
26376
|
+
if (sep < 0) continue;
|
|
26377
|
+
const boxId = key.slice(0, sep);
|
|
26378
|
+
if (!dirtyBoxes.has(boxId)) continue;
|
|
26379
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) continue;
|
|
26380
|
+
if (!perBox.has(boxId)) perBox.set(boxId, {});
|
|
26381
|
+
perBox.get(boxId)[Number(key.slice(sep + 5))] = d2;
|
|
26382
|
+
}
|
|
26383
|
+
for (const [boxId, obj] of perBox) {
|
|
26384
|
+
const cur = this._safeNodeStyle(boxId, {});
|
|
26385
|
+
updates.push({ id: boxId, style: { ...cur, _cellFx: obj } });
|
|
26386
|
+
}
|
|
26387
|
+
}
|
|
26388
|
+
if (updates.length) {
|
|
26389
|
+
try {
|
|
26390
|
+
this._graph.updateNodeData(updates);
|
|
26391
|
+
this._graph.render();
|
|
26392
|
+
} catch {
|
|
26393
|
+
}
|
|
26394
|
+
}
|
|
26395
|
+
if (!this._hasFramedFx()) {
|
|
26396
|
+
this._glowRaf = null;
|
|
26397
|
+
return;
|
|
26398
|
+
}
|
|
26399
|
+
this._glowRaf = requestAnimationFrame(loop);
|
|
26400
|
+
};
|
|
26401
|
+
this._glowRaf = requestAnimationFrame(loop);
|
|
26402
|
+
}
|
|
26403
|
+
/** 清除某节点 style 上的光晕帧字段(光晕环不再绘制) */
|
|
26404
|
+
_clearGlowStyle(nodeId) {
|
|
26405
|
+
try {
|
|
26406
|
+
const cur = this._safeNodeStyle(nodeId, {});
|
|
26407
|
+
this._graph.updateNodeData([{
|
|
26408
|
+
id: nodeId,
|
|
26409
|
+
style: {
|
|
26410
|
+
...cur,
|
|
26411
|
+
_glowColor: void 0,
|
|
26412
|
+
_glowSpread: void 0,
|
|
26413
|
+
_glowOpacity: void 0,
|
|
26414
|
+
_glowDuration: void 0,
|
|
26415
|
+
_glowWidth: void 0,
|
|
26416
|
+
_glowLevel: void 0,
|
|
26417
|
+
_glowBlink: void 0,
|
|
26418
|
+
_glowT: void 0
|
|
26419
|
+
}
|
|
26420
|
+
}]);
|
|
26421
|
+
} catch {
|
|
26422
|
+
}
|
|
26423
|
+
}
|
|
26424
|
+
/**
|
|
26425
|
+
* 归一化「整体闪烁」参数:false / 未给 → 0(只画光环);true → 0.85;数字 → clamp(0,1)。
|
|
26426
|
+
* 数值含义 = 图元最暗时被压掉的透明度幅度(1 → 图元完全隐去,0.5 → 最暗降到 50%)。
|
|
26427
|
+
*/
|
|
26428
|
+
_normBlink(v) {
|
|
26429
|
+
if (v === true) return 0.85;
|
|
26430
|
+
if (v === false || v == null) return 0;
|
|
26431
|
+
const n = Number(v);
|
|
26432
|
+
return Number.isFinite(n) ? Math.max(0, Math.min(1, n)) : 0;
|
|
26433
|
+
}
|
|
26434
|
+
/** 归一化强度倍率(intensity/level/strength):默认 1,范围 0.2~3 */
|
|
26435
|
+
_normLevel(v) {
|
|
26436
|
+
const n = Number(v);
|
|
26437
|
+
return Number.isFinite(n) && n > 0 ? Math.max(0.2, Math.min(3, n)) : 1;
|
|
26438
|
+
}
|
|
26439
|
+
/**
|
|
26440
|
+
* 节点 / 箱内电表户光晕(发光环 + 霓虹辉光)动画。
|
|
26441
|
+
* @param {string} ref 节点 id / psrId(图元光环)或资产编号 assetNo(该户表位光环)
|
|
26442
|
+
* @param {object} [opts] - { color, spread/radius, duration, opacity, width, intensity/level/strength, blink/flash }
|
|
26443
|
+
* 渲染栈:blur 柔光底衬 + 贴合图元的实心光块(面光)→ 图元 → 外扩散环 ×2 →
|
|
26444
|
+
* 霓虹主环(粗线 + canvas shadowBlur 辉光)→ 偏白亮芯环(刺眼高光)。
|
|
26445
|
+
* intensity:强度倍率,默认 1(线宽/辉光/底光同时放大);想“更夸张”给 1.5~2.5,想收敛给 0.5~0.7。
|
|
26446
|
+
* spread:光环外扩半径,默认按图元大小自适应(短边 ×0.7,16~46px)。
|
|
26447
|
+
* blink:图元「整体闪烁」开关/强度(默认 false 只画光环;true = 0.85;数字 0~1)。
|
|
26448
|
+
* 开启后图元本体透明度随相位「亮 ↔ 暗」交替(与光环相位互补,始终有强视觉元素),
|
|
26449
|
+
* 图元变暗的瞬间底衬光块最亮 —— 整块明暗交替比细线醒目得多。
|
|
26450
|
+
* width:主环初始线宽 (px),默认 5.6(节点)/ 3.6(户表位),随相位收细。
|
|
26451
|
+
* @returns {boolean} 是否命中并启动
|
|
26452
|
+
*/
|
|
26453
|
+
setNodeGlow(ref2, opts = {}) {
|
|
26454
|
+
this._assertReady();
|
|
26455
|
+
const t = this._resolveTarget(ref2);
|
|
26456
|
+
if (!t) {
|
|
26457
|
+
console.warn(`[TopoApi] setNodeGlow: 无法解析目标 ${ref2}(节点用 id/psrId,电表户用 assetNo)`);
|
|
26458
|
+
return false;
|
|
26459
|
+
}
|
|
26460
|
+
const width = parseFloat(opts.width);
|
|
26461
|
+
const spread = opts.spread != null ? opts.spread : opts.radius;
|
|
26462
|
+
const cfg = {
|
|
26463
|
+
color: opts.color || "#00C8FF",
|
|
26464
|
+
spread: Number.isFinite(parseFloat(spread)) ? parseFloat(spread) : null,
|
|
26465
|
+
// null → 按图元大小自适应
|
|
26466
|
+
opacity: opts.opacity != null ? opts.opacity : 0.95,
|
|
26467
|
+
duration: opts.duration || 1600,
|
|
26468
|
+
width: Number.isFinite(width) && width > 0 ? width : null,
|
|
26469
|
+
level: this._normLevel(opts.intensity != null ? opts.intensity : opts.level != null ? opts.level : opts.strength),
|
|
26470
|
+
blink: this._normBlink(opts.blink != null ? opts.blink : opts.flash),
|
|
26471
|
+
_start: performance.now(),
|
|
26472
|
+
t: 0
|
|
26473
|
+
};
|
|
26474
|
+
if (t.kind === "node") {
|
|
26475
|
+
this._glowNodes.set(t.id, cfg);
|
|
26476
|
+
this._updateNodes([{
|
|
26477
|
+
id: t.id,
|
|
26478
|
+
style: {
|
|
26479
|
+
_glowColor: cfg.color,
|
|
26480
|
+
_glowSpread: cfg.spread == null ? void 0 : cfg.spread,
|
|
26481
|
+
_glowOpacity: cfg.opacity,
|
|
26482
|
+
_glowDuration: cfg.duration,
|
|
26483
|
+
_glowWidth: cfg.width == null ? void 0 : cfg.width,
|
|
26484
|
+
_glowLevel: cfg.level,
|
|
26485
|
+
_glowBlink: cfg.blink || void 0,
|
|
26486
|
+
_glowT: 0
|
|
26487
|
+
}
|
|
26488
|
+
}]);
|
|
26489
|
+
} else {
|
|
26490
|
+
const d2 = this._cellFxGet(t.boxId, t.index);
|
|
26491
|
+
d2.glow = cfg;
|
|
26492
|
+
this._flushCellFx(t.boxId);
|
|
26493
|
+
}
|
|
26494
|
+
this._ensureFxLoop();
|
|
26495
|
+
return true;
|
|
26496
|
+
}
|
|
26497
|
+
/**
|
|
26498
|
+
* 停止节点 / 箱内电表户的光晕动画。
|
|
26499
|
+
* @param {string} ref 节点 id / psrId / 资产编号
|
|
26500
|
+
*/
|
|
26501
|
+
removeNodeGlow(ref2) {
|
|
26502
|
+
this._assertReady();
|
|
26503
|
+
const t = this._resolveTarget(ref2);
|
|
26504
|
+
if (!t) return false;
|
|
26505
|
+
if (t.kind === "node") {
|
|
26506
|
+
if (this._glowNodes.delete(t.id)) this._clearGlowStyle(t.id);
|
|
26507
|
+
return true;
|
|
26508
|
+
}
|
|
26509
|
+
const d2 = this._cellFx.get(this._cellFxKey(t.boxId, t.index));
|
|
26510
|
+
if (!d2) return false;
|
|
26511
|
+
delete d2.glow;
|
|
26512
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) this._cellFx.delete(this._cellFxKey(t.boxId, t.index));
|
|
26513
|
+
this._flushCellFx(t.boxId);
|
|
26514
|
+
return true;
|
|
26515
|
+
}
|
|
26516
|
+
/** 停止全部光晕动画(节点 + 箱内电表户) */
|
|
26517
|
+
removeAllNodeGlows() {
|
|
26518
|
+
this._removeAllGlowsInternal();
|
|
26519
|
+
if (this._hasFramedFx()) this._ensureFxLoop();
|
|
26520
|
+
}
|
|
26521
|
+
_removeAllGlowsInternal() {
|
|
26522
|
+
if (this._glowRaf) {
|
|
26523
|
+
cancelAnimationFrame(this._glowRaf);
|
|
26524
|
+
this._glowRaf = null;
|
|
26525
|
+
}
|
|
26526
|
+
const updates = [];
|
|
26527
|
+
if (this._graph && this._glowNodes.size) {
|
|
26528
|
+
for (const [id2] of this._glowNodes) {
|
|
26529
|
+
if (!this._graph.getNodeData(id2)) continue;
|
|
26530
|
+
updates.push({
|
|
26531
|
+
id: id2,
|
|
26532
|
+
style: {
|
|
26533
|
+
_glowColor: void 0,
|
|
26534
|
+
_glowSpread: void 0,
|
|
26535
|
+
_glowOpacity: void 0,
|
|
26536
|
+
_glowDuration: void 0,
|
|
26537
|
+
_glowWidth: void 0,
|
|
26538
|
+
_glowLevel: void 0,
|
|
26539
|
+
_glowBlink: void 0,
|
|
26540
|
+
_glowT: void 0
|
|
26541
|
+
}
|
|
26542
|
+
});
|
|
26543
|
+
}
|
|
26544
|
+
}
|
|
26545
|
+
this._glowNodes.clear();
|
|
26546
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
26547
|
+
for (const [key, d2] of this._cellFx) {
|
|
26548
|
+
if (!d2.glow) continue;
|
|
26549
|
+
delete d2.glow;
|
|
26550
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) {
|
|
26551
|
+
this._cellFx.delete(key);
|
|
26552
|
+
} else {
|
|
26553
|
+
const sep = key.indexOf("#cust");
|
|
26554
|
+
if (sep > 0) dirty.add(key.slice(0, sep));
|
|
26555
|
+
}
|
|
26556
|
+
}
|
|
26557
|
+
if (this._graph) {
|
|
26558
|
+
if (updates.length) {
|
|
26559
|
+
try {
|
|
26560
|
+
this._graph.updateNodeData(updates);
|
|
26561
|
+
this._graph.render();
|
|
26562
|
+
} catch {
|
|
26563
|
+
}
|
|
26564
|
+
}
|
|
26565
|
+
for (const boxId of dirty) this._flushCellFx(boxId);
|
|
26566
|
+
}
|
|
26567
|
+
}
|
|
25999
26568
|
// ============================================================
|
|
26000
26569
|
// 七、文字标注
|
|
26001
26570
|
// ============================================================
|
|
26002
|
-
/**
|
|
26003
|
-
|
|
26571
|
+
/**
|
|
26572
|
+
* 设置附加文字(ref = 节点 id/psrId → 节点旁标注;assetNo → 该户表位上方小标注)
|
|
26573
|
+
* opts: { color, fontSize, position: 'top'|'bottom' }
|
|
26574
|
+
*/
|
|
26575
|
+
setText(ref2, text2, opts = {}) {
|
|
26004
26576
|
this._assertReady();
|
|
26577
|
+
const t = this._resolveTarget(ref2);
|
|
26578
|
+
if (!t) {
|
|
26579
|
+
console.warn(`[TopoApi] setText: 无法解析目标 ${ref2}`);
|
|
26580
|
+
return;
|
|
26581
|
+
}
|
|
26582
|
+
if (t.kind === "customer") {
|
|
26583
|
+
const d2 = this._cellFxGet(t.boxId, t.index);
|
|
26584
|
+
d2.note = {
|
|
26585
|
+
text: String(text2),
|
|
26586
|
+
color: opts.color || "#FFD700",
|
|
26587
|
+
size: Math.min(opts.fontSize || 8, 9),
|
|
26588
|
+
position: opts.position === "bottom" ? "bottom" : "top",
|
|
26589
|
+
mark: "text"
|
|
26590
|
+
};
|
|
26591
|
+
this._flushCellFx(t.boxId);
|
|
26592
|
+
return;
|
|
26593
|
+
}
|
|
26594
|
+
const nodeId = t.id;
|
|
26005
26595
|
this._snapshotNodeStyle(nodeId);
|
|
26006
26596
|
this._nodeLabels.set(nodeId, { text: text2, opts });
|
|
26007
26597
|
this._updateNodes([{
|
|
@@ -26015,16 +26605,28 @@ class TopoApi {
|
|
|
26015
26605
|
}
|
|
26016
26606
|
}]);
|
|
26017
26607
|
}
|
|
26018
|
-
/**
|
|
26019
|
-
removeText(
|
|
26608
|
+
/** 移除附加文字(ref = 节点 id/psrId/资产编号) */
|
|
26609
|
+
removeText(ref2) {
|
|
26020
26610
|
this._assertReady();
|
|
26611
|
+
const t = this._resolveTarget(ref2);
|
|
26612
|
+
if (!t) return;
|
|
26613
|
+
if (t.kind === "customer") {
|
|
26614
|
+
const key = this._cellFxKey(t.boxId, t.index);
|
|
26615
|
+
const d2 = this._cellFx.get(key);
|
|
26616
|
+
if (!d2) return;
|
|
26617
|
+
delete d2.note;
|
|
26618
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) this._cellFx.delete(key);
|
|
26619
|
+
this._flushCellFx(t.boxId);
|
|
26620
|
+
return;
|
|
26621
|
+
}
|
|
26622
|
+
const nodeId = t.id;
|
|
26021
26623
|
this._nodeLabels.delete(nodeId);
|
|
26022
26624
|
this._updateNodes([{
|
|
26023
26625
|
id: nodeId,
|
|
26024
26626
|
style: { annotationText: "", annotationColor: "", annotationSize: 0 }
|
|
26025
26627
|
}]);
|
|
26026
26628
|
}
|
|
26027
|
-
/**
|
|
26629
|
+
/** 移除所有附加文字(含箱内电表户标注) */
|
|
26028
26630
|
removeAllTexts() {
|
|
26029
26631
|
this._assertReady();
|
|
26030
26632
|
const updates = [];
|
|
@@ -26033,6 +26635,18 @@ class TopoApi {
|
|
|
26033
26635
|
}
|
|
26034
26636
|
if (updates.length) this._updateNodes(updates);
|
|
26035
26637
|
this._nodeLabels.clear();
|
|
26638
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
26639
|
+
for (const [key, d2] of this._cellFx) {
|
|
26640
|
+
if (!d2.note) continue;
|
|
26641
|
+
delete d2.note;
|
|
26642
|
+
if (!d2.hl && !d2.note && !d2.glow && !d2.pulse) {
|
|
26643
|
+
this._cellFx.delete(key);
|
|
26644
|
+
} else {
|
|
26645
|
+
const sep = key.indexOf("#cust");
|
|
26646
|
+
if (sep > 0) dirty.add(key.slice(0, sep));
|
|
26647
|
+
}
|
|
26648
|
+
}
|
|
26649
|
+
for (const boxId of dirty) this._flushCellFx(boxId);
|
|
26036
26650
|
}
|
|
26037
26651
|
/** 按设备类型设字号 */
|
|
26038
26652
|
setDeviceText(cat, fontSize2) {
|
|
@@ -26062,22 +26676,30 @@ class TopoApi {
|
|
|
26062
26676
|
// 八、卡片盒
|
|
26063
26677
|
// ============================================================
|
|
26064
26678
|
/**
|
|
26065
|
-
*
|
|
26066
|
-
* @param {string}
|
|
26067
|
-
* @param {object} content - { title, fields, buttons, closable, width }
|
|
26679
|
+
* 在节点 / 箱内电表户旁弹卡片。
|
|
26680
|
+
* @param {string} ref 节点 id / psrId / 资产编号(assetNo → 卡片定位在该户表位旁)
|
|
26681
|
+
* @param {object} [content] - { title, fields, buttons, closable, width };
|
|
26682
|
+
* 缺省自动按「设备 / 客户档案」生成字段
|
|
26068
26683
|
*/
|
|
26069
|
-
showCard(
|
|
26684
|
+
showCard(ref2, content) {
|
|
26070
26685
|
var _a, _b, _c, _d, _e, _f;
|
|
26071
26686
|
this._assertReady();
|
|
26072
|
-
this.
|
|
26687
|
+
const t = this._resolveTarget(ref2);
|
|
26688
|
+
if (!t) {
|
|
26689
|
+
console.warn(`[TopoApi] showCard: 无法解析目标 ${ref2}(节点用 id/psrId,电表户用 assetNo)`);
|
|
26690
|
+
return;
|
|
26691
|
+
}
|
|
26692
|
+
const isCustomer = t.kind === "customer";
|
|
26693
|
+
const cardKey = isCustomer ? this._cellFxKey(t.boxId, t.index) : t.id;
|
|
26694
|
+
this.hideCard(cardKey);
|
|
26073
26695
|
const container = ((_b = (_a = this._graph).getContainer) == null ? void 0 : _b.call(_a)) || ((_f = (_e = (_d = (_c = this._graph).getCanvas) == null ? void 0 : _d.call(_c)) == null ? void 0 : _e.getContainer) == null ? void 0 : _f.call(_e));
|
|
26074
26696
|
if (!container) return;
|
|
26075
|
-
const
|
|
26076
|
-
if (!
|
|
26697
|
+
const world = this._targetWorld(ref2);
|
|
26698
|
+
if (!world) return;
|
|
26077
26699
|
let client = null;
|
|
26078
26700
|
try {
|
|
26079
26701
|
if (typeof this._graph.getClientByCanvas === "function") {
|
|
26080
|
-
const r = this._graph.getClientByCanvas([
|
|
26702
|
+
const r = this._graph.getClientByCanvas([world.x, world.y]);
|
|
26081
26703
|
client = Array.isArray(r) ? { x: r[0], y: r[1] } : r;
|
|
26082
26704
|
}
|
|
26083
26705
|
} catch (e2) {
|
|
@@ -26090,12 +26712,37 @@ class TopoApi {
|
|
|
26090
26712
|
const zoom = this._graph.getZoom();
|
|
26091
26713
|
const canvasCenter = this._canvasCenterXY();
|
|
26092
26714
|
const containerRect = container.getBoundingClientRect();
|
|
26093
|
-
screenX = containerRect.left + canvasCenter.x +
|
|
26094
|
-
screenY = containerRect.top + canvasCenter.y +
|
|
26715
|
+
screenX = containerRect.left + canvasCenter.x + world.x * zoom;
|
|
26716
|
+
screenY = containerRect.top + canvasCenter.y + world.y * zoom;
|
|
26717
|
+
}
|
|
26718
|
+
content = content || {};
|
|
26719
|
+
let cTitle = content.title;
|
|
26720
|
+
let cFields = content.fields;
|
|
26721
|
+
if (!cTitle || !Array.isArray(cFields) || !cFields.length) {
|
|
26722
|
+
if (isCustomer) {
|
|
26723
|
+
const c = t.customer || {};
|
|
26724
|
+
cTitle = cTitle || c.realConsName || c.consName || `表位 ${t.index + 1}`;
|
|
26725
|
+
cFields = [
|
|
26726
|
+
["户名", c.realConsName || c.consName],
|
|
26727
|
+
["资产编号", c.assetNo],
|
|
26728
|
+
["客户编号", c.consId],
|
|
26729
|
+
["户号", c.consNo],
|
|
26730
|
+
["电表编号", c.meterId]
|
|
26731
|
+
].filter(([, v]) => v != null && v !== "").map(([label, value]) => ({ label, value }));
|
|
26732
|
+
} else {
|
|
26733
|
+
const node = t.node || {};
|
|
26734
|
+
cTitle = cTitle || node.name || "设备信息";
|
|
26735
|
+
cFields = [
|
|
26736
|
+
["类型", node.catLabel],
|
|
26737
|
+
["设备名称", node.name],
|
|
26738
|
+
["PSR编号", node.psrId],
|
|
26739
|
+
["状态", node.status === "0" ? "正常" : node.status]
|
|
26740
|
+
].filter(([, v]) => v != null && v !== "").map(([label, value]) => ({ label, value }));
|
|
26741
|
+
}
|
|
26095
26742
|
}
|
|
26096
26743
|
const el = document.createElement("div");
|
|
26097
26744
|
el.className = "topo-card";
|
|
26098
|
-
el.dataset.nodeId =
|
|
26745
|
+
el.dataset.nodeId = cardKey;
|
|
26099
26746
|
el.style.cssText = `
|
|
26100
26747
|
position: fixed; left: ${screenX + 30}px; top: ${screenY - 20}px;
|
|
26101
26748
|
width: ${content.width || 240}px; z-index: 1000;
|
|
@@ -26105,22 +26752,22 @@ class TopoApi {
|
|
|
26105
26752
|
`;
|
|
26106
26753
|
const head = document.createElement("div");
|
|
26107
26754
|
head.style.cssText = "display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid #2c2c30;";
|
|
26108
|
-
head.innerHTML = `<span style="font-weight:600;color:#fff;">${
|
|
26755
|
+
head.innerHTML = `<span style="font-weight:600;color:#fff;">${String(cTitle || "").replace(/</g, "<")}</span>`;
|
|
26109
26756
|
if (content.closable !== false) {
|
|
26110
26757
|
const closeBtn = document.createElement("button");
|
|
26111
26758
|
closeBtn.textContent = "×";
|
|
26112
26759
|
closeBtn.style.cssText = "border:none;background:none;color:#888;font-size:16px;cursor:pointer;";
|
|
26113
|
-
closeBtn.onclick = () => this.hideCard(
|
|
26760
|
+
closeBtn.onclick = () => this.hideCard(cardKey);
|
|
26114
26761
|
head.appendChild(closeBtn);
|
|
26115
26762
|
}
|
|
26116
26763
|
el.appendChild(head);
|
|
26117
|
-
if (
|
|
26764
|
+
if (cFields && cFields.length) {
|
|
26118
26765
|
const body = document.createElement("div");
|
|
26119
26766
|
body.style.cssText = "padding:8px 12px;";
|
|
26120
|
-
for (const f2 of
|
|
26767
|
+
for (const f2 of cFields) {
|
|
26121
26768
|
const row2 = document.createElement("div");
|
|
26122
26769
|
row2.style.cssText = "display:flex;gap:8px;margin-bottom:4px;";
|
|
26123
|
-
row2.innerHTML = `<span style="color:#8a8a96;flex:none;width:56px;">${f2.label}</span><span style="color:#eee;">${f2.value}</span>`;
|
|
26770
|
+
row2.innerHTML = `<span style="color:#8a8a96;flex:none;width:56px;">${String(f2.label || "").replace(/</g, "<")}</span><span style="color:#eee;">${String(f2.value != null ? f2.value : "").replace(/</g, "<")}</span>`;
|
|
26124
26771
|
body.appendChild(row2);
|
|
26125
26772
|
}
|
|
26126
26773
|
el.appendChild(body);
|
|
@@ -26134,21 +26781,34 @@ class TopoApi {
|
|
|
26134
26781
|
b.style.cssText = btn.type === "primary" ? "padding:3px 10px;border:1px solid #00c8ff;border-radius:4px;background:#00c8ff;color:#000;cursor:pointer;font-size:12px;" : "padding:3px 10px;border:1px solid #3a3a42;border-radius:4px;background:#18181c;color:#ddd;cursor:pointer;font-size:12px;";
|
|
26135
26782
|
b.onclick = () => {
|
|
26136
26783
|
if (btn.onClick) btn.onClick();
|
|
26137
|
-
|
|
26784
|
+
if (isCustomer) {
|
|
26785
|
+
this._emit("card:button", {
|
|
26786
|
+
nodeId: t.boxId,
|
|
26787
|
+
boxId: t.boxId,
|
|
26788
|
+
index: t.index,
|
|
26789
|
+
customer: t.customer,
|
|
26790
|
+
cardKey,
|
|
26791
|
+
buttonIndex: idx
|
|
26792
|
+
});
|
|
26793
|
+
} else {
|
|
26794
|
+
this._emit("card:button", { nodeId: t.id, buttonIndex: idx });
|
|
26795
|
+
}
|
|
26138
26796
|
};
|
|
26139
26797
|
foot.appendChild(b);
|
|
26140
26798
|
});
|
|
26141
26799
|
el.appendChild(foot);
|
|
26142
26800
|
}
|
|
26143
26801
|
document.body.appendChild(el);
|
|
26144
|
-
this._cards.set(
|
|
26802
|
+
this._cards.set(cardKey, el);
|
|
26145
26803
|
}
|
|
26146
|
-
/**
|
|
26147
|
-
hideCard(
|
|
26148
|
-
const
|
|
26804
|
+
/** 关闭指定节点 / 电表户旁的卡片(ref 可传 id/psrId/assetNo) */
|
|
26805
|
+
hideCard(ref2) {
|
|
26806
|
+
const t = this._resolveTarget(ref2);
|
|
26807
|
+
const key = t ? t.kind === "node" ? t.id : this._cellFxKey(t.boxId, t.index) : String(ref2);
|
|
26808
|
+
const el = this._cards.get(key);
|
|
26149
26809
|
if (el) {
|
|
26150
26810
|
el.remove();
|
|
26151
|
-
this._cards.delete(
|
|
26811
|
+
this._cards.delete(key);
|
|
26152
26812
|
}
|
|
26153
26813
|
}
|
|
26154
26814
|
/** 关闭所有卡片 */
|
|
@@ -26482,12 +27142,17 @@ class TopoApi {
|
|
|
26482
27142
|
}
|
|
26483
27143
|
/**
|
|
26484
27144
|
* 添加拓扑高亮线
|
|
26485
|
-
* @param {string[]}
|
|
27145
|
+
* @param {string[]} refs - 节点路径(每点可为 id/psrId;assetNo 落到所在计量箱)
|
|
26486
27146
|
* @param {object} [opts] - { color, width, id }
|
|
26487
27147
|
* @returns {string} 线 ID
|
|
26488
27148
|
*/
|
|
26489
|
-
addTopoLine(
|
|
27149
|
+
addTopoLine(refs, opts = {}) {
|
|
26490
27150
|
this._assertReady();
|
|
27151
|
+
const nodeIds = refs.map((n) => this._targetNodeId(n)).filter((v) => v != null);
|
|
27152
|
+
if (nodeIds.length < 2) {
|
|
27153
|
+
console.warn("[TopoApi] addTopoLine: 有效节点不足 2 个,无法画线");
|
|
27154
|
+
return opts.id || `topo-${Date.now()}`;
|
|
27155
|
+
}
|
|
26491
27156
|
const lineId = opts.id || `topo-${Date.now()}`;
|
|
26492
27157
|
const color2 = opts.color || "#00C8FF";
|
|
26493
27158
|
const width = opts.width || 3;
|
|
@@ -28074,6 +28739,8 @@ const THEMES = {
|
|
|
28074
28739
|
cyan: "#00C8FF",
|
|
28075
28740
|
gold: "#FFD700",
|
|
28076
28741
|
gray: "#8B8B8B",
|
|
28742
|
+
// 开关/熔丝(Kxx)标签文字:画在红色箱体(red)里的文字色,随主题切换
|
|
28743
|
+
switchText: "#FFFFFF",
|
|
28077
28744
|
// 计量箱(JX):深色主题黑底白字;浅色主题白底深框深字(随画布配套)
|
|
28078
28745
|
meterFill: "#000000",
|
|
28079
28746
|
meterText: "#FFFFFF",
|
|
@@ -28102,6 +28769,9 @@ const THEMES = {
|
|
|
28102
28769
|
cyan: "#0087B8",
|
|
28103
28770
|
gold: "#B8860B",
|
|
28104
28771
|
gray: "#9C9C9C",
|
|
28772
|
+
// 开关标签:浅色主题下箱体仍是深红 red(#C62828),此处保持浅色字
|
|
28773
|
+
// (白字对比度 ≈5.6:1;若改成深字,请连 red 一起调浅,否则只剩 ≈2.3:1 会发糊)
|
|
28774
|
+
switchText: "#FFFFFF",
|
|
28105
28775
|
meterFill: "#FFFFFF",
|
|
28106
28776
|
// 浅色主题:白底 + 深框深字(随白画布配套)
|
|
28107
28777
|
meterText: "#3A3A3A",
|
|
@@ -28305,6 +28975,19 @@ function withAlpha(color2, a2) {
|
|
|
28305
28975
|
}
|
|
28306
28976
|
const LW = 1.8;
|
|
28307
28977
|
const LW_HI = 2.2;
|
|
28978
|
+
function mixWhite(color2, k) {
|
|
28979
|
+
if (typeof color2 !== "string") return color2;
|
|
28980
|
+
const m = color2.match(/^#([0-9a-f]{6}|[0-9a-f]{3})$/i);
|
|
28981
|
+
if (!m) return color2;
|
|
28982
|
+
let hex2 = m[1];
|
|
28983
|
+
if (hex2.length === 3) hex2 = hex2.split("").map((h) => h + h).join("");
|
|
28984
|
+
const n = parseInt(hex2, 16);
|
|
28985
|
+
const mix = (v) => Math.round(v + (255 - v) * Math.max(0, Math.min(1, k)));
|
|
28986
|
+
const r = mix(n >> 16 & 255), g = mix(n >> 8 & 255), b = mix(n & 255);
|
|
28987
|
+
return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
|
|
28988
|
+
}
|
|
28989
|
+
const clampNum = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
|
|
28990
|
+
const NO_HIT = { pointerEvents: "none" };
|
|
28308
28991
|
function drawHighlight(attributes, group) {
|
|
28309
28992
|
if (!attributes.highlight) return;
|
|
28310
28993
|
const [w, h] = attributes.size;
|
|
@@ -28398,7 +29081,7 @@ function _wrapLinesFixed(str2, chars2) {
|
|
|
28398
29081
|
if (line) lines.push(line);
|
|
28399
29082
|
return lines;
|
|
28400
29083
|
}
|
|
28401
|
-
function _drawWrappedTextFixed(group, str2, x, y, fontSize2, fill, chars2, lineGap = 1.3, weight = 400, align = "center") {
|
|
29084
|
+
function _drawWrappedTextFixed(group, str2, x, y, fontSize2, fill, chars2, lineGap = 1.3, weight = 400, align = "center", extra = null) {
|
|
28402
29085
|
if (!str2) return;
|
|
28403
29086
|
const lines = _wrapLinesFixed(str2, chars2);
|
|
28404
29087
|
if (!lines.length) return;
|
|
@@ -28407,6 +29090,7 @@ function _drawWrappedTextFixed(group, str2, x, y, fontSize2, fill, chars2, lineG
|
|
|
28407
29090
|
group.appendChild(
|
|
28408
29091
|
new Text({
|
|
28409
29092
|
style: {
|
|
29093
|
+
...extra || {},
|
|
28410
29094
|
text: lines[i],
|
|
28411
29095
|
x,
|
|
28412
29096
|
y: y + lineHeight2 * i,
|
|
@@ -28480,7 +29164,7 @@ function drawBreaker(attributes, group) {
|
|
|
28480
29164
|
}
|
|
28481
29165
|
group.appendChild(new Rect({ style: { x: bx, y: by, width: bw, height: bh, radius: r, fill: P.red, stroke: withAlpha(C2.white, 0.22), lineWidth: 1 } }));
|
|
28482
29166
|
if (attributes.labelText) {
|
|
28483
|
-
_drawWrappedText(group, attributes.labelText, 0, 0.6, 11.5,
|
|
29167
|
+
_drawWrappedText(group, attributes.labelText, 0, 0.6, 11.5, C2.switchText, bw - 8, 1.15, 700);
|
|
28484
29168
|
}
|
|
28485
29169
|
}
|
|
28486
29170
|
function drawCableTerminal(attributes, group) {
|
|
@@ -28522,6 +29206,55 @@ function drawMeterFace(group, cx, cy, meterW, meterH, lineColor, markCell, cellI
|
|
|
28522
29206
|
if (markCell) markCell(dot2, cellIdx);
|
|
28523
29207
|
group.appendChild(dot2);
|
|
28524
29208
|
}
|
|
29209
|
+
function cellFxAt(attributes, i) {
|
|
29210
|
+
const fx = attributes && attributes._cellFx;
|
|
29211
|
+
return fx && typeof fx === "object" ? fx[i] || null : null;
|
|
29212
|
+
}
|
|
29213
|
+
function drawCellGlowRings(group, cx, cy, baseR, g, t) {
|
|
29214
|
+
glowStack(group, cx, cy, baseR, {
|
|
29215
|
+
color: g.color,
|
|
29216
|
+
t: t == null ? g.t : t,
|
|
29217
|
+
spread: g.spread,
|
|
29218
|
+
opacity: g.opacity,
|
|
29219
|
+
width: g.width,
|
|
29220
|
+
level: g.level
|
|
29221
|
+
});
|
|
29222
|
+
}
|
|
29223
|
+
function cellBlinkOpacity(g) {
|
|
29224
|
+
if (!g || !g.blink) return 1;
|
|
29225
|
+
return 1 - g.blink * glowBlinkK(g.t == null ? 0 : g.t);
|
|
29226
|
+
}
|
|
29227
|
+
function drawCellGlowBackdrop(group, cx, cy, meterW, meterH, g) {
|
|
29228
|
+
if (!g) return;
|
|
29229
|
+
glowBackdrop(group, cx, cy, meterW, meterH, {
|
|
29230
|
+
color: g.color,
|
|
29231
|
+
t: g.t,
|
|
29232
|
+
spread: g.spread != null ? g.spread : 14,
|
|
29233
|
+
level: g.level,
|
|
29234
|
+
blink: g.blink
|
|
29235
|
+
});
|
|
29236
|
+
}
|
|
29237
|
+
function drawCellHighlight(group, cx, cy, baseR, hl) {
|
|
29238
|
+
group.appendChild(new Circle({
|
|
29239
|
+
style: {
|
|
29240
|
+
...NO_HIT,
|
|
29241
|
+
cx,
|
|
29242
|
+
cy,
|
|
29243
|
+
r: baseR + 3,
|
|
29244
|
+
stroke: hl.color || "#FFD700",
|
|
29245
|
+
lineWidth: hl.width || 2,
|
|
29246
|
+
lineDash: hl.dash || [4, 3],
|
|
29247
|
+
fill: "none"
|
|
29248
|
+
}
|
|
29249
|
+
}));
|
|
29250
|
+
}
|
|
29251
|
+
function drawCellNote(group, cx, cy, baseR, note) {
|
|
29252
|
+
const size = note.size || 7;
|
|
29253
|
+
const color2 = note.color || C2.gold;
|
|
29254
|
+
const top = !note.position || note.position === "top";
|
|
29255
|
+
const y = top ? cy - baseR - 5 - size / 2 : cy + baseR + 6 + size / 2;
|
|
29256
|
+
_drawWrappedTextFixed(group, note.text, cx, y, size, color2, 6, 1.15, 600, "center", NO_HIT);
|
|
29257
|
+
}
|
|
28525
29258
|
function drawMeterBox(attributes, group) {
|
|
28526
29259
|
const [w, h] = attributes.size;
|
|
28527
29260
|
const boxColor = nodeColor(attributes);
|
|
@@ -28531,6 +29264,8 @@ function drawMeterBox(attributes, group) {
|
|
|
28531
29264
|
const hover = attributes._custHover === 0;
|
|
28532
29265
|
const baseColor = cellColorAt(attributes, 0) || boxColor || C2.white;
|
|
28533
29266
|
const lineColor = sel ? C2.gold : hover ? C2.cyan : baseColor;
|
|
29267
|
+
const fx = cellFxAt(attributes, 0);
|
|
29268
|
+
const cellGroup = new Group({});
|
|
28534
29269
|
const markCell = (shape) => {
|
|
28535
29270
|
if (!nodeId) return;
|
|
28536
29271
|
shape.__custBox = nodeId;
|
|
@@ -28538,12 +29273,15 @@ function drawMeterBox(attributes, group) {
|
|
|
28538
29273
|
};
|
|
28539
29274
|
const meterW = 20;
|
|
28540
29275
|
const meterH = 26;
|
|
28541
|
-
|
|
29276
|
+
const cx = 0;
|
|
29277
|
+
const cy = -1;
|
|
29278
|
+
const baseR = Math.max(12, meterH / 2 + 2);
|
|
29279
|
+
if (fx && fx.glow) drawCellGlowBackdrop(cellGroup, cx, cy, meterW, meterH, fx.glow);
|
|
28542
29280
|
if (sel || hover) {
|
|
28543
29281
|
const ring = new Rect({
|
|
28544
29282
|
style: {
|
|
28545
|
-
x: -meterW / 2 - 4,
|
|
28546
|
-
y: -meterH / 2 - 4,
|
|
29283
|
+
x: cx - meterW / 2 - 4,
|
|
29284
|
+
y: cy - meterH / 2 - 4,
|
|
28547
29285
|
width: meterW + 8,
|
|
28548
29286
|
height: meterH + 8,
|
|
28549
29287
|
radius: 5,
|
|
@@ -28553,12 +29291,31 @@ function drawMeterBox(attributes, group) {
|
|
|
28553
29291
|
}
|
|
28554
29292
|
});
|
|
28555
29293
|
markCell(ring);
|
|
28556
|
-
|
|
29294
|
+
cellGroup.appendChild(ring);
|
|
29295
|
+
}
|
|
29296
|
+
const faceStart = cellGroup.children.length;
|
|
29297
|
+
drawMeterFace(cellGroup, cx, cy, meterW, meterH, lineColor, markCell, 0);
|
|
29298
|
+
const faceEnd = cellGroup.children.length;
|
|
29299
|
+
if (fx) {
|
|
29300
|
+
if (fx.hl) drawCellHighlight(cellGroup, cx, cy, baseR, fx.hl);
|
|
29301
|
+
if (fx.note && fx.note.text) drawCellNote(cellGroup, cx, cy, baseR, fx.note);
|
|
29302
|
+
if (fx.glow) drawCellGlowRings(cellGroup, cx, cy, baseR, fx.glow, fx.glow.t || 0);
|
|
28557
29303
|
}
|
|
28558
29304
|
const name = attributes._customers && attributes._customers[0] ? attributes._customers[0].realConsName || attributes._customers[0].consName || "" : "";
|
|
28559
29305
|
if (name) {
|
|
28560
|
-
_drawWrappedTextFixed(
|
|
29306
|
+
_drawWrappedTextFixed(cellGroup, name, cx, meterH / 2 + 7, 7, lineColor, 4, 1.15, 500);
|
|
29307
|
+
}
|
|
29308
|
+
if (fx && fx.pulse && fx.pulse.opacity != null && fx.pulse.opacity < 1) {
|
|
29309
|
+
cellGroup.style.opacity = fx.pulse.opacity;
|
|
29310
|
+
}
|
|
29311
|
+
const blinkOp0 = fx && fx.glow ? cellBlinkOpacity(fx.glow) : 1;
|
|
29312
|
+
if (blinkOp0 < 1) {
|
|
29313
|
+
for (let k = faceStart; k < faceEnd && k < cellGroup.children.length; k++) {
|
|
29314
|
+
const s2 = cellGroup.children[k].style;
|
|
29315
|
+
s2.opacity = (s2.opacity == null ? 1 : s2.opacity) * blinkOp0;
|
|
29316
|
+
}
|
|
28561
29317
|
}
|
|
29318
|
+
group.appendChild(cellGroup);
|
|
28562
29319
|
} else if (attributes._meterBoxMode === "grid" && attributes._customers) {
|
|
28563
29320
|
const frameColor = boxColor || C2.meterText;
|
|
28564
29321
|
group.appendChild(new Rect({ style: { x: -w / 2, y: -h / 2, width: w, height: h, fill: withAlpha(frameColor, 0.04), stroke: frameColor, lineWidth: 1, radius: 4 } }));
|
|
@@ -28583,6 +29340,7 @@ function drawMeterBox(attributes, group) {
|
|
|
28583
29340
|
shape.__custBox = nodeId;
|
|
28584
29341
|
shape.__custIndex = i;
|
|
28585
29342
|
};
|
|
29343
|
+
const hitLayer = new Group({});
|
|
28586
29344
|
for (let i = 0; i < customers.length; i++) {
|
|
28587
29345
|
const row2 = Math.floor(i / cols);
|
|
28588
29346
|
const col = i % cols;
|
|
@@ -28592,8 +29350,11 @@ function drawMeterBox(attributes, group) {
|
|
|
28592
29350
|
const hover = i === custHover;
|
|
28593
29351
|
const baseColor = cellColorAt(attributes, i) || C2.meterText;
|
|
28594
29352
|
const lineColor = sel ? C2.gold : hover ? C2.cyan : baseColor;
|
|
29353
|
+
const fx = cellFxAt(attributes, i);
|
|
29354
|
+
const cellGroup = new Group({});
|
|
29355
|
+
if (fx && fx.glow) drawCellGlowBackdrop(cellGroup, cx, cy, meterW, meterH, fx.glow);
|
|
28595
29356
|
if (sel || hover) {
|
|
28596
|
-
|
|
29357
|
+
cellGroup.appendChild(new Circle({
|
|
28597
29358
|
style: {
|
|
28598
29359
|
cx,
|
|
28599
29360
|
cy,
|
|
@@ -28605,14 +29366,36 @@ function drawMeterBox(attributes, group) {
|
|
|
28605
29366
|
}
|
|
28606
29367
|
}));
|
|
28607
29368
|
}
|
|
28608
|
-
|
|
28609
|
-
|
|
28610
|
-
|
|
28611
|
-
|
|
29369
|
+
const faceStart = cellGroup.children.length;
|
|
29370
|
+
drawMeterFace(cellGroup, cx, cy, meterW, meterH, lineColor, markCell, i);
|
|
29371
|
+
const faceEnd = cellGroup.children.length;
|
|
29372
|
+
if (fx) {
|
|
29373
|
+
if (fx.hl) drawCellHighlight(cellGroup, cx, cy, hitR, fx.hl);
|
|
29374
|
+
if (fx.note && fx.note.text) drawCellNote(cellGroup, cx, cy, hitR, fx.note);
|
|
29375
|
+
if (fx.glow) drawCellGlowRings(cellGroup, cx, cy, hitR, fx.glow, fx.glow.t || 0);
|
|
29376
|
+
}
|
|
29377
|
+
if (showName) {
|
|
29378
|
+
const name = customers[i].realConsName || customers[i].consName || "";
|
|
29379
|
+
if (name) {
|
|
29380
|
+
_drawWrappedTextFixed(cellGroup, name, cx, cy + meterH / 2 + 6.5, 6.5, lineColor, 4, 1.15, 500);
|
|
29381
|
+
}
|
|
29382
|
+
}
|
|
29383
|
+
const hitCell = new Rect({
|
|
29384
|
+
style: {
|
|
29385
|
+
x: cx - cellW / 2 - METER_GAP / 2,
|
|
29386
|
+
y: cy - cellW / 2 - METER_GAP / 2,
|
|
29387
|
+
width: cellW + METER_GAP,
|
|
29388
|
+
height: cellH + METER_GAP,
|
|
29389
|
+
fill: "rgba(255,255,255,0.01)",
|
|
29390
|
+
stroke: "none",
|
|
29391
|
+
cursor: "pointer"
|
|
29392
|
+
}
|
|
29393
|
+
});
|
|
29394
|
+
markCell(hitCell, i);
|
|
29395
|
+
hitLayer.appendChild(hitCell);
|
|
28612
29396
|
if (showName) {
|
|
28613
29397
|
const name = customers[i].realConsName || customers[i].consName || "";
|
|
28614
29398
|
if (name) {
|
|
28615
|
-
_drawWrappedTextFixed(group, name, cx, cy + meterH / 2 + 6.5, 6.5, lineColor, 4, 1.15, 500);
|
|
28616
29399
|
const hitName = new Circle({
|
|
28617
29400
|
style: {
|
|
28618
29401
|
cx,
|
|
@@ -28624,10 +29407,22 @@ function drawMeterBox(attributes, group) {
|
|
|
28624
29407
|
}
|
|
28625
29408
|
});
|
|
28626
29409
|
markCell(hitName, i);
|
|
28627
|
-
|
|
29410
|
+
hitLayer.appendChild(hitName);
|
|
29411
|
+
}
|
|
29412
|
+
}
|
|
29413
|
+
if (fx && fx.pulse && fx.pulse.opacity != null && fx.pulse.opacity < 1) {
|
|
29414
|
+
cellGroup.style.opacity = fx.pulse.opacity;
|
|
29415
|
+
}
|
|
29416
|
+
const blinkOp = fx && fx.glow ? cellBlinkOpacity(fx.glow) : 1;
|
|
29417
|
+
if (blinkOp < 1) {
|
|
29418
|
+
for (let k = faceStart; k < faceEnd && k < cellGroup.children.length; k++) {
|
|
29419
|
+
const s2 = cellGroup.children[k].style;
|
|
29420
|
+
s2.opacity = (s2.opacity == null ? 1 : s2.opacity) * blinkOp;
|
|
28628
29421
|
}
|
|
28629
29422
|
}
|
|
29423
|
+
group.appendChild(cellGroup);
|
|
28630
29424
|
}
|
|
29425
|
+
group.appendChild(hitLayer);
|
|
28631
29426
|
} else {
|
|
28632
29427
|
const s2 = Math.min(w, h) || 21;
|
|
28633
29428
|
const ink = boxColor || C2.meterText;
|
|
@@ -28715,6 +29510,134 @@ const DRAWERS = {
|
|
|
28715
29510
|
other: drawUnknown
|
|
28716
29511
|
};
|
|
28717
29512
|
const _pulseState = /* @__PURE__ */ new Map();
|
|
29513
|
+
function pushGlowRing(group, cx, cy, r, lineWidth, strokeOpacity, color2, blur) {
|
|
29514
|
+
if (strokeOpacity <= 0.015 || lineWidth <= 0) return;
|
|
29515
|
+
const style = {
|
|
29516
|
+
...NO_HIT,
|
|
29517
|
+
cx,
|
|
29518
|
+
cy,
|
|
29519
|
+
r,
|
|
29520
|
+
stroke: color2,
|
|
29521
|
+
lineWidth,
|
|
29522
|
+
strokeOpacity,
|
|
29523
|
+
fill: "none",
|
|
29524
|
+
lineCap: "round"
|
|
29525
|
+
};
|
|
29526
|
+
if (blur > 0.5) {
|
|
29527
|
+
style.shadowColor = color2;
|
|
29528
|
+
style.shadowBlur = blur;
|
|
29529
|
+
}
|
|
29530
|
+
group.appendChild(new Circle({ style }));
|
|
29531
|
+
}
|
|
29532
|
+
function glowStack(group, cx, cy, baseR, g) {
|
|
29533
|
+
const color2 = g.color || "#00C8FF";
|
|
29534
|
+
const level = g.level > 0 ? g.level : 1;
|
|
29535
|
+
const tt = clampNum(g.t == null ? 0 : g.t, 0, 1);
|
|
29536
|
+
const spread = g.spread != null ? g.spread : 14;
|
|
29537
|
+
const maxOp = g.opacity == null ? 0.95 : g.opacity;
|
|
29538
|
+
const alpha = Math.min(1, maxOp * Math.pow(1 - tt, 1.1) * Math.min(1.5, level));
|
|
29539
|
+
if (alpha <= 0.02) return;
|
|
29540
|
+
const r = baseR + spread * (1 - (1 - tt) * (1 - tt));
|
|
29541
|
+
const lw = Math.max(1.3, (g.width || 3.6) * level * (0.34 + 0.66 * (1 - tt)));
|
|
29542
|
+
pushGlowRing(group, cx, cy, r + spread * 1.1, lw * 0.4, alpha * 0.26, color2, 0);
|
|
29543
|
+
pushGlowRing(group, cx, cy, r + spread * 0.55, lw * 0.62, alpha * 0.5, color2, lw * 2.5);
|
|
29544
|
+
pushGlowRing(group, cx, cy, r, lw, alpha, color2, lw * 6);
|
|
29545
|
+
pushGlowRing(group, cx, cy, r, Math.max(1.1, lw * 0.34), Math.min(1, alpha * 1.05), mixWhite(color2, 0.62), lw * 2);
|
|
29546
|
+
}
|
|
29547
|
+
function glowBackdrop(group, x, y, w, h, g) {
|
|
29548
|
+
const color2 = g.color || "#00C8FF";
|
|
29549
|
+
const level = g.level > 0 ? g.level : 1;
|
|
29550
|
+
const tt = clampNum(g.t == null ? 0 : g.t, 0, 1);
|
|
29551
|
+
const spread = g.spread != null ? g.spread : Math.max(14, Math.min(w, h) * 0.7);
|
|
29552
|
+
const blink = g.blink || 0;
|
|
29553
|
+
const fade = Math.pow(1 - tt, 1.1);
|
|
29554
|
+
const bk = glowBlinkK(tt);
|
|
29555
|
+
const base = Math.min(0.6, level * (0.26 * fade + 0.5 * blink * bk));
|
|
29556
|
+
const aura = Math.min(0.45, level * (0.2 * fade + 0.42 * blink * bk));
|
|
29557
|
+
if (base <= 0.02 && aura <= 0.02) return;
|
|
29558
|
+
const rad2 = Math.max(3, Math.min(w, h) * 0.16);
|
|
29559
|
+
const blurPx = clampNum(Math.min(w, h) * 0.28, 4, 18) * level;
|
|
29560
|
+
group.appendChild(new Rect({
|
|
29561
|
+
style: {
|
|
29562
|
+
...NO_HIT,
|
|
29563
|
+
x: x - w / 2 - spread * 0.85,
|
|
29564
|
+
y: y - h / 2 - spread * 0.85,
|
|
29565
|
+
width: w + spread * 1.7,
|
|
29566
|
+
height: h + spread * 1.7,
|
|
29567
|
+
radius: rad2 + 4,
|
|
29568
|
+
fill: color2,
|
|
29569
|
+
fillOpacity: aura,
|
|
29570
|
+
stroke: "none",
|
|
29571
|
+
filter: `blur(${blurPx.toFixed(1)}px)`
|
|
29572
|
+
}
|
|
29573
|
+
}));
|
|
29574
|
+
group.appendChild(new Rect({
|
|
29575
|
+
style: {
|
|
29576
|
+
...NO_HIT,
|
|
29577
|
+
x: x - w / 2 - 2.5,
|
|
29578
|
+
y: y - h / 2 - 2.5,
|
|
29579
|
+
width: w + 5,
|
|
29580
|
+
height: h + 5,
|
|
29581
|
+
radius: rad2,
|
|
29582
|
+
fill: color2,
|
|
29583
|
+
fillOpacity: base,
|
|
29584
|
+
stroke: "none"
|
|
29585
|
+
}
|
|
29586
|
+
}));
|
|
29587
|
+
}
|
|
29588
|
+
function glowBlinkK(t) {
|
|
29589
|
+
return 0.5 - 0.5 * Math.cos((t || 0) * Math.PI * 2);
|
|
29590
|
+
}
|
|
29591
|
+
function glowParams(attributes) {
|
|
29592
|
+
const color2 = attributes._glowColor;
|
|
29593
|
+
if (!color2) return null;
|
|
29594
|
+
const [w, h] = attributes.size || [40, 40];
|
|
29595
|
+
const t = clampNum(attributes._glowT == null ? 0 : attributes._glowT, 0, 1);
|
|
29596
|
+
const level = attributes._glowLevel > 0 ? attributes._glowLevel : 1;
|
|
29597
|
+
const spread = attributes._glowSpread != null ? attributes._glowSpread : clampNum(Math.min(w, h) * 0.7, 16, 46);
|
|
29598
|
+
return {
|
|
29599
|
+
color: color2,
|
|
29600
|
+
t,
|
|
29601
|
+
w,
|
|
29602
|
+
h,
|
|
29603
|
+
level,
|
|
29604
|
+
baseR: Math.max(w, h) / 2 + 3,
|
|
29605
|
+
spread,
|
|
29606
|
+
maxOp: attributes._glowOpacity == null ? 0.95 : attributes._glowOpacity,
|
|
29607
|
+
lw0: attributes._glowWidth || 5.6,
|
|
29608
|
+
// 主环初始线宽(可调,默认 5.6)
|
|
29609
|
+
blink: attributes._glowBlink || 0
|
|
29610
|
+
// 整体闪烁强度(0=关;setNodeGlow blink 参数)
|
|
29611
|
+
};
|
|
29612
|
+
}
|
|
29613
|
+
function drawGlowBackdrop(attributes, group) {
|
|
29614
|
+
const p = glowParams(attributes);
|
|
29615
|
+
if (!p) return;
|
|
29616
|
+
glowBackdrop(group, 0, 0, p.w, p.h, {
|
|
29617
|
+
color: p.color,
|
|
29618
|
+
t: p.t,
|
|
29619
|
+
spread: p.spread,
|
|
29620
|
+
level: p.level,
|
|
29621
|
+
blink: p.blink
|
|
29622
|
+
});
|
|
29623
|
+
}
|
|
29624
|
+
function nodeBlinkOpacity(attributes) {
|
|
29625
|
+
const blink = attributes._glowBlink;
|
|
29626
|
+
if (!blink) return 1;
|
|
29627
|
+
return 1 - blink * glowBlinkK(attributes._glowT == null ? 0 : attributes._glowT);
|
|
29628
|
+
}
|
|
29629
|
+
function drawGlow(attributes, group) {
|
|
29630
|
+
const p = glowParams(attributes);
|
|
29631
|
+
if (!p) return;
|
|
29632
|
+
glowStack(group, 0, 0, p.baseR, {
|
|
29633
|
+
color: p.color,
|
|
29634
|
+
t: p.t,
|
|
29635
|
+
spread: p.spread,
|
|
29636
|
+
opacity: p.maxOp,
|
|
29637
|
+
width: p.lw0,
|
|
29638
|
+
level: p.level
|
|
29639
|
+
});
|
|
29640
|
+
}
|
|
28718
29641
|
class SvgSymbolNode extends BaseNode {
|
|
28719
29642
|
drawKeyShape(attributes, container) {
|
|
28720
29643
|
const [w, h] = attributes.size;
|
|
@@ -28744,7 +29667,11 @@ class SvgSymbolNode extends BaseNode {
|
|
|
28744
29667
|
const target = rot ? new Group({ style: { transform: `rotate(${rot}deg)` } }) : key;
|
|
28745
29668
|
if (rot) key.appendChild(target);
|
|
28746
29669
|
const draw = DRAWERS[attributes.cat] || drawUnknown;
|
|
29670
|
+
drawGlowBackdrop(attributes, target);
|
|
29671
|
+
const symStart = (target.children || []).length;
|
|
28747
29672
|
draw(attributes, target);
|
|
29673
|
+
const symEnd = (target.children || []).length;
|
|
29674
|
+
drawGlow(attributes, target);
|
|
28748
29675
|
drawHighlight(attributes, target);
|
|
28749
29676
|
drawAnnotation(attributes, target);
|
|
28750
29677
|
if (attributes.labelText && !attributes._stationTitle && attributes.cat !== "bus" && attributes.cat !== "cableTerminal" && attributes.cat !== "consumer" && attributes.cat !== "switch" && attributes.cat !== "meterBox") {
|
|
@@ -28761,6 +29688,15 @@ class SvgSymbolNode extends BaseNode {
|
|
|
28761
29688
|
for (const child of items) child.style.opacity = op;
|
|
28762
29689
|
}
|
|
28763
29690
|
}
|
|
29691
|
+
const blinkOp = nodeBlinkOpacity(attributes);
|
|
29692
|
+
if (blinkOp < 1) {
|
|
29693
|
+
const kids = target.children || [];
|
|
29694
|
+
for (let i = symStart; i < symEnd && i < kids.length; i++) {
|
|
29695
|
+
const s2 = kids[i].style;
|
|
29696
|
+
const base = s2.opacity == null ? 1 : s2.opacity;
|
|
29697
|
+
s2.opacity = base * blinkOp;
|
|
29698
|
+
}
|
|
29699
|
+
}
|
|
28764
29700
|
return key;
|
|
28765
29701
|
}
|
|
28766
29702
|
}
|
|
@@ -28858,6 +29794,56 @@ const _sfc_main$2 = {
|
|
|
28858
29794
|
function cellFromShape(shape) {
|
|
28859
29795
|
return shape && shape.__custBox != null && shape.__custIndex != null ? { nodeId: shape.__custBox, index: shape.__custIndex } : null;
|
|
28860
29796
|
}
|
|
29797
|
+
function cellIndexAtPoint(nodeId, evt) {
|
|
29798
|
+
const st = styleOf(nodeId);
|
|
29799
|
+
if (!st || st._meterBoxMode !== "grid" || !Array.isArray(st._customers) || !st._customers.length) return null;
|
|
29800
|
+
const c = evt && (evt.canvas || evt.viewport) || {};
|
|
29801
|
+
if (c.x == null || c.y == null || st.x == null || st.y == null) return null;
|
|
29802
|
+
let lx = c.x - st.x;
|
|
29803
|
+
let ly = c.y - st.y;
|
|
29804
|
+
const rot = (st.rotate || 0) * Math.PI / 180;
|
|
29805
|
+
if (rot) {
|
|
29806
|
+
const cos = Math.cos(-rot);
|
|
29807
|
+
const sin = Math.sin(-rot);
|
|
29808
|
+
const nx = lx * cos - ly * sin;
|
|
29809
|
+
const ny = lx * sin + ly * cos;
|
|
29810
|
+
lx = nx;
|
|
29811
|
+
ly = ny;
|
|
29812
|
+
}
|
|
29813
|
+
const cols = st._meterBoxCols || Math.min(st._customers.length, MAX_COLS);
|
|
29814
|
+
const cellW = METER_CELL_W;
|
|
29815
|
+
const cellH = st._meterBoxShowName ? METER_CELL_H : METER_CELL_W;
|
|
29816
|
+
const rows = st._meterBoxRows || Math.ceil(st._customers.length / cols);
|
|
29817
|
+
const gridW = cols * cellW + (cols - 1) * METER_GAP;
|
|
29818
|
+
const gridH = rows * cellH + (rows - 1) * METER_GAP;
|
|
29819
|
+
const col = Math.floor((lx + gridW / 2) / (cellW + METER_GAP));
|
|
29820
|
+
const row2 = Math.floor((ly + gridH / 2) / (cellH + METER_GAP));
|
|
29821
|
+
if (col < 0 || col >= cols || row2 < 0 || row2 >= rows) return null;
|
|
29822
|
+
const i = row2 * cols + col;
|
|
29823
|
+
return i < st._customers.length ? i : null;
|
|
29824
|
+
}
|
|
29825
|
+
const DEBUG_CELL = typeof location !== "undefined" && /[?&]debugCell\b/.test(location.search);
|
|
29826
|
+
function dbgCell(tag, detail) {
|
|
29827
|
+
if (DEBUG_CELL) console.log("[cell-debug]", tag, detail);
|
|
29828
|
+
}
|
|
29829
|
+
function nodeIdOfShape(shape) {
|
|
29830
|
+
if (!shape || !nodeByIdMap) return null;
|
|
29831
|
+
let el = shape;
|
|
29832
|
+
for (let hops = 0; el && hops < 16; hops++) {
|
|
29833
|
+
if (el.id && nodeByIdMap.has(el.id)) return el.id;
|
|
29834
|
+
el = el.parentElement;
|
|
29835
|
+
}
|
|
29836
|
+
return null;
|
|
29837
|
+
}
|
|
29838
|
+
function resolveCell(evt, shape, nodeIdHint, allowBboxFallback = true) {
|
|
29839
|
+
const direct = cellFromShape(shape);
|
|
29840
|
+
if (direct) return direct;
|
|
29841
|
+
let nid = nodeIdHint || nodeIdOfShape(shape);
|
|
29842
|
+
if (!nid && allowBboxFallback) nid = hitTestNodeAtCanvas(evt);
|
|
29843
|
+
if (!nid) return null;
|
|
29844
|
+
const idx = cellIndexAtPoint(nid, evt);
|
|
29845
|
+
return idx == null ? null : { nodeId: nid, index: idx };
|
|
29846
|
+
}
|
|
28861
29847
|
function neighborsOf(id2) {
|
|
28862
29848
|
const upstream = [];
|
|
28863
29849
|
const downstream = [];
|
|
@@ -28936,6 +29922,32 @@ const _sfc_main$2 = {
|
|
|
28936
29922
|
emit("node-select", { node, upstream, downstream });
|
|
28937
29923
|
if (api) api._emit("node:click", { nodeId: id2, node });
|
|
28938
29924
|
}
|
|
29925
|
+
function selectNodeRef(ref2) {
|
|
29926
|
+
if (!api || !graph || ref2 == null) return;
|
|
29927
|
+
const t = api._resolveTarget(ref2);
|
|
29928
|
+
if (!t) {
|
|
29929
|
+
selectNodeWithInfo(String(ref2));
|
|
29930
|
+
return;
|
|
29931
|
+
}
|
|
29932
|
+
if (t.kind === "customer") {
|
|
29933
|
+
selectCustomerWithInfo(t.boxId, t.index);
|
|
29934
|
+
} else {
|
|
29935
|
+
selectNodeWithInfo(t.id);
|
|
29936
|
+
}
|
|
29937
|
+
}
|
|
29938
|
+
function selectCustomerRef(nodeRef, index) {
|
|
29939
|
+
if (!api || !graph || nodeRef == null) return;
|
|
29940
|
+
const t = api._resolveTarget(nodeRef);
|
|
29941
|
+
if (!t) {
|
|
29942
|
+
selectNodeWithInfo(String(nodeRef));
|
|
29943
|
+
return;
|
|
29944
|
+
}
|
|
29945
|
+
if (t.kind === "customer") {
|
|
29946
|
+
selectCustomerWithInfo(t.boxId, t.index);
|
|
29947
|
+
return;
|
|
29948
|
+
}
|
|
29949
|
+
selectCustomerWithInfo(t.id, index);
|
|
29950
|
+
}
|
|
28939
29951
|
function routeNodeTap(id2) {
|
|
28940
29952
|
if (!graph || id2 == null) return;
|
|
28941
29953
|
const customers = customersOf(id2);
|
|
@@ -29069,14 +30081,21 @@ const _sfc_main$2 = {
|
|
|
29069
30081
|
graph.on("node:click", (evt) => {
|
|
29070
30082
|
const id2 = evt.target.id;
|
|
29071
30083
|
if (!id2) return;
|
|
29072
|
-
const cell =
|
|
29073
|
-
|
|
30084
|
+
const cell = resolveCell(evt, evt.originalTarget, id2);
|
|
30085
|
+
dbgCell("node:click", {
|
|
30086
|
+
id: id2,
|
|
30087
|
+
shape: evt.originalTarget && evt.originalTarget.nodeName,
|
|
30088
|
+
mark: cellFromShape(evt.originalTarget),
|
|
30089
|
+
cell,
|
|
30090
|
+
canvas: evt.canvas
|
|
30091
|
+
});
|
|
30092
|
+
if (cell && customersOf(id2) && cell.nodeId === id2) {
|
|
29074
30093
|
selectCustomerWithInfo(id2, cell.index);
|
|
29075
30094
|
return;
|
|
29076
30095
|
}
|
|
29077
30096
|
routeNodeTap(id2);
|
|
29078
30097
|
});
|
|
29079
|
-
function
|
|
30098
|
+
function hitTestNodeAtCanvas2(evt) {
|
|
29080
30099
|
const c = evt && (evt.canvas || evt.viewport) || {};
|
|
29081
30100
|
const cx = c.x, cy = c.y;
|
|
29082
30101
|
if (cx == null || cy == null) return null;
|
|
@@ -29126,8 +30145,14 @@ const _sfc_main$2 = {
|
|
|
29126
30145
|
api._emit("edge:click", { edgeId: id2, source: src, target: tgt });
|
|
29127
30146
|
});
|
|
29128
30147
|
graph.on("canvas:click", (evt) => {
|
|
29129
|
-
const hitId =
|
|
30148
|
+
const hitId = hitTestNodeAtCanvas2(evt);
|
|
29130
30149
|
if (hitId) {
|
|
30150
|
+
const cell = resolveCell(evt, null, hitId);
|
|
30151
|
+
dbgCell("canvas:click", { hitId, cell, canvas: evt.canvas });
|
|
30152
|
+
if (cell && cell.nodeId === hitId && customersOf(hitId)) {
|
|
30153
|
+
selectCustomerWithInfo(hitId, cell.index);
|
|
30154
|
+
return;
|
|
30155
|
+
}
|
|
29131
30156
|
routeNodeTap(hitId);
|
|
29132
30157
|
return;
|
|
29133
30158
|
}
|
|
@@ -29149,7 +30174,7 @@ const _sfc_main$2 = {
|
|
|
29149
30174
|
const gCanvas = graph.getCanvas && graph.getCanvas();
|
|
29150
30175
|
const doc = gCanvas && gCanvas.document;
|
|
29151
30176
|
if (doc) {
|
|
29152
|
-
doc.addEventListener("pointermove", (e2) => setCellHover(
|
|
30177
|
+
doc.addEventListener("pointermove", (e2) => setCellHover(resolveCell(e2, e2 && e2.target, null, false)));
|
|
29153
30178
|
}
|
|
29154
30179
|
hoverLeaveHandler = () => setCellHover(null);
|
|
29155
30180
|
container.value.addEventListener("pointerleave", hoverLeaveHandler);
|
|
@@ -29248,9 +30273,9 @@ const _sfc_main$2 = {
|
|
|
29248
30273
|
}, {}),
|
|
29249
30274
|
// 保留原有快捷方法
|
|
29250
30275
|
resetView,
|
|
29251
|
-
selectNode:
|
|
29252
|
-
/** 选中计量箱内第 index
|
|
29253
|
-
selectCustomer:
|
|
30276
|
+
selectNode: selectNodeRef,
|
|
30277
|
+
/** 选中计量箱内第 index 个用户(nodeRef=箱节点id/psrId;传 assetNo 时自动定位该户) */
|
|
30278
|
+
selectCustomer: selectCustomerRef,
|
|
29254
30279
|
/** 当前选中的“箱内用户”信息({ nodeId, index, customer, node } | null) */
|
|
29255
30280
|
getSelectedCustomer,
|
|
29256
30281
|
exportPng,
|
|
@@ -29289,7 +30314,7 @@ const _sfc_main$2 = {
|
|
|
29289
30314
|
};
|
|
29290
30315
|
}
|
|
29291
30316
|
};
|
|
29292
|
-
const TopoGraph = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-
|
|
30317
|
+
const TopoGraph = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-a4a4c398"]]);
|
|
29293
30318
|
const _hoisted_1$1 = ["data-theme"];
|
|
29294
30319
|
const _hoisted_2$1 = { class: "panel-head" };
|
|
29295
30320
|
const _hoisted_3$1 = {
|
|
@@ -29662,7 +30687,7 @@ const _sfc_main = {
|
|
|
29662
30687
|
}
|
|
29663
30688
|
};
|
|
29664
30689
|
const DistanceTop10 = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-438b6017"]]);
|
|
29665
|
-
const VERSION = "0.1.
|
|
30690
|
+
const VERSION = "0.1.10";
|
|
29666
30691
|
const DESCRIPTION = "配电台区单线图拓扑成图引擎";
|
|
29667
30692
|
export {
|
|
29668
30693
|
DESCRIPTION,
|