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