工业仿真(simulation)--前端(三)-画布编辑(1)

63 阅读3分钟

页面展示

如图一展示,红框区域就是画布的操作区域

图一

包含三部分

  1. 工具箱【在操作区域的上方,包括基本对象,物流设施,资源三部分】
  2. 画布【可编辑的图形界面】
  3. 其他小部件【包括下方的模式转换器,搜索栏,标尺】

这里面最重要的是画布部分,采用的是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)
}

然后工具里面的节点就可以拖出来放入到画布中

工具箱到此结束,在下一篇文章中,我会讲解画布的生成