|
@@ -0,0 +1,166 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="org-chart-container" ref="container"></div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+ import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
|
|
|
|
|
+ import { Graph, treeToGraphData } from '@antv/g6';
|
|
|
|
|
+
|
|
|
|
|
+ // 定义 props 接口
|
|
|
|
|
+ interface TreeData {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ label: string;
|
|
|
|
|
+ children?: TreeData[];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const props = defineProps<{
|
|
|
|
|
+ treeData: TreeData;
|
|
|
|
|
+ }>();
|
|
|
|
|
+
|
|
|
|
|
+ // 转换数据
|
|
|
|
|
+ const data = treeToGraphData(props.treeData);
|
|
|
|
|
+ // 图表容器引用
|
|
|
|
|
+ const container = ref<HTMLDivElement | null>(null);
|
|
|
|
|
+ let graph: any = null;
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化图表
|
|
|
|
|
+ const initGraph = () => {
|
|
|
|
|
+ if (!container.value) return;
|
|
|
|
|
+
|
|
|
|
|
+ // 销毁旧实例
|
|
|
|
|
+ if (graph) {
|
|
|
|
|
+ graph.destroy();
|
|
|
|
|
+ graph = null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 创建新的 G6 实例
|
|
|
|
|
+ graph = new Graph({
|
|
|
|
|
+ container: container.value,
|
|
|
|
|
+ // width: window.innerWidth,
|
|
|
|
|
+ // height: window.innerHeight,
|
|
|
|
|
+ data,
|
|
|
|
|
+ layout: {
|
|
|
|
|
+ type: 'dagre',
|
|
|
|
|
+ rankdir: 'LR', // 水平方向布局
|
|
|
|
|
+ nodesep: 100,
|
|
|
|
|
+ ranksep: 120,
|
|
|
|
|
+ preventOverlap: true, // 防止节点重叠
|
|
|
|
|
+ nodeStrength: -50, // 节点之间的斥力
|
|
|
|
|
+ edgeStrength: 0.5, // 边的弹性系数
|
|
|
|
|
+ iterations: 200, // 迭代次数
|
|
|
|
|
+ animation: true, // 启用布局动画
|
|
|
|
|
+ },
|
|
|
|
|
+ behaviors: [
|
|
|
|
|
+ 'drag-canvas',
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'zoom-canvas',
|
|
|
|
|
+ sensitivity: 1.5, // 配置灵敏度
|
|
|
|
|
+ key: 'zoom-behavior', // 为交互指定key,便于后续更新
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ autoFit: {
|
|
|
|
|
+ type: 'view', // 自适应类型:'view' 或 'center'
|
|
|
|
|
+ options: {
|
|
|
|
|
+ // 仅适用于 'view' 类型
|
|
|
|
|
+ when: 'always', // 何时适配:'overflow'(仅当内容溢出时) 或 'always'(总是适配)
|
|
|
|
|
+ direction: 'both', // 适配方向:'x'、'y' 或 'both'
|
|
|
|
|
+ },
|
|
|
|
|
+ animation: {
|
|
|
|
|
+ // 自适应动画效果
|
|
|
|
|
+ duration: 1000, // 动画持续时间(毫秒)
|
|
|
|
|
+ easing: 'ease-in-out', // 动画缓动函数
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ node: {
|
|
|
|
|
+ type: 'circle', // 节点类型
|
|
|
|
|
+ style: {
|
|
|
|
|
+ fill: '#e6f7ff', // 填充色
|
|
|
|
|
+ stroke: '#91d5ff', // 边框色
|
|
|
|
|
+ lineWidth: 1, // 边框宽度
|
|
|
|
|
+ r: 20, // 半径
|
|
|
|
|
+ labelText: (d) => d.id, // 标签文本
|
|
|
|
|
+ },
|
|
|
|
|
+ // 节点状态样式
|
|
|
|
|
+ state: {
|
|
|
|
|
+ hover: {
|
|
|
|
|
+ lineWidth: 2,
|
|
|
|
|
+ stroke: '#69c0ff',
|
|
|
|
|
+ },
|
|
|
|
|
+ selected: {
|
|
|
|
|
+ fill: '#bae7ff',
|
|
|
|
|
+ stroke: '#1890ff',
|
|
|
|
|
+ lineWidth: 2,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ edge: {
|
|
|
|
|
+ type: 'polyline', // 边类型
|
|
|
|
|
+ style: {
|
|
|
|
|
+ stroke: '#91d5ff', // 边的颜色
|
|
|
|
|
+ lineWidth: 2, // 边的宽度
|
|
|
|
|
+ endArrow: true, // 是否有箭头
|
|
|
|
|
+ },
|
|
|
|
|
+ // 边的状态样式
|
|
|
|
|
+ state: {
|
|
|
|
|
+ selected: {
|
|
|
|
|
+ stroke: '#1890ff',
|
|
|
|
|
+ lineWidth: 3,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 渲染
|
|
|
|
|
+ graph.render();
|
|
|
|
|
+
|
|
|
|
|
+ // 添加交互效果
|
|
|
|
|
+ graph.on('node:mouseenter', (evt) => {
|
|
|
|
|
+ const node = evt.item;
|
|
|
|
|
+ graph.setItemState(node, 'hover', true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ graph.on('node:mouseleave', (evt) => {
|
|
|
|
|
+ const node = evt.item;
|
|
|
|
|
+ graph.setItemState(node, 'hover', false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 响应窗口大小变化
|
|
|
|
|
+ window.addEventListener('resize', handleResize);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 处理窗口大小变化
|
|
|
|
|
+ const handleResize = () => {
|
|
|
|
|
+ if (graph.get('destroyed')) return;
|
|
|
|
|
+ if (container.value) {
|
|
|
|
|
+ graph.changeSize(container.value.clientWidth, container.value.clientHeight);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 监听 treeData 变化
|
|
|
|
|
+ watch(
|
|
|
|
|
+ () => props.treeData,
|
|
|
|
|
+ () => {
|
|
|
|
|
+ initGraph();
|
|
|
|
|
+ },
|
|
|
|
|
+ { deep: true },
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 生命周期钩子
|
|
|
|
|
+ onMounted(() => {
|
|
|
|
|
+ initGraph();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ onBeforeUnmount(() => {
|
|
|
|
|
+ window.removeEventListener('resize', handleResize);
|
|
|
|
|
+ if (graph) {
|
|
|
|
|
+ graph.destroy();
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+ .org-chart-container {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+</style>
|