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.
@@ -54,6 +54,13 @@
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 @@
373
380
  try {
374
381
  this._graph.focusElement(nodeId);
375
382
  } catch {
376
- const center = this._graph.getCanvasCenter();
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 @@
381
388
  getViewport() {
382
389
  this._assertReady();
383
390
  const zoom = this._graph.getZoom();
384
- const center = this._graph.getCanvasCenter();
391
+ const center = this._canvasCenterXY();
385
392
  return { zoom, center };
386
393
  }
387
394
  // ============================================================
@@ -754,11 +761,25 @@
754
761
  if (!container) return;
755
762
  const pos = this._layout.pos.get(nodeId);
756
763
  if (!pos) return;
757
- const zoom = this._graph.getZoom();
758
- const canvasCenter = this._graph.getCanvasCenter();
759
- const containerRect = container.getBoundingClientRect();
760
- const screenX = containerRect.left + canvasCenter.x + pos.x * zoom;
761
- const screenY = containerRect.top + canvasCenter.y + pos.y * zoom;
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 @@
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 @@
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 s = getSymbolSpec(n ? n.cat : "other");
1271
1343
  return { w: s.w, h: s.h };
1272
1344
  };
@@ -1924,6 +1996,10 @@
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 @@
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 w = busW || spec.w;
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: spec.h },
2607
+ data: { ...n, width: w, height: h },
2530
2608
  style: {
2531
2609
  x: p.x,
2532
2610
  y: p.y,
2533
- size: [w, spec.h],
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 @@
27789
27873
  group.appendChild(new Polygon({ style: { points: [[0, -5], [0, 5], [9, 0]], fill: "none", stroke: C.red, lineWidth: 1.4 } }));
27790
27874
  }
27791
27875
  function drawMeterBox(attributes, group) {
27792
- group.appendChild(new Rect({ style: { x: -9.5, y: -9.5, width: 19, height: 19, fill: C.meterFill, stroke: C.meterText, lineWidth: 0.9 } }));
27793
- text(attributes, group, "JX", 0, 0.5, 7.5, C.meterText);
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: C.white, lineWidth: 1.6 } }));
27879
+ group.appendChild(new Line({ style: { x1: 0, y1: -14, x2: 0, y2: -7, stroke: C.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: C.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: C.meterFill, stroke: C.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 i2 = 0; i2 < customers.length; i2++) {
27898
+ const row2 = Math.floor(i2 / cols);
27899
+ const col = i2 % 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: C.meterText, lineWidth: 0.8 } }));
27903
+ group.appendChild(new Line({ style: { x1: cx, y1: cy - meterR, x2: cx, y2: cy - meterR + 4, stroke: C.meterText, lineWidth: 0.8 } }));
27904
+ if (showName) {
27905
+ const name = customers[i2].realConsName || customers[i2].consName || "";
27906
+ if (name) {
27907
+ group.appendChild(new Text({ style: { text: name, x: cx, y: cy + meterR + 6, fontSize: 7, fill: C.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: C.meterFill, stroke: C.meterText, lineWidth: 0.9 } }));
27913
+ text(attributes, group, "JX", 0, 0.5, 7.5, C.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: C.white, lineWidth: 1.8 } }));
@@ -27916,8 +28037,10 @@
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: "dark" }
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 @@
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 @@
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 d = Math.abs(cx - st.x) + Math.abs(cy - st.y);
28234
+ if (d < bestD) {
28235
+ bestD = d;
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 @@
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 @@
28118
28278
  });
28119
28279
  graph.on("viewportchange", (evt) => {
28120
28280
  const zoom = graph.getZoom();
28121
- const center = graph.getCanvasCenter();
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 @@
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 @@
28223
28391
  };
28224
28392
  }
28225
28393
  };
28226
- const TopoGraph = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-562b2247"]]);
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 @@
28533
28701
  }
28534
28702
  };
28535
28703
  const DistanceTop10 = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-438b6017"]]);
28536
- const VERSION = "0.1.4";
28704
+ const VERSION = "0.1.6";
28537
28705
  const DESCRIPTION = "配电台区单线图拓扑成图引擎";
28538
28706
  exports2.DESCRIPTION = DESCRIPTION;
28539
28707
  exports2.DistanceTop10 = DistanceTop10;
@@ -28544,11 +28712,14 @@
28544
28712
  exports2.TopoGraph = TopoGraph;
28545
28713
  exports2.VERSION = VERSION;
28546
28714
  exports2.assertOrthogonal = assertOrthogonal;
28715
+ exports2.buildCustomerMap = buildCustomerMap;
28547
28716
  exports2.buildGraphData = buildGraphData;
28548
28717
  exports2.calculateTopDistances = calculateTopDistances;
28549
28718
  exports2.computeOrthogonalLayout = computeOrthogonalLayout;
28550
28719
  exports2.edgeStyle = edgeStyle;
28720
+ exports2.getMeterBoxSize = getMeterBoxSize;
28551
28721
  exports2.getSymbolSpec = getSymbolSpec;
28722
+ exports2.injectCustomers = injectCustomers;
28552
28723
  exports2.parseTopo = parseTopo;
28553
28724
  Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
28554
28725
  });