|
|
@@ -1,20 +1,21 @@
|
|
|
<template>
|
|
|
- <div ref="containerRef" class="org-chart-container" />
|
|
|
+ <div ref="containerRef" class="org-chart-container" id="org-chart-container" />
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { ref, onMounted, onBeforeUnmount } from 'vue'
|
|
|
-import { Graph, Node, Point } from '@antv/x6'
|
|
|
+ import { ref, onMounted, onBeforeUnmount } from 'vue';
|
|
|
+ import { Graph, Node, Point } from '@antv/x6';
|
|
|
+ import {useFullscreen} from "/@/hooks/useFullscreen.js";
|
|
|
|
|
|
-export default {
|
|
|
- name: 'OrgChart',
|
|
|
- setup() {
|
|
|
- const containerRef = ref(null)
|
|
|
- let graph = null
|
|
|
+ export default {
|
|
|
+ name: 'OrgChart',
|
|
|
+ setup() {
|
|
|
+ const containerRef = ref(null);
|
|
|
+ let graph = null;
|
|
|
|
|
|
- // 注册节点和边的类型
|
|
|
- const registerChartElements = () => {
|
|
|
- Graph.registerNode(
|
|
|
+ // 注册节点和边的类型
|
|
|
+ const registerChartElements = () => {
|
|
|
+ Graph.registerNode(
|
|
|
'org-node',
|
|
|
{
|
|
|
width: 180,
|
|
|
@@ -75,9 +76,9 @@ export default {
|
|
|
},
|
|
|
},
|
|
|
true
|
|
|
- )
|
|
|
+ );
|
|
|
|
|
|
- Graph.registerEdge(
|
|
|
+ Graph.registerEdge(
|
|
|
'org-edge',
|
|
|
{
|
|
|
zIndex: -1,
|
|
|
@@ -93,191 +94,216 @@ export default {
|
|
|
},
|
|
|
},
|
|
|
true
|
|
|
- )
|
|
|
- }
|
|
|
+ );
|
|
|
+ };
|
|
|
|
|
|
- // 创建节点
|
|
|
- const createNode = (x, y, rank, name, image) => {
|
|
|
- return graph.addNode({
|
|
|
- x,
|
|
|
- y,
|
|
|
- shape: 'org-node',
|
|
|
- attrs: {
|
|
|
- avatar: {
|
|
|
- opacity: 0.7,
|
|
|
- // 'xlink:href': image,
|
|
|
+ // 创建节点
|
|
|
+ const createNode = (x, y, rank, name, image) => {
|
|
|
+ return graph.addNode({
|
|
|
+ x,
|
|
|
+ y,
|
|
|
+ shape: 'org-node',
|
|
|
+ attrs: {
|
|
|
+ avatar: {
|
|
|
+ opacity: 0.7,
|
|
|
+ // 'xlink:href': image,
|
|
|
+ },
|
|
|
+ rank: {
|
|
|
+ text: rank,
|
|
|
+ wordSpacing: '-5px',
|
|
|
+ letterSpacing: 0,
|
|
|
+ },
|
|
|
+ name: {
|
|
|
+ text: name,
|
|
|
+ fontSize: 13,
|
|
|
+ fontFamily: 'Arial',
|
|
|
+ letterSpacing: 0,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ // 禁用节点拖动
|
|
|
+ movable: false,
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 创建连线
|
|
|
+ const createLink = (source, target, vertices) => {
|
|
|
+ return graph.addEdge({
|
|
|
+ vertices,
|
|
|
+ source: {
|
|
|
+ cell: source,
|
|
|
},
|
|
|
- rank: {
|
|
|
- text: rank,
|
|
|
- wordSpacing: '-5px',
|
|
|
- letterSpacing: 0,
|
|
|
+ target: {
|
|
|
+ cell: target,
|
|
|
},
|
|
|
- name: {
|
|
|
- text: name,
|
|
|
- fontSize: 13,
|
|
|
- fontFamily: 'Arial',
|
|
|
- letterSpacing: 0,
|
|
|
+ shape: 'org-edge',
|
|
|
+ // 禁用连线拖动
|
|
|
+ connector: {
|
|
|
+ name: 'normal',
|
|
|
+ args: {
|
|
|
+ style: {
|
|
|
+ pointerEvents: 'none',
|
|
|
+ },
|
|
|
+ },
|
|
|
},
|
|
|
- },
|
|
|
- // 禁用节点拖动
|
|
|
- movable: false,
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- // 创建连线
|
|
|
- const createLink = (source, target, vertices) => {
|
|
|
- return graph.addEdge({
|
|
|
- vertices,
|
|
|
- source: {
|
|
|
- cell: source,
|
|
|
- },
|
|
|
- target: {
|
|
|
- cell: target,
|
|
|
- },
|
|
|
- shape: 'org-edge',
|
|
|
- // 禁用连线拖动
|
|
|
- connector: {
|
|
|
- name: 'normal',
|
|
|
- args: {
|
|
|
- style: {
|
|
|
+ // 禁用连线调整
|
|
|
+ router: {
|
|
|
+ name: 'manhattan',
|
|
|
+ args: {
|
|
|
+ padding: 10,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ attrs: {
|
|
|
+ line: {
|
|
|
+ // 禁止选中连线
|
|
|
pointerEvents: 'none',
|
|
|
},
|
|
|
},
|
|
|
- },
|
|
|
- // 禁用连线调整
|
|
|
- router: {
|
|
|
- name: 'manhattan',
|
|
|
- args: {
|
|
|
- padding: 10,
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 初始化图表
|
|
|
+ const initChart = () => {
|
|
|
+ if (!containerRef.value) return;
|
|
|
+
|
|
|
+ // 创建图表实例
|
|
|
+ graph = new Graph({
|
|
|
+ container: containerRef.value,
|
|
|
+ connecting: {
|
|
|
+ anchor: 'orth',
|
|
|
+ },
|
|
|
+ // 禁用鼠标滚轮缩放
|
|
|
+ mousewheel: {
|
|
|
+ enabled: true,
|
|
|
},
|
|
|
- },
|
|
|
- attrs: {
|
|
|
- line: {
|
|
|
- // 禁止选中连线
|
|
|
- pointerEvents: 'none',
|
|
|
+ // 禁用平移
|
|
|
+ panning: true,
|
|
|
+ // 禁用框选
|
|
|
+ selecting: {
|
|
|
+ enabled: false,
|
|
|
},
|
|
|
- },
|
|
|
- })
|
|
|
- }
|
|
|
+ interacting: {
|
|
|
+ nodeMovable: false,
|
|
|
+ edgeMovable: false,
|
|
|
+ },
|
|
|
+ });
|
|
|
|
|
|
- // 初始化图表
|
|
|
- const initChart = () => {
|
|
|
- if (!containerRef.value) return
|
|
|
+ // 图表数据
|
|
|
+ const male = 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*kUy8SrEDp6YAAAAAAAAAAAAAARQnAQ';
|
|
|
+ const female = 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*f6hhT75YjkIAAAAAAAAAAAAAARQnAQ';
|
|
|
|
|
|
- // 创建图表实例
|
|
|
- graph = new Graph({
|
|
|
- container: containerRef.value,
|
|
|
- connecting: {
|
|
|
- anchor: 'orth',
|
|
|
- },
|
|
|
- // 禁用鼠标滚轮缩放
|
|
|
- mousewheel: {
|
|
|
- enabled: true,
|
|
|
- },
|
|
|
- // 禁用平移
|
|
|
- panning: true,
|
|
|
- // 禁用框选
|
|
|
- selecting: {
|
|
|
- enabled: false,
|
|
|
- },
|
|
|
- interacting: {
|
|
|
- nodeMovable: false,
|
|
|
- edgeMovable: false,
|
|
|
- },
|
|
|
- })
|
|
|
+ // 创建节点
|
|
|
+ const bart = createNode(300, 70, 'CEO', 'Bart Simpson', male);
|
|
|
+ const homer = createNode(90, 200, 'VP Marketing', 'Homer Simpson', male);
|
|
|
+ const marge = createNode(300, 200, 'VP Sales', 'Marge Simpson', female);
|
|
|
+ const lisa = createNode(500, 200, 'VP Production', 'Lisa Simpson', female);
|
|
|
+ const maggie = createNode(400, 350, 'Manager', 'Maggie Simpson', female);
|
|
|
+ const lenny = createNode(190, 350, 'Manager', 'Lenny Leonard', male);
|
|
|
+ const carl = createNode(190, 500, 'Manager', 'Carl Carlson', male);
|
|
|
|
|
|
- // 图表数据
|
|
|
- const male =
|
|
|
- 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*kUy8SrEDp6YAAAAAAAAAAAAAARQnAQ'
|
|
|
- const female =
|
|
|
- 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*f6hhT75YjkIAAAAAAAAAAAAAARQnAQ'
|
|
|
+ // 创建连线
|
|
|
+ createLink(bart, marge, [
|
|
|
+ {
|
|
|
+ x: 385,
|
|
|
+ y: 180,
|
|
|
+ },
|
|
|
+ ]);
|
|
|
+ createLink(bart, homer, [
|
|
|
+ {
|
|
|
+ x: 385,
|
|
|
+ y: 180,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ x: 175,
|
|
|
+ y: 180,
|
|
|
+ },
|
|
|
+ ]);
|
|
|
+ createLink(bart, lisa, [
|
|
|
+ {
|
|
|
+ x: 385,
|
|
|
+ y: 180,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ x: 585,
|
|
|
+ y: 180,
|
|
|
+ },
|
|
|
+ ]);
|
|
|
+ createLink(homer, lenny, [
|
|
|
+ {
|
|
|
+ x: 175,
|
|
|
+ y: 380,
|
|
|
+ },
|
|
|
+ ]);
|
|
|
+ createLink(homer, carl, [
|
|
|
+ {
|
|
|
+ x: 175,
|
|
|
+ y: 530,
|
|
|
+ },
|
|
|
+ ]);
|
|
|
+ createLink(marge, maggie, [
|
|
|
+ {
|
|
|
+ x: 385,
|
|
|
+ y: 380,
|
|
|
+ },
|
|
|
+ ]);
|
|
|
|
|
|
- // 创建节点
|
|
|
- const bart = createNode(300, 70, 'CEO', 'Bart Simpson', male)
|
|
|
- const homer = createNode(90, 200, 'VP Marketing', 'Homer Simpson', male)
|
|
|
- const marge = createNode(300, 200, 'VP Sales', 'Marge Simpson', female)
|
|
|
- const lisa = createNode(500, 200, 'VP Production', 'Lisa Simpson', female)
|
|
|
- const maggie = createNode(400, 350, 'Manager', 'Maggie Simpson', female)
|
|
|
- const lenny = createNode(190, 350, 'Manager', 'Lenny Leonard', male)
|
|
|
- const carl = createNode(190, 500, 'Manager', 'Carl Carlson', male)
|
|
|
+ // 调整视图以适应所有节点
|
|
|
+ graph.zoomToFit({ padding: 20, maxScale: 1 });
|
|
|
+ };
|
|
|
|
|
|
- // 创建连线
|
|
|
- createLink(bart, marge, [
|
|
|
- {
|
|
|
- x: 385,
|
|
|
- y: 180,
|
|
|
- },
|
|
|
- ])
|
|
|
- createLink(bart, homer, [
|
|
|
- {
|
|
|
- x: 385,
|
|
|
- y: 180,
|
|
|
- },
|
|
|
- {
|
|
|
- x: 175,
|
|
|
- y: 180,
|
|
|
- },
|
|
|
- ])
|
|
|
- createLink(bart, lisa, [
|
|
|
- {
|
|
|
- x: 385,
|
|
|
- y: 180,
|
|
|
- },
|
|
|
- {
|
|
|
- x: 585,
|
|
|
- y: 180,
|
|
|
- },
|
|
|
- ])
|
|
|
- createLink(homer, lenny, [
|
|
|
- {
|
|
|
- x: 175,
|
|
|
- y: 380,
|
|
|
- },
|
|
|
- ])
|
|
|
- createLink(homer, carl, [
|
|
|
- {
|
|
|
- x: 175,
|
|
|
- y: 530,
|
|
|
- },
|
|
|
- ])
|
|
|
- createLink(marge, maggie, [
|
|
|
- {
|
|
|
- x: 385,
|
|
|
- y: 380,
|
|
|
- },
|
|
|
- ])
|
|
|
+ const zoomIn = () => {
|
|
|
+ const currentZoom = graph.zoom();
|
|
|
+ console.log('currentZoom', currentZoom);
|
|
|
+ graph.zoomTo(currentZoom + 0.1, { animation: true }); // 带动画放大
|
|
|
+ };
|
|
|
+
|
|
|
+ const zoomOut = () => {
|
|
|
+ const currentZoom = graph.zoom();
|
|
|
+ console.log('currentZoom', currentZoom);
|
|
|
+ graph.zoomTo(currentZoom - 0.1, { animation: true }); // 带动画放大
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleFullscreen = () => {
|
|
|
+ // 全屏功能
|
|
|
+ const container = document.getElementById('org-chart-container');
|
|
|
+
|
|
|
+ const { toggle } = useFullscreen(container);
|
|
|
|
|
|
- // 调整视图以适应所有节点
|
|
|
- graph.zoomToFit({ padding: 20, maxScale: 1 })
|
|
|
- }
|
|
|
+ // 调整内容适应
|
|
|
+ setTimeout(() => graph.zoomTo({ padding: 20, maxScale: 2 }), 300);
|
|
|
+ toggle();
|
|
|
+ };
|
|
|
|
|
|
- // 组件挂载后初始化图表
|
|
|
- onMounted(() => {
|
|
|
- registerChartElements()
|
|
|
- initChart()
|
|
|
- })
|
|
|
+ // 组件挂载后初始化图表
|
|
|
+ onMounted(() => {
|
|
|
+ registerChartElements();
|
|
|
+ initChart();
|
|
|
+ });
|
|
|
|
|
|
- // 组件卸载前清理图表
|
|
|
- onBeforeUnmount(() => {
|
|
|
- if (graph) {
|
|
|
- graph.dispose()
|
|
|
- graph = null
|
|
|
- }
|
|
|
- })
|
|
|
+ // 组件卸载前清理图表
|
|
|
+ onBeforeUnmount(() => {
|
|
|
+ if (graph) {
|
|
|
+ graph.dispose();
|
|
|
+ graph = null;
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
- return {
|
|
|
- containerRef,
|
|
|
- }
|
|
|
- },
|
|
|
-}
|
|
|
+ return {
|
|
|
+ containerRef,
|
|
|
+ zoomIn,
|
|
|
+ zoomOut,
|
|
|
+ handleFullscreen
|
|
|
+ };
|
|
|
+ },
|
|
|
+ };
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-.org-chart-container {
|
|
|
- width: 100%;
|
|
|
- height: 485px;
|
|
|
- border: 1px solid #e5e7eb;
|
|
|
- border-radius: 6px;
|
|
|
- overflow: hidden;
|
|
|
-}
|
|
|
-</style>
|
|
|
+ .org-chart-container {
|
|
|
+ width: 100%;
|
|
|
+ height: 485px;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 6px;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+</style>
|