topo-engine 0.1.10 → 0.1.12

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.
@@ -1,4 +1,4 @@
1
- import { ref, onMounted, watch, onBeforeUnmount, openBlock, createElementBlock, createElementVNode, toDisplayString, createCommentVNode, computed, Fragment as Fragment$1, renderList, normalizeClass, normalizeStyle, withModifiers, createTextVNode } from "vue";
1
+ import { ref, computed, onMounted, watch, onBeforeUnmount, openBlock, createElementBlock, createElementVNode, toDisplayString, createCommentVNode, normalizeClass, createTextVNode, withDirectives, Fragment as Fragment$1, renderList, vShow, normalizeStyle, withModifiers } from "vue";
2
2
  import { register, Polyline as Polyline$1, BaseNode, Graph } from "@antv/g6";
3
3
  function _typeof(o) {
4
4
  "@babel/helpers - typeof";
@@ -27319,12 +27319,24 @@ class TopoApi {
27319
27319
  }
27320
27320
  const TYPE_MAP = {
27321
27321
  "0302": { cat: "transformer", label: "配电变压器", order: 1 },
27322
+ "0110": { cat: "transformer", label: "配电变压器", order: 1 },
27323
+ // 柱上变压器(PMS 另一套类型码,与 0302 共用图元)
27322
27324
  "3301": { cat: "switch", label: "开关", order: 2 },
27325
+ "321500000": { cat: "switch", label: "开关", order: 2 },
27326
+ // 开关/断路器(另一套类型码,与 3301 共用图元)
27323
27327
  "3303": { cat: "bus", label: "低压母线", order: 3 },
27324
27328
  "3202": { cat: "cableTerminal", label: "电缆终端头", order: 4 },
27325
27329
  "310100000": { cat: "line", label: "低压导线", order: 5 },
27330
+ "3201": { cat: "line", label: "低压导线", order: 5 },
27331
+ // 低压导线(另一套类型码,与 310100000 共用图元)
27332
+ "3102": { cat: "line", label: "低压导线", order: 5 },
27333
+ // 低压电杆/导线点(同上,共用低压导线图元)
27334
+ "3101": { cat: "line", label: "低压导线", order: 5 },
27335
+ // 低压导线分段(同上,共用低压导线图元)
27326
27336
  "320300000": { cat: "junction", label: "密母分接点", order: 4 },
27327
27337
  "3112": { cat: "meterBox", label: "计量箱", order: 6 },
27338
+ "31120003": { cat: "meterBox", label: "计量箱", order: 6 },
27339
+ // 计量箱(另一套类型码,与 3112 共用图元)
27328
27340
  "3218000": { cat: "consumer", label: "用户接入点", order: 7 },
27329
27341
  "32500000": { cat: "station", label: "低压配电箱", order: 0 }
27330
27342
  };
@@ -27357,7 +27369,7 @@ function parseTopo(raw) {
27357
27369
  }
27358
27370
  }
27359
27371
  let roots = [...nodes].filter((n) => (indeg.get(n.id) || 0) === 0);
27360
- const prio = (n) => n.psrType === "0302" ? 0 : n.psrType === "3301" ? 1 : 2;
27372
+ const prio = (n) => n.psrType === "0302" || n.psrType === "0110" ? 0 : n.psrType === "3301" || n.psrType === "321500000" ? 1 : 2;
27361
27373
  roots.sort((a2, b) => prio(a2) - prio(b) || a2.id - b.id);
27362
27374
  const rootId = roots.length ? roots[0].id : nodes[0].id;
27363
27375
  const children = /* @__PURE__ */ new Map();
@@ -27376,19 +27388,20 @@ function parseTopo(raw) {
27376
27388
  }
27377
27389
  }
27378
27390
  const shortName = (n) => {
27379
- if (n.psrType === "3301") {
27391
+ if (n.psrType === "3301" || n.psrType === "321500000") {
27380
27392
  const no = extractSwitchNo(n.name);
27381
27393
  if (no) return no;
27394
+ if (/母联/.test(n.name || "")) return "母联";
27382
27395
  return /熔丝/.test(n.name || "") ? "熔丝" : "开关";
27383
27396
  }
27384
- if (n.psrType === "310100000") return extractLineShort(n.name);
27397
+ if (n.psrType === "310100000" || n.psrType === "3201" || n.psrType === "3102" || n.psrType === "3101") return extractLineShort(n.name);
27385
27398
  if (n.psrType === "3218000") {
27386
27399
  const m = String(n.name || "").match(/([\dA-Za-z#\-]+用户接入点)/);
27387
27400
  return m ? m[1].replace("用户接入点", "") : "用户";
27388
27401
  }
27389
27402
  if (n.psrType === "3303") return "母线";
27390
- if (n.psrType === "0302") return "柱上变";
27391
- if (n.psrType === "3112") return "";
27403
+ if (n.psrType === "0302" || n.psrType === "0110") return "柱上变";
27404
+ if (n.psrType === "3112" || n.psrType === "31120003") return "";
27392
27405
  if (n.psrType === "320300000") {
27393
27406
  const m = String(n.name || "").match(/(\d+栋\d+单元\d+层)/);
27394
27407
  return m ? m[1] : "分接点";
@@ -27407,9 +27420,9 @@ function parseTopo(raw) {
27407
27420
  cat: type.cat,
27408
27421
  catLabel: type.label,
27409
27422
  shortName: shortName(n),
27410
- isLine: n.psrType === "310100000",
27423
+ isLine: n.psrType === "310100000" || n.psrType === "3201" || n.psrType === "3102" || n.psrType === "3101",
27411
27424
  insideStation: stationPsrIds.has(n.psrId),
27412
- isFuse: n.psrType === "3301" && /熔丝/.test(nm),
27425
+ isFuse: (n.psrType === "3301" || n.psrType === "321500000") && /熔丝/.test(nm),
27413
27426
  status: n.status
27414
27427
  };
27415
27428
  });
@@ -27479,10 +27492,11 @@ function stationPlans(model) {
27479
27492
  const containSet = new Set(st.contain);
27480
27493
  const parents = model.edges.filter((e2) => containSet.has(e2.target) && !containSet.has(e2.source));
27481
27494
  const outEdges = model.edges.filter((e2) => containSet.has(e2.source) && !containSet.has(e2.target));
27482
- const busNode = st.contain.find((id2) => {
27495
+ const busList = st.contain.filter((id2) => {
27483
27496
  const n = nodesById.get(id2);
27484
27497
  return n && n.cat === "bus";
27485
- }) || null;
27498
+ });
27499
+ let busNode = busList.length ? busList[0] : null;
27486
27500
  const hasParent = new Set(model.edges.map((e2) => e2.target));
27487
27501
  const innerRoot = st.contain.map((c) => nodesById.get(c)).filter(Boolean).find((n) => !hasParent.has(n.id) && n.cat !== "bus");
27488
27502
  let inNode = parents.length ? parents[0].target : null;
@@ -27498,9 +27512,78 @@ function stationPlans(model) {
27498
27512
  return n && n.cat !== "bus" && !outNodes.has(k);
27499
27513
  }) || null;
27500
27514
  }
27515
+ const multiBusStation = busList.length > 1;
27516
+ let busNodes = busList;
27517
+ let ties = [];
27518
+ let busOuts = /* @__PURE__ */ new Map();
27519
+ let busLeaves = /* @__PURE__ */ new Map();
27520
+ let primaryBus = busNode;
27521
+ if (inNode) {
27522
+ const feed = model.edges.find(
27523
+ (e2) => e2.source === inNode && containSet.has(e2.target) && (nodesById.get(e2.target) || {}).cat === "bus"
27524
+ );
27525
+ if (feed) primaryBus = feed.target;
27526
+ }
27527
+ if (multiBusStation) busNode = primaryBus;
27528
+ if (multiBusStation) {
27529
+ const busSet = new Set(busList);
27530
+ const catOf = (id2) => (nodesById.get(id2) || {}).cat;
27531
+ for (const id2 of st.contain) {
27532
+ const n = nodesById.get(id2);
27533
+ if (!n || n.cat === "bus") continue;
27534
+ const fromBus = model.edges.find(
27535
+ (e2) => e2.target === id2 && containSet.has(e2.source) && catOf(e2.source) === "bus"
27536
+ );
27537
+ const toBus = model.edges.find(
27538
+ (e2) => e2.source === id2 && containSet.has(e2.target) && catOf(e2.target) === "bus"
27539
+ );
27540
+ if (fromBus && toBus) ties.push({ id: id2, from: fromBus.source, to: toBus.target });
27541
+ }
27542
+ const tieIds = new Set(ties.map((t) => t.id));
27543
+ const busOfNode = (id2) => {
27544
+ const pe = model.edges.find((e2) => e2.target === id2 && busSet.has(e2.source));
27545
+ return pe ? pe.source : busNode;
27546
+ };
27547
+ for (const b of busList) {
27548
+ busOuts.set(b, []);
27549
+ busLeaves.set(b, []);
27550
+ }
27551
+ for (const e2 of outEdges) {
27552
+ const b = busOfNode(e2.source);
27553
+ if (!busOuts.has(b)) busOuts.set(b, []);
27554
+ busOuts.get(b).push({ node: e2.source, child: e2.target });
27555
+ }
27556
+ for (const id2 of st.contain) {
27557
+ const n = nodesById.get(id2);
27558
+ if (!n || n.cat === "bus") continue;
27559
+ if (id2 === inNode || innerRoot && id2 === innerRoot.id || tieIds.has(id2)) continue;
27560
+ if (outEdges.some((e2) => e2.source === id2)) continue;
27561
+ const b = busOfNode(id2);
27562
+ if (!busLeaves.has(b)) busLeaves.set(b, []);
27563
+ busLeaves.get(b).push(id2);
27564
+ }
27565
+ const order = busNode ? [busNode] : [];
27566
+ const seen = new Set(order);
27567
+ for (let i = 0; i < order.length; i++) {
27568
+ for (const t of ties) {
27569
+ if (t.from === order[i] && !seen.has(t.to)) {
27570
+ seen.add(t.to);
27571
+ order.push(t.to);
27572
+ } else if (t.to === order[i] && !seen.has(t.from)) {
27573
+ seen.add(t.from);
27574
+ order.push(t.from);
27575
+ }
27576
+ }
27577
+ }
27578
+ for (const b of busList) if (!seen.has(b)) {
27579
+ seen.add(b);
27580
+ order.push(b);
27581
+ }
27582
+ busNodes = order;
27583
+ }
27501
27584
  const outSources = new Set(outEdges.map((e2) => e2.source));
27502
27585
  const drawn = new Set([busNode, inNode, innerRoot && innerRoot.id, ...outSources].filter(Boolean));
27503
- const leafOuts = st.contain.filter((id2) => !drawn.has(id2)).sort((a2, b) => {
27586
+ const leafOuts = multiBusStation ? [] : st.contain.filter((id2) => !drawn.has(id2)).sort((a2, b) => {
27504
27587
  const sa = String((nodesById.get(a2) || {}).shortName || a2);
27505
27588
  const sb = String((nodesById.get(b) || {}).shortName || b);
27506
27589
  return sa.localeCompare(sb, "zh", { numeric: true });
@@ -27513,6 +27596,11 @@ function stationPlans(model) {
27513
27596
  inNode,
27514
27597
  innerRoot: innerRoot ? innerRoot.id : null,
27515
27598
  busNode,
27599
+ busNodes,
27600
+ multiBus: multiBusStation,
27601
+ ties,
27602
+ busOuts,
27603
+ busLeaves,
27516
27604
  leafOuts,
27517
27605
  outs: outEdges.map((e2) => ({ child: e2.target, node: e2.source }))
27518
27606
  });
@@ -27540,6 +27628,130 @@ function stationInner(plan, model, band = 50, nodeMap) {
27540
27628
  ...plan.outs.map((o) => ({ id: o.node, child: o.child, s: specOf(o.node) })),
27541
27629
  ...(plan.leafOuts || []).map((id2) => ({ id: id2, child: null, s: specOf(id2) }))
27542
27630
  ];
27631
+ if (plan.multiBus && plan.busNodes && plan.busNodes.length > 1) {
27632
+ const TIE_GAP = 22;
27633
+ const busH = bs0.h;
27634
+ const busSwitchIds = /* @__PURE__ */ new Map();
27635
+ for (const b of plan.busNodes) {
27636
+ const outs = (plan.busOuts.get(b) || []).map((o) => o.node);
27637
+ const leaves = plan.busLeaves.get(b) || [];
27638
+ busSwitchIds.set(b, [.../* @__PURE__ */ new Set([...outs, ...leaves])]);
27639
+ }
27640
+ const busWidthOf = (b) => {
27641
+ const ids = busSwitchIds.get(b) || [];
27642
+ let mv = 0;
27643
+ for (const id2 of ids) mv = Math.max(mv, vert(specOf(id2)).w);
27644
+ const st = mv + 72;
27645
+ return Math.max(120, ids.length * st);
27646
+ };
27647
+ const order = plan.busNodes;
27648
+ const gapTies = /* @__PURE__ */ new Map();
27649
+ const placedTies = /* @__PURE__ */ new Set();
27650
+ for (let i = 0; i + 1 < order.length; i++) {
27651
+ const L2 = order[i], R = order[i + 1];
27652
+ for (const t of plan.ties) {
27653
+ if (placedTies.has(t.id)) continue;
27654
+ const link = t.from === L2 && t.to === R || t.from === R && t.to === L2;
27655
+ if (!link) continue;
27656
+ if (!gapTies.has(L2)) gapTies.set(L2, []);
27657
+ gapTies.get(L2).push({ ...t, left: L2, right: R });
27658
+ placedTies.add(t.id);
27659
+ }
27660
+ }
27661
+ for (const t of plan.ties) {
27662
+ if (placedTies.has(t.id)) continue;
27663
+ const L2 = t.from, R = t.to;
27664
+ if (!gapTies.has(L2)) gapTies.set(L2, []);
27665
+ gapTies.get(L2).push({ ...t, left: L2, right: R });
27666
+ placedTies.add(t.id);
27667
+ }
27668
+ const buses = [];
27669
+ const tieDefs = [];
27670
+ let cursor = 0;
27671
+ for (const bid of order) {
27672
+ const w = busWidthOf(bid);
27673
+ buses.push({ id: bid, w, x: cursor + w / 2 });
27674
+ cursor += w;
27675
+ const ts = gapTies.get(bid) || [];
27676
+ if (ts.length) {
27677
+ const TIE_SIDE = 22;
27678
+ const TIE_MIN_GAP = 168;
27679
+ const totalTieW = ts.reduce((s2, t) => s2 + specOf(t.id).w, 0) + (ts.length - 1) * TIE_SIDE;
27680
+ const gapTotal = Math.max(TIE_MIN_GAP, totalTieW + 2 * TIE_SIDE);
27681
+ let tc = cursor + (gapTotal - totalTieW) / 2;
27682
+ for (const t of ts) {
27683
+ const tsp = specOf(t.id);
27684
+ const dy = busH / 2 + 34 + tsp.h / 2;
27685
+ tieDefs.push({
27686
+ id: t.id,
27687
+ from: t.from,
27688
+ to: t.to,
27689
+ left: t.left,
27690
+ right: t.right,
27691
+ w: tsp.w,
27692
+ h: tsp.h,
27693
+ x: tc + tsp.w / 2,
27694
+ dy
27695
+ });
27696
+ tc += tsp.w + TIE_SIDE;
27697
+ }
27698
+ cursor += gapTotal;
27699
+ }
27700
+ }
27701
+ const primary = buses.find((b) => b.id === plan.busNode) || buses[0];
27702
+ const dx = -primary.x;
27703
+ for (const b of buses) b.x += dx;
27704
+ for (const t of tieDefs) t.x += dx;
27705
+ const inPosMb = plan.inNode ? (() => {
27706
+ const s2 = vert(specOf(plan.inNode));
27707
+ return { x: 0, y: -(busH / 2 + VGAP + s2.h / 2), w: s2.w, h: s2.h, rot: 90 };
27708
+ })() : null;
27709
+ let minX2 = Infinity, maxX2 = -Infinity, minY2 = -busH / 2, maxY2 = busH / 2;
27710
+ for (const b of buses) {
27711
+ minX2 = Math.min(minX2, b.x - b.w / 2);
27712
+ maxX2 = Math.max(maxX2, b.x + b.w / 2);
27713
+ }
27714
+ for (const t of tieDefs) {
27715
+ minX2 = Math.min(minX2, t.x - t.w / 2);
27716
+ maxX2 = Math.max(maxX2, t.x + t.w / 2);
27717
+ }
27718
+ if (inPosMb) {
27719
+ minY2 = Math.min(minY2, inPosMb.y - inPosMb.h / 2);
27720
+ maxY2 = Math.max(maxY2, inPosMb.y + inPosMb.h / 2);
27721
+ }
27722
+ if (plan.innerRoot) {
27723
+ const s2 = specOf(plan.innerRoot);
27724
+ minX2 = Math.min(minX2, -s2.w / 2);
27725
+ maxX2 = Math.max(maxX2, s2.w / 2);
27726
+ const inTop = inPosMb ? inPosMb.y - inPosMb.h / 2 : -busH / 2 - VGAP;
27727
+ minY2 = Math.min(minY2, inTop - VGAP - s2.h);
27728
+ }
27729
+ let maxSwH = 0;
27730
+ for (const [, ids] of busSwitchIds) for (const id2 of ids) maxSwH = Math.max(maxSwH, vert(specOf(id2)).h);
27731
+ const switchBottomMaxMb = busH / 2 + VGAP + maxSwH;
27732
+ maxY2 = Math.max(maxY2, switchBottomMaxMb);
27733
+ const needK02Mb = plan.outs.length >= 2 && !plan.outs.every(
27734
+ (o) => (nmap.get(o.child) || {}).cat === "consumer"
27735
+ );
27736
+ const sinkExtraMb = Math.max(0, switchBottomMaxMb + (needK02Mb ? band + 100 : 0) - maxY2);
27737
+ const spanW = maxX2 - minX2;
27738
+ return {
27739
+ inPos: inPosMb,
27740
+ outDefs: plan.outs.map((o) => ({ id: o.node, child: o.child, s: specOf(o.node) })),
27741
+ bs: { w: spanW, h: busH },
27742
+ VGAP,
27743
+ HGAP,
27744
+ HM,
27745
+ VM,
27746
+ vert,
27747
+ specOf,
27748
+ estW: spanW + 2 * HM,
27749
+ estH: maxY2 - minY2 + 2 * VM + sinkExtraMb,
27750
+ fanW: spanW,
27751
+ box: { minX: minX2, maxX: maxX2, minY: minY2, maxY: maxY2 },
27752
+ multiBus: { buses, ties: tieDefs, busH, busSwitchIds, TIE_GAP }
27753
+ };
27754
+ }
27543
27755
  let maxVW = 0;
27544
27756
  for (const o of outDefs) maxVW = Math.max(maxVW, vert(o.s).w);
27545
27757
  const step2 = maxVW + 72;
@@ -27748,6 +27960,7 @@ function fanSubtreeXRange(model, plans, root2, pos, nodeMap) {
27748
27960
  return [minX, maxX];
27749
27961
  }
27750
27962
  function expandStations(model, plans, pos, edgeCP, band, bandClear, vTrunkStations, nodeMap) {
27963
+ var _a;
27751
27964
  const nmap = nodeMap || new Map(model.nodes.map((n) => [n.id, n]));
27752
27965
  for (const plan of plans) {
27753
27966
  const sp = pos.get(plan.sid);
@@ -27817,6 +28030,7 @@ function expandStations(model, plans, pos, edgeCP, band, bandClear, vTrunkStatio
27817
28030
  const frames = [];
27818
28031
  const rotateMap = /* @__PURE__ */ new Map();
27819
28032
  const busWidth = /* @__PURE__ */ new Map();
28033
+ const tieNodes = /* @__PURE__ */ new Set();
27820
28034
  for (const plan of plans) {
27821
28035
  const sp = pos.get(plan.sid);
27822
28036
  if (!sp) continue;
@@ -27833,73 +28047,151 @@ function expandStations(model, plans, pos, edgeCP, band, bandClear, vTrunkStatio
27833
28047
  dirs.push({ ...o, dir: Math.abs(dy) < 0.5 ? "trunk" : dy > 0 ? "V" : "U" });
27834
28048
  }
27835
28049
  const devs = [];
27836
- if (plan.busNode) devs.push({ id: plan.busNode, x: xS, y: yS, w: bs.w, h: bs.h, rot: 0 });
28050
+ const mb = inner.multiBus || null;
28051
+ const outPos = /* @__PURE__ */ new Map();
28052
+ const busOfSwitch = /* @__PURE__ */ new Map();
28053
+ if (mb) for (const [b, ids] of mb.busSwitchIds) for (const id2 of ids) busOfSwitch.set(id2, b);
28054
+ if (mb) {
28055
+ for (const b of mb.buses) devs.push({ id: b.id, x: xS + b.x, y: yS, w: b.w, h: bs.h, rot: 0 });
28056
+ for (const t of mb.ties) {
28057
+ tieNodes.add(t.id);
28058
+ devs.push({ id: t.id, x: xS + t.x, y: yS + (t.dy || 0), w: t.w, h: t.h, rot: 0 });
28059
+ }
28060
+ } else if (plan.busNode) {
28061
+ devs.push({ id: plan.busNode, x: xS, y: yS, w: bs.w, h: bs.h, rot: 0 });
28062
+ }
27837
28063
  if (inner.inPos) {
27838
28064
  const p = inner.inPos;
27839
28065
  devs.push({ id: plan.inNode, x: xS + p.x, y: yS + p.y, w: p.w, h: p.h, rot: p.rot });
27840
28066
  }
27841
- if (plan.innerRoot && plan.inNode) {
28067
+ if (plan.innerRoot) {
27842
28068
  const s2 = specOf(plan.innerRoot);
27843
- const inTop = yS + inner.inPos.y - inner.inPos.h / 2;
27844
- devs.push({ id: plan.innerRoot, x: xS, y: inTop - VGAP - s2.h / 2, w: s2.w, h: s2.h, rot: 0 });
28069
+ if (plan.inNode && inner.inPos) {
28070
+ const inTop = yS + inner.inPos.y - inner.inPos.h / 2;
28071
+ devs.push({ id: plan.innerRoot, x: xS, y: inTop - VGAP - s2.h / 2, w: s2.w, h: s2.h, rot: 0 });
28072
+ } else if (plan.busNode) {
28073
+ const bx = mb ? ((_a = mb.buses.find((b) => b.id === plan.busNode)) == null ? void 0 : _a.x) ?? 0 : 0;
28074
+ devs.push({ id: plan.innerRoot, x: xS + bx, y: yS - (bs.h / 2 + VGAP + s2.h / 2), w: s2.w, h: s2.h, rot: 0 });
28075
+ }
27845
28076
  }
27846
28077
  const vOuts = dirs.filter((o) => o.node !== plan.busNode).sort((a2, b) => pos.get(a2.child).x - pos.get(b.child).x);
27847
28078
  const leafOuts = (plan.leafOuts || []).map((id2) => ({ node: id2, child: null, dir: "V" }));
27848
- const vAll = [...vOuts, ...leafOuts];
27849
28079
  const busHalf = bs.w / 2;
27850
28080
  const uniqNodes = [];
27851
28081
  const seenNodes = /* @__PURE__ */ new Set();
27852
- for (const o of vAll) {
27853
- if (!seenNodes.has(o.node)) {
27854
- seenNodes.add(o.node);
27855
- uniqNodes.push({ node: o.node, firstChild: o.child });
28082
+ if (mb) {
28083
+ for (const b of mb.buses) {
28084
+ const outs = vOuts.filter((o) => busOfSwitch.get(o.node) === b.id);
28085
+ const leaves = (plan.busLeaves.get(b.id) || []).map((id2) => ({ node: id2, child: null, dir: "V" }));
28086
+ const list = [];
28087
+ const seen = /* @__PURE__ */ new Set();
28088
+ for (const o of [...outs, ...leaves]) {
28089
+ if (!seen.has(o.node)) {
28090
+ seen.add(o.node);
28091
+ list.push(o);
28092
+ }
28093
+ }
28094
+ list.forEach((o, k) => {
28095
+ const v = vert(specOf(o.node));
28096
+ const x = list.length === 1 ? 0 : -b.w / 2 + (k + 0.5) * b.w / list.length;
28097
+ const p = { id: o.node, x: xS + b.x + x, y: yS + (bs.h / 2 + VGAP + v.h / 2), w: v.w, h: v.h, rot: 90 };
28098
+ outPos.set(o.node, p);
28099
+ devs.push(p);
28100
+ });
28101
+ }
28102
+ } else {
28103
+ const vAll = [...vOuts, ...leafOuts];
28104
+ for (const o of vAll) {
28105
+ if (!seenNodes.has(o.node)) {
28106
+ seenNodes.add(o.node);
28107
+ uniqNodes.push({ node: o.node, firstChild: o.child });
28108
+ }
28109
+ }
28110
+ const rowPos = (list) => list.map((o, k) => {
28111
+ const s2 = specOf(o.node);
28112
+ const v = vert(s2);
28113
+ const x = list.length === 1 ? 0 : -busHalf + (k + 0.5) * (2 * busHalf) / list.length;
28114
+ return { o, x, v };
28115
+ });
28116
+ for (const { o, x, v } of rowPos(uniqNodes)) {
28117
+ const p = { id: o.node, x: xS + x, y: yS + (bs.h / 2 + VGAP + v.h / 2), w: v.w, h: v.h, rot: 90 };
28118
+ outPos.set(o.node, p);
28119
+ devs.push(p);
27856
28120
  }
27857
- }
27858
- const rowPos = (list) => list.map((o, k) => {
27859
- const s2 = specOf(o.node);
27860
- const v = vert(s2);
27861
- const x = list.length === 1 ? 0 : -busHalf + (k + 0.5) * (2 * busHalf) / list.length;
27862
- return { o, x, v };
27863
- });
27864
- const outPos = /* @__PURE__ */ new Map();
27865
- for (const { o, x, v } of rowPos(uniqNodes)) {
27866
- const p = { id: o.node, x: xS + x, y: yS + (bs.h / 2 + VGAP + v.h / 2), w: v.w, h: v.h, rot: 90 };
27867
- outPos.set(o.node, p);
27868
- devs.push(p);
27869
28121
  }
27870
28122
  for (const d2 of devs) {
27871
28123
  pos.set(d2.id, { x: d2.x, y: d2.y });
27872
28124
  if (d2.rot) rotateMap.set(d2.id, d2.rot);
27873
28125
  }
27874
- if (plan.busNode && inner.inPos) {
27875
- const inBottom = pos.get(plan.inNode).y + inner.inPos.h / 2;
27876
- edgeCP.set(`${plan.inNode}-${plan.busNode}`, [[xS, inBottom], [xS, yS - bs.h / 2]]);
27877
- }
27878
- if (plan.innerRoot && plan.inNode) {
27879
- const ir = pos.get(plan.innerRoot);
27880
- const inTop = pos.get(plan.inNode).y - inner.inPos.h / 2;
27881
- edgeCP.set(`${plan.innerRoot}-${plan.inNode}`, [[xS, ir.y + specOf(plan.innerRoot).h / 2], [xS, inTop]]);
27882
- }
27883
- if (plan.busNode) {
27884
- for (const o of dirs) {
27885
- if (o.node === plan.busNode) continue;
27886
- const s2 = specOf(o.node);
27887
- const v = vert(s2);
27888
- const p = outPos.get(o.node);
27889
- const key = `${plan.busNode}-${o.node}`;
27890
- const top = p.y - v.h / 2;
27891
- edgeCP.set(key, [[p.x, yS + bs.h / 2], [p.x, top]]);
28126
+ if (mb) {
28127
+ if (plan.innerRoot && plan.inNode && inner.inPos) {
28128
+ const ir = pos.get(plan.innerRoot);
28129
+ const inTop = pos.get(plan.inNode).y - inner.inPos.h / 2;
28130
+ edgeCP.set(`${plan.innerRoot}-${plan.inNode}`, [[xS, ir.y + specOf(plan.innerRoot).h / 2], [xS, inTop]]);
28131
+ }
28132
+ if (plan.innerRoot && !plan.inNode && plan.busNode) {
28133
+ const ir = pos.get(plan.innerRoot);
28134
+ const pb = mb.buses.find((b) => b.id === plan.busNode) || mb.buses[0];
28135
+ edgeCP.set(`${plan.innerRoot}-${plan.busNode}`, [[xS + pb.x, ir.y + specOf(plan.innerRoot).h / 2], [xS + pb.x, yS - bs.h / 2]]);
28136
+ }
28137
+ if (plan.inNode && plan.busNode) {
28138
+ const pb = mb.buses.find((b) => b.id === plan.busNode) || mb.buses[0];
28139
+ const inBottom = pos.get(plan.inNode).y + inner.inPos.h / 2;
28140
+ edgeCP.set(`${plan.inNode}-${plan.busNode}`, [[xS + pb.x, inBottom], [xS + pb.x, yS - bs.h / 2]]);
28141
+ }
28142
+ for (const t of mb.ties) {
28143
+ const lb = mb.buses.find((b) => b.id === t.left);
28144
+ const rb = mb.buses.find((b) => b.id === t.right);
28145
+ if (!lb || !rb) continue;
28146
+ const lEnd = xS + lb.x + lb.w / 2;
28147
+ const rEnd = xS + rb.x - rb.w / 2;
28148
+ const tL = xS + t.x - t.w / 2;
28149
+ const tR = xS + t.x + t.w / 2;
28150
+ const yT = yS + (t.dy || 0);
28151
+ if (t.from === t.left) edgeCP.set(`${t.from}-${t.id}`, [[lEnd, yS], [lEnd, yT], [tL, yT]]);
28152
+ else edgeCP.set(`${t.from}-${t.id}`, [[rEnd, yS], [rEnd, yT], [tR, yT]]);
28153
+ if (t.to === t.right) edgeCP.set(`${t.id}-${t.to}`, [[tR, yT], [rEnd, yT], [rEnd, yS]]);
28154
+ else edgeCP.set(`${t.id}-${t.to}`, [[tL, yT], [lEnd, yT], [lEnd, yS]]);
28155
+ }
28156
+ for (const [nodeId, p] of outPos) {
28157
+ const b = busOfSwitch.get(nodeId) || plan.busNode;
28158
+ const v = vert(specOf(nodeId));
28159
+ edgeCP.set(`${b}-${nodeId}`, [[p.x, yS + bs.h / 2], [p.x, p.y - v.h / 2]]);
27892
28160
  }
27893
- }
27894
- const busOrIn = plan.busNode || plan.inNode;
27895
- if (busOrIn) {
27896
- const busP = plan.busNode ? pos.get(plan.busNode) : pos.get(plan.inNode);
27897
- const busY = (busP || { y: yS }).y;
27898
- for (const id2 of plan.leafOuts || []) {
27899
- const p = outPos.get(id2);
27900
- if (!p) continue;
27901
- const v = vert(specOf(id2));
27902
- edgeCP.set(`${busOrIn}-${id2}`, [[p.x, busY + bs.h / 2], [p.x, p.y - v.h / 2]]);
28161
+ } else {
28162
+ if (plan.busNode && inner.inPos) {
28163
+ const inBottom = pos.get(plan.inNode).y + inner.inPos.h / 2;
28164
+ edgeCP.set(`${plan.inNode}-${plan.busNode}`, [[xS, inBottom], [xS, yS - bs.h / 2]]);
28165
+ }
28166
+ if (plan.innerRoot && plan.inNode) {
28167
+ const ir = pos.get(plan.innerRoot);
28168
+ const inTop = pos.get(plan.inNode).y - inner.inPos.h / 2;
28169
+ edgeCP.set(`${plan.innerRoot}-${plan.inNode}`, [[xS, ir.y + specOf(plan.innerRoot).h / 2], [xS, inTop]]);
28170
+ } else if (plan.innerRoot && plan.busNode) {
28171
+ const ir = pos.get(plan.innerRoot);
28172
+ edgeCP.set(`${plan.innerRoot}-${plan.busNode}`, [[xS, ir.y + specOf(plan.innerRoot).h / 2], [xS, yS - bs.h / 2]]);
28173
+ }
28174
+ if (plan.busNode) {
28175
+ for (const o of dirs) {
28176
+ if (o.node === plan.busNode) continue;
28177
+ const s2 = specOf(o.node);
28178
+ const v = vert(s2);
28179
+ const p = outPos.get(o.node);
28180
+ const key = `${plan.busNode}-${o.node}`;
28181
+ const top = p.y - v.h / 2;
28182
+ edgeCP.set(key, [[p.x, yS + bs.h / 2], [p.x, top]]);
28183
+ }
28184
+ }
28185
+ const busOrIn = plan.busNode || plan.inNode;
28186
+ if (busOrIn) {
28187
+ const busP = plan.busNode ? pos.get(plan.busNode) : pos.get(plan.inNode);
28188
+ const busY = (busP || { y: yS }).y;
28189
+ for (const id2 of plan.leafOuts || []) {
28190
+ const p = outPos.get(id2);
28191
+ if (!p) continue;
28192
+ const v = vert(specOf(id2));
28193
+ edgeCP.set(`${busOrIn}-${id2}`, [[p.x, busY + bs.h / 2], [p.x, p.y - v.h / 2]]);
28194
+ }
27903
28195
  }
27904
28196
  }
27905
28197
  const inTarget = plan.inNode ? plan.inNode : plan.busNode;
@@ -28058,18 +28350,21 @@ function expandStations(model, plans, pos, edgeCP, band, bandClear, vTrunkStatio
28058
28350
  const cw = specOf(o.child).w;
28059
28351
  const ch = specOf(o.child).h;
28060
28352
  const level = levelOf.get(o) ?? bandLevel;
28061
- if (o.node === plan.busNode) {
28353
+ if (o.node === plan.busNode || mb && mb.buses.some((b) => b.id === o.node)) {
28354
+ const bb = mb ? mb.buses.find((b) => b.id === o.node) : null;
28355
+ const anchorX = bb ? xS + bb.x : xS;
28356
+ const anchorHalf = bb ? bb.w / 2 : busHalf;
28062
28357
  if (o.dir === "trunk") {
28063
28358
  edgeCP.set(`${o.node}-${o.child}`, [
28064
- [xS + busHalf, yS],
28065
- [xS + busHalf, level],
28359
+ [anchorX + anchorHalf, yS],
28360
+ [anchorX + anchorHalf, level],
28066
28361
  [childPos.x - cw / 2, level],
28067
28362
  [childPos.x - cw / 2, childPos.y - ch / 2]
28068
28363
  ]);
28069
28364
  } else {
28070
28365
  edgeCP.set(`${o.node}-${o.child}`, [
28071
- [xS, yS + bs.h / 2],
28072
- [xS, level],
28366
+ [anchorX, yS + bs.h / 2],
28367
+ [anchorX, level],
28073
28368
  [childPos.x, level],
28074
28369
  [childPos.x, childPos.y - ch / 2]
28075
28370
  ]);
@@ -28096,10 +28391,10 @@ function expandStations(model, plans, pos, edgeCP, band, bandClear, vTrunkStatio
28096
28391
  }
28097
28392
  edgeCP.delete(`${plan.sid}-${o.child}`);
28098
28393
  }
28099
- let bminX = -busHalf;
28100
- let bmaxX = busHalf;
28101
- let bminY = -bs.h / 2;
28102
- let bmaxY = bs.h / 2;
28394
+ let bminX = mb ? Infinity : -busHalf;
28395
+ let bmaxX = mb ? -Infinity : busHalf;
28396
+ let bminY = mb ? Infinity : -bs.h / 2;
28397
+ let bmaxY = mb ? -Infinity : bs.h / 2;
28103
28398
  for (const d2 of devs) {
28104
28399
  bminX = Math.min(bminX, d2.x - xS - d2.w / 2);
28105
28400
  bmaxX = Math.max(bmaxX, d2.x - xS + d2.w / 2);
@@ -28128,9 +28423,22 @@ function expandStations(model, plans, pos, edgeCP, band, bandClear, vTrunkStatio
28128
28423
  }
28129
28424
  pos.set(plan.sid, { x: fx, y: fy });
28130
28425
  frames.push({ id: plan.sid, x: fx, y: fy, w: fw, h: fh, name: plan.name });
28131
- if (plan.busNode) busWidth.set(plan.busNode, bs.w);
28426
+ if (mb) {
28427
+ for (const b of mb.buses) busWidth.set(b.id, b.w);
28428
+ } else if (plan.busNode) {
28429
+ busWidth.set(plan.busNode, bs.w);
28430
+ }
28431
+ for (const e2 of model.edges) {
28432
+ if (!plan.containSet.has(e2.source) || !plan.containSet.has(e2.target)) continue;
28433
+ const key = `${e2.source}-${e2.target}`;
28434
+ if (edgeCP.has(key)) continue;
28435
+ const pa = pos.get(e2.source);
28436
+ const pb = pos.get(e2.target);
28437
+ if (!pa || !pb) continue;
28438
+ edgeCP.set(key, [[pa.x, pa.y], [pa.x, pb.y], [pb.x, pb.y]]);
28439
+ }
28132
28440
  }
28133
- return { frames, rotateMap, busWidth };
28441
+ return { frames, rotateMap, busWidth, tieNodes };
28134
28442
  }
28135
28443
  function computeOrthogonalLayout(model, opts = {}) {
28136
28444
  const {
@@ -28637,6 +28945,7 @@ function computeOrthogonalLayout(model, opts = {}) {
28637
28945
  const stationFrames = [];
28638
28946
  const stationRotate = /* @__PURE__ */ new Map();
28639
28947
  const stationBusWidth = /* @__PURE__ */ new Map();
28948
+ const stationTies = /* @__PURE__ */ new Set();
28640
28949
  if (plans.length) {
28641
28950
  const depthOf = /* @__PURE__ */ new Map([[extModel.rootId, 0]]);
28642
28951
  const vis = /* @__PURE__ */ new Set([extModel.rootId]);
@@ -28652,10 +28961,11 @@ function computeOrthogonalLayout(model, opts = {}) {
28652
28961
  }
28653
28962
  }
28654
28963
  plans.sort((a2, b) => (depthOf.get(b.sid) || 0) - (depthOf.get(a2.sid) || 0));
28655
- const { frames, rotateMap, busWidth } = expandStations(model, plans, pos, edgeCP, band, bandClear, vChainStations, nodeMap);
28964
+ const { frames, rotateMap, busWidth, tieNodes } = expandStations(model, plans, pos, edgeCP, band, bandClear, vChainStations, nodeMap);
28656
28965
  stationFrames.push(...frames);
28657
28966
  for (const [k, v] of rotateMap) stationRotate.set(k, v);
28658
28967
  for (const [k, v] of busWidth) stationBusWidth.set(k, v);
28968
+ if (tieNodes) for (const id2 of tieNodes) stationTies.add(id2);
28659
28969
  }
28660
28970
  const nodeSpec = (id2) => {
28661
28971
  const f2 = stationFrames.find((x) => x.id === id2);
@@ -28703,6 +29013,7 @@ function computeOrthogonalLayout(model, opts = {}) {
28703
29013
  stationFrames,
28704
29014
  stationRotate,
28705
29015
  stationBusWidth,
29016
+ stationTies,
28706
29017
  bounds: { minX: minX + dx, minY: minY + dy, maxX: maxX + dx, maxY: maxY + dy },
28707
29018
  stats: { trunkLen: 0, leaves: 0, nodes: model.nodes.length }
28708
29019
  };
@@ -28790,6 +29101,7 @@ function setEdgeTheme(themeName) {
28790
29101
  function buildGraphData(model, layout) {
28791
29102
  const nodes = model.nodes.map((n) => {
28792
29103
  const spec = getSymbolSpec(n.cat);
29104
+ const isTie = !!(layout.stationTies && layout.stationTies.has(n.id));
28793
29105
  const busW = layout.stationBusWidth && layout.stationBusWidth.get(n.id);
28794
29106
  const meterBoxDim = n.cat === "meterBox" ? getMeterBoxSize(n) : null;
28795
29107
  const w = busW || (meterBoxDim ? meterBoxDim.w : spec.w);
@@ -28808,6 +29120,8 @@ function buildGraphData(model, layout) {
28808
29120
  // 禁用 G6 内置 label(label:false),避免其单行画出长名不换行。
28809
29121
  label: false,
28810
29122
  labelText: n.cat === "bus" || n.cat === "cableTerminal" || n.cat === "consumer" ? "" : n.shortName || "",
29123
+ // 母联标记:canvasNode 据此把(短名)标签居中画在「半矩形」内部
29124
+ _tieLabel: isTie,
28811
29125
  rotate: layout.stationRotate && layout.stationRotate.get(n.id) || 0,
28812
29126
  highlight: 0,
28813
29127
  cursor: "pointer",
@@ -29026,6 +29340,12 @@ function drawLabelUnder(attributes, group) {
29026
29340
  const x0 = w / 2 + 6;
29027
29341
  _drawWrappedTextFixed(group, label, x0, -h / 2, 8, C2.white, 4, 1.3, 500, "left");
29028
29342
  }
29343
+ function drawTieLabel(attributes, group) {
29344
+ const label = attributes.labelText || "";
29345
+ if (!label) return;
29346
+ const [w, h] = attributes.size;
29347
+ _drawWrappedText(group, label, 0, -(h / 2 + 22), 7, C2.white, Math.max(72, w + 34), 1.2, 500);
29348
+ }
29029
29349
  function text(attributes, group, str2, x, y, size, fill, weight = 400) {
29030
29350
  if (!str2) return;
29031
29351
  group.appendChild(
@@ -29163,7 +29483,7 @@ function drawBreaker(attributes, group) {
29163
29483
  group.appendChild(new Line({ style: { x1: xBox, y1: 0, x2: xEdge, y2: 0, stroke: P.red, lineWidth: lw, lineCap: "round" } }));
29164
29484
  }
29165
29485
  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 } }));
29166
- if (attributes.labelText) {
29486
+ if (attributes.labelText && !attributes._tieLabel) {
29167
29487
  _drawWrappedText(group, attributes.labelText, 0, 0.6, 11.5, C2.switchText, bw - 8, 1.15, 700);
29168
29488
  }
29169
29489
  }
@@ -29670,6 +29990,7 @@ class SvgSymbolNode extends BaseNode {
29670
29990
  drawGlowBackdrop(attributes, target);
29671
29991
  const symStart = (target.children || []).length;
29672
29992
  draw(attributes, target);
29993
+ if (attributes._tieLabel && attributes.labelText) drawTieLabel(attributes, target);
29673
29994
  const symEnd = (target.children || []).length;
29674
29995
  drawGlow(attributes, target);
29675
29996
  drawHighlight(attributes, target);
@@ -29718,10 +30039,58 @@ const _hoisted_4$2 = { class: "topo-meta" };
29718
30039
  const _hoisted_5$2 = ["title"];
29719
30040
  const _hoisted_6$2 = {
29720
30041
  key: 1,
30042
+ class: "topo-legend"
30043
+ };
30044
+ const _hoisted_7$2 = { class: "topo-legend-body" };
30045
+ const _hoisted_8$2 = {
30046
+ class: "topo-legend-icon",
30047
+ viewBox: "0 0 32 20",
30048
+ width: "32",
30049
+ height: "20",
30050
+ "aria-hidden": "true"
30051
+ };
30052
+ const _hoisted_9$2 = ["stroke"];
30053
+ const _hoisted_10$2 = ["fill", "stroke"];
30054
+ const _hoisted_11$2 = ["stroke"];
30055
+ const _hoisted_12$2 = ["stroke"];
30056
+ const _hoisted_13$2 = ["stroke"];
30057
+ const _hoisted_14$2 = ["fill"];
30058
+ const _hoisted_15$1 = ["stroke"];
30059
+ const _hoisted_16$1 = ["stroke"];
30060
+ const _hoisted_17$1 = ["stroke"];
30061
+ const _hoisted_18$1 = ["fill"];
30062
+ const _hoisted_19$1 = ["fill"];
30063
+ const _hoisted_20$1 = ["fill", "stroke"];
30064
+ const _hoisted_21$1 = ["stroke"];
30065
+ const _hoisted_22$1 = ["fill"];
30066
+ const _hoisted_23$1 = ["stroke"];
30067
+ const _hoisted_24$1 = ["stroke"];
30068
+ const _hoisted_25$1 = ["stroke"];
30069
+ const _hoisted_26$1 = ["stroke"];
30070
+ const _hoisted_27$1 = ["fill", "stroke"];
30071
+ const _hoisted_28$1 = ["fill"];
30072
+ const _hoisted_29$1 = ["stroke"];
30073
+ const _hoisted_30$1 = ["fill"];
30074
+ const _hoisted_31$1 = ["fill", "stroke"];
30075
+ const _hoisted_32$1 = ["fill"];
30076
+ const _hoisted_33$1 = ["stroke"];
30077
+ const _hoisted_34$1 = ["fill"];
30078
+ const _hoisted_35$1 = ["fill"];
30079
+ const _hoisted_36$1 = ["fill", "stroke"];
30080
+ const _hoisted_37$1 = ["stroke", "stroke-width"];
30081
+ const _hoisted_38$1 = ["fill"];
30082
+ const _hoisted_39 = ["stroke"];
30083
+ const _hoisted_40 = ["fill"];
30084
+ const _hoisted_41 = ["stroke"];
30085
+ const _hoisted_42 = ["fill"];
30086
+ const _hoisted_43 = ["fill"];
30087
+ const _hoisted_44 = { class: "topo-legend-label" };
30088
+ const _hoisted_45 = {
30089
+ key: 2,
29721
30090
  class: "topo-loading"
29722
30091
  };
29723
- const _hoisted_7$2 = {
29724
- key: 2,
30092
+ const _hoisted_46 = {
30093
+ key: 3,
29725
30094
  class: "topo-error"
29726
30095
  };
29727
30096
  const _sfc_main$2 = {
@@ -29733,10 +30102,14 @@ const _sfc_main$2 = {
29733
30102
  customerData: { type: Array, default: null },
29734
30103
  /** 主题:'dark'(默认,黑底 SCADA 风)| 'light'(白底);支持 v-model:theme 双向绑定 */
29735
30104
  theme: { type: String, default: "light" },
29736
- /** 左上角工具条(标题/统计/主题/适应/导出)是否显示,默认不显示 */
29737
- toolbar: { type: Boolean, default: false }
30105
+ /** 左上角工具条(标题/统计/主题/图例/适应/导出)是否显示,默认不显示 */
30106
+ toolbar: { type: Boolean, default: false },
30107
+ /** 是否显示左下角图例面板,默认 false(关闭);支持 v-model:legend */
30108
+ legend: { type: Boolean, default: false },
30109
+ /** 自定义图例项(可选):[{ label, shape, color?, text?, key? }];不传则按当前图内容自动生成 */
30110
+ legendItems: { type: Array, default: null }
29738
30111
  },
29739
- emits: ["node-select", "customer-select", "loaded", "top-distances", "update:theme"],
30112
+ emits: ["node-select", "customer-select", "loaded", "top-distances", "update:theme", "update:legend"],
29740
30113
  setup(__props, { expose: __expose, emit: __emit }) {
29741
30114
  const props = __props;
29742
30115
  const emit = __emit;
@@ -29746,6 +30119,49 @@ const _sfc_main$2 = {
29746
30119
  const title = ref("");
29747
30120
  const stats = ref({ total: 0, depth: 0, leaves: 0, byCat: {} });
29748
30121
  const edgeCount = ref(0);
30122
+ const legendCollapsed = ref(false);
30123
+ const hasCustomerData = ref(false);
30124
+ const hasSwitch = ref(false);
30125
+ const hasFuse = ref(false);
30126
+ const LEGEND_DEFS = [
30127
+ { cat: "transformer", label: "配电变压器", shape: "transformer" },
30128
+ { cat: "switch", label: "开关 / 断路器", shape: "switch" },
30129
+ { cat: "fuse", label: "低压熔丝", shape: "fuse" },
30130
+ { cat: "bus", label: "低压母线", shape: "bus" },
30131
+ { cat: "cableTerminal", label: "电缆终端头", shape: "cableTerminal" },
30132
+ { cat: "junction", label: "密母分接点", shape: "junction" },
30133
+ { cat: "meterBox", label: "计量箱", shape: "meterBox" },
30134
+ { cat: "consumer", label: "用户接入点", shape: "consumer" },
30135
+ { cat: "line", label: "低压导线点", shape: "line" },
30136
+ { cat: "station", label: "低压配电箱", shape: "station" },
30137
+ { cat: "other", label: "其它设备", shape: "other" }
30138
+ ];
30139
+ const legendPalette = computed(() => THEMES[themeName()] || THEMES.dark);
30140
+ function legendColor(item, role) {
30141
+ if (item && item.color) return item.color;
30142
+ return legendPalette.value[role] || "#888888";
30143
+ }
30144
+ const legendEntries = computed(() => {
30145
+ const custom = props.legendItems;
30146
+ if (Array.isArray(custom) && custom.length) {
30147
+ return custom.map((it, i) => ({ key: it.key || `custom-${i}`, ...it }));
30148
+ }
30149
+ const byCat = stats.value.byCat || {};
30150
+ const include = (d2) => {
30151
+ if (d2.cat === "switch") return hasSwitch.value;
30152
+ if (d2.cat === "fuse") return hasFuse.value;
30153
+ return !!byCat[d2.cat];
30154
+ };
30155
+ const list = LEGEND_DEFS.filter(include).map((d2) => ({ ...d2, key: d2.cat }));
30156
+ if (list.length) {
30157
+ list.push({ key: "edge-trunk", label: "主干导线", shape: "edge", role: "trunkEdge" });
30158
+ list.push({ key: "edge-branch", label: "分支导线", shape: "edge", role: "branchEdge" });
30159
+ }
30160
+ if (hasCustomerData.value) {
30161
+ list.push({ key: "meterUser", label: "客户电表", shape: "meterUser" });
30162
+ }
30163
+ return list;
30164
+ });
29749
30165
  let graph = null;
29750
30166
  let model = null;
29751
30167
  let selectedId = null;
@@ -30035,6 +30451,9 @@ const _sfc_main$2 = {
30035
30451
  title.value = String(model.title || "").replace(/\.+$/, "");
30036
30452
  stats.value = model.stats;
30037
30453
  edgeCount.value = model.edges.length;
30454
+ hasCustomerData.value = model.nodes.some((n) => Array.isArray(n._customers) && n._customers.length);
30455
+ hasSwitch.value = model.nodes.some((n) => n.cat === "switch" && !n.isFuse);
30456
+ hasFuse.value = model.nodes.some((n) => n.cat === "switch" && n.isFuse);
30038
30457
  layoutData = computeOrthogonalLayout(model, {
30039
30458
  nodeGap: 26,
30040
30459
  // 主干相邻节点边界间距
@@ -30300,6 +30719,11 @@ const _sfc_main$2 = {
30300
30719
  title: __props.theme === "light" ? "切换到深色主题" : "切换到浅色主题",
30301
30720
  onClick: _cache[0] || (_cache[0] = ($event) => emit("update:theme", __props.theme === "light" ? "dark" : "light"))
30302
30721
  }, toDisplayString(__props.theme === "light" ? "🌙 深色" : "☀️ 浅色"), 9, _hoisted_5$2),
30722
+ createElementVNode("button", {
30723
+ class: "btn",
30724
+ title: "显示 / 隐藏图例",
30725
+ onClick: _cache[1] || (_cache[1] = ($event) => emit("update:legend", !__props.legend))
30726
+ }, toDisplayString(__props.legend ? "🗺 图例 ✓" : "🗺 图例"), 1),
30303
30727
  createElementVNode("button", {
30304
30728
  class: "btn",
30305
30729
  onClick: resetView
@@ -30309,12 +30733,334 @@ const _sfc_main$2 = {
30309
30733
  onClick: exportPng
30310
30734
  }, "导出 PNG")
30311
30735
  ])) : createCommentVNode("", true),
30312
- loading.value ? (openBlock(), createElementBlock("div", _hoisted_6$2, "正在解析台区拓扑…")) : error.value ? (openBlock(), createElementBlock("div", _hoisted_7$2, toDisplayString(error.value), 1)) : createCommentVNode("", true)
30736
+ __props.legend && !loading.value && !error.value && legendEntries.value.length ? (openBlock(), createElementBlock("div", _hoisted_6$2, [
30737
+ createElementVNode("div", {
30738
+ class: "topo-legend-title",
30739
+ onClick: _cache[2] || (_cache[2] = ($event) => legendCollapsed.value = !legendCollapsed.value)
30740
+ }, [
30741
+ createElementVNode("span", {
30742
+ class: normalizeClass(["topo-legend-arrow", { open: !legendCollapsed.value }])
30743
+ }, "▸", 2),
30744
+ _cache[3] || (_cache[3] = createTextVNode(" 图例 ", -1))
30745
+ ]),
30746
+ withDirectives(createElementVNode("div", _hoisted_7$2, [
30747
+ (openBlock(true), createElementBlock(Fragment$1, null, renderList(legendEntries.value, (it) => {
30748
+ return openBlock(), createElementBlock("div", {
30749
+ key: it.key || it.label,
30750
+ class: "topo-legend-row"
30751
+ }, [
30752
+ (openBlock(), createElementBlock("svg", _hoisted_8$2, [
30753
+ it.shape === "transformer" ? (openBlock(), createElementBlock(Fragment$1, { key: 0 }, [
30754
+ createElementVNode("line", {
30755
+ x1: "2",
30756
+ y1: "10",
30757
+ x2: "8",
30758
+ y2: "10",
30759
+ stroke: legendColor(it, "trunkLine"),
30760
+ "stroke-width": "1.6",
30761
+ "stroke-linecap": "round"
30762
+ }, null, 8, _hoisted_9$2),
30763
+ createElementVNode("circle", {
30764
+ cx: "12",
30765
+ cy: "10",
30766
+ r: "5",
30767
+ fill: legendColor(it, "orange"),
30768
+ stroke: legendColor(it, "trunkLine"),
30769
+ "stroke-width": "1.2"
30770
+ }, null, 8, _hoisted_10$2),
30771
+ createElementVNode("circle", {
30772
+ cx: "22",
30773
+ cy: "10",
30774
+ r: "5",
30775
+ fill: "none",
30776
+ stroke: legendColor(it, "red"),
30777
+ "stroke-width": "2"
30778
+ }, null, 8, _hoisted_11$2),
30779
+ createElementVNode("line", {
30780
+ x1: "27",
30781
+ y1: "10",
30782
+ x2: "31",
30783
+ y2: "10",
30784
+ stroke: legendColor(it, "red"),
30785
+ "stroke-width": "1.6",
30786
+ "stroke-linecap": "round"
30787
+ }, null, 8, _hoisted_12$2)
30788
+ ], 64)) : it.shape === "switch" ? (openBlock(), createElementBlock(Fragment$1, { key: 1 }, [
30789
+ createElementVNode("line", {
30790
+ x1: "2",
30791
+ y1: "10",
30792
+ x2: "7",
30793
+ y2: "10",
30794
+ stroke: legendColor(it, "red"),
30795
+ "stroke-width": "1.6",
30796
+ "stroke-linecap": "round"
30797
+ }, null, 8, _hoisted_13$2),
30798
+ createElementVNode("rect", {
30799
+ x: "7",
30800
+ y: "5",
30801
+ width: "18",
30802
+ height: "10",
30803
+ rx: "2",
30804
+ fill: legendColor(it, "red")
30805
+ }, null, 8, _hoisted_14$2),
30806
+ createElementVNode("line", {
30807
+ x1: "25",
30808
+ y1: "10",
30809
+ x2: "30",
30810
+ y2: "10",
30811
+ stroke: legendColor(it, "red"),
30812
+ "stroke-width": "1.6",
30813
+ "stroke-linecap": "round"
30814
+ }, null, 8, _hoisted_15$1)
30815
+ ], 64)) : it.shape === "fuse" ? (openBlock(), createElementBlock(Fragment$1, { key: 2 }, [
30816
+ createElementVNode("line", {
30817
+ x1: "2",
30818
+ y1: "10",
30819
+ x2: "6",
30820
+ y2: "10",
30821
+ stroke: legendColor(it, "red"),
30822
+ "stroke-width": "1.6",
30823
+ "stroke-linecap": "round"
30824
+ }, null, 8, _hoisted_16$1),
30825
+ createElementVNode("rect", {
30826
+ x: "6",
30827
+ y: "5",
30828
+ width: "20",
30829
+ height: "10",
30830
+ rx: "2",
30831
+ fill: "none",
30832
+ stroke: legendColor(it, "red"),
30833
+ "stroke-width": "1.6"
30834
+ }, null, 8, _hoisted_17$1),
30835
+ createElementVNode("circle", {
30836
+ cx: "12",
30837
+ cy: "10",
30838
+ r: "2.2",
30839
+ fill: legendColor(it, "red")
30840
+ }, null, 8, _hoisted_18$1),
30841
+ createElementVNode("rect", {
30842
+ x: "16",
30843
+ y: "7",
30844
+ width: "7",
30845
+ height: "6",
30846
+ fill: legendColor(it, "red")
30847
+ }, null, 8, _hoisted_19$1)
30848
+ ], 64)) : it.shape === "bus" ? (openBlock(), createElementBlock("rect", {
30849
+ key: 3,
30850
+ x: "5",
30851
+ y: "8",
30852
+ width: "22",
30853
+ height: "4",
30854
+ rx: "2",
30855
+ fill: legendColor(it, "white"),
30856
+ "fill-opacity": "0.5",
30857
+ stroke: legendColor(it, "white"),
30858
+ "stroke-width": "1.2"
30859
+ }, null, 8, _hoisted_20$1)) : it.shape === "cableTerminal" ? (openBlock(), createElementBlock(Fragment$1, { key: 4 }, [
30860
+ createElementVNode("line", {
30861
+ x1: "3",
30862
+ y1: "10",
30863
+ x2: "9",
30864
+ y2: "10",
30865
+ stroke: legendColor(it, "red"),
30866
+ "stroke-width": "1.6",
30867
+ "stroke-linecap": "round"
30868
+ }, null, 8, _hoisted_21$1),
30869
+ createElementVNode("polygon", {
30870
+ points: "9,4 20,10 9,16",
30871
+ fill: legendColor(it, "red")
30872
+ }, null, 8, _hoisted_22$1)
30873
+ ], 64)) : it.shape === "junction" ? (openBlock(), createElementBlock(Fragment$1, { key: 5 }, [
30874
+ createElementVNode("line", {
30875
+ x1: "10",
30876
+ y1: "10",
30877
+ x2: "22",
30878
+ y2: "10",
30879
+ stroke: legendColor(it, "red"),
30880
+ "stroke-width": "1.8",
30881
+ "stroke-linecap": "round"
30882
+ }, null, 8, _hoisted_23$1),
30883
+ createElementVNode("line", {
30884
+ x1: "16",
30885
+ y1: "4",
30886
+ x2: "16",
30887
+ y2: "16",
30888
+ stroke: legendColor(it, "red"),
30889
+ "stroke-width": "1.8",
30890
+ "stroke-linecap": "round"
30891
+ }, null, 8, _hoisted_24$1),
30892
+ createElementVNode("line", {
30893
+ x1: "11",
30894
+ y1: "5",
30895
+ x2: "21",
30896
+ y2: "15",
30897
+ stroke: legendColor(it, "red"),
30898
+ "stroke-width": "1.8",
30899
+ "stroke-linecap": "round"
30900
+ }, null, 8, _hoisted_25$1),
30901
+ createElementVNode("line", {
30902
+ x1: "21",
30903
+ y1: "5",
30904
+ x2: "11",
30905
+ y2: "15",
30906
+ stroke: legendColor(it, "red"),
30907
+ "stroke-width": "1.8",
30908
+ "stroke-linecap": "round"
30909
+ }, null, 8, _hoisted_26$1)
30910
+ ], 64)) : it.shape === "meterBox" ? (openBlock(), createElementBlock(Fragment$1, { key: 6 }, [
30911
+ createElementVNode("rect", {
30912
+ x: "10",
30913
+ y: "2",
30914
+ width: "16",
30915
+ height: "16",
30916
+ rx: "3",
30917
+ fill: legendColor(it, "meterFill"),
30918
+ stroke: legendColor(it, "meterText"),
30919
+ "stroke-width": "1.4"
30920
+ }, null, 8, _hoisted_27$1),
30921
+ createElementVNode("text", {
30922
+ x: "18",
30923
+ y: "13.5",
30924
+ "text-anchor": "middle",
30925
+ "font-size": "8",
30926
+ "font-weight": "700",
30927
+ fill: legendColor(it, "meterText")
30928
+ }, "JX", 8, _hoisted_28$1)
30929
+ ], 64)) : it.shape === "consumer" ? (openBlock(), createElementBlock(Fragment$1, { key: 7 }, [
30930
+ createElementVNode("circle", {
30931
+ cx: "16",
30932
+ cy: "10",
30933
+ r: "7",
30934
+ fill: "none",
30935
+ stroke: legendColor(it, "white"),
30936
+ "stroke-width": "2"
30937
+ }, null, 8, _hoisted_29$1),
30938
+ createElementVNode("text", {
30939
+ x: "16",
30940
+ y: "13",
30941
+ "text-anchor": "middle",
30942
+ "font-size": "9",
30943
+ "font-weight": "700",
30944
+ fill: legendColor(it, "white")
30945
+ }, "J", 8, _hoisted_30$1)
30946
+ ], 64)) : it.shape === "line" ? (openBlock(), createElementBlock(Fragment$1, { key: 8 }, [
30947
+ createElementVNode("circle", {
30948
+ cx: "16",
30949
+ cy: "10",
30950
+ r: "4.6",
30951
+ fill: legendColor(it, "dotFill"),
30952
+ stroke: legendColor(it, "cyan"),
30953
+ "stroke-width": "1.6"
30954
+ }, null, 8, _hoisted_31$1),
30955
+ createElementVNode("circle", {
30956
+ cx: "16",
30957
+ cy: "10",
30958
+ r: "1.5",
30959
+ fill: legendColor(it, "cyan")
30960
+ }, null, 8, _hoisted_32$1)
30961
+ ], 64)) : it.shape === "meterUser" ? (openBlock(), createElementBlock(Fragment$1, { key: 9 }, [
30962
+ createElementVNode("rect", {
30963
+ x: "12",
30964
+ y: "2",
30965
+ width: "8",
30966
+ height: "15",
30967
+ rx: "2",
30968
+ fill: "none",
30969
+ stroke: legendColor(it, "meterText"),
30970
+ "stroke-width": "1.2"
30971
+ }, null, 8, _hoisted_33$1),
30972
+ createElementVNode("rect", {
30973
+ x: "13.5",
30974
+ y: "4",
30975
+ width: "5",
30976
+ height: "2.2",
30977
+ rx: "1",
30978
+ fill: legendColor(it, "meterText"),
30979
+ "fill-opacity": "0.6"
30980
+ }, null, 8, _hoisted_34$1),
30981
+ createElementVNode("circle", {
30982
+ cx: "16",
30983
+ cy: "11",
30984
+ r: "1.4",
30985
+ fill: legendColor(it, "meterText")
30986
+ }, null, 8, _hoisted_35$1)
30987
+ ], 64)) : it.shape === "station" ? (openBlock(), createElementBlock("rect", {
30988
+ key: 10,
30989
+ x: "6",
30990
+ y: "4",
30991
+ width: "20",
30992
+ height: "12",
30993
+ rx: "2",
30994
+ fill: legendColor(it, "stationFill"),
30995
+ stroke: legendColor(it, "white"),
30996
+ "stroke-width": "1.4"
30997
+ }, null, 8, _hoisted_36$1)) : it.shape === "edge" ? (openBlock(), createElementBlock("line", {
30998
+ key: 11,
30999
+ x1: "3",
31000
+ y1: "10",
31001
+ x2: "29",
31002
+ y2: "10",
31003
+ stroke: legendColor(it, it.role || "trunkEdge"),
31004
+ "stroke-width": it.role === "branchEdge" ? 1.2 : 2.4,
31005
+ "stroke-linecap": "round"
31006
+ }, null, 8, _hoisted_37$1)) : it.shape === "square" ? (openBlock(), createElementBlock("rect", {
31007
+ key: 12,
31008
+ x: "10",
31009
+ y: "4",
31010
+ width: "12",
31011
+ height: "12",
31012
+ rx: "2",
31013
+ fill: it.color || legendColor(it, "cyan")
31014
+ }, null, 8, _hoisted_38$1)) : it.shape === "ring" ? (openBlock(), createElementBlock("circle", {
31015
+ key: 13,
31016
+ cx: "16",
31017
+ cy: "10",
31018
+ r: "6",
31019
+ fill: "none",
31020
+ stroke: it.color || legendColor(it, "cyan"),
31021
+ "stroke-width": "2.4"
31022
+ }, null, 8, _hoisted_39)) : it.shape === "triangle" ? (openBlock(), createElementBlock("polygon", {
31023
+ key: 14,
31024
+ points: "10,16 22,16 16,4",
31025
+ fill: it.color || legendColor(it, "red")
31026
+ }, null, 8, _hoisted_40)) : it.shape === "hline" ? (openBlock(), createElementBlock("line", {
31027
+ key: 15,
31028
+ x1: "3",
31029
+ y1: "10",
31030
+ x2: "29",
31031
+ y2: "10",
31032
+ stroke: it.color || legendColor(it, "cyan"),
31033
+ "stroke-width": "2.4",
31034
+ "stroke-linecap": "round"
31035
+ }, null, 8, _hoisted_41)) : it.shape === "text" ? (openBlock(), createElementBlock("text", {
31036
+ key: 16,
31037
+ x: "16",
31038
+ y: "14",
31039
+ "text-anchor": "middle",
31040
+ "font-size": "11",
31041
+ "font-weight": "700",
31042
+ fill: it.color || legendColor(it, "white")
31043
+ }, toDisplayString(it.text || "JX"), 9, _hoisted_42)) : (openBlock(), createElementBlock("circle", {
31044
+ key: 17,
31045
+ cx: "16",
31046
+ cy: "10",
31047
+ r: "5",
31048
+ fill: it.color || legendColor(it, "cyan")
31049
+ }, null, 8, _hoisted_43))
31050
+ ])),
31051
+ createElementVNode("span", _hoisted_44, toDisplayString(it.label), 1)
31052
+ ]);
31053
+ }), 128))
31054
+ ], 512), [
31055
+ [vShow, !legendCollapsed.value]
31056
+ ])
31057
+ ])) : createCommentVNode("", true),
31058
+ loading.value ? (openBlock(), createElementBlock("div", _hoisted_45, "正在解析台区拓扑…")) : error.value ? (openBlock(), createElementBlock("div", _hoisted_46, toDisplayString(error.value), 1)) : createCommentVNode("", true)
30313
31059
  ], 8, _hoisted_1$2);
30314
31060
  };
30315
31061
  }
30316
31062
  };
30317
- const TopoGraph = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-a4a4c398"]]);
31063
+ const TopoGraph = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-0736fa17"]]);
30318
31064
  const _hoisted_1$1 = ["data-theme"];
30319
31065
  const _hoisted_2$1 = { class: "panel-head" };
30320
31066
  const _hoisted_3$1 = {
@@ -30687,7 +31433,7 @@ const _sfc_main = {
30687
31433
  }
30688
31434
  };
30689
31435
  const DistanceTop10 = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-438b6017"]]);
30690
- const VERSION = "0.1.10";
31436
+ const VERSION = "0.1.12";
30691
31437
  const DESCRIPTION = "配电台区单线图拓扑成图引擎";
30692
31438
  export {
30693
31439
  DESCRIPTION,