npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@wwchao6411/power-topology-graph

v0.0.15

Published

`PowerTopologyGraph` 是主站网络拓扑通用组件,基于 AntV X6 3.x 渲染分区、业务分组、设备、逻辑总线、链路、拖拽同步、小地图和选中高亮。

Downloads

1,719

Readme

PowerTopologyGraph 组件使用说明

PowerTopologyGraph 是主站网络拓扑通用组件,基于 AntV X6 3.x 渲染分区、业务分组、设备、逻辑总线、链路、拖拽同步、小地图和选中高亮。

组件核心渲染逻辑由 core/PowerTopologyGraphRenderer.ts 承载,Vue 和 React 只是适配层。业务系统、逻辑总线、端口、连线路由和调试快照都由组件内部根据连接关系数据生成。

组件包会生成 TypeScript 声明文件,发布产物入口为 dist/types/index.d.ts,宿主项目可以直接从包入口导入 props、事件、模型、布局和设备类型相关类型。

安装

pnpm add @wwchao6411/power-topology-graph @antv/x6

Vue 项目还需要安装 vue;React 项目还需要安装 react。这些依赖作为 peer dependency 由宿主项目提供。

快速接入

组件需要一个稳定高度的父容器。layout 是必传 prop;没有保存布局时传 { overrides: {} }。数据可以传已转换的 records: TopologyRecord[],也可以直接传与后端拓扑图接口一致的 data: { nodeList, edgeList }

import type { LayoutOverrides, TopologyRecord } from '@wwchao6411/power-topology-graph'

export const records: TopologyRecord[] = [
  {
    sourceName: '核心交换机A',
    sourceType: 'core_switch',
    sourceZone: '安全一区',
    sourcePort: 'GE0/0/1',
    targetName: '接入交换机A',
    targetType: 'access_switch',
    targetZone: '安全一区',
    targetPort: 'GE0/0/24',
    linkType: '主链路',
    business: '调度业务',
  },
  {
    sourceName: '接入交换机A',
    sourceType: 'access_switch',
    sourceZone: '安全一区',
    sourcePort: 'GE0/0/2',
    targetName: '调度服务器01',
    targetType: 'server',
    targetZone: '安全一区',
    targetPort: 'eth0',
    linkType: '业务链路',
    business: '调度业务',
  },
]

export const layout: LayoutOverrides = { overrides: {} }

Vue 示例

<script setup lang="ts">
import { ref, shallowRef } from 'vue'
import {
  VuePowerTopologyGraph,
  type LayoutOverrides,
  type DeviceTypeConfigMap,
  type PowerTopologyGraphSelectionStyle,
  type TopologyDevice,
  type TopologyElementClickPayload,
  type TopologyLink,
  type TopologyModel,
  type TopologyRecord,
} from '@wwchao6411/power-topology-graph'

type GraphRef = InstanceType<typeof VuePowerTopologyGraph>

const graphRef = ref<GraphRef | null>(null)
const records = shallowRef<TopologyRecord[]>([])
const layout = shallowRef<LayoutOverrides>({ overrides: {} })
const showTerminals = ref(true)
const selectionStyle: PowerTopologyGraphSelectionStyle = {
  color: '#ff4d4f',
  strokeWidth: 6,
  antLineStrokeWidth: 3,
}
const deviceTypes: DeviceTypeConfigMap = {
  camera: {
    label: '摄像头',
    icon: '/assets/camera.png',
    terminal: true,
  },
}
const model = shallowRef<TopologyModel | null>(null)
const selectedDevice = shallowRef<TopologyDevice | null>(null)
const selectedLink = shallowRef<TopologyLink | null>(null)
const clickedElement = shallowRef<TopologyElementClickPayload | null>(null)

function saveLayout() {
  layout.value = graphRef.value?.exportLayout() ?? { overrides: {} }
  localStorage.setItem('power-topology-layout-v1', JSON.stringify(layout.value))
}

function resetLayout() {
  layout.value = graphRef.value?.resetLayout() ?? { overrides: {} }
}

async function exportPng() {
  const dataUri = await graphRef.value?.exportImage({ type: 'png' })
  if (!dataUri) return
  const link = document.createElement('a')
  link.href = dataUri
  link.download = 'power-topology.png'
  link.click()
}
</script>

<template>
  <div class="graph-panel">
    <div class="graph-actions">
      <button type="button" @click="graphRef?.fitView()">适配视图</button>
      <button type="button" @click="saveLayout">保存布局</button>
      <button type="button" @click="resetLayout">重置布局</button>
      <button type="button" @click="exportPng">导出图片</button>
      <button type="button" @click="graphRef?.relayout()">重新渲染</button>
      <label>
        <input v-model="showTerminals" type="checkbox" />
        显示终端
      </label>
    </div>

    <VuePowerTopologyGraph
      ref="graphRef"
      :records="records"
      :layout="layout"
      :show-terminals="showTerminals"
      :container-height="720"
      :container-border-hit-width="20"
      :selection-style="selectionStyle"
      :device-types="deviceTypes"
      @model-change="model = $event"
      @select-device="selectedDevice = $event"
      @select-link="selectedLink = $event"
      @element-click="clickedElement = $event"
    />
  </div>
</template>

<style scoped>
.graph-actions {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
}
</style>

如果传入 containerHeight,组件会优先使用该高度;数字按 px 处理,字符串按 CSS 高度值原样使用。没有传入时,组件会在挂载时按当前视口自动计算画布高度,并在窗口或父级尺寸变化时更新。宿主已经提供稳定外层高度时,组件会保留宿主高度。

React 示例

import { useMemo, useRef, useState } from 'react'
import {
  ReactPowerTopologyGraph,
  type LayoutOverrides,
  type DeviceTypeConfigMap,
  type PowerTopologyGraphSelectionStyle,
  type ReactPowerTopologyGraphRef,
  type TopologyDevice,
  type TopologyElementClickPayload,
  type TopologyLink,
  type TopologyModel,
  type TopologyRecord,
} from '@wwchao6411/power-topology-graph'

export function TopologyPanel() {
  const graphRef = useRef<ReactPowerTopologyGraphRef | null>(null)
  const [records, setRecords] = useState<TopologyRecord[]>([])
  const [layout, setLayout] = useState<LayoutOverrides>({ overrides: {} })
  const [showTerminals, setShowTerminals] = useState(true)
  const selectionStyle = useMemo<PowerTopologyGraphSelectionStyle>(() => ({
    color: '#ff4d4f',
    strokeWidth: 6,
    antLineStrokeWidth: 3,
  }), [])
  const deviceTypes = useMemo<DeviceTypeConfigMap>(() => ({
    camera: {
      label: '摄像头',
      icon: '/assets/camera.png',
      terminal: true,
    },
  }), [])
  const [model, setModel] = useState<TopologyModel | null>(null)
  const [selectedDevice, setSelectedDevice] = useState<TopologyDevice | null>(null)
  const [selectedLink, setSelectedLink] = useState<TopologyLink | null>(null)
  const [clickedElement, setClickedElement] = useState<TopologyElementClickPayload | null>(null)

  const stats = useMemo(() => ({
    devices: model?.devices.length ?? 0,
    links: model?.links.length ?? 0,
  }), [model])

  function saveLayout() {
    const nextLayout = graphRef.current?.exportLayout() ?? { overrides: {} }
    setLayout(nextLayout)
    localStorage.setItem('power-topology-layout-v1', JSON.stringify(nextLayout))
  }

  function resetLayout() {
    setLayout(graphRef.current?.resetLayout() ?? { overrides: {} })
  }

  async function exportPng() {
    const dataUri = await graphRef.current?.exportImage({ type: 'png' })
    if (!dataUri) return
    const link = document.createElement('a')
    link.href = dataUri
    link.download = 'power-topology.png'
    link.click()
  }

  return (
    <section style={{ height: 720 }}>
      <header>
        <button type="button" onClick={() => graphRef.current?.fitView()}>适配视图</button>
        <button type="button" onClick={saveLayout}>保存布局</button>
        <button type="button" onClick={resetLayout}>重置布局</button>
        <button type="button" onClick={exportPng}>导出图片</button>
        <button type="button" onClick={() => graphRef.current?.relayout()}>重新渲染</button>
        <label>
          <input
            type="checkbox"
            checked={showTerminals}
            onChange={(event) => setShowTerminals(event.target.checked)}
          />
          显示终端
        </label>
        <span>{stats.devices} 台设备 / {stats.links} 条链路</span>
      </header>

      <ReactPowerTopologyGraph
        ref={graphRef}
        records={records}
        layout={layout}
        showTerminals={showTerminals}
        containerHeight="calc(100vh - 96px)"
        containerBorderHitWidth={20}
        selectionStyle={selectionStyle}
        deviceTypes={deviceTypes}
        onModelChange={setModel}
        onSelectDevice={setSelectedDevice}
        onSelectLink={setSelectedLink}
        onElementClick={setClickedElement}
      />
    </section>
  )
}

Props

Vue 使用 kebab-case 传参,例如 show-terminalsterminal-group-visibilitycontainer-heightcontainer-border-hit-widthbusiness-group-max-devices-per-rowterminal-node-horizontal-gapshow-bus-labelsbus-click-modeline-sizesunknown-device-ip-text;React 使用 camelCase。

| 名称 | 类型 | 必填 | 默认值 | 说明 | | --- | --- | --- | --- | --- | | data | PowerTopologyGraphInputData | 否 | - | 拓扑 JSON,支持链路数组、{ edgeList, nodeList? }{ code, msg, data };传入时优先于 records。 | | records | TopologyRecord[] | 否 | - | 原始连接关系数组。组件会根据它生成设备、业务分组、逻辑总线和链路。 | | layout | LayoutOverrides | 是 | - | 用户保存的覆盖坐标。没有保存布局时传 { overrides: {} }。 | | showTerminals | boolean | 是 | - | 是否显示终端节点。默认内置终端类型为 serverhostunknown,也包含 deviceTypesterminal: true 的自定义类型。关闭后会按可见设备重新计算紧凑布局。 | | getHiddenDeviceIds | () => string[] | 否 | - | 返回需要隐藏的设备 ID 数组;与 showTerminals=false 叠加生效,最终隐藏集合为该数组和终端设备的并集。 | | terminalGroupVisibility | Record<string, 'hidden' \| 'compact' \| 'visible'> | 否 | - | 受控的终端分组三态显示状态,key 为 systemId,同时适用于核心总线直连终端分组和普通业务分组。 | | defaultTerminalGroupVisibility | Record<string, 'hidden' \| 'compact' \| 'visible'> | 否 | - | 非受控模式下的终端分组初始状态。未传时,核心直连分组默认 compact,普通业务分组超过预览阈值才默认 compact。 | | allowEdit | boolean | 否 | true | 是否允许拖拽 X6 元素。为 false 时禁止拖拽设备、分区、业务分组等节点元素;画布平移、缩放、点击选中仍可使用。 | | enableHistory | boolean | 否 | false | 是否启用 X6 History 撤销重做能力。启用后注册 History 和快捷键,支持 Ctrl/Meta+Z 撤销、Ctrl/Meta+Shift+Z 重做,并可通过 ref 调用 undo() / redo()。 | | containerHeight | number \| string | 否 | 自动计算 | 画布容器高度。数字按 px 处理,字符串按 CSS 高度值使用;未传时自动按视口剩余高度计算。 | | containerBorderHitWidth | number | 否 | 20 | 分区和业务分组边框拖拽命中宽度,单位为 X6 画布坐标 pt。 | | focusZoom | number | 否 | 1 | 点击设备或调用 focusNodeById(nodeId) 时的聚焦缩放倍数;建议在 1.02.0 间取值,避免节点标签溢出视口。 | | businessGroupMaxDevicesPerRow | number | 否 | 10 | 控制业务分组中一行最多显示的终端设备节点数;超出后从第二行开始生成行总线。 | | nodeSize | number | 否 | 50 | 配置设备节点宽高,单位为 X6 画布坐标 pt。 | | deviceImageSize | number | 否 | 36 | 配置设备节点图片宽高,单位为 X6 画布坐标 pt;图片会居中放在节点范围内。 | | busNodeMinGap | number | 否 | 跟随 nodeSize | 配置设备与总线之间的最小距离,单位为 X6 画布坐标 pt;未传时为 1 个节点高度。 | | sharedUplinkBusNodeGap | number | 否 | 跟随 busNodeMinGap | 配置共享上联总线或共享上联行总线顶部到对应接入设备底部的最小距离,单位为 X6 画布坐标 pt。 | | terminalNodeHorizontalGap | number | 否 | nodeSize / 2 | 配置终端列之间的额外水平空白,单位为 X6 画布坐标 pt;未传时为 0.5 个节点宽度,最小值为 0。 | | busGeometryStaggerGap | number | 否 | 5 | 配置不同总线或分总线几何重叠时的 X 坐标错开尺寸,单位为 X6 画布坐标 pt。 | | networkLayerStaggerGap | number | 否 | 5 | 配置同层网络设备或业务分组横排时奇数 index 项的 Y 坐标错开尺寸,单位为 X6 画布坐标 pt;设为 0 可关闭。 | | showBusLabels | boolean | 否 | false | 是否在可视总线左端显示总线名称。默认不显示,只影响文字显示,不影响总线几何和连接点。 | | unknownDeviceIpText | string | 否 | 未知 | 设备节点副标题显示 IP;设备没有 IP 时显示该兜底文案。 | | connectionRouter | PowerTopologyGraphConnectionRouter | 否 | manhattan | 配置普通连接线使用的 X6 路由,可选 manhattanorthnormal;蚂蚁线会复用已有普通边的 source、target 和 router,保持与连接线重叠。 | | busClickMode | 'full' \| 'bus-local' \| 'first-hop' | 否 | bus-local | 配置点击可视业务总线后的高亮范围:bus-local 高亮本层业务总线、owner 设备和一跳终端,first-hop 仅高亮总线 owner 网络设备,full 启用全链路追溯。 | | lineSizes | PowerTopologyGraphLineSizes | 否 | 核心总线 3pt、业务总线 2pt、其余线条 1pt,接入点 4pt | 配置核心总线、支线、业务总线、上联总线、普通连接线和总线接入点大小。 | | layoutAlgorithm | PowerTopologyGraphLayoutAlgorithm | 否 | 默认布局算法 | 自定义布局算法。未传时使用组件内置 defaultX6TopologyLayoutAlgorithm。 | | dataTransformer | PowerTopologyGraphDataTransformer | 否 | 默认转换算法 | 自定义 dataTopologyRecord[] 的转换算法;未传时使用组件内置转换模块。 | | searchFields | SearchField[] | 否 | ['assetName', 'ip'] | 配置 searchDevice(options) 的内置精确匹配字段,可选 idassetNameipmac。 | | searchMatcher | PowerTopologyGraphSearchMatcher | 否 | - | 自定义 searchDevice(options) 匹配方法;传入后完全替代内置 searchFields 匹配。 | | selectionStyle | PowerTopologyGraphSelectionStyle | 否 | { color: '#1677ff', strokeWidth: 5, antLineStrokeWidth: 3 } | 点击设备或链路后,选中框和蓝色蚂蚁线的颜色、宽度配置;strokeWidth 控制选中框,antLineStrokeWidth 控制蚂蚁线。 | | selectionMultiple | boolean | 否 | true | 是否允许同时选中多个 X6 元素。 | | selectionRubberband | boolean | 否 | true | 是否允许按住 Ctrl / Command 后在画布空白处左键拖拽框选设备、分区、业务分组、逻辑总线端点和边等 X6 元素。 | | selectionMovable | boolean | 否 | true | 是否允许拖动 Selection 选框整体移动已选中的 X6 元素。 | | selectionStrict | boolean | 否 | true | 是否启用严格框选;为 true 时元素须完全位于选框内才会被选中,为 false 时与选框相交即可选中。 | | deviceTypes | DeviceTypeConfigMap | 否 | 内置设备类型配置 | 追加或覆盖设备类型的显示名、图标和是否属于终端设备。 | | deviceTypeMap | Record<string, string> | 否 | {} | 节点悬浮 tooltip 的设备类型显示映射;优先使用导入数据保留的原始类型 key 查表,命中时显示映射值,未命中时显示原始 key;空值显示 -unknown 未配置映射时也显示 -。 | | topologyConfig | PowerTopologyGraphConfig | 否 | 见下文 | 拓扑行为配置对象,包含终端半隐藏阈值、半隐藏每侧预览数量、终端跨连线过滤、孤立设备网格和实验布局模式。 |

data

data 用于直接接收拓扑 JSON,适合宿主项目不想先手动转换为 TopologyRecord[] 的场景。可以直接传 docs/datas/链路信息.json 同形态的 { nodeList, edgeList }

import type { TopologyDiagramData } from '@wwchao6411/power-topology-graph'
import linkInfo from './链路信息.json'

const data: TopologyDiagramData = linkInfo

也可以传带节点详情的 { edgeList, nodeList? }

import type { TopologyDiagramData } from '@wwchao6411/power-topology-graph'

const data: TopologyDiagramData = {
  nodeList: [
    { id: 1, assetName: '公司网络机房核心交换机', type: 'SW', zone: 'I', ip: '192.168.12.1', isTerminal: 0 },
    { id: 2, assetName: '隐藏设备(10.76.199.150)', type: null, zone: null, isTerminal: 1 },
  ],
  edgeList: [
    { srcNodeId: 1, srcInterface: 'GE1/0/28', dstNodeId: 2, dstInterface: null, dstIp: '10.76.199.150', edgeType: 5 },
  ],
}

docs/datas/链路信息.json 当前已经包含 nodeListedgeList,后续读取这一份 JSON 即可获得节点与链路信息;节点详情优先用于类型、分区、业务、mac 和终端语义,isTerminal / is_terminal = 0 会强制按互联网络设备处理,未知非终端类型归一为 access_switch。若传入的拓扑图 JSON 中 nodeList 为空,组件清空 X6 内容并在画布中心显示“缺失拓扑节点”;若 nodeList 存在但 edgeList 为空,组件会把每个节点作为孤立设备按默认网格布局摆放。nodeList.mac 会保留到最终 TopologyDevice.mac,普通 CSV / Excel / 手写 records 不要求提供 mac。完整映射规则见根目录 docs/topology-data-transform.md

宿主需要项目专属规则时,可以通过 dataTransformer 覆盖转换算法:

import {
  defaultPowerTopologyGraphDataTransformer,
  type PowerTopologyGraphDataTransformer,
} from '@wwchao6411/power-topology-graph'

const dataTransformer: PowerTopologyGraphDataTransformer = (input, fallback = []) => {
  const records = defaultPowerTopologyGraphDataTransformer(input, fallback)
  return records.map((record) => ({
    ...record,
    business: record.business || '默认业务',
  }))
}

records

records 表示连接关系,每一条记录描述一条原始链路。字段完整时组件才能推导端口、业务系统、分区和原始链路高亮。

import type { TopologyRecord } from '@wwchao6411/power-topology-graph'

const records: TopologyRecord[] = [
  {
    sourceName: '边界防火墙A',
    sourceType: 'firewall',
    sourceZone: '边界区',
    sourcePort: 'GE1/0/1',
    targetName: '核心交换机A',
    targetType: 'core_switch',
    targetZone: '安全一区',
    targetPort: 'GE0/0/1',
    linkType: '主链路',
    business: '出口区互联网出口',
  },
]

| 字段 | 类型 | 说明 | | --- | --- | --- | | sourceId / targetId | string \| undefined | 源/目标设备唯一值。为空时使用对应设备名称作为唯一值。 | | sourceName / targetName | string | 源/目标设备显示名称。同名设备在 ID 不同时会渲染为不同拓扑节点。 | | sourceType / targetType | DeviceType | 设备类型。内置类型为 firewallcore_switchaccess_switchencryptorrouterforward_isolationreverse_isolationserverhostunknown,也可传入 deviceTypes 中配置的自定义类型。 | | sourceRawType / targetRawType | string \| undefined | 可选原始设备类型 key,优先用于节点悬浮 tooltip 的 deviceTypeMap 查表;不影响节点图标、终端语义和布局类型。 | | sourceIp / targetIp | string \| undefined | 可选设备 IP,会进入节点悬浮 tooltip。 | | sourceZone / targetZone | string | 所属分区,支持任意非空分区名;组件不会把导入分区限制在固定枚举内。 | | sourcePort / targetPort | string | 原始端口名,会用于详情、tooltip 和高亮路径。 | | linkType | string | 链路标签;为空时通常按主链路处理。 | | business | string | 业务系统名称,用于推导业务分组和链路归属;终端直连网络设备且为空时,业务分组名回退为该网络设备名称。 |

组件会先保留输入中已经标记为 core_switch 的核心设备,允许同一分区存在多台核心设备。若某分区没有显式核心,组件会在 buildTopology(...) 阶段按上下文推断:名称包含 核心 / 主干 的网络设备优先;其次在与分区外相连的交换机类网络设备中,选择本分区内连接非终端网络设备数量最多的一台;最后选择同分区内连接非终端网络设备数量最多的交换机类网络设备。最后一档至少需要 2 个同区网络设备邻居,避免普通两两互联设备被误判为核心。

分区名优先使用输入数据中的非空值;CSV / Excel 空分区通常由解析层按设备类型兜底,防火墙默认进入「边界区」,其他设备默认进入「安全一区」。使用后端拓扑图 data 时,空分区会优先继承对端分区。布局阶段只渲染当前可见设备和可见逻辑总线涉及的分区,没有可见内容的分区不会生成空框;内置的「安全一区、边界区、安全二区」只影响排序优先级,其他分区会按数据出现顺序追加。

终端直连网络设备时,业务分组名优先使用链路 business;为空时回退为该网络设备名称。非核心、非终端的网络设备都会作为业务分组 root 保留;即使只连接其他互联设备,或只有物理直连但不最终归属本分组的终端,也会显示独立业务分组和设备直连链路,只是不生成业务总线。渲染时只有该网络设备存在最终归属该业务分组、且当前页面可见的直连终端,业务总线才会显示;业务总线被隐藏后,原本连接该业务总线的上联会直接连到对应网络设备节点。

业务总线会根据同组可见终端节点中心点范围自动调整宽度和 X 坐标,默认左右预留 20pt,最小宽度 60pt;隐藏状态下的业务总线跳过重算。拖拽终端节点、初始化布局完成、以及宿主增删终端后重新渲染时,组件会同步修正逻辑总线几何、端点和连接线。

业务分组可视框不显示黑色边框。当前视图中某个业务分组只有 1 台可见设备时,组件不显示该业务分组标题和背景色,只保留设备本身和相关连线。

网络设备作为业务分组 root 的归属优先级高于其它业务分组扩展;两台网络设备互联时,各自仍保留在自己的业务分组内,不会被并入对方分组。业务分组只在同分区内扩展,并跳过核心交换机、边界区设备、终端设备和其他业务分组 root;业务分组不允许嵌套,网络设备上下级互联时仍保持同级业务分组。

终端只连接一个非核心网络设备时,会归入该网络设备分组;终端连接多个网络设备时,组件会先过滤核心设备,再按网络设备到核心设备的 BFS 最小跳数选择离核心更远的分组,同层按设备名称字典序选择。

同分区、同业务且候选业务分组唯一时,直连核心交换机的终端会归入该业务分组;如果同业务存在多个候选分组,组件不会自动并入任意一个分组,避免歧义归属。没有同业务分组时,纯核心直连终端会按 business 生成独立分组;business 为空时按“核心交换机名称 + 直连设备”生成可见业务分组。

同分区内 firewallaccess_switch 相连,且 business出口区 开头时,会推导为 出口区 分组;边界区 内不执行该出口区分组推导。

同一真实设备如果同时属于多个业务分组,组件会在每个业务分组内生成独立可视节点,并通过 data.originalDeviceId 保留真实设备 ID;选中、高亮、悬浮和事件回调仍按真实设备追溯。

设备节点图片下方第一行显示设备名称,第二行默认显示设备 IP;没有 IP 时显示 unknownDeviceIpText,默认是 未知

layout

layoutcollectLayout() 返回的覆盖坐标。组件会先生成默认网格布局,再用 layout.overrides 覆写匹配的设备、容器和逻辑总线坐标。

当宿主只覆盖某个设备节点坐标时,组件会在覆盖后重新修正当前内存布局:父级业务分组和分区会向外扩展以继续包含该节点;如果该节点是核心总线 owner,对应可视总线会重新对齐 owner 设备中心并按需要延长总线宽度;如果该节点是业务总线 owner,业务总线左端会保持在 owner 节点中心线,即 owner.x + owner.width / 2 === bus.x,上联连接和右侧终端接入共享该总线端点。owner_bus 仅保留在模型中用于结构和选中路径追溯,不渲染为额外支线。

后端拓扑图 JSON 中的 nodeList.positionX / positionY 可以通过 layoutOverridesFromTopologyDiagramData(...) 转换为 LayoutOverrides 后传给 layout prop。组件本身不会从 data prop 隐式读取坐标覆盖,保持 data 管连接关系、layout 管坐标的受控边界。

import type { LayoutOverrides } from '@wwchao6411/power-topology-graph'

const emptyLayout: LayoutOverrides = { overrides: {} }

const savedLayout: LayoutOverrides = {
  overrides: {
    'zone-安全一区': { x: 40, y: 64 },
    'system-device-1tr9bq6': { x: 320, y: 220 },
    'device-1tr9bq6': { x: 360, y: 282 },
    'device-1uvq4ob': { x: 620, y: 282 },
    'bus-device-1tr9bq6': { x: 340, y: 302 },
  },
}

上面的 zone-* 是分区,system-* 是业务分组,device-* 是设备,bus-* / row-bus-* 是逻辑总线。实际 key 由组件内部根据导入数据生成,业务代码不需要手写这些 key;推荐只保存和回传 collectLayout() / exportLayout() 的结果。

同一业务分组内有 2 个及以上设备连接同一核心外联设备时,会生成对应的共享上联总线;组内设备按右侧实际行号接入对应共享上联总线或共享上联行总线,再由共享上联总线统一连接外部核心总线。非核心网络设备之间保持设备到设备直连,不再改写到业务总线或共享上联总线。超过 businessGroupMaxDevicesPerRow 后会切分共享上联 row-bus-*,并且只在这组共享上联总线内部相连,不会接到网络设备总线。

核心总线直连终端分组进入半隐藏 compact 状态时,主共享上联总线仍保持可见,且共享上联总线到核心总线的结构边保持完整展示态端点;组件只隐藏超出预览数量的终端和终端接入段,不会新增分组或代表终端直连核心交换机的替代线。

设备与总线之间默认保持 1 个节点高度的最小间距,可通过 busNodeMinGap 调整;共享上联总线和共享上联行总线仍可通过 sharedUplinkBusNodeGap 单独覆盖。终端列之间默认保持 0.5 个节点宽度的额外水平空白,可通过 terminalNodeHorizontalGap 覆盖。

如果同一行下方同时存在共享上联总线和下一行业务分总线,组件会按“当前行设备 -> 共享上联总线 / 共享上联行总线 -> 业务分总线 -> 下一行设备”的顺序预留行间空间。

业务分组内最终归属该分组、且与 root 网络设备直连的终端按所在行连接总线:第一行连接业务总线,第二行连接第一条行总线,第三行连接第二条行总线,以此类推。多宿主终端如果物理直连某业务分组 root、但最终归属其它业务分组,链路会保留为设备到终端的直接链路,不会落到该 root 的业务总线或行总线。连接线跨到下方业务分组时,上方端点统一从上方业务分组最后一条可用的业务总线或行总线出线。业务互联设备没有最终归属终端,或终端在当前视图中被隐藏时,组件只显示业务分组和设备直连链路,不显示空业务总线;相关上联连接线会回连到业务总线 owner 设备节点。

同一个业务分组内,不同主总线数组下的终端会分行摆放,避免业务总线终端和共享上联终端混在同一行。

开启 showBusLabels 后,可视总线左端会显示总线名称。设备或分组接入总线时,真实接入点显示黑色实心小圆点;水平总线跨越纵向支线但不连通时,通过半圆跳线区分“跨越”和“接入”。双挂设备接入主备总线时保留两根独立竖向支线,设备侧连接点保持约 10-15px 间距,不合并成单线后再分叉。

互联设备(非终端类型)与核心总线相连的支线默认 1pt;核心总线默认 3pt,业务总线默认 2pt,共享上联总线和普通连接线默认 1pt,总线接入点默认 4pt,可通过 lineSizes 调整。点击可视总线会高亮当前可见的总线邻域;垂直接入支线不参与点击选中。

同分区业务分组在布局前会根据分组间设备原始链路先划分关联批次;有关联的业务分组会作为一个批次优先进入层级横排,并尽量放在核心总线同一侧靠近摆放。批次内业务分组优先按主用、备用、A/1、B/2 等设备命名顺序排列;没有这些特征时按原始分组顺序保留稳定布局。一个关联批次内所有分组布置完成后,才会继续布置下一个批次和无关联分组。

分区内以核心交换机和核心总线为 0 层,通过同分区网络设备链路 BFS 推导最小跳数。单核心总线只使用正数层级向下摆放,且核心总线位于分区顶部;主备核心总线时,只接主线的网络设备使用负数层级向上摆放,只接备线或同时接主备线的网络设备使用正数层级向下摆放。层级绝对值越大离核心越远,同一层级横向排成一行且不分行;核心总线直连终端分组排在第一层同侧业务分组的最后一个;上下相邻层级间有关联的业务分组会优先对齐 X 中心,形成竖列关系。业务总线、共享上联总线和行总线继承所属分组层级。

业务分组内设备如果连接到其他分区,布局会优先把该业务分组放在当前分区靠近目标分区的边缘;连接左侧分区时靠左,连接右侧分区时靠右。

同层网络设备或业务分组横排时,组件会按当前排序 index 的奇偶数轻微错开 Y 坐标,减少横向连接线共线;同一逻辑父容器内的总线发生几何重叠时,组件会自动按上下和左右 lane 错开,避免可视总线互相覆盖。

有已保存布局时,直接把它作为 layout prop 传入即可:

<script setup lang="ts">
import { shallowRef } from 'vue'
import {
  VuePowerTopologyGraph,
  type LayoutOverrides,
  type TopologyRecord,
} from '@wwchao6411/power-topology-graph'

const graphRef = shallowRef<InstanceType<typeof VuePowerTopologyGraph> | null>(null)
const records = shallowRef<TopologyRecord[]>([])
const layout = shallowRef<LayoutOverrides>({
  overrides: {
    'zone-安全一区': { x: 40, y: 64 },
    'device-1tr9bq6': { x: 360, y: 282 },
    'bus-device-1tr9bq6': { x: 340, y: 302 },
  },
})

function saveLayout() {
  layout.value = graphRef.value?.exportLayout() ?? { overrides: {} }
}
</script>

<template>
  <VuePowerTopologyGraph
    ref="graphRef"
    :records="records"
    :layout="layout"
    :show-terminals="true"
  />
</template>

保存到后端或 localStorage 时,也保存完整的 LayoutOverrides 对象,不要只保存内部的 overrides 字段:

localStorage.setItem('power-topology-layout-v1', JSON.stringify(layout.value))

隐藏终端或 getHiddenDeviceIds 返回非空数组时组件不会套用已保存覆盖坐标,因为设备被过滤后需要重新计算紧凑布局。隐藏终端后只剩网络设备的业务分组仍按层级横向单行摆放,只连接隐藏终端或没有最终归属终端的业务总线会一并隐藏,相关上联连接线会回连到业务总线 owner 设备节点。

showTerminals

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="false"
/>

false 时不渲染所有终端类型设备,也会过滤只连接到隐藏终端的行总线和链路。适合只查看核心网络、边界设备和网络设备。

unknown 也是内置终端类型。通过 deviceTypes 标记为 terminal: true 的自定义类型同样会被 showTerminals=false 隐藏。

getHiddenDeviceIds

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  :get-hidden-device-ids="() => hiddenDeviceIds"
/>

该函数返回需要额外隐藏的设备 ID 数组。设备 ID 使用拓扑模型中的 device.id;后端 nodeList.id 导入后通常对应 device-${id}。当 showTerminals=false 时,最终隐藏集合为该函数返回值与所有终端设备的并集。

terminalGroupVisibility

核心总线直连终端分组和普通业务分组都支持 hiddencompactvisible 三态显示。普通业务分组 hidden 会隐藏全部终端,NodeTool 锚点是该分组 root 网络设备;核心直连分组没有非核心 root,hidden 会保留首台未被外部隐藏的终端作为 NodeTool 锚点,其余终端隐藏。compact 只显示前 topologyConfig.terminalPreviewLimit 个终端;当前可见终端数不超过 5 台时全部放在业务总线或共享上联总线下方,超过 5 台时继续按总线上下预览;visible 使用现有总线 / 分总线 / 共享上联总线布局。默认情况下,showTerminals=true 且宿主未传 terminalGroupVisibility 时,核心直连分组进入 compact,普通业务分组仅在终端数超过预览阈值时进入 compact,小分组直接 visiblegetHiddenDeviceIds 优先级最高,即使分组切到 visible,外部指定隐藏的设备仍不会显示。

核心直连分组在 compactvisible 间切换时,上联骨架保持一致:共享上联总线、共享上联到核心总线的结构边以及核心总线不会因为部分终端被隐藏而替换为设备直连线。

三态工具挂在当前锚点设备右上角,并通过 X6 NodeTool 渲染。隐藏态显示总终端数量 Badge 和展开按钮;半隐藏态显示折叠按钮、总终端数量 Badge 和展示按钮;完整展示态显示半隐藏按钮和折叠按钮。局部切换会重新紧凑布局,保持当前缩放倍率,并把视图中心移动到当前锚点设备。普通业务分组处于隐藏态时,拖动锚点网络设备等同于拖动业务分组框;随后切换为半隐藏或完整展示时,展开后的分组会保持在拖动后的锚点位置。该位置只保存在当前 renderer 实例中,不会自动写入 layout

<script setup lang="ts">
import { shallowRef } from 'vue'
import type { PowerTopologyTerminalGroupVisibilityMap } from '@wwchao6411/power-topology-graph'

const terminalGroupVisibility = shallowRef<PowerTopologyTerminalGroupVisibilityMap>({})
</script>

<template>
  <VuePowerTopologyGraph
    :records="records"
    :layout="layout"
    :show-terminals="showTerminals"
    :terminal-group-visibility="terminalGroupVisibility"
    @update:terminal-group-visibility="terminalGroupVisibility = $event"
    @update:show-terminals="showTerminals = $event"
  />
</template>
const [terminalGroupVisibility, setTerminalGroupVisibility] = useState<PowerTopologyTerminalGroupVisibilityMap>({})

<ReactPowerTopologyGraph
  records={records}
  layout={layout}
  showTerminals={showTerminals}
  terminalGroupVisibility={terminalGroupVisibility}
  onTerminalGroupVisibilityChange={setTerminalGroupVisibility}
  onShowTerminalsChange={setShowTerminals}
/>

不传 terminalGroupVisibility 时,组件内部托管局部状态;需要初始化默认状态时传 defaultTerminalGroupVisibility

topologyConfig

topologyConfig 用于承载新增拓扑行为配置,避免把相关开关拆成多个零散 prop。

| 字段 | 默认值 | 说明 | | --- | --- | --- | | terminalPreviewLimit | 10 | 分组半隐藏态保留的前 N 个终端数量。核心直连分组未受控时默认进入半隐藏;普通业务分组超过该阈值才默认半隐藏。 | | terminalPreviewRowSize | 5 | 分组半隐藏态中业务总线或共享上联总线上方和下方各自一行最多显示的终端数量;可见终端数不超过 5 台时仅使用下方一行。 | | ignoreTerminalCrossLinks | false | 为 true 时,渲染阶段只保留终端到其最终归属分组 root 网络设备的原始链路,其它终端跨网络设备连线不绘制;不修改 model.linksoriginalLinks。 | | noLinkGrid.columns | 5 | 孤立设备在分区或全图底部网格中的默认列数。 | | layoutMode | 'classic' | 当前默认布局仍是 classic'regionalExperimental' 仅作为后续区域网络实验布局预留值。 |

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  :topology-config="{
    terminalPreviewLimit: 10,
    terminalPreviewRowSize: 5,
    ignoreTerminalCrossLinks: true,
  }"
/>

allowEdit

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="true"
  :allow-edit="false"
/>

allowEdit 默认为 true,保持组件原有可拖拽行为。传入 false 后,X6 节点元素不再响应拖拽,包括设备、分区和业务分组;画布平移、滚轮缩放、点击选中、tooltip 和导出能力不受影响。只读预览、审批页或详情页建议传 false,避免用户误改布局。

enableHistory

<VuePowerTopologyGraph
  ref="graphRef"
  :records="records"
  :layout="layout"
  :show-terminals="true"
  :enable-history="true"
/>

enableHistory 默认为 false,不开启时不会注册 History 插件或键盘快捷键。启用后组件注册 X6 History,历史栈上限为 50,并支持 Ctrl+Z / Meta+Z 撤销、Ctrl+Shift+Z / Meta+Shift+Z 重做。初始化和受控 props 触发的全量重渲染会临时暂停 History 记录,避免把程序化加载写入用户可撤销栈;拖拽后的端口、端点和连线路径重算属于内部渲染同步,不会进入用户可撤销栈。

lineSizes

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="true"
  :line-sizes="{
    coreBusStrokeWidth: 3,
    busBranchStrokeWidth: 1,
    businessBusStrokeWidth: 2,
    uplinkBusStrokeWidth: 1,
    connectionStrokeWidth: 1,
    busAccessMarkerSize: 4,
  }"
/>

lineSizes 只控制可视尺寸,不改变布局归属和端口分配规则。默认核心总线为 3,业务总线为 2,互联设备到核心总线的垂直接入支线、共享上联总线和普通连接线均为 1,总线接入点为 4。垂直接入支线用于表达接入关系,不支持点击选中。

busClickMode

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="true"
  bus-click-mode="bus-local"
/>

busClickMode 控制点击可视业务总线后的关联高亮范围。默认 bus-local 保留当前业务总线本层、owner 网络设备和一跳终端 / 行总线;first-hop 只保留当前业务总线和 owner 网络设备;full 会沿当前可见总线邻域追溯到核心链路。当前实现中业务总线是 visual-bus:* 可视 edge,组件会通过 busId 回到逻辑总线计算范围。

selectionStyle

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="true"
  :selection-style="{ color: '#ff4d4f', strokeWidth: 6, antLineStrokeWidth: 3 }"
/>

selectionStyle.color 控制点击设备或链路后的选中描边和蚂蚁线颜色;selectionStyle.strokeWidth 控制选中描边宽度;selectionStyle.antLineStrokeWidth 控制蚂蚁线宽度。未传时使用默认蓝色 #1677ff,选中描边宽度为 5,蚂蚁线宽度为 3

selectionMultipleselectionRubberbandselectionMovableselectionStrict 会直接同步到 X6 Selection 插件。组件默认支持 Ctrl / Command 点选多选,也支持 Ctrl / Command + 空白画布左键拖拽框选;普通空白拖拽保留给画布平移,避免和框选冲突。框选会同时包含节点和边:设备节点、分区框、业务分组框、逻辑总线端点、普通边和可视总线 edge 都可以进入 X6 Selection;组件生成的临时选中蚂蚁线覆盖物不会参与框选。普通点击设备或链路不会触发 X6 Selection 插件选中,而是继续走组件既有业务选中、详情面板和蚂蚁线逻辑。

点击设备后显示的蚂蚁线会覆盖在实际连接路径上,并按 focusZoom 把该节点平移缩放到视口中心;已有普通链路会复用原 X6 edge 的 source、target、router 和 vertices,避免高亮线重新路由后与黑色连接线错位。静态普通连接线默认使用 manhattan 路由避让真实设备,也可通过 connectionRouter="orth"connectionRouter="normal" 切换;orthmanhattan 路由下同一设备同侧多条连线会使用分散端口,重复可见链路会按稳定 lane 错开,并在设备端通过内部连接柱沿图片边缘法线出线;不同 layer 的非核心网络设备直连优先使用图片上下边连接点,减少端点附近短折返和共线覆盖;业务总线 / 分总线与共享上联总线 / 分总线之间的连接保持直线,不参与连接柱或折线绕行。manhattan 避障只保留真实设备节点作为障碍,分区框、业务分组框、逻辑总线端点和选中临时端点会从障碍图排除。设备选择拖拽过程中通过 X6 Selection 的 movingRouterFallback: 'orth' 临时降级,拖拽结束后恢复当前配置的原路由。

deviceTypes

import type { DeviceTypeConfigMap } from '@wwchao6411/power-topology-graph'

const deviceTypes: DeviceTypeConfigMap = {
  camera: {
    label: '摄像头',
    icon: '/assets/camera.png',
    terminal: true,
  },
  access_switch: {
    label: '业务交换机',
  },
}
<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  :device-types="deviceTypes"
/>

deviceTypes 的 key 必须与 TopologyRecord.sourceType / targetType 一致。每个类型配置包含:

| 字段 | 类型 | 说明 | | --- | --- | --- | | label | string | 设备类型显示名,用于节点 label 宽度计算。节点副标题默认显示 IP,节点悬浮 tooltip 的设备类型文案由 deviceTypeMap 控制。 | | icon | string | 图标 URL 或打包后的图片地址。未传时使用内置默认图标;自定义类型默认使用未知设备图标。 | | terminal | boolean | 是否按终端设备处理。为 true 时参与业务分组终端布局、行总线拆分和 showTerminals=false 过滤。 |

内置设备类型默认值:

| 类型 | 显示名 | 是否终端 | 默认图标 | | --- | --- | --- | --- | | firewall | 防火墙 | 否 | 防火墙.png | | core_switch | 核心交换机 | 否 | 核心层交换机.png | | access_switch | 接入交换机 | 否 | 汇聚层交换机.png | | encryptor | 加密机 | 否 | 加密装置.png | | router | 路由器 | 否 | 路由器.png | | forward_isolation | 正向隔离 | 否 | 正向隔离装置.png | | reverse_isolation | 反向隔离 | 否 | 反向隔离装置.png | | server | 服务器 | 是 | 普通服务器.png | | host | 终端 | 是 | 办公终端.png | | unknown | 未知 | 是 | 未知设备.png |

CSV / Excel 解析时,类型字段精确匹配优先,大小写不敏感;SWRTFWFIDBIDVEAD 会分别映射到交换机、路由器、防火墙、正向隔离、反向隔离、加密装置等网络设备语义。只有类型为空或未知时才按名称做模糊识别。主网、配电和其它未配置类型默认按终端语义处理,可通过 deviceTypes[type].terminal = false 覆盖。使用后端拓扑图 data 时,nodeList.isTerminal / nodeList.is_terminal = 0 是更强的网络设备信号,未知非终端类型会归一为 access_switch

containerHeight

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  :container-height="720"
/>
<ReactPowerTopologyGraph
  records={records}
  layout={layout}
  showTerminals
  containerHeight="calc(100vh - 96px)"
/>

传入数字时按 px 设置高度,例如 720 等同于 720px;传入字符串时可使用任意合法 CSS 高度值。未传入时组件会自动计算高度,避免宿主没有稳定高度时 X6 autoResize 持续触发。

containerBorderHitWidth

<ReactPowerTopologyGraph
  records={records}
  layout={layout}
  showTerminals
  containerBorderHitWidth={24}
/>

分区和业务分组内部空白区域默认用于拖动画布,只有边框命中区域用于拖动容器。这个值越大,越容易拖动容器边框;太大则会压缩画布平移的可点击区域。

businessGroupMaxDevicesPerRow

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  :business-group-max-devices-per-row="10"
/>
<ReactPowerTopologyGraph
  records={records}
  layout={layout}
  showTerminals
  businessGroupMaxDevicesPerRow={10}
/>

该值控制业务分组中终端设备每行最多显示多少个。组件会把同一个值同时用于拓扑建模和布局:超过上限的终端从下一行开始摆放,并生成对应的行总线和链路改写。传入小数会向下取整,最小值为 1;未传时默认 10。

nodeSize

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  :node-size="50"
/>
<ReactPowerTopologyGraph
  records={records}
  layout={layout}
  showTerminals
  nodeSize={50}
/>

nodeSize 控制设备节点的宽高,单位为 X6 画布坐标 pt;未传时默认 50。设备与总线的默认最小间距跟随 nodeSize,可由 busNodeMinGap 覆盖。

deviceImageSize

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  :device-image-size="36"
/>
<ReactPowerTopologyGraph
  records={records}
  layout={layout}
  showTerminals
  deviceImageSize={36}
/>

deviceImageSize 控制设备节点内部图片的宽高,单位为 X6 画布坐标 pt;未传时默认 36。图片会居中放在 nodeSize 定义的节点范围内。

busNodeMinGap

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  :bus-node-min-gap="120"
/>
<ReactPowerTopologyGraph
  records={records}
  layout={layout}
  showTerminals
  busNodeMinGap={120}
/>

该值控制设备与总线之间的最小距离,单位为 X6 画布坐标 pt;未传时默认等于当前 nodeSize,即 1 个节点高度,最小值为 0。

sharedUplinkBusNodeGap

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  :shared-uplink-bus-node-gap="12"
/>
<ReactPowerTopologyGraph
  records={records}
  layout={layout}
  showTerminals
  sharedUplinkBusNodeGap={12}
/>

该值控制共享上联总线或共享上联行总线顶部到对应接入设备底部的最小距离,单位为 X6 画布坐标 pt;未传时默认跟随 busNodeMinGap,最小值为 0。

terminalNodeHorizontalGap

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  :terminal-node-horizontal-gap="50"
/>
<ReactPowerTopologyGraph
  records={records}
  layout={layout}
  showTerminals
  terminalNodeHorizontalGap={50}
/>

该值控制终端列之间的额外水平空白,单位为 X6 画布坐标 pt;未传时默认等于当前 nodeSize / 2,即 0.5 个节点宽度,最小值为 0。终端列自身仍会按节点和标签宽度计算,避免长标签互相重叠。

busGeometryStaggerGap

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  :bus-geometry-stagger-gap="8"
/>
<ReactPowerTopologyGraph
  records={records}
  layout={layout}
  showTerminals
  busGeometryStaggerGap={8}
/>

busGeometryStaggerGap 控制不同总线、分总线或堆叠分组上联通道的 X 坐标错开尺寸,默认 5,最小值为 0。该值只影响总线几何错开,不改变总线归属、连线改写和端口分配规则。

networkLayerStaggerGap

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  :network-layer-stagger-gap="5"
/>
<ReactPowerTopologyGraph
  records={records}
  layout={layout}
  showTerminals
  networkLayerStaggerGap={5}
/>

networkLayerStaggerGap 控制同一 layer 横排中的网络设备或业务分组按 index 奇偶产生的 Y 坐标错开尺寸,默认 5,最小值为 0。偶数 index 保持原 Y,奇数 index 向下错开;该值只影响自动布局阶段,用户保存或导入的 layout.overrides 仍具有最终优先级。

unknownDeviceIpText

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  unknown-device-ip-text="未配置IP"
/>

设备节点副标题默认显示 sourceIp / targetIp 推导出的 IP。没有 IP 时显示该文案;未传时显示 未知

layoutAlgorithm

import { defaultX6TopologyLayoutAlgorithm } from '@wwchao6411/power-topology-graph'
import type { PowerTopologyGraphLayoutAlgorithm } from '@wwchao6411/power-topology-graph'

const layoutAlgorithm: PowerTopologyGraphLayoutAlgorithm = (input) => {
  const layout = graphRef.value?.getDefaultLayoutAlgorithm()?.(input)
    ?? defaultX6TopologyLayoutAlgorithm(input)

  // 可在这里调整 layout.placed 中的节点坐标,或完全返回自定义布局结果。
  return layout
}
<VuePowerTopologyGraph
  ref="graphRef"
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  :layout-algorithm="layoutAlgorithm"
/>

自定义算法需要返回完整 X6TopologyLayoutnodesplacedlinksparentById 都必须完整。未传 layoutAlgorithm 时组件使用默认算法;默认算法和规则说明见 默认布局算法说明

搜索配置

searchDevice({ keyword }) 默认只对设备名称和 IP 做精确匹配,匹配前会 trim 并忽略大小写;局部字符串不会命中。需要扩展内置字段时传 searchFields

<VuePowerTopologyGraph
  :records="records"
  :layout="layout"
  :show-terminals="showTerminals"
  :search-fields="['id', 'assetName', 'ip', 'mac']"
/>
<ReactPowerTopologyGraph
  records={records}
  layout={layout}
  showTerminals
  searchFields={['id', 'assetName', 'ip', 'mac']}
/>

assetName 对应 TopologyDevice.nameid 优先匹配原始 nodeList.id / record.sourceId / record.targetId,没有原始 ID 时回退匹配内部 device.idmac 仅来自后端拓扑图 nodeList.mac。传入 searchMatcher(device, keyword) 后,组件完全使用外部 matcher,不再执行内置字段匹配。

事件

Vue 事件名为 kebab-case;React 使用对应 callback。

| Vue | React | 回调参数 | 触发时机 | | --- | --- | --- | --- | | model-change | onModelChange | TopologyModel | recordsshowTerminalsgetHiddenDeviceIds 或布局重建后触发。 | | select-device | onSelectDevice | TopologyDevice \| null | 点击真实设备时返回设备;不会同时触发 select-link。 | | select-link | onSelectLink | TopologyLink \| TopologyElementClickPayload \| null | 点击普通链路时返回链路,点击可视总线时返回 kind: 'bus' 的 payload 并高亮总线邻域;不会同时触发 select-device。 | | select-group | onSelectGroup | TopologyElementClickPayload \| null | 点击业务分组时返回 kind: 'system' 的 payload。 | | select-zone | onSelectZone | TopologyElementClickPayload \| null | 点击分区时返回 kind: 'zone' 的 payload。 | | element-click | onElementClick | TopologyElementClickPayload | 点击设备、可选普通链路、分区、业务分组、可视总线或空白画布时触发;垂直接入支线不可点击。 | | selection-change | onSelectionChange | PowerTopologyGraphSelectionChangePayload | X6 Selection 选中集合变化时触发;selectedCells 返回完整可选 X6 cell 集合,selected 保留当前选中节点集合。 | | update:terminal-group-visibility | onTerminalGroupVisibilityChange | Record<string, 'hidden' \| 'compact' \| 'visible'> | 用户通过三态工具切换业务分组终端显示状态时触发。 | | update:show-terminals | onShowTerminalsChange | boolean | 局部显示状态导致所有终端全部隐藏或全部显示时触发,宿主可同步全局终端开关。 |

function onModelChange(model: TopologyModel) {
  console.log(model.devices, model.links, model.systems, model.busLines)
}

function onSelectDevice(device: TopologyDevice | null) {
  console.log(device?.name ?? '未选中设备')
}

function onElementClick(payload: TopologyElementClickPayload) {
  console.log(payload.kind, payload.cellId)
}

暴露方法

Vue 通过组件 ref 调用,React 通过 forwardRef 调用。

| 方法 | 返回值 | 说明 | | --- | --- | --- | | getGraph() | Graph \| null | 获取当前 AntV X6 Graph 实例。组件未挂载或已销毁时返回 null,可用于外部二次处理、调用 X6 API 或自行导出图片。 | | undo() | void | 当 enableHistory=true 且历史栈可撤销时调用 X6 graph.undo();未启用或无可撤销记录时不执行操作。 | | redo() | void | 当 enableHistory=true 且重做栈可用时调用 X6 graph.redo();未启用或无可重做记录时不执行操作。 | | canUndo() | boolean | 返回当前 X6 History 是否存在可撤销记录。未启用 History 时返回 false。 | | canRedo() | boolean | 返回当前 X6 History 是否存在可重做记录。未启用 History 时返回 false。 | | collectLayout() | LayoutOverrides | 收集当前设备、分区、业务分组和逻辑总线坐标,用于保存布局。 | | exportLayout() | LayoutOverrides | collectLayout() 的语义化别名,适合作为“导出布局信息”按钮调用。 | | resetLayout() | LayoutOverrides | 清空内部覆盖布局并重新渲染,返回 { overrides: {} };受控场景下宿主也应同步更新自己的 layout 状态。 | | getDefaultLayoutAlgorithm() | PowerTopologyGraphLayoutAlgorithm \| undefined | 获取组件默认布局算法,便于宿主在自定义算法中复用默认结果后再微调坐标。 | | getDefaultDataTransformer() | PowerTopologyGraphDataTransformer | 获取组件默认数据转换算法,便于宿主在自定义转换中复用内置链路数组和 { edgeList, nodeList? } 解析规则。 | | transformData(input, transformer?) | TopologyRecord[] | 使用传入转换算法或组件默认算法,把拓扑 JSON 转换为连接关系数组。 | | fitView() | void | 调用 X6 zoomToFit,让当前拓扑适配画布视口。 | | focusNodeById(nodeId) | void | 按 X6 节点 ID 主动聚焦节点,使用当前 focusZoom 平移缩放到视口中心。 | | relayout() | void | 按当前 props 重新生成拓扑布局并重新渲染。 | | getTopologyData() | PowerTopologyGraphData | 获取当前业务模型、布局、X6 JSON 和调试快照。 | | exportImage(options) | Promise<string> | 导出当前画布图片,返回 PNG/JPEG data URI 或 SVG 字符串;PNG/JPEG 默认按未缩放内容区域导出。 | | searchDevice(options) | SearchResult[] | 按 searchFieldssearchMatcher 精确搜索当前模型。默认聚焦首个命中设备,并把所在业务分组切到 visible;传 autoFocus: false 时只返回结果。 | | getSelectedNodes() | Node[] | 获取当前 X6 Selection 选中的可选节点,包含设备节点、分区框、业务分组框和逻辑总线端点。 | | getSelectedCells() | Cell[] | 获取当前 X6 Selection 选中的完整可选 cell 集合,包含节点和 edge。 | | clearSelection() | void | 清空 X6 Selection 选中集合,并清空组件当前业务选中状态。 | | selectAll() | void | 选中当前画布内所有可选 X6 cell;临时选中蚂蚁线覆盖物会被排除。 |

保存和恢复布局

const STORAGE_KEY = 'power-topology-layout-v1'

function loadLayout(): LayoutOverrides {
  const text = localStorage.getItem(STORAGE_KEY)
  if (!text) return { overrides: {} }
  try {
    return JSON.parse(text) as LayoutOverrides
  } catch {
    return { overrides: {} }
  }
}

function saveLayout() {
  const layout = graphRef.value?.exportLayout() ?? { overrides: {} }
  localStorage.setItem(STORAGE_KEY, JSON.stringify(layout))
}

保存布局时建议只在 showTerminals === truegetHiddenDeviceIds 返回空数组且没有业务分组局部隐藏时允许操作;隐藏设备后的紧凑布局通常不应覆盖完整布局。

导出图片

async function exportImage() {
  const dataUri = await graphRef.value?.exportImage({
    type: 'png',
    image: {
      backgroundColor: '#ffffff',
      padding: 24,
      quality: 1,
    },
  })
  if (!dataUri) return
  const link = document.createElement('a')
  link.href = dataUri
  link.download = 'power-topology.png'
  link.click()
}

type 支持 pngjpegsvg,默认是 pngpng / jpeg 使用 image 选项,svg 使用 svg 选项。

PNG / JPEG 导出会默认使用 X6 graph.getContentArea({ useCellGeometry: true }) 计算未受当前画布缩放影响的真实内容区域,并显式传入 viewBox。因此页面经过 zoomToFit 缩小后再导出,也不会因为当前缩放比例过低导致基础像素尺寸偏小。图片导出默认 ratio: 2,默认关闭 X6 copyStyles,避免大图导出时遍历 SVG 节点并频繁调用 getComputedStyle;如宿主显式传入 image.viewBoximage.ratioimage.copyStyles,组件会优先使用宿主配置。

需要直接使用 X6 实例时,可以通过 getGraph() 获取:

async function exportByGraph() {
  const graph = graphRef.value?.getGraph()
  if (!graph) return

  graph.zoomToFit({ padding: 36, maxScale: 1 })
  const dataUri = await graph.toPNGAsync({
    backgroundColor: '#ffffff',
    padding: 24,
  })

  console.log(dataUri)
}

getGraph() 返回的是组件内部正在使用的同一个 X6 Graph 对象。外部可以读取、导出或添加临时工具,但不建议直接删除组件创建的节点、边或改写 data.parentId 逻辑归属;这类结构仍应由 recordslayout 和组件自身渲染流程驱动。分区和业务分组不使用 X6 group / 父子节点关系实现。

获取拓扑数据

const topologyData = graphRef.value?.getTopologyData()

console.log(topologyData?.model?.devices)
console.log(topologyData?.layout)
console.log(topologyData?.debugSnapshot)

model 是组件根据 datarecords 推导出的业务拓扑;layout 是当前可保存布局;graphJson 是 X6 图数据;debugSnapshot 与浏览器控制台里的 window.__POWER_TOPOLOGY_GRAPH_DATA__ 同源。

数据工具

包入口同时导出常用数据工具,适合放在导入、导出和调试面板中使用。

| API | 说明 | | --- | --- | | parseRecords(text) | 解析 CSV 文本为 TopologyRecord[],并校验必填表头。 | | recordsToCsv(records) | 导出标准中文表头 CSV。 | | parseDelimited(text) | 仅把 CSV 文本解析为二维字符串数组。 | | rowsFromUnknown(value) | 把 Excel 解析结果转成二维字符串数组。 | | validateImportHeaders(rows) | 检查二维表格是否包含必填字段。 | | mapRowsToRecords(rows) | 把二维表格映射为 TopologyRecord[]。 | | recordsFromTopologyDiagramData(payload) | 把链路数组、{ edgeList, nodeList? }{ code, msg, data } 拓扑图结构映射为 TopologyRecord[]。 | | layoutOverridesFromTopologyDiagramData(payload) | 从拓扑图结构的 nodeList.positionX / positionY 提取 LayoutOverrides,坐标 key 使用 device-${id}。 | | recordsFromTopologyModelData(payload) | 把导出的 TopologyModel 调试 JSON 通过 devicesoriginalLinks 反解为 TopologyRecord[]。 | | recordsFromTopologyRecordItems(items) | 把带中文表头、英文驼峰或下划线别名的对象数组归一化为 TopologyRecord[]。 | | recordFromTopologyRecordObject(value) | 归一化单条连接关系对象,适合宿主自定义导入流程复用。 | | resolvePowerTopologyGraphRecords(input, fallback?) | 解析组件可接受的数据输入;数组直接返回,拓扑图 JSON 会先转换为 TopologyRecord[]。 | | defaultPowerTopologyGraphDataTransformer(input, fallback?) | 组件默认数据转换算法,可供宿主自定义算法复用。 | | transformPowerTopologyGraphData(input, transformer?) | 使用指定算法或默认算法转换拓扑 JSON。 | | buildTopology(records) | 生成拓扑业务模型,可用于导出 JSON 或调试。 |

import { parseRecords, recordsToCsv, buildTopology } from '@wwchao6411/power-topology-graph'

const records = parseRecords(csvText)
const model = buildTopology(records)
const csv = recordsToCsv(records)

console.log(model.devices.length, model.busLines.length, csv)

类型速查

type KnownDeviceType =
  | 'firewall'
  | 'core_switch'
  | 'access_switch'
  | 'encryptor'
  | 'router'
  | 'forward_isolation'
  | 'reverse_isolation'
  | 'server'
  | 'host'
  | 'unknown'
type DeviceType = KnownDeviceType | (string & {})
type ZoneName = string
type PowerTopologyGraphInputData = TopologyRecord[] | TopologyLinkInfoEdge[] | TopologyDiagramData | TopologyDiagramResponse

type TopologyLinkInfoEdge = TopologyDiagramEdge

interface TopologyDiagramData {
  nodeList?: TopologyDiagramNode[]
  edgeList: TopologyLinkInfoEdge[]
}

interface TopologyDiagramNode {
  id?: string | number
  assetName?: string
  type?: string | number | null
  zone?: string | null
  business?: string | null
  ip?: string | null
  mac?: string | null
  isTerminal?: boolean | string | number | null
  is_terminal?: boolean | string | number | null
}

interface TopologyDiagramEdge {
  srcNodeId?: string | number
  srcInterface?: string | null
  srcIp?: string | null
  dstNodeId?: string | number
  dstInterface?: string | null
  dstIp?: string | null
  edgeType?: string | number | null
}

interface TopologyDiagramResponse {
  code?: number
  msg?: string
  data?: TopologyDiagramData
}

interface DeviceTypeConfig {
  label: string
  icon: string
  terminal: boolean
}

type DeviceTypeConfigMap = Record<string, Partial<DeviceTypeConfig>>

interface TopologyDevice {
  id: string
  rawId?: string
  name: string
  type: DeviceType
  ip?: string
  mac?: string
  zone: ZoneName
}

interface PowerTopologyGraphSelectionStyle {
  color?: string
  strokeWidth?: number
  antLineStrokeWidth?: number
}

interface PowerTopologyGraphLineSizes {
  coreBusStrokeWidth?: number
  busBranchStrokeWidth?: number
  businessBusStrokeWidth?: number
  uplinkBusStrokeWidth?: number
  connectionStrokeWidth?: number
  busAccessMarkerSize?: number
}

type PowerTopologyGraphConnectionRouter = 'manhattan' | 'orth' | 'normal'
type PowerTopologyTerminalGroupVisibility = 'hidden' | 'compact' | 'visible'
type SearchField = 'id' | 'assetName' | 'ip' | 'mac'
type PowerTopologyGraphSearchMatcher = (device: TopologyDevice, keyword: string) => boolean

interface SearchOptions {
  keyword: string
  autoFocus?: boolean
}

interface SearchResult {
  nodeId: string
  deviceName: string
  ip: string
  groupId: string
  zoneId: string
}

interface PowerTopologyGraphConfig {
  terminalPreviewLimit?: number
  terminalPreviewRowSize?: number
  ignoreTerminalCrossLinks?: boolean
  noLinkGrid?: {
    columns?: number
    cellWidth?: number
    cellHeight?: number
    gap?: number
  }
  layoutMode?: 'classic' | 'regionalExperimental'
}

interface PowerTopologyGraphProps {
  data?: PowerTopologyGraphInputData
  records?: TopologyRecord[]
  layout: LayoutOverrides
  showTerminals: boolean
  getHiddenDeviceIds?: () => string[]
  terminalGroupVisibility?: Record<string, PowerTopologyTerminalGroupVisibility>
  defaultTerminalGroupVisibility?: Record<string, PowerTopologyTerminalGroupVisibility>
  allowEdit?: boolean
  enableHistory?: boolean
  containerHeight?: number | string
  containerBorderHitWidth?: number
  focusZoom?: number
  businessGroupMaxDevicesPerRow?: number
  nodeSize?: number
  deviceImageSize?: number
  busNodeMinGap?: number
  sharedUplinkBusNodeGap?: number
  terminalNodeHorizontalGap?: number
  busGeometryStaggerGap?: number
  networkLayerStaggerGap?: number
  showBusLabels?: boolean
  unknownDeviceIpText?: string
  connectionRouter?: PowerTopologyGraphConnectionRouter
  busClickMode?: 'full' | 'bus-local' | 'first-hop'
  lineSizes?: PowerTopologyGraphLineSizes
  layoutAlgorithm?: PowerTopologyGraphLayoutAlgorithm
  dataTransformer?: PowerTopologyGraphDataTransformer
  searchFields?: SearchField[]
  searchMatcher?: PowerTopologyGraphSearchMatcher
  selectionStyle?: PowerTopologyGraphSelectionStyle
  selectionMultiple?: boolean
  selectionRubberband?: boolean
  selectionMovable?: boolean
  selectionStrict?: boolean
  deviceTypes?: DeviceTypeConfigMap
  deviceTypeMap?: Record<string, string>
  topologyConfig?: PowerTopologyGraphConfig
}

interface PowerTopologyGraphSelectionChangePayload {
  selected: Node[]
  selectedCells: Cell[]
  added: Cell[]
  removed: Cell[]
}

interface PowerTopologyGraphRendererHandle {
  getGraph: () => Graph | null
  undo: () => void
  redo: () => void
  canUndo: () => boolean
  canRedo: () => boolean
  collectLayout: () => LayoutOverrides
  exportLayout: () => LayoutOverrides
  resetLayout: () => LayoutOverrides
  getDefaultLayoutAlgorithm: () => PowerTopologyGraphLayoutAlgorithm
  getDefaultDataTransformer: () => PowerTopologyGraphDataTransformer
  transformData: (input: PowerTopologyGraphInputData, transformer?: PowerTopologyGraphDataTransformer) => TopologyRecord[]
  fitView: () => void
  focusNodeById: (nodeId: string) => void
  relayout: () => void
  getTopologyData: () => PowerTopologyGraphData
  exportImage: (options?: PowerTopologyGraphExportImageOptions) => Promise<string>
  searchDevice: (options: SearchOptions) => SearchResult[]
  getSelectedNodes: () => Node[]
  getSelectedCells: () => Cell[]
  clearSelection: () => void
  selectAll: () => void
}

interface LayoutOverrides {
  overrides: Record<string, { x: number; y: number }>
}

interface TopologyModel {
  devices: TopologyDevice[]
  links: TopologyLink[]
  originalLinks: TopologyLink[]
  systems: SystemGroup[]
  busLines: BusLine[]
  validation: ValidationIssue[]
}

更多完整类型以 services/topology/types.ts 和包入口导出的 type * 为准。

TypeScript 声明文件

组件发布前会执行:

pnpm run build

该命令包含 vue-tsc -p tsconfig.build.json,会生成:

dist/types/index.d.ts
dist/types/vue/PowerTopologyGraph.vue.d.ts
dist/types/react/PowerTopologyGraph.d.ts
dist/types/core/PowerTopologyGraphTypes.d.ts
dist/types/services/topology/types.d.ts

package.json 中的 types 指向 ./dist/types/index.d.ts。宿主项目只需要从包入口导入类型,不需要引用内部源码路径:

import type {
  DeviceTypeConfigMap,
  LayoutOverrides,
  PowerTopologyGraphProps,
  PowerTopologyGraphSelectionStyle,
  TopologyRecord,
} from '@wwchao6411/power-topology-graph'

调试

  • 组件渲染后会在 window.__POWER_TOPOLOGY_GRAPH_DATA__ 暴露调试快照,可在浏览器控制台查看节点、端点、端口和链路状态。
  • model-change 返回的是业务拓扑模型,适合排查设备合并、业务分组、逻辑总线和链路改写。
  • collectLayout() 返回的是当前覆盖坐标,适合排查拖拽保存结果。
  • 端口和链路异常时,优先确认 records 中设备名、端口、分区和业务字段是否完整。

发布

组件包配置独立放在本目录,不修改外层拓扑应用的 package.jsontsconfig.jsonvite.config.ts。发布时从组件目录执行:

cd src/components/PowerTopologyGraph
pnpm install
npm view @wwchao6411/power-topology-graph version
pnpm test:unit
pnpm run build
npm whoami
npm pack --dry-run
npm publish --access public

prepublishOnly 会在发布前自动重跑单测和构建。发布前需要先用 npm view @wwchao6411/power-topology-graph version 确认线上最新版本,确保 npm whoami 能返回拥有 @wwchao6411/power-topology-graph 发布权限的账号,并通过 npm pack --dry-run 检查即将上传的文件列表;若同一个版本已经发布到 npm,需要先升级 package.jsonversion,再重新发布。

更新内容

v0.0.15

  • 新增 busClickMode prop,可将点击业务总线后的高亮范围配置为 fullbus-localfirst-hop,默认 bus-local
  • 优化业务分组总线几何:业务总线宽度和 X 坐标会根据同组可见终端中心点范围自动重算,默认左右 padding 为 20pt,最小宽度为 60pt;隐藏状态下的业务总线跳过重算,并在拖拽或重新布局后同步端点和连线。
  • 新增 enableHistory prop,默认关闭;启用后注册 X6 History 和键盘快捷键,并通过 Vue / React ref 暴露 undo()redo()canUndo()canRedo()
  • 新增 X6 Selection 多选和框选配置:selectionMultipleselectionRubberbandselectionMovableselectionStrict,框选支持节点和边,并暴露 getSelectedNodes()getSelectedCells()clearSelection()selectAll()selection-change / onSelectionChange
  • 新增设备聚焦交互:点击设备节点会按 focusZoom 自动平移缩放到视口中心,Vue / React ref 同步暴露 focusNodeById(nodeId) 供列表联动调用。

v0.0.14

  • 新增 topologyConfig:支持终端分组半隐藏阈值、半隐藏预览行容量、终端跨网络设备连线渲染过滤、孤立设备网格配置,并预留 regionalExperimental 实验布局模式。
  • 新增终端分组三态 terminalGroupVisibility,核心总线直连终端分组和普通业务分组都可在 hiddencompactvisible 之间切换;Badge 显示分组总终端数并可点击展开全部终端,外部隐藏设备仍保持最高优先级。
  • 新增 searchDevice(options) 暴露方法,支持按 searchFields 精确匹配 idassetNameipmac,也可通过 searchMatcher 完全接管搜索规则并自动展开定位。
  • 新增 terminalNodeHorizontalGap 顶层 prop,可配置终端列额外水平间距;半隐藏态当前可见终端不超过 5 台时统一放在总线下方。
  • 调整默认终端列额外水平间距为 nodeSize / 2;业务总线左端按 root.x + root.width / 2 对齐,并通过设备节点覆盖中心连接点形成左右分段的可视效果;核心总线直连终端分组排在第一层同侧业务分组最后。
  • 非核心网络设备之间保持设备直连,不再改写到业务总线或共享上联总线;正交 / 曼哈顿路由下会使用不重叠的设备侧连接柱,不同 layer 直连优先使用设备图片上下边连接点。
  • owner_bus 仅保留为结构和选中路径追溯链路,不再渲染为业务总线 owner 额外支线。
  • 核心直连分组在 compactvisible 间切换时保持共享上联总线到核心总线的骨架连接,不退化成终端或 owner 设备直连核心的替代线。
  • 设备类型解析改为精确类型优先,并补充主网、配电和其它终端类型默认映射。

v0.0.13

  • 更新内置默认设备图标映射:access_switch 使用 汇聚层交换机.pngrouter 使用 路由器.pngforward_isolation 使用 正向隔离装置.pngreverse_isolation 使用 反向隔离装置.pngencryptor 使用 加密装置.png
  • 按最终归属终端生成业务总线:只有最终归属该业务分组、且与 root 网络设备直连的终端链路会改写到业务总线或行总线,多宿主终端归属其它分组时保留直接链路。
  • 更新内置链路样例数据,便于示例应用和包接入方观察当前默认拓扑行为。

v0.0.12

  • 组件包构建将 @antv/x6@antv/x6/* 子路径统一作为 external 处理,避免发布产物内联 X6 并导致宿主项目出现多份 X6 或初始化顺序问题。
  • 构建产物保留 @antv/x6/es@antv/x6/es/plugin/export@antv/x6/es/plugin/selection@antv/x6/es/plugin/history 等外部 import,由宿主项目依赖解析。

v0.0.11

  • 修复 React 适配层在 getHiddenDeviceIds 函数引用不变、返回隐藏设备集合变化时不会触发 renderer 更新的问题,与 Vue 适配层保持一致。
  • 调整根工程 TypeScript 构建边界,生产构建不再把 *.spec.ts 测试文件纳入 vue-tsc --noEmit,避免测试侧 Node API 类型影响示例应用构建。

v0.0.10

  • 新增核心交换机自动推断:分区内已有 core_switch 时直接保留;没有显式核心时,依次按名称包含 核心 / 主干、外联交换机类设备、本区网络邻居数量推断核心。
  • 多台交换机类设备同时与分区外设备相连时,只选择本分区内连接非终端网络设备数量最多的一台作为核心交换机。
  • 新增 encryptorrouterforward_isolationreverse_isolation 内置非终端网络设备类型,并兼容中文类型“加密机、路由器、正向隔离、反向隔离”。
  • 后端拓扑图 nodeList 同时兼容 isTerminalis_terminal;当值为 0 时优先按互联网络设备处理,未知非终端类型归一为 access_switch

v0.0.9

  • 新增 nodeSizedeviceImageSizebusNodeMinGapsharedUplinkBusNodeGapbusGeometryStaggerGapnetworkLayerStaggerGap,可分别配置设备节点尺寸、设备图片尺寸、设备与总线最小间距、共享上联总线到接入设备的最小间距、总线几何错开尺寸和同层网络设备 Y 错位尺寸。
  • 兼容空拓扑图数据:nodeList 为空时画布中心显示“缺失拓扑节点”;nodeList 有值但 edgeList 为空时,按网格布局展示孤立设备节点。
  • 新增 connectionRouter prop,可在 manhattanorthnormal 之间切换普通连接线 X6 路由,默认改为 manhattan
  • 设备选择拖拽过程中通过 Selection movingRouterFallback: 'orth' 临时降级路由,拖拽结束后恢复当前 connectionRouter 配置。
  • 修复 manhattan 路由下反向选中路径的蚂蚁线错位问题;已有普通链路高亮会复用原 X6 edge 的 source、target 和 router。
  • selectionStyle.antLineStrokeWidth 可单独配置高亮蚂蚁线宽度,默认 3pt,不再依赖选中框宽度。
  • select-device / onSelectDevice 继续返回选中的 TopologyDeviceelement-click / onElementClick 在设备点击时同时携带 cellId、业务设备信息和 X6 节点 data

v0.0.8

  • 优化 exportImage({ type: 'png' | 'jpeg' }):默认使用未受当前缩放影响的内容区域生成 viewBox,让导出清晰度和页面缩放状态解耦。
  • PNG / JPEG 默认 ratio: 2,并默认关闭 X6 copyStyles,降低大拓扑图片导出时的主线程开销;宿主显式传入的 viewBoxratiocopyStyles 仍会保留。

v0.0.7

  • 修正名称包含“防火墙”的非终端设备类型归一化,避免 SWaccess_switch、空类型或截断类型被误识别为交换机。
  • 默认样例数据中的边界防火墙设备类型同步为 firewall,确保按设备类型显示防火墙图标。

v0.0.5

  • 新增 containerHeight prop,可直接指定画布容器高度;数字按 px 使用,字符串按 CSS 高度值原样使用。
  • 未传 containerHeight 时,组件会自动按视口剩余高度计算 shell 高度,避免宿主高度不稳定导致 X6 autoResize 持续触发。
  • Vue / React 适配层同步支持高度优先级:显式传入高度优先,其次保留宿主稳定外层高度,最后才自动计算。
  • 新增 containerHeight 高度解析单元测试,并随包构建生成最新 TypeScript 声明文件。

v0.0.4

  • 新增 getGraph() 暴露方法,可通过 Vue / React ref 获取组件内部 AntV X6 Graph 实例。
  • 外部可基于 Graph 实例执行二次处理、读取画布状态或调用 X6 自带导出能力。
  • TypeScript 声明文件同步包含 getGraph(): Graph | null

v0.0.3

  • 新增 allowEdit prop,默认为 true
  • allowEdit=false 时禁止拖拽 X6 节点元素,适合只读预览、审批页和详情页。
  • Vue / React 适配层均已透传 allowEdit,并同步生成 TypeScript 声明文件。

v0.0.2

  • 新增 selectionStyle prop,可配置选中描边和蚂蚁线颜色、宽度。
  • 新增 deviceTypes prop,可配置设备类型显示名、图标和终端语义。
  • 新增内置 unknown 设备类型,默认终端语义,默认图标为 未知设备.png
  • 组件包发布产物包含 dist/types/index.d.ts 及 Vue / React / core / topology 类型声明文件。

注意事项

  • 宿主容器必须有稳定高度;组件内部画布最小高度为 620px
  • 设备图标已随组件资产打包,DEVICE_ICON_MAP 不依赖宿主项目的 /icons/* 路径。
  • 组件只负责渲染和交互,不直接读写 localStorage;连接关系、布局保存和恢复由宿主应用管理。
  • 逻辑总线不会创建 X6 bus node;真实连接点由 bus_endpoint 节点承载,接入点显示实心小圆点,其它端点保持透明。
  • 不要在运行时手写布局覆盖 key,使用 collectLayout() 收集后原样保存。
  • 如果要发布新版本,先运行 pnpm test:unitpnpm run build