页面展示
如图一展示,红框区域就是画布的操作区域
图一
包含三部分
- 工具箱【在操作区域的上方,包括基本对象,物流设施,资源三部分】
- 画布【可编辑的图形界面】
- 其他小部件【包括下方的模式转换器,搜索栏,标尺】
这里面最重要的是画布部分,采用的是antV x6组件库,官网地址:简介 | X6 图编辑引擎
在这篇文章里面,我主要讲解工具箱,画布和标尺等组件我会放到后面的文章里面
工具箱
工具箱效果如图二
图二
由于在工具箱里面的节点需要拖拽到画布中,所以工具箱依赖于画布组件,如果读者属性AntV x6,就知道想要实现这样的功能需要两个依赖库
npm install @antv/x6 --save
npm install @antv/x6-plugin-dnd --save
那接下来我没看源代码
源代码-html部分
CanvasToolBox.vue
<template>
<div
class="tool-box-container"
:style="{
borderBottom: `1px solid ${themeStyle[theme].borderColor1}`,
color: themeStyle[theme].textColor2
}"
>
<div
class="tool-box-icon"
:style="{
borderRight: `1px solid ${themeStyle[theme].borderColor1}`
}"
>
<svg-icon name="tool" :color="themeStyle[theme].textColor1"></svg-icon>
</div>
<div class="tool-box-category">
<div
class="category-title"
:style="{
backgroundColor: themeStyle[theme].backgroundColor2
}"
>
<div
class="color-block"
:style="{
backgroundColor: themeStyle[theme].backgroundColor1,
left: getColorBlockLeft
}"
></div>
<template v-for="(item, index) in toolBoxMenu" :key="index">
<div class="title-item" @click="canvasToolStore.changeToolCategory(item.title)">
<span :style="{ color: currentToolCategory === item.title ? '#0087dc' : undefined }">
{{ item.title }}
</span>
</div>
</template>
</div>
<div class="tool-content">
<template v-for="(item, index) in getChildrenItem" :key="index">
<div class="content-item" @mousedown="initToolBoxDragEvents($event, item)">
<div class="item-icon">
<svg-icon
v-if="item?.icon"
:name="item.icon"
size="2em"
:color="themeStyle[theme].textColor1"
></svg-icon>
</div>
<span>{{ item.name }}</span>
</div>
</template>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useStyleStore } from '@renderer/store/globalStyle/style'
import { storeToRefs } from 'pinia'
import { useCanvasToolStore } from '@renderer/store/canvas/canvasTool'
import { computed } from 'vue'
import { initToolBoxDragEvents } from '@renderer/store/canvas/canvasEvents'
const styleStore = useStyleStore()
const { theme, themeStyle } = storeToRefs(styleStore)
const canvasToolStore = useCanvasToolStore()
const { toolBoxMenu, currentToolCategory } = storeToRefs(canvasToolStore)
const getColorBlockLeft = computed(() => {
const index = toolBoxMenu.value.findIndex((item) => item.title === currentToolCategory.value)
return `${index * 100 + 10}px`
})
const getChildrenItem = computed(() => {
const index = toolBoxMenu.value.findIndex((item) => item.title === currentToolCategory.value)
return toolBoxMenu.value[index].children
})
</script>
<style scoped lang="scss">
.tool-box-container {
width: 100%;
display: flex;
justify-content: left;
align-items: center;
height: 70px;
.tool-box-icon {
height: 100%;
width: 30px;
}
.tool-box-category {
height: 100%;
width: calc(100% - 30px);
user-select: none;
.category-title {
width: calc(100% - 22px);
height: 25px;
padding: 0px 10px;
font-size: $font-size-medium;
display: flex;
justify-content: left;
align-items: center;
position: relative;
.color-block {
position: absolute;
height: 25px;
width: 100px;
top: 0px;
transition: left 0.3s ease-in-out;
}
.title-item {
text-align: center;
width: 100px;
z-index: 10;
}
}
.tool-content {
height: 45px;
width: 100%;
display: flex;
justify-content: left;
align-items: center;
font-size: $font-size-small;
.content-item {
display: flex;
justify-content: center;
align-items: center;
gap: 5px;
width: 100px;
.item-icon {
width: 30px;
height: 30px;
border-radius: 3px;
}
}
}
}
}
</style>
源代码-store部分
canvasTool.ts
import { defineStore } from 'pinia'
export const useCanvasToolStore = defineStore('canvasTool', {
state: () => ({
toolBoxMenu: [
{
title: '基本对象',
children: [
{
name: '发生器',
type: 'produce',
icon: 'nodeEnergy'
},
{
name: '吸收器',
type: 'absorb',
icon: 'nodeRecycle'
},
{
name: '缓冲区',
type: 'buffer',
icon: 'nodeBuffer'
},
{
name: '加工站',
type: 'process',
icon: 'nodeMachine'
},
{
name: '批处理',
type: 'batchProcess',
icon: 'nodeMachine'
},
{
name: '装配站',
type: 'assembly',
icon: 'nodeMachine'
},
{
name: '拆卸站',
type: 'disassemble',
icon: 'nodeMachine'
}
]
},
{
title: '物流设施',
children: [
{
name: '传送带',
type: 'belt',
icon: 'nodeConveyor'
},
{
name: 'AGV',
type: 'AGV',
icon: 'nodeAGV'
},
{
name: '道路',
type: 'road',
icon: 'nodeRoad'
},
{
name: '十字路',
type: 'crossing',
icon: 'nodeCrossing'
}
]
},
{
title: '资源',
children: [
{
name: '工人池',
type: 'workerPool',
icon: 'nodeMasses'
},
{
name: '区域',
type: 'customArea',
icon: 'nodeArea'
},
{
name: '警戒线',
type: 'warningLine',
icon: 'nodeWarningLine'
},
{
name: '文字',
type: 'plaintext',
icon: 'plaintext'
},
{
name: '节点',
type: 'customNode',
icon: 'customNode'
},
{
name: '连接线',
type: 'customLink',
icon: 'customNode'
}
]
}
],
//当前选择的工具类
currentToolCategory: '基本对象'
}),
actions: {
// 切换工具类
changeToolCategory(category: string) {
this.currentToolCategory = category
}
}
})
那现在我们也只是建立了一个静态的页面,但是我们在html部分中,通过鼠标按下事件,向外部传递了一个事件,这是一个关键部分
<div class="content-item" @mousedown="initToolBoxDragEvents($event, item)">
<div class="item-icon">
<svg-icon
v-if="item?.icon"
:name="item.icon"
size="2em"
:color="themeStyle[theme].textColor1"
></svg-icon>
</div>
<span>{{ item.name }}</span>
</div>
然后我们就需要AntV x6里面的Dnd插件,我们在创建画布实例后,就需要再次注册一个Dnd插件,如下
dnd = new Dnd({
//canvas是画布组件实例
target: canvas,
//拖拽结束时的回调函数
validateNode(droppingNode: Node): boolean {
//给节点设置连接桩
setNodePort(droppingNode)
//渲染进程给主进程发送消息,添加节点
renderAddNode(droppingNode)
//返回false,这里不添加节点
return false
}
})
然后我们就需要写一个方法去接收鼠标按下的事件了,代码如下
export const initToolBoxDragEvents = (e: MouseEvent, item: any): void => {
const node = canvas.createNode({
shape: 'ordinary-node',
data: {
name: item.name,
type: item.type,
icon: item.icon
}
})
dnd.start(node, e)
}
然后工具里面的节点就可以拖出来放入到画布中
工具箱到此结束,在下一篇文章中,我会讲解画布的生成