topo-engine 0.1.4 → 0.1.6
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 +43 -1
- package/dist/index.d.ts +5 -0
- package/dist/style.css +11 -11
- package/dist/topo-engine.cjs +189 -18
- package/dist/topo-engine.cjs.map +1 -1
- package/dist/topo-engine.js +189 -18
- package/dist/topo-engine.js.map +1 -1
- package/dist/topo-engine.umd.cjs +189 -18
- package/dist/topo-engine.umd.cjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -174,7 +174,7 @@ const g6Data = buildGraphData(model, layout); // → G6 可直接消费的 { n
|
|
|
174
174
|
| `3303` | 低压母线 | 白色短粗线(无文字标签) |
|
|
175
175
|
| `3202` / `320300000` | 电缆终端头 / 分接点 | 三角 / 红色米字(无文字标签) |
|
|
176
176
|
| `310100000` | 低压导线 | 青色连接点(名称缩写为杆号区间) |
|
|
177
|
-
| `3112` | 计量箱 | 黑底白框 "JX" |
|
|
177
|
+
| `3112` | 计量箱 | 黑底白框 "JX"(有客户数据时显示为电能表图标 + 用户名) |
|
|
178
178
|
| `3218000` | 用户接入点 | 白圈 "J"(名称缩写为户号) |
|
|
179
179
|
| `32500000` | 低压配电箱(台区) | 站房 |
|
|
180
180
|
|
|
@@ -354,6 +354,48 @@ topo.off('node:click', handler); // 不传 handler 则移除该事件全部监
|
|
|
354
354
|
- **母线 / 电缆终端头**:不渲染额外文字标签(母线不显示"母线",电缆终端头不显示名称)
|
|
355
355
|
- **站房标题**:使用独立属性 `_stationTitle` 渲染,避免 G6 默认 label 重复绘制
|
|
356
356
|
|
|
357
|
+
## 计量箱客户数据
|
|
358
|
+
|
|
359
|
+
计量箱(psrType `3112`)支持关联客户数据,渲染时根据客户数量自动切换显示模式:
|
|
360
|
+
|
|
361
|
+
| 模式 | 条件 | 渲染效果 |
|
|
362
|
+
| ---- | ---- | ---- |
|
|
363
|
+
| 无客户数据 | 未传入 customerData 或无匹配 | 原始 JX 黑底白字方框 |
|
|
364
|
+
| 单用户 | customerList 有 1 个用户 | 电能表图标(圆圈 + 竖线)+ 用户名 |
|
|
365
|
+
| 多用户(显示名称) | customerList 2~20 个用户 | 方框内电能表网格,每个格子含图标 + 用户名(每行最多 5 个) |
|
|
366
|
+
| 多用户(仅图标) | customerList >20 个用户 | 方框内电能表网格,仅显示图标(格子更紧凑) |
|
|
367
|
+
|
|
368
|
+
客户数据格式(与拓扑 JSON 配套的 customer JSON):
|
|
369
|
+
|
|
370
|
+
```json
|
|
371
|
+
[
|
|
372
|
+
{
|
|
373
|
+
"psrId": "4230000000109100079",
|
|
374
|
+
"consumerList": [
|
|
375
|
+
{ "consId": "...", "consName": "...", "realConsName": "张三", "meterId": "..." }
|
|
376
|
+
]
|
|
377
|
+
}
|
|
378
|
+
]
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
配套文件命名规则:拓扑文件为 `XXX.json` 时,客户文件为 `XXXcustomer.json`(Demo 模式下自动加载)。
|
|
382
|
+
|
|
383
|
+
### TopoGraph 新增 Props
|
|
384
|
+
|
|
385
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
386
|
+
| ---- | ---- | ---- | ---- |
|
|
387
|
+
| `customerData` | `Array` | `null` | 客户数据数组,传入后自动注入到计量箱节点 |
|
|
388
|
+
|
|
389
|
+
### 工具函数
|
|
390
|
+
|
|
391
|
+
```javascript
|
|
392
|
+
import { buildCustomerMap, injectCustomers, getMeterBoxSize } from 'topo-engine';
|
|
393
|
+
|
|
394
|
+
const customerMap = buildCustomerMap(customerJsonArray); // psrId → consumerList
|
|
395
|
+
injectCustomers(model, customerMap); // 注入到模型的 meterBox 节点
|
|
396
|
+
const size = getMeterBoxSize(node); // 获取动态尺寸 { w, h } | null
|
|
397
|
+
```
|
|
398
|
+
|
|
357
399
|
## 布局与渲染参数
|
|
358
400
|
|
|
359
401
|
`computeOrthogonalLayout(model, options)`:
|
package/dist/index.d.ts
CHANGED
|
@@ -108,6 +108,11 @@ export function edgeStyle(datum: any): any;
|
|
|
108
108
|
export function calculateTopDistances(edges: any[], nodes: any[], edgeCP: Map<string, number[][]>, fishboneRibs: Set<string>, topN?: number): any[];
|
|
109
109
|
export function getSymbolSpec(cat: string): { w: number, h: number };
|
|
110
110
|
|
|
111
|
+
// 计量箱客户数据
|
|
112
|
+
export function buildCustomerMap(customerData: Array<any>): Map<string, Array<any>>;
|
|
113
|
+
export function injectCustomers(model: any, customerMap: Map<string, Array<any>>): void;
|
|
114
|
+
export function getMeterBoxSize(node: any): { w: number, h: number } | null;
|
|
115
|
+
|
|
111
116
|
// 常量
|
|
112
117
|
export const SYMBOL_SPEC: Record<string, { w: number, h: number }>;
|
|
113
118
|
export const FONT: string;
|
package/dist/style.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
/* —— 主题变量:dark 为默认值,light 由 [data-theme='light'] 覆盖 —— */
|
|
3
|
-
.topo-wrap[data-v-
|
|
3
|
+
.topo-wrap[data-v-b02bff98] {
|
|
4
4
|
--tp-bg: #050505;
|
|
5
5
|
--tp-toolbar-bg: rgba(20, 20, 20, 0.88);
|
|
6
6
|
--tp-toolbar-border: #333;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
overflow: hidden;
|
|
20
20
|
background: var(--tp-bg);
|
|
21
21
|
}
|
|
22
|
-
.topo-wrap[data-theme='light'][data-v-
|
|
22
|
+
.topo-wrap[data-theme='light'][data-v-b02bff98] {
|
|
23
23
|
--tp-bg: #ffffff;
|
|
24
24
|
--tp-toolbar-bg: rgba(255, 255, 255, 0.92);
|
|
25
25
|
--tp-toolbar-border: #d9d9d9;
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
--tp-text: #555;
|
|
34
34
|
--tp-error: #d32f2f;
|
|
35
35
|
}
|
|
36
|
-
.topo-container[data-v-
|
|
36
|
+
.topo-container[data-v-b02bff98] {
|
|
37
37
|
width: 100%;
|
|
38
38
|
height: 100%;
|
|
39
39
|
}
|
|
40
|
-
.topo-toolbar[data-v-
|
|
40
|
+
.topo-toolbar[data-v-b02bff98] {
|
|
41
41
|
position: absolute;
|
|
42
42
|
top: 10px;
|
|
43
43
|
left: 12px;
|
|
@@ -52,14 +52,14 @@
|
|
|
52
52
|
color: var(--tp-text);
|
|
53
53
|
z-index: 10;
|
|
54
54
|
}
|
|
55
|
-
.topo-title[data-v-
|
|
55
|
+
.topo-title[data-v-b02bff98] {
|
|
56
56
|
color: var(--tp-title);
|
|
57
57
|
font-weight: 600;
|
|
58
58
|
}
|
|
59
|
-
.topo-meta[data-v-
|
|
59
|
+
.topo-meta[data-v-b02bff98] {
|
|
60
60
|
color: var(--tp-meta);
|
|
61
61
|
}
|
|
62
|
-
.btn[data-v-
|
|
62
|
+
.btn[data-v-b02bff98] {
|
|
63
63
|
margin-left: 4px;
|
|
64
64
|
padding: 3px 10px;
|
|
65
65
|
border: 1px solid var(--tp-btn-border);
|
|
@@ -69,12 +69,12 @@
|
|
|
69
69
|
cursor: pointer;
|
|
70
70
|
font-size: 12px;
|
|
71
71
|
}
|
|
72
|
-
.btn[data-v-
|
|
72
|
+
.btn[data-v-b02bff98]:hover {
|
|
73
73
|
border-color: var(--tp-accent);
|
|
74
74
|
color: var(--tp-accent);
|
|
75
75
|
}
|
|
76
|
-
.topo-loading[data-v-
|
|
77
|
-
.topo-error[data-v-
|
|
76
|
+
.topo-loading[data-v-b02bff98],
|
|
77
|
+
.topo-error[data-v-b02bff98] {
|
|
78
78
|
position: absolute;
|
|
79
79
|
inset: 0;
|
|
80
80
|
display: flex;
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
z-index: 5;
|
|
86
86
|
background: var(--tp-overlay-bg);
|
|
87
87
|
}
|
|
88
|
-
.topo-error[data-v-
|
|
88
|
+
.topo-error[data-v-b02bff98] {
|
|
89
89
|
color: var(--tp-error);
|
|
90
90
|
}
|
|
91
91
|
|
package/dist/topo-engine.cjs
CHANGED
|
@@ -54,6 +54,13 @@ class TopoApi {
|
|
|
54
54
|
_assertReady() {
|
|
55
55
|
if (!this._graph) throw new Error("[TopoApi] 图实例未就绪,请等待渲染完成");
|
|
56
56
|
}
|
|
57
|
+
/** 画布中心兼容取值:G6 v5 getCanvasCenter 返回数组 [x,y,z],历史代码按 {x,y} 对象使用 */
|
|
58
|
+
_canvasCenterXY() {
|
|
59
|
+
const c = this._graph.getCanvasCenter();
|
|
60
|
+
if (Array.isArray(c)) return { x: c[0], y: c[1] };
|
|
61
|
+
if (c && typeof c === "object") return { x: c.x || 0, y: c.y || 0 };
|
|
62
|
+
return { x: 0, y: 0 };
|
|
63
|
+
}
|
|
57
64
|
/** 安全合并 style:保留现有字段,只覆盖传入字段 */
|
|
58
65
|
_safeNodeStyle(nodeId, style) {
|
|
59
66
|
try {
|
|
@@ -373,7 +380,7 @@ class TopoApi {
|
|
|
373
380
|
try {
|
|
374
381
|
this._graph.focusElement(nodeId);
|
|
375
382
|
} catch {
|
|
376
|
-
const center = this.
|
|
383
|
+
const center = this._canvasCenterXY();
|
|
377
384
|
this._graph.translateTo({ x: center.x - p.x * (zoom || this._graph.getZoom()), y: center.y - p.y * (zoom || this._graph.getZoom()) });
|
|
378
385
|
}
|
|
379
386
|
}
|
|
@@ -381,7 +388,7 @@ class TopoApi {
|
|
|
381
388
|
getViewport() {
|
|
382
389
|
this._assertReady();
|
|
383
390
|
const zoom = this._graph.getZoom();
|
|
384
|
-
const center = this.
|
|
391
|
+
const center = this._canvasCenterXY();
|
|
385
392
|
return { zoom, center };
|
|
386
393
|
}
|
|
387
394
|
// ============================================================
|
|
@@ -754,11 +761,25 @@ class TopoApi {
|
|
|
754
761
|
if (!container) return;
|
|
755
762
|
const pos = this._layout.pos.get(nodeId);
|
|
756
763
|
if (!pos) return;
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
764
|
+
let client = null;
|
|
765
|
+
try {
|
|
766
|
+
if (typeof this._graph.getClientByCanvas === "function") {
|
|
767
|
+
const r = this._graph.getClientByCanvas([pos.x, pos.y]);
|
|
768
|
+
client = Array.isArray(r) ? { x: r[0], y: r[1] } : r;
|
|
769
|
+
}
|
|
770
|
+
} catch (e2) {
|
|
771
|
+
}
|
|
772
|
+
let screenX, screenY;
|
|
773
|
+
if (client && isFinite(client.x)) {
|
|
774
|
+
screenX = client.x;
|
|
775
|
+
screenY = client.y;
|
|
776
|
+
} else {
|
|
777
|
+
const zoom = this._graph.getZoom();
|
|
778
|
+
const canvasCenter = this._canvasCenterXY();
|
|
779
|
+
const containerRect = container.getBoundingClientRect();
|
|
780
|
+
screenX = containerRect.left + canvasCenter.x + pos.x * zoom;
|
|
781
|
+
screenY = containerRect.top + canvasCenter.y + pos.y * zoom;
|
|
782
|
+
}
|
|
762
783
|
const el = document.createElement("div");
|
|
763
784
|
el.className = "topo-card";
|
|
764
785
|
el.dataset.nodeId = nodeId;
|
|
@@ -1216,6 +1237,53 @@ const SYMBOL_SPEC = {
|
|
|
1216
1237
|
function getSymbolSpec(cat) {
|
|
1217
1238
|
return SYMBOL_SPEC[cat] || SYMBOL_SPEC.other;
|
|
1218
1239
|
}
|
|
1240
|
+
function buildCustomerMap(customerData) {
|
|
1241
|
+
const map = /* @__PURE__ */ new Map();
|
|
1242
|
+
if (!Array.isArray(customerData)) return map;
|
|
1243
|
+
for (const entry of customerData) {
|
|
1244
|
+
if (entry && entry.psrId && Array.isArray(entry.consumerList)) {
|
|
1245
|
+
map.set(String(entry.psrId), entry.consumerList);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
return map;
|
|
1249
|
+
}
|
|
1250
|
+
const METER_CELL_W = 26;
|
|
1251
|
+
const METER_CELL_H = 38;
|
|
1252
|
+
const METER_GAP = 4;
|
|
1253
|
+
const METER_PAD = 6;
|
|
1254
|
+
const MAX_COLS = 5;
|
|
1255
|
+
const SHOW_NAME_LIMIT = 20;
|
|
1256
|
+
function injectCustomers(model, customerMap) {
|
|
1257
|
+
for (const node of model.nodes) {
|
|
1258
|
+
if (node.cat !== "meterBox") continue;
|
|
1259
|
+
const customers = customerMap.get(node.psrId);
|
|
1260
|
+
if (!customers || customers.length === 0) continue;
|
|
1261
|
+
node._customers = customers;
|
|
1262
|
+
if (customers.length === 1) {
|
|
1263
|
+
node._meterBoxMode = "single";
|
|
1264
|
+
node._meterBoxW = 30;
|
|
1265
|
+
node._meterBoxH = 38;
|
|
1266
|
+
} else {
|
|
1267
|
+
const showName = customers.length <= SHOW_NAME_LIMIT;
|
|
1268
|
+
const cols = Math.min(customers.length, MAX_COLS);
|
|
1269
|
+
const rows = Math.ceil(customers.length / cols);
|
|
1270
|
+
const cellW = METER_CELL_W;
|
|
1271
|
+
const cellH = showName ? METER_CELL_H : METER_CELL_W;
|
|
1272
|
+
node._meterBoxMode = "grid";
|
|
1273
|
+
node._meterBoxCols = cols;
|
|
1274
|
+
node._meterBoxRows = rows;
|
|
1275
|
+
node._meterBoxShowName = showName;
|
|
1276
|
+
node._meterBoxW = METER_PAD * 2 + cols * cellW + (cols - 1) * METER_GAP;
|
|
1277
|
+
node._meterBoxH = METER_PAD * 2 + rows * cellH + (rows - 1) * METER_GAP;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
function getMeterBoxSize(node) {
|
|
1282
|
+
if (node._meterBoxW != null && node._meterBoxH != null) {
|
|
1283
|
+
return { w: node._meterBoxW, h: node._meterBoxH };
|
|
1284
|
+
}
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1219
1287
|
function stationPlans(model) {
|
|
1220
1288
|
const nodesById = new Map(model.nodes.map((n) => [n.id, n]));
|
|
1221
1289
|
const plans = [];
|
|
@@ -1267,6 +1335,10 @@ function stationInner(plan, model, band = 50, nodeMap) {
|
|
|
1267
1335
|
const nmap = nodeMap || new Map(model.nodes.map((n) => [n.id, n]));
|
|
1268
1336
|
const specOf = (id2) => {
|
|
1269
1337
|
const n = nmap.get(id2);
|
|
1338
|
+
if (n && n.cat === "meterBox") {
|
|
1339
|
+
const dim = getMeterBoxSize(n);
|
|
1340
|
+
if (dim) return dim;
|
|
1341
|
+
}
|
|
1270
1342
|
const s2 = getSymbolSpec(n ? n.cat : "other");
|
|
1271
1343
|
return { w: s2.w, h: s2.h };
|
|
1272
1344
|
};
|
|
@@ -1924,6 +1996,10 @@ function computeOrthogonalLayout(model, opts = {}) {
|
|
|
1924
1996
|
const sz = (id2) => {
|
|
1925
1997
|
const n = extNodeMap.get(id2);
|
|
1926
1998
|
if (n && n._size) return n._size;
|
|
1999
|
+
if (n && n.cat === "meterBox") {
|
|
2000
|
+
const dim = getMeterBoxSize(n);
|
|
2001
|
+
if (dim) return dim;
|
|
2002
|
+
}
|
|
1927
2003
|
const spec = getSymbolSpec(n ? n.cat : "other");
|
|
1928
2004
|
return { w: spec.w, h: spec.h };
|
|
1929
2005
|
};
|
|
@@ -2522,22 +2598,30 @@ function buildGraphData(model, layout) {
|
|
|
2522
2598
|
const nodes = model.nodes.map((n) => {
|
|
2523
2599
|
const spec = getSymbolSpec(n.cat);
|
|
2524
2600
|
const busW = layout.stationBusWidth && layout.stationBusWidth.get(n.id);
|
|
2525
|
-
const
|
|
2601
|
+
const meterBoxDim = n.cat === "meterBox" ? getMeterBoxSize(n) : null;
|
|
2602
|
+
const w = busW || (meterBoxDim ? meterBoxDim.w : spec.w);
|
|
2603
|
+
const h = meterBoxDim ? meterBoxDim.h : spec.h;
|
|
2526
2604
|
const p = layout.pos.get(n.id) || { x: 0, y: 0 };
|
|
2527
2605
|
return {
|
|
2528
2606
|
id: n.id,
|
|
2529
|
-
data: { ...n, width: w, height:
|
|
2607
|
+
data: { ...n, width: w, height: h },
|
|
2530
2608
|
style: {
|
|
2531
2609
|
x: p.x,
|
|
2532
2610
|
y: p.y,
|
|
2533
|
-
size: [w,
|
|
2611
|
+
size: [w, h],
|
|
2534
2612
|
// 自定义字段(进 attributes,供 svg-symbol 节点绘制使用)
|
|
2535
2613
|
cat: n.cat,
|
|
2536
|
-
labelText: n.cat === "bus" || n.cat === "cableTerminal" ? "" : n.shortName || "",
|
|
2614
|
+
labelText: n.cat === "bus" || n.cat === "cableTerminal" || n.cat === "consumer" ? "" : n.shortName || "",
|
|
2537
2615
|
rotate: layout.stationRotate && layout.stationRotate.get(n.id) || 0,
|
|
2538
2616
|
highlight: 0,
|
|
2539
2617
|
cursor: "pointer",
|
|
2540
|
-
_nodeId: n.id
|
|
2618
|
+
_nodeId: n.id,
|
|
2619
|
+
// 计量箱客户数据
|
|
2620
|
+
_meterBoxMode: n._meterBoxMode || null,
|
|
2621
|
+
_meterBoxCols: n._meterBoxCols || 0,
|
|
2622
|
+
_meterBoxRows: n._meterBoxRows || 0,
|
|
2623
|
+
_meterBoxShowName: n._meterBoxShowName || false,
|
|
2624
|
+
_customers: n._customers || null
|
|
2541
2625
|
}
|
|
2542
2626
|
};
|
|
2543
2627
|
});
|
|
@@ -27789,8 +27873,45 @@ function drawStationTerminal(attributes, group) {
|
|
|
27789
27873
|
group.appendChild(new Polygon({ style: { points: [[0, -5], [0, 5], [9, 0]], fill: "none", stroke: C2.red, lineWidth: 1.4 } }));
|
|
27790
27874
|
}
|
|
27791
27875
|
function drawMeterBox(attributes, group) {
|
|
27792
|
-
|
|
27793
|
-
|
|
27876
|
+
const [w, h] = attributes.size;
|
|
27877
|
+
if (attributes._meterBoxMode === "single") {
|
|
27878
|
+
group.appendChild(new Circle({ style: { cx: 0, cy: -3, r: 11, fill: "none", stroke: C2.white, lineWidth: 1.6 } }));
|
|
27879
|
+
group.appendChild(new Line({ style: { x1: 0, y1: -14, x2: 0, y2: -7, stroke: C2.white, lineWidth: 1.6 } }));
|
|
27880
|
+
const name = attributes._customers && attributes._customers[0] ? attributes._customers[0].realConsName || attributes._customers[0].consName || "" : "";
|
|
27881
|
+
if (name) {
|
|
27882
|
+
group.appendChild(new Text({ style: { text: name, x: 0, y: 12, fontSize: 8, fill: C2.white, fontFamily: FONT, textAlign: "center", textBaseline: "middle" } }));
|
|
27883
|
+
}
|
|
27884
|
+
} else if (attributes._meterBoxMode === "grid" && attributes._customers) {
|
|
27885
|
+
group.appendChild(new Rect({ style: { x: -w / 2, y: -h / 2, width: w, height: h, fill: C2.meterFill, stroke: C2.meterText, lineWidth: 0.9, radius: 2 } }));
|
|
27886
|
+
const cols = attributes._meterBoxCols || 5;
|
|
27887
|
+
const customers = attributes._customers;
|
|
27888
|
+
const showName = attributes._meterBoxShowName;
|
|
27889
|
+
const cellW = METER_CELL_W;
|
|
27890
|
+
const cellH = showName ? METER_CELL_H : METER_CELL_W;
|
|
27891
|
+
const gridW = cols * cellW + (cols - 1) * METER_GAP;
|
|
27892
|
+
const rows = attributes._meterBoxRows || Math.ceil(customers.length / cols);
|
|
27893
|
+
const gridH = rows * cellH + (rows - 1) * METER_GAP;
|
|
27894
|
+
const startX = -gridW / 2;
|
|
27895
|
+
const startY = -gridH / 2;
|
|
27896
|
+
const meterR = 9;
|
|
27897
|
+
for (let i = 0; i < customers.length; i++) {
|
|
27898
|
+
const row2 = Math.floor(i / cols);
|
|
27899
|
+
const col = i % cols;
|
|
27900
|
+
const cx = startX + col * (cellW + METER_GAP) + cellW / 2;
|
|
27901
|
+
const cy = startY + row2 * (cellH + METER_GAP) + cellW / 2;
|
|
27902
|
+
group.appendChild(new Circle({ style: { cx, cy, r: meterR, fill: "none", stroke: C2.meterText, lineWidth: 0.8 } }));
|
|
27903
|
+
group.appendChild(new Line({ style: { x1: cx, y1: cy - meterR, x2: cx, y2: cy - meterR + 4, stroke: C2.meterText, lineWidth: 0.8 } }));
|
|
27904
|
+
if (showName) {
|
|
27905
|
+
const name = customers[i].realConsName || customers[i].consName || "";
|
|
27906
|
+
if (name) {
|
|
27907
|
+
group.appendChild(new Text({ style: { text: name, x: cx, y: cy + meterR + 6, fontSize: 7, fill: C2.meterText, fontFamily: FONT, textAlign: "center", textBaseline: "middle" } }));
|
|
27908
|
+
}
|
|
27909
|
+
}
|
|
27910
|
+
}
|
|
27911
|
+
} else {
|
|
27912
|
+
group.appendChild(new Rect({ style: { x: -9.5, y: -9.5, width: 19, height: 19, fill: C2.meterFill, stroke: C2.meterText, lineWidth: 0.9 } }));
|
|
27913
|
+
text(attributes, group, "JX", 0, 0.5, 7.5, C2.meterText);
|
|
27914
|
+
}
|
|
27794
27915
|
}
|
|
27795
27916
|
function drawConsumer(attributes, group) {
|
|
27796
27917
|
group.appendChild(new Circle({ style: { cx: 0, cy: 0, r: 12.5, fill: "none", stroke: C2.white, lineWidth: 1.8 } }));
|
|
@@ -27916,8 +28037,10 @@ const _sfc_main$2 = {
|
|
|
27916
28037
|
props: {
|
|
27917
28038
|
dataUrl: { type: String, default: "" },
|
|
27918
28039
|
data: { type: Object, default: null },
|
|
28040
|
+
/** 客户数据(计量箱关联的用户列表),直接传入 JSON 数组 */
|
|
28041
|
+
customerData: { type: Array, default: null },
|
|
27919
28042
|
/** 主题:'dark'(默认,黑底 SCADA 风)| 'light'(白底);支持 v-model:theme 双向绑定 */
|
|
27920
|
-
theme: { type: String, default: "
|
|
28043
|
+
theme: { type: String, default: "light" }
|
|
27921
28044
|
},
|
|
27922
28045
|
emits: ["node-select", "loaded", "top-distances", "update:theme"],
|
|
27923
28046
|
setup(__props, { expose: __expose, emit: __emit }) {
|
|
@@ -28033,6 +28156,10 @@ const _sfc_main$2 = {
|
|
|
28033
28156
|
lastRaw.value = raw;
|
|
28034
28157
|
const theme = applyTheme();
|
|
28035
28158
|
model = parseTopo(raw);
|
|
28159
|
+
if (props.customerData && props.customerData.length) {
|
|
28160
|
+
const customerMap = buildCustomerMap(props.customerData);
|
|
28161
|
+
injectCustomers(model, customerMap);
|
|
28162
|
+
}
|
|
28036
28163
|
nodeByIdMap = new Map(model.nodes.map((n) => [n.id, n]));
|
|
28037
28164
|
title.value = String(model.title || "").replace(/\.+$/, "");
|
|
28038
28165
|
stats.value = model.stats;
|
|
@@ -28084,6 +28211,34 @@ const _sfc_main$2 = {
|
|
|
28084
28211
|
const id2 = evt.target.id;
|
|
28085
28212
|
selectNodeWithInfo(id2);
|
|
28086
28213
|
});
|
|
28214
|
+
function hitTestNodeAtCanvas(evt) {
|
|
28215
|
+
const c = evt && (evt.canvas || evt.viewport) || {};
|
|
28216
|
+
const cx = c.x, cy = c.y;
|
|
28217
|
+
if (cx == null || cy == null) return null;
|
|
28218
|
+
let best = null, bestD = Infinity;
|
|
28219
|
+
let all = [];
|
|
28220
|
+
try {
|
|
28221
|
+
all = graph.getNodeData();
|
|
28222
|
+
} catch {
|
|
28223
|
+
return null;
|
|
28224
|
+
}
|
|
28225
|
+
for (const nd of all || []) {
|
|
28226
|
+
if (nd && nd.data && nd.data.isStationFrame) continue;
|
|
28227
|
+
const st = nd && nd.style;
|
|
28228
|
+
if (!st || st.x == null || st.y == null) continue;
|
|
28229
|
+
const size = st.size || [0, 0];
|
|
28230
|
+
const w = size[0] || 0, h = size[1] || 0;
|
|
28231
|
+
if (w <= 0 || h <= 0) continue;
|
|
28232
|
+
if (cx >= st.x - w / 2 && cx <= st.x + w / 2 && cy >= st.y - h / 2 && cy <= st.y + h / 2) {
|
|
28233
|
+
const d2 = Math.abs(cx - st.x) + Math.abs(cy - st.y);
|
|
28234
|
+
if (d2 < bestD) {
|
|
28235
|
+
bestD = d2;
|
|
28236
|
+
best = nd.id;
|
|
28237
|
+
}
|
|
28238
|
+
}
|
|
28239
|
+
}
|
|
28240
|
+
return best;
|
|
28241
|
+
}
|
|
28087
28242
|
graph.on("node:pointerenter", (evt) => {
|
|
28088
28243
|
const id2 = evt.target.id;
|
|
28089
28244
|
const data3 = graph.getNodeData(id2);
|
|
@@ -28106,6 +28261,11 @@ const _sfc_main$2 = {
|
|
|
28106
28261
|
api._emit("edge:click", { edgeId: id2, source: src, target: tgt });
|
|
28107
28262
|
});
|
|
28108
28263
|
graph.on("canvas:click", (evt) => {
|
|
28264
|
+
const hitId = hitTestNodeAtCanvas(evt);
|
|
28265
|
+
if (hitId) {
|
|
28266
|
+
selectNodeWithInfo(hitId);
|
|
28267
|
+
return;
|
|
28268
|
+
}
|
|
28109
28269
|
if (selectedId) {
|
|
28110
28270
|
const data3 = graph.getNodeData(selectedId);
|
|
28111
28271
|
const existing = data3 && data3.style || {};
|
|
@@ -28118,7 +28278,8 @@ const _sfc_main$2 = {
|
|
|
28118
28278
|
});
|
|
28119
28279
|
graph.on("viewportchange", (evt) => {
|
|
28120
28280
|
const zoom = graph.getZoom();
|
|
28121
|
-
const
|
|
28281
|
+
const raw2 = graph.getCanvasCenter();
|
|
28282
|
+
const center = Array.isArray(raw2) ? { x: raw2[0], y: raw2[1] } : raw2;
|
|
28122
28283
|
api._emit("viewport:change", { zoom, center });
|
|
28123
28284
|
});
|
|
28124
28285
|
graph.render().then(() => {
|
|
@@ -28164,6 +28325,13 @@ const _sfc_main$2 = {
|
|
|
28164
28325
|
if (lastRaw.value) render(lastRaw.value);
|
|
28165
28326
|
}
|
|
28166
28327
|
);
|
|
28328
|
+
vue.watch(
|
|
28329
|
+
() => props.customerData,
|
|
28330
|
+
() => {
|
|
28331
|
+
if (lastRaw.value) render(lastRaw.value);
|
|
28332
|
+
},
|
|
28333
|
+
{ deep: true }
|
|
28334
|
+
);
|
|
28167
28335
|
vue.onBeforeUnmount(() => {
|
|
28168
28336
|
if (api) {
|
|
28169
28337
|
api.destroy();
|
|
@@ -28223,7 +28391,7 @@ const _sfc_main$2 = {
|
|
|
28223
28391
|
};
|
|
28224
28392
|
}
|
|
28225
28393
|
};
|
|
28226
|
-
const TopoGraph = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-
|
|
28394
|
+
const TopoGraph = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-b02bff98"]]);
|
|
28227
28395
|
const _hoisted_1$1 = ["data-theme"];
|
|
28228
28396
|
const _hoisted_2$1 = { class: "panel-head" };
|
|
28229
28397
|
const _hoisted_3$1 = {
|
|
@@ -28533,7 +28701,7 @@ const _sfc_main = {
|
|
|
28533
28701
|
}
|
|
28534
28702
|
};
|
|
28535
28703
|
const DistanceTop10 = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-438b6017"]]);
|
|
28536
|
-
const VERSION = "0.1.
|
|
28704
|
+
const VERSION = "0.1.6";
|
|
28537
28705
|
const DESCRIPTION = "配电台区单线图拓扑成图引擎";
|
|
28538
28706
|
exports.DESCRIPTION = DESCRIPTION;
|
|
28539
28707
|
exports.DistanceTop10 = DistanceTop10;
|
|
@@ -28544,10 +28712,13 @@ exports.TopoApi = TopoApi;
|
|
|
28544
28712
|
exports.TopoGraph = TopoGraph;
|
|
28545
28713
|
exports.VERSION = VERSION;
|
|
28546
28714
|
exports.assertOrthogonal = assertOrthogonal;
|
|
28715
|
+
exports.buildCustomerMap = buildCustomerMap;
|
|
28547
28716
|
exports.buildGraphData = buildGraphData;
|
|
28548
28717
|
exports.calculateTopDistances = calculateTopDistances;
|
|
28549
28718
|
exports.computeOrthogonalLayout = computeOrthogonalLayout;
|
|
28550
28719
|
exports.edgeStyle = edgeStyle;
|
|
28720
|
+
exports.getMeterBoxSize = getMeterBoxSize;
|
|
28551
28721
|
exports.getSymbolSpec = getSymbolSpec;
|
|
28722
|
+
exports.injectCustomers = injectCustomers;
|
|
28552
28723
|
exports.parseTopo = parseTopo;
|
|
28553
28724
|
//# sourceMappingURL=topo-engine.cjs.map
|