| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- <template>
- <div class="org-chart-container" ref="container"></div>
- </template>
- <script setup lang="ts">
- import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue';
- import { Graph, treeToGraphData, NodeEvent, CanvasEvent } from '@antv/g6';
- // import { Graph, treeToGraphData, register, ExtensionCategory, NodeEvent, Polyline, CanvasEvent } from '@antv/g6';
- /**
- * @description: 为了确保图的正确渲染和交互,建议按照 G6 标准数据结构组织数据。
- * 每个元素(节点、边、组合)应包含一个 data 字段,用于存放业务数据和自定义属性。
- */
- // 定义 props 接口
- interface TreeData {
- id: string;
- data: { name: string };
- children?: TreeData[];
- }
- const props = defineProps<{
- treeData: TreeData;
- }>();
- const emits = defineEmits<{
- (event: 'node-click', nodeData: any): void;
- (event: 'canvas-click'): void;
- }>();
- let data = treeToGraphData(props.treeData); // 通过 treeToGraphData 方法,将树形结构数据转换为 G6 的标准数据结构
- const container = ref<HTMLDivElement | null>(null); // 图表容器引用
- let graph: any = null;
- let pending = false; // 防止并发 initGraph
- // 初始化图表
- const initGraph = async () => {
- if (pending || !container.value) return;
- pending = true;
- // 销毁旧实例
- if (graph) {
- graph.off();
- graph.destroy();
- graph = null;
- }
- // 等待下一帧,确保销毁完成
- await nextTick();
- // 创建新的 G6 实例
- graph = new Graph({
- container: container.value,
- padding: [20, 20, 20, 20], // 图表内边距
- data,
- node: {
- type: 'rect', // 使用内置的矩形节点类型
- style: {
- labelText: (d: any) => d.data.name, // 节点文本
- labelFill: '#333', // 文本颜色
- labelFontSize: 14, // 文本大小
- size: [250, 50],
- lineWidth: 1, // 边框宽度
- // lineDash: [5, 5], // 虚线边框
- stroke: '#1777FF', // 边框色
- fill: '#E7F1FF', // 填充色
- radius: 8,
- labelPlacement: 'center',
- ports: [{ placement: 'top' }, { placement: 'bottom' }],
- },
- // 节点状态样式
- state: {
- selected: {
- fill: '#1777FF',
- stroke: '#1777FF',
- lineWidth: 1,
- labelFill: '#fff', // 选中状态下文本颜色
- labelFontSize: 16, // 选中状态下文本大小
- },
- },
- },
- edge: {
- type: 'ant-line', // 边类型
- style: {
- stroke: '#1777FF', // 边的颜色
- lineWidth: 1, // 边的宽度
- lineDash: [10, 10],
- endArrow: true, // 是否有箭头
- router: {
- type: 'orth',
- },
- },
- },
- layout: {
- type: 'dagre',
- nodesep: 100,
- ranksep: 120,
- preventOverlap: true, // 防止节点重叠
- nodeStrength: -50, // 节点之间的斥力
- edgeStrength: 0.5, // 边的弹性系数
- iterations: 200, // 迭代次数
- animation: true, // 启用布局动画
- },
- autoFit: {
- type: 'center', // 自适应类型:'view' 或 'center'
- // options: {
- // // 仅适用于 'view' 类型
- // when: 'always', // 何时适配:'overflow'(仅当内容溢出时) 或 'always'(总是适配)
- // direction: 'both', // 适配方向:'x'、'y' 或 'both'
- // },
- // animation: {
- // // 自适应动画效果
- // duration: 1000, // 动画持续时间(毫秒)
- // easing: 'ease-in-out', // 动画缓动函数
- // },
- },
- autoResize: true, // 自动调整大小
- behaviors: [
- 'drag-canvas',
- {
- type: 'zoom-canvas',
- sensitivity: 0.5, // 配置灵敏度
- key: 'zoom-behavior', // 为交互指定key,便于后续更新
- },
- 'focus-element',
- {
- type: 'click-select',
- state: 'selected',
- unselectedState: 'inactive',
- multiple: true,
- trigger: ['shift'],
- },
- ],
- });
- // 渲染
- graph.render();
- // 监听节点点击事件
- graph.on(NodeEvent.CLICK, (evt) => {
- const { target } = evt;
- const nodeData = graph.getNodeData(target.id); // 获取节点数据
- emits('node-click', nodeData);
- });
- graph.on(CanvasEvent.CLICK, () => {
- graph?.fitCenter();
- emits('canvas-click');
- });
- window.addEventListener('resize', handleResize);
- pending = false;
- };
- // 处理窗口大小变化
- const handleResize = () => {
- if (graph && container.value) {
- graph.resize(container.value.offsetWidth, container.value.offsetHeight);
- graph.fitCenter();
- }
- };
- // 监听 treeData 变化
- watch(
- () => props.treeData,
- () => {
- data = treeToGraphData(props.treeData);
- initGraph();
- },
- { deep: true },
- );
- // 生命周期钩子
- onMounted(() => {
- data = treeToGraphData(props.treeData);
- initGraph();
- });
- onBeforeUnmount(() => {
- window.removeEventListener('resize', handleResize);
- if (graph) {
- graph.off(); // 移除所有事件监听
- graph.destroy();
- graph = null;
- }
- });
- </script>
- <style lang="scss" scoped>
- .org-chart-container {
- width: 100%;
- height: 100%;
- }
- </style>
|