公司需求需要做一个思维导图类似于飞书后台管理的okr思维导图,也是第一次接触,但是我感觉好像canvas也能画,我太菜了不会,就去找了好多的案例框架啥的,一开始用的SuperFlow 这个思维导图,但是没研究出来偶然看见了antv–x6这个框架,其中也设计到vue2使用这个框架,但是因为项目是vue3所以做了一些改变
下面是数据结构…一定要给结构里面添加X跟Y的值,这样你的画布在渲染这个地方的时候才能显示的出来,如果是从后台拿数据,那么就循环添加一下做一个x跟y的计算就好
接下来就是创建画布了,通过ID去绑定,其他的东西官网上面都有,一搜就出来,这里我就不多做解释了
graph.addNode就是创建节点,给它放在循环里面,这样就可以循环树状的节点
子组件关键内容已经给标记出来了,不懂直接复制代码去试一下就懂了
<template>
<div class="wrap">
<div class="home">
<div class="menu-bar">
<!-- 画布部分 -->
<div class="canvas-card">
<div id="container" ></div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Graph,Shape } from '@antv/x6'
import { defineComponent,onMounted, reactive } from 'vue'
import '@antv/x6-vue-shape'
import Cyndi from "./Cyndi.vue";
import { OkrView } from '@/apis/okr'
import { useStore } from '@/store'
import ColleagueOkrVue from '../MyOKR/ColleagueOkr.vue';
export default defineComponent({
setup() {
const state = reactive({
moduleList: [
{
id: 1,
name: '测试1',
o: '12312312',
kr: [
'123123123',
'321321312'
],
krCount: 2,
proess: 0,
x: 200,
y:300,
children: [
{
id: 2,
name: '测试子集',
o: '12312312',
kr: [
'123123123',
'321321312'
],
krCount: 2,
proess: 0,
x: 600,
y:220
},
{
id: 3,
name: '测试子集2',
o: '12312312',
kr: [
'123123123',
'321321312'
],
krCount: 2,
proess: 0,
x: 600,
y:350
}
],
}
], // 列表可拖动模块
})
const getData = () => {
const params = {
employeeNum:'10015',
period:''
}
state.moduleList = res.result
res.result.map((item, index) => {
item.isShow = false
if (res.result.length > 1) {
item.x = 200
item.y = 300 * index
} else {
item.x = 200
item.y = 300
}
if (item.children.length !== 0) {
item.map((row, rowIndex) => {
if (item.children.length > 1) {
row.x = 600
row.y = 150 * rowIndex
} else {
row.x = 600
row.y = 150
}
})
}
})
state.moduleList = res.result
if (state.moduleList !== []) {
restGraph()
}
}
let graph: Graph;
const restGraph = () => {
let container = document.getElementById('container') as HTMLElement
graph = new Graph({
container:container, // 画布容器 创建画布
height: document.body.offsetHeight,
background: {
color: "#f2f1f6", // 设置画布背景颜色
},
panning: {
enabled: true //支持拖拽
},
mousewheel: {
enabled: true // 支持滚动放大缩小
},
grid: {
type: 'mesh',
size: 5, // 网格大小 10px
visible: true, // 渲染网格背景
args: {
color: '#f0f2f5', // 网格线/点颜色
thickness: 1 // 网格线宽度/网格点大小
}
},
rotating: {
enabled: true,
},
connecting: {
allowBlank: false,
connector: 'smooth',
},
})
// 第二种封装抽离写法
Graph.registerNode("cyndi-shape", {
inherit: "vue-shape",
x: 200,
y: 150,
component: {
template: `
`,
components: {
Cyndi
},
},
});
state.moduleList.map((item:any) => {
graph.addNode({ //这个addNode就是创建节点
id:item.id,
shape: "cyndi-shape",
x: item.x,
y: item.y,
data:item
})
if (item.children) {
item.children.map(row => {
console.log(row, '123');
graph.addNode({ //这个addNode就是创建节点
id:row.id,
shape: "cyndi-shape",
x: row.x,
y: row.y,
data:row
})
graph.addEdge({
source: {
cell: item.id,
anchor: {
name: 'right',
args: {
dx:300,
dy: 70,
},
},
},
target: {
cell: row.id,
attrs: {
line: {
strokt: '#ff3040',
strokeWidth: 2,
targetMarker: null,
}
},
anchor: {
name: 'left',
args: {
dy: 70,
},
},
}
})
})
}
})
}
onMounted(() => {
getData()
})
return {
getData,
state
};
}
})
</script>
下面是子组件
<template>
<div class="count_main">
<div class="title">
<div class="img">
<img src="@/assets/images/myOkr/okrNotice.png" alt="">
</div>
<div class="name">{{data.name || ''}}
</div>
<!-- :color="rateStatus[item.rateStatus]" -->
<div class="num">
<el-progress
type="circle"
stroke-width="20"
:percentage="data.process"
/>
{{data.process || ''}}
</div>
</div>
<div class="count_main_list">
<div class="main_target">
<span class="main_target_sort">0{{data.id}}</span>
<span class="main_target_text">{{data.o}}</span>
</div>
<div class="main_okr_list" v-show="data.isShow == true">
<ul>
<li v-for="(item,index) in data.kr" :key="index">
<span>KR{{index + 1 }}</span>
<span class="text">{{item}}</span>
</li>
</ul>
</div>
</div>
<div class="count_suspension" @click="handleShow(data)">{{data.krCount}}</div>
</div>
</template>
<script lang="ts">
import { Node } from "@antv/x6";
import { defineComponent, onMounted, ref, inject } from "vue";
import { ElProgress } from 'element-plus'
export default defineComponent({
components: { ElProgress },
name: "Cyndi",
setup() {
const rateStatus = {
1: '#5dc928',
99: '#ff6b6c',
55: '#ffb900'
}
const data = ref({
process: null,
name:'',
isShow: false,
kr: [],
krCount:''
});
onMounted(() => {
const getNode: Function | undefined = inject<Function>("getNode");
const node: Node = getNode?.();
data.value = node?.data;
console.log(data.value,11111111111111111);
// 监听数据改变事件
node?.on("change:data", ({ current }) => {
data.value = current.title;
});
});
const handleShow = (item) =>{
return item.isShow = !item.isShow
}
return {
data,
rateStatus,
handleShow
};
},
});
</script>
<style lang="scss">
.count_main{
width: 300px;
background: #fff;
margin-top:20px;
border-radius: 5px;
box-shadow: 1px 1px 1px 1px #eef0f4;
position: relative;
.title{
display:flex;
border-top: 2px solid green;
line-height: 30px;
border-bottom: 1px solid #f3f3f3;
.img{
img{
width: 20px;
height: 20px;
border-radius: 50%;
vertical-align: middle;
}
}
.name{
flex:3;
padding-left: 10px;
box-sizing: border-box;
line-height: 33px;
color:#5d6b82;
}
.num{
width:50px
}
}
.count_main_list{
height: 100%;
.main_target{
// height: 30px;
// line-height: 30px;
padding: 10px;
box-sizing: border-box;
.main_target_sort{
display: inline-block;
background-color: #0052cc;
color: #fff;
padding:2px 3px;
box-sizing: border-box;
border-radius: 5px;
}
.main_target_text{
display: inline-block;
color: #5d6b82;
margin-left: 5px;
}
}
}
.count_suspension{
position: absolute;
top:60%;
right: -20px;
font-size: 12px;
border-radius: 50%;
background: #fff;
padding: 2px 4px;
box-sizing: border-box;
}
.count_suspension:hover{
background: #0052cc;
color: #fff;
cursor: pointer;
}
.main_okr_list{
padding:0px 20px;
box-sizing: border-box;
li{
padding: 5px 0;
box-sizing: border-box;
span{
background: #e5f4ff;
color: #3175d7;
padding: 2px 4px;
box-sizing: border-box;
border-radius: 5px;
}
.text{
color: #7b879a;
background: none;
}
}
}
}
</style>
<style lang="scss" scoped>
::v-deep .el-progress-circle {
width: 20px !important;
height: 20px !important;
}
::v-deep .el-progress--circle, .el-progress--dashboard{
padding-top: 4px;
display: inline-block;
box-sizing: border-box;
span{
display: inline-block;
padding-left: 20px;
box-sizing:border-box;
line-height: 33px;
}
}
</style>
写的不好请见谅,讲解的比较粗糙,如果有不懂的欢迎随时私信