king
2025-05-13 1779677cece5864b62a65df4b01a4a69496e149a
src/menu/components/chart/antv-X6/index.jsx
@@ -1,93 +1,21 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Popover, Tooltip, message } from 'antd'
import { ToolOutlined, DeleteOutlined, FontColorsOutlined, VerticalAlignTopOutlined, VerticalAlignBottomOutlined, SaveOutlined, ZoomInOutlined, ZoomOutOutlined, OneToOneOutlined, DoubleLeftOutlined, DownloadOutlined } from '@ant-design/icons'
import { Graph, Shape } from '@antv/x6'
import { Stencil } from '@antv/x6-plugin-stencil'
import { Transform } from '@antv/x6-plugin-transform'
import { Selection } from '@antv/x6-plugin-selection'
import { Snapline } from '@antv/x6-plugin-snapline'
import { Keyboard } from '@antv/x6-plugin-keyboard'
import { Clipboard } from '@antv/x6-plugin-clipboard'
import { History } from '@antv/x6-plugin-history'
import { Export } from '@antv/x6-plugin-export'
import { Popover } from 'antd'
import { ToolOutlined, DeleteOutlined, FontColorsOutlined } from '@ant-design/icons'
import { Graph } from '@antv/x6'
import MKEmitter from '@/utils/events.js'
import asyncComponent from '@/utils/asyncComponent'
import asyncIconComponent from '@/utils/asyncIconComponent'
import { resetStyle, getTables, checkComponent } from '@/utils/utils-custom.js'
import { resetStyle } from '@/utils/utils-custom.js'
import lanes from './lane.json'
import xflows from './xflow.json'
import './index.scss'
const SettingComponent = asyncIconComponent(() => import('@/menu/datasource'))
const ChartCompileForm = asyncIconComponent(() => import('./chartcompile'))
const NodeUpdate = asyncIconComponent(() => import('./nodeupdate'))
const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
const NormalHeader = asyncComponent(() => import('@/menu/components/share/normalheader'))
// #region 初始化图形
const groups = {
  top: {
    position: 'top',
    attrs: {
      circle: {
        r: 4,
        magnet: true,
        stroke: 'var(--mk-sys-color)',
        strokeWidth: 1,
        fill: '#fff',
        style: {
          visibility: 'hidden'
        }
      }
    }
  },
  right: {
    position: 'right',
    attrs: {
      circle: {
        r: 4,
        magnet: true,
        stroke: 'var(--mk-sys-color)',
        strokeWidth: 1,
        fill: '#fff',
        style: {
          visibility: 'hidden'
        }
      }
    }
  },
  bottom: {
    position: 'bottom',
    attrs: {
      circle: {
        r: 4,
        magnet: true,
        stroke: 'var(--mk-sys-color)',
        strokeWidth: 1,
        fill: '#fff',
        style: {
          visibility: 'hidden'
        }
      }
    }
  },
  left: {
    position: 'left',
    attrs: {
      circle: {
        r: 4,
        magnet: true,
        stroke: 'var(--mk-sys-color)',
        strokeWidth: 1,
        fill: '#fff',
        style: {
          visibility: 'hidden'
        }
      }
    }
  }
}
Graph.registerNode(
  'lane',
@@ -115,7 +43,7 @@
      },
      'name-rect': {
        width: 200,
        height: 30,
        height: 36,
        fill: '#5F95FF',
        stroke: '#fff',
        strokeWidth: 1,
@@ -128,9 +56,10 @@
        textAnchor: 'middle',
        fontWeight: 'bold',
        fill: '#fff',
        fontSize: 12,
        fontSize: 14,
      },
    },
    zIndex: 0
  },
  true,
)
@@ -139,27 +68,18 @@
  'mk-rect',
  {
    inherit: 'rect',
    width: 66,
    height: 36,
    width: 100,
    height: 32,
    attrs: {
      body: {
        strokeWidth: 1,
        stroke: '#5F95FF',
        fill: '#EFF4FF'
        stroke: '#000000',
        fill: '#FFFFFF'
      },
      text: {
        fontSize: 12,
        fill: '#262626'
      }
    },
    ports: {
      groups,
      items: [
        { group: 'top' },
        { group: 'right' },
        { group: 'bottom' },
        { group: 'left' }
      ]
    }
  },
  true
@@ -169,27 +89,18 @@
  'mk-polygon',
  {
    inherit: 'polygon',
    width: 66,
    height: 36,
    width: 100,
    height: 32,
    attrs: {
      body: {
        strokeWidth: 1,
        stroke: '#5F95FF',
        fill: '#EFF4FF'
        stroke: '#000000',
        fill: '#FFFFFF'
      },
      text: {
        fontSize: 12,
        fill: '#262626'
      }
    },
    ports: {
      groups,
      items: [
        { group: 'top' },
        { group: 'right' },
        { group: 'bottom' },
        { group: 'left' }
      ]
    }
  },
  true
@@ -199,27 +110,18 @@
  'mk-circle',
  {
    inherit: 'circle',
    width: 36,
    height: 36,
    width: 40,
    height: 40,
    attrs: {
      body: {
        strokeWidth: 1,
        stroke: '#5F95FF',
        fill: '#EFF4FF'
        stroke: '#000000',
        fill: '#FFFFFF'
      },
      text: {
        fontSize: 12,
        fill: '#262626'
      }
    },
    ports: {
      groups,
      items: [
        { group: 'top' },
        { group: 'right' },
        { group: 'bottom' },
        { group: 'left' }
      ]
    }
  },
  true
@@ -229,27 +131,18 @@
  'mk-ellipse',
  {
    inherit: 'ellipse',
    width: 66,
    height: 36,
    width: 100,
    height: 32,
    attrs: {
      body: {
        strokeWidth: 1,
        stroke: '#5F95FF',
        fill: '#EFF4FF'
        stroke: '#000000',
        fill: '#FFFFFF'
      },
      text: {
        fontSize: 12,
        fill: '#262626'
      }
    },
    ports: {
      groups,
      items: [
        { group: 'top' },
        { group: 'right' },
        { group: 'bottom' },
        { group: 'left' }
      ]
    }
  },
  true
@@ -259,13 +152,13 @@
  'mk-star',
  {
    inherit: 'polygon',
    width: 36,
    height: 36,
    width: 40,
    height: 40,
    points: '100,10 40,198 190,78 10,78 160,198',
    attrs: {
      body: {
        fill: '#EFF4FF',
        stroke: '#5F95FF',
        fill: '#FFFFFF',
        stroke: '#000000',
        strokeWidth: 1,
        fillRule: 'nonzero'
      },
@@ -273,15 +166,26 @@
        fontSize: 12,
        fill: '#262626'
      }
    },
    ports: {
      groups,
      items: [
        { group: 'top' },
        { group: 'right' },
        { group: 'bottom' },
        { group: 'left' }
      ]
    }
  },
  true
)
Graph.registerNode(
  'mk-text',
  {
    inherit: 'rect',
    width: 100,
    height: 50,
    attrs: {
      body: {
        strokeWidth: 0,
        fill: 'transparent'
      },
      text: {
        fontSize: 12,
        fill: '#262626'
      }
    }
  },
  true
@@ -295,15 +199,8 @@
  }
  state = {
    card: null,
    eventListener: null,
    toolunfold: true,
    nodeunfold: true,
    node: null
    card: null
  }
  selectNode = null
  mkGraph = null
  UNSAFE_componentWillMount () {
    const { card } = this.props
@@ -311,7 +208,7 @@
    if (card.isNew) {
      let _plot = {
        width: card.width || 24,
        height: 400,
        height: 500,
        subtype: card.subtype,
        name: card.name,
        grid: {
@@ -322,7 +219,9 @@
            thickness: 1
          }
        },
        gridType: 'dot'
        gridType: 'dot',
        function: 'show',
        supModule: []
      }
      let _card = {
@@ -334,7 +233,7 @@
        width: _plot.width,
        name: _plot.name,
        subtype: card.subtype,
        setting: { interType: 'system' },
        setting: { interType: '' },
        style: {
          borderWidth: '1px', borderColor: 'rgb(217, 217, 217)',
          marginLeft: '8px', marginRight: '8px', marginTop: '8px', marginBottom: '8px'
@@ -379,8 +278,8 @@
    if (
      card.plot.subtype !== res.plot.subtype ||
      card.plot.gridType !== res.plot.gridType ||
      card.plot.backgroundColor !== res.plot.backgroundColor
      (res.plot.gridType && card.plot.gridType !== res.plot.gridType) ||
      (res.plot.gridType && card.plot.backgroundColor !== res.plot.backgroundColor)
    ) {
      let _element = document.getElementById(card.uuid + 'container')
      if (_element) {
@@ -401,8 +300,6 @@
      this.xflowrender()
    } else if (card.plot.subtype === 'lane') {
      this.lanerender()
    // } else if (card.plot.subtype === 'xflow') {
      // this.xflowrender()
    }
  }
@@ -417,7 +314,7 @@
        max: 2
      },
      autoResize: true,
      panning: true,
      interacting: false,
      background: {
        color: card.plot.backgroundColor || 'transparent'
      },
@@ -439,224 +336,20 @@
        allowBlank: false,
        snap: {
          radius: 20
        },
        createEdge() {
          return new Shape.Edge({
            attrs: {
              line: {
                stroke: '#A2B1C3',
                strokeWidth: 2,
                targetMarker: {
                  name: 'block',
                  width: 12,
                  height: 8
                }
              }
            },
            zIndex: 0
          })
        },
        validateConnection({ targetMagnet }) {
          return !!targetMagnet
        }
      },
      highlighting: {
        magnetAdsorbed: {
          name: 'stroke',
          args: {
            attrs: {
              fill: '#5F95FF',
              stroke: '#5F95FF'
            }
          }
        }
      }
    })
    // #region 使用插件
    graph
      .use(new Transform({
        resizing: true,
        rotating: true
      }))
      .use(new Selection())
      .use(new Snapline())
      .use(new Keyboard())
      .use(new Clipboard())
      .use(new History())
      .use(new Export())
    // #region 初始化 stencil
    const stencil = new Stencil({
      title: '流程图',
      target: graph,
      stencilGraphWidth: 180,
      stencilGraphHeight: 180,
      groups: [
        {
          title: '通用节点',
          name: 'group1'
        },
        {
          title: '自定义',
          name: 'group2',
          graphHeight: 120,
          layoutOptions: {
            rowHeight: 70
          }
        }
      ],
      layoutOptions: {
        columns: 2,
        columnWidth: 80,
        rowHeight: 55
    let cells = []
    xflows.forEach((item) => {
      if (item.shape === 'edge') {
        cells.push(graph.createEdge(item))
      } else {
        cells.push(graph.createNode(item))
      }
    })
    document.getElementById(card.uuid + 'stencil').appendChild(stencil.container)
    // #region 快捷键与事件
    graph.bindKey(['meta+c', 'ctrl+c'], () => {
      const cells = graph.getSelectedCells()
      if (cells.length) {
        graph.copy(cells)
      }
      return false
    })
    graph.bindKey(['meta+x', 'ctrl+x'], () => {
      const cells = graph.getSelectedCells()
      if (cells.length) {
        graph.cut(cells)
      }
      return false
    })
    graph.bindKey(['meta+v', 'ctrl+v'], () => {
      if (!graph.isClipboardEmpty()) {
        graph.paste({ offset: 32 })
      }
      return false
    })
    // undo redo
    graph.bindKey(['meta+z', 'ctrl+z'], () => {
      if (graph.canUndo()) {
        graph.undo()
      }
      return false
    })
    graph.bindKey(['meta+shift+z', 'ctrl+shift+z'], () => {
      if (graph.canRedo()) {
        graph.redo()
      }
      return false
    })
    // 删除元素
    graph.bindKey(['backspace', 'delete'], () => {
      const cells = graph.getSelectedCells()
      if (cells.length) {
        graph.removeCells(cells)
        this.selectNode = null
        this.setState({node: null})
      }
    })
    // 控制连接桩显示/隐藏
    const showPorts = (ports, show) => {
      for (let i = 0, len = ports.length; i < len; i += 1) {
        ports[i].style.visibility = show ? 'visible' : 'hidden'
      }
    }
    graph.on('node:mouseenter', () => {
      const container = document.getElementById(card.uuid + 'container')
      const ports = container.querySelectorAll('.x6-port-body')
      showPorts(ports, true)
    })
    graph.on('node:mouseleave', () => {
      const container = document.getElementById(card.uuid + 'container')
      const ports = container.querySelectorAll('.x6-port-body')
      showPorts(ports, false)
    })
    graph.on('node:click', ({ e, x, y, node, view }) => {
      this.selectNode = node
      this.setState({node: node.store.data})
    })
    graph.on('edge:click', ({ e, x, y, edge, view }) => {
      this.selectNode = edge
      this.setState({node: edge.store.data})
      graph.clearTransformWidgets()
    })
    graph.on('blank:click', ({ e, x, y }) => {
      this.selectNode = null
      this.setState({node: null})
    })
    const r1 = graph.createNode({
      shape: 'mk-rect',
      label: '开始',
      attrs: {
        body: {
          rx: 20,
          ry: 26
        }
      }
    })
    const r2 = graph.createNode({
      shape: 'mk-rect',
      label: '过程'
    })
    const r3 = graph.createNode({
      shape: 'mk-rect',
      attrs: {
        body: {
          rx: 6,
          ry: 6
        }
      },
      label: '可选过程'
    })
    const r4 = graph.createNode({
      shape: 'mk-polygon',
      attrs: {
        body: {
          refPoints: '0,10 10,0 20,10 10,20'
        }
      },
      label: '决策'
    })
    const r5 = graph.createNode({
      shape: 'mk-polygon',
      attrs: {
        body: {
          refPoints: '10,0 40,0 30,20 0,20'
        }
      },
      label: '数据'
    })
    const r6 = graph.createNode({
      shape: 'mk-circle',
      label: '连接'
    })
    stencil.load([r1, r2, r3, r4, r5, r6], 'group1')
    const p1 = graph.createNode({
      shape: 'mk-ellipse',
      label: 'ellipse'
    })
    const p2 = graph.createNode({
      shape: 'mk-star',
      label: ''
    })
    stencil.load([p1, p2], 'group2')
    this.mkGraph = graph
    graph.resetCells(cells)
    graph.positionContent('center')
  }
  lanerender = () => {
@@ -664,16 +357,13 @@
    const graph = new Graph({
      container: document.getElementById(card.uuid + 'container'),
      grid: card.plot.grid,
      scaling: {
        min: 0.5,
        max: 2
      },
      autoResize: true,
      panning: true,
      background: {
        color: card.plot.backgroundColor || 'transparent'
      },
      interacting: false,
      background: { color: '#ffffff' },
      mousewheel: {
        enabled: true,
        zoomAtMousePosition: true,
@@ -692,343 +382,28 @@
        allowBlank: false,
        snap: {
          radius: 20
        },
        createEdge() {
          return new Shape.Edge({
            attrs: {
              line: {
                stroke: '#A2B1C3',
                strokeWidth: 2,
                targetMarker: {
                  name: 'block',
                  width: 12,
                  height: 8
                }
              }
            },
            zIndex: 2
          })
        },
        validateConnection({ targetMagnet }) {
          return !!targetMagnet
        }
      },
      highlighting: {
        magnetAdsorbed: {
          name: 'stroke',
          args: {
            attrs: {
              fill: '#5F95FF',
              stroke: '#5F95FF'
            }
          }
        }
      },
      translating: {
        restrict(cellView) {
          const cell = cellView.cell
          const parentId = cell.prop('parent')
          if (parentId) {
            const parentNode = graph.getCellById(parentId)
            if (parentNode) {
              return parentNode.getBBox().moveAndExpand({
                x: 0,
                y: 30,
                width: 0,
                height: -30,
              })
            }
          }
          return cell.getBBox()
        }
      }
    })
    // #region 使用插件
    graph
      .use(new Transform({
        resizing: true,
        rotating: true
      }))
      .use(new Selection())
      .use(new Snapline())
      .use(new Keyboard())
      .use(new Clipboard())
      .use(new History())
      .use(new Export())
    // #region 初始化 stencil
    const stencil = new Stencil({
      title: '流程图',
      target: graph,
      stencilGraphWidth: 180,
      stencilGraphHeight: 180,
      groups: [
        {
          title: '通用节点',
          name: 'group1'
        },
        {
          title: '自定义',
          name: 'group2',
          graphHeight: 120,
          layoutOptions: {
            rowHeight: 70
          }
        }
      ],
      layoutOptions: {
        columns: 2,
        columnWidth: 80,
        rowHeight: 55
      }
    })
    document.getElementById(card.uuid + 'stencil').appendChild(stencil.container)
    // #region 快捷键与事件
    graph.bindKey(['meta+c', 'ctrl+c'], () => {
      const cells = graph.getSelectedCells()
      if (cells.length) {
        graph.copy(cells)
      }
      return false
    })
    graph.bindKey(['meta+x', 'ctrl+x'], () => {
      const cells = graph.getSelectedCells()
      if (cells.length) {
        graph.cut(cells)
      }
      return false
    })
    graph.bindKey(['meta+v', 'ctrl+v'], () => {
      if (!graph.isClipboardEmpty()) {
        graph.paste({ offset: 32 })
      }
      return false
    })
    // undo redo
    graph.bindKey(['meta+z', 'ctrl+z'], () => {
      if (graph.canUndo()) {
        graph.undo()
      }
      return false
    })
    graph.bindKey(['meta+shift+z', 'ctrl+shift+z'], () => {
      if (graph.canRedo()) {
        graph.redo()
      }
      return false
    })
    // 删除元素
    graph.bindKey(['backspace', 'delete'], () => {
      const cells = graph.getSelectedCells()
      if (cells.length) {
        graph.removeCells(cells)
        this.selectNode = null
        this.setState({node: null})
      }
    })
    // 控制连接桩显示/隐藏
    const showPorts = (ports, show) => {
      for (let i = 0, len = ports.length; i < len; i += 1) {
        ports[i].style.visibility = show ? 'visible' : 'hidden'
      }
    }
    graph.on('node:mouseenter', () => {
      const container = document.getElementById(card.uuid + 'container')
      const ports = container.querySelectorAll('.x6-port-body')
      showPorts(ports, true)
    })
    graph.on('node:mouseleave', () => {
      const container = document.getElementById(card.uuid + 'container')
      const ports = container.querySelectorAll('.x6-port-body')
      showPorts(ports, false)
    })
    graph.on('node:click', ({ e, x, y, node, view }) => {
      this.selectNode = node
      this.setState({node: node.store.data})
    })
    graph.on('edge:click', ({ e, x, y, edge, view }) => {
      this.selectNode = edge
      this.setState({node: edge.store.data})
      graph.clearTransformWidgets()
    })
    graph.on('blank:click', ({ e, x, y }) => {
      this.selectNode = null
      this.setState({node: null})
    })
    const r1 = graph.createNode({
      shape: 'mk-rect',
      label: '开始',
      attrs: {
        body: {
          rx: 20,
          ry: 26
        }
      }
    })
    const r2 = graph.createNode({
      shape: 'mk-rect',
      label: '过程'
    })
    const r3 = graph.createNode({
      shape: 'mk-rect',
      attrs: {
        body: {
          rx: 6,
          ry: 6
        }
      },
      label: '可选过程'
    })
    const r4 = graph.createNode({
      shape: 'mk-polygon',
      attrs: {
        body: {
          refPoints: '0,10 10,0 20,10 10,20'
        }
      },
      label: '决策'
    })
    const r5 = graph.createNode({
      shape: 'mk-polygon',
      attrs: {
        body: {
          refPoints: '10,0 40,0 30,20 0,20'
        }
      },
      label: '数据'
    })
    const r6 = graph.createNode({
      shape: 'mk-circle',
      label: '连接'
    })
    stencil.load([r1, r2, r3, r4, r5, r6], 'group1')
    const p1 = graph.createNode({
      shape: 'mk-ellipse',
      label: 'ellipse'
    })
    const p2 = graph.createNode({
      shape: 'mk-star',
      label: ''
    })
    stencil.load([p1, p2], 'group2')
    let data = [{"id":"1","shape":"lane","width":200,"height":500,"position":{"x":60,"y":60},"label":"<Function>"},{"id":"2","shape":"lane","width":200,"height":500,"position":{"x":260,"y":60},"label":"<Function>"},{"id":"3","shape":"lane","width":200,"height":500,"position":{"x":460,"y":60},"label":"<Function>"},{"id":"4","shape":"lane","width":200,"height":500,"position":{"x":660,"y":60},"label":"<Function>"}]
    let cells = []
    data.forEach((item) => {
      if (item.shape === 'lane-edge') {
    lanes.forEach((item) => {
      if (item.shape === 'edge') {
        cells.push(graph.createEdge(item))
      } else {
        cells.push(graph.createNode(item))
      }
    })
    graph.resetCells(cells)
    graph.zoomToFit({ padding: 10, maxScale: 1 })
    this.mkGraph = graph
  }
  setTop = () => {
    if (!this.selectNode) {
      message.warning('请选择节点!')
      return
    }
    this.selectNode.toFront()
  }
  setBottom = () => {
    if (!this.selectNode) {
      message.warning('请选择节点!')
      return
    }
    // let cells = this.mkGraph.getCells()
    this.selectNode.toBack()
  }
  // zoom() 可获取或者设置缩放比例
  setZoomIn = () => {
    this.mkGraph.zoom(0.1)
  }
  setZoomOut = () => {
    this.mkGraph.zoom(-0.1)
  }
  setZoomInt = () => {
    this.mkGraph.zoomTo(1)
  }
  save = () => {
    // let nodes = this.mkGraph.toJSON()
  }
  savePicture = () => {
    const { card } = this.state
    this.mkGraph.exportPNG(card.name, {padding: 20})
  }
  changeProps = (value, key) => {
    const { node } = this.state
    if (node.shape === 'edge') {
      if (key === 'title') {
        this.selectNode.setLabels(value)
      } else if (key === 'stroke') {
        this.selectNode.attr('line/stroke', value)
      } else if (key === 'strokeWidth') {
        this.selectNode.attr('line/strokeWidth', value)
      } else if (key === 'lineType') {
        if (value === 'dash') {
          this.selectNode.attr('line/strokeDasharray', 5)
        } else {
          this.selectNode.attr('line/strokeDasharray', 0)
        }
      } else if (key === 'fontSize') {
        this.selectNode.attr('text/fontSize', value)
      } else if (key === 'fontFill') {
        this.selectNode.attr('text/fill', value)
      }
    } else {
      if (key === 'title') {
        this.selectNode.attr('text/text', value)
      } else if (key === 'fill') {
        this.selectNode.attr('body/fill', value)
      } else if (key === 'stroke') {
        this.selectNode.attr('body/stroke', value)
      } else if (key === 'fontSize') {
        this.selectNode.attr('text/fontSize', value)
      } else if (key === 'fontFill') {
        this.selectNode.attr('text/fill', value)
      }
    }
    graph.positionContent('top')
  }
  updateComponent = (card) => {
    card.width = card.plot.width
    card.name = card.plot.name
    card.subtype = card.plot.subtype
    card.$c_ds = true
    card.errors = checkComponent(card)
    if (card.errors.length === 0) {
      card.$tables = getTables(card)
    }
    card.errors = []
    card.$tables = []
    this.setState({
      card: card
@@ -1061,7 +436,7 @@
  }
  render() {
    const { card, toolunfold, nodeunfold, node } = this.state
    const { card } = this.state
    let _style = resetStyle(card.style)
    return (
@@ -1069,69 +444,20 @@
        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
          <div className="mk-popover-control">
            <ChartCompileForm config={card} plotchange={this.plotchange}/>
            <CopyComponent type="antvG6" card={card}/>
            <CopyComponent type="antvX6" card={card}/>
            <FontColorsOutlined className="style" title="调整样式" onClick={this.changeStyle}/>
            <DeleteOutlined className="close" title="delete" onClick={() => this.props.deletecomponent(card.uuid)}/>
            <SettingComponent config={card} updateConfig={this.updateComponent}/>
          </div>
        } trigger="hover">
          <ToolOutlined/>
        </Popover>
        <NormalHeader config={card} updateComponent={this.updateComponent}/>
        <div className="mk-toolbar">
          <div className="left-tool">
            <Tooltip title="置前">
              <VerticalAlignTopOutlined onClick={this.setTop}/>
            </Tooltip>
            <Tooltip title="置后">
              <VerticalAlignBottomOutlined onClick={this.setBottom}/>
            </Tooltip>
            <Tooltip title="保存">
              <SaveOutlined onClick={this.save}/>
            </Tooltip>
            <Tooltip title="导出图片">
              <DownloadOutlined onClick={this.savePicture}/>
            </Tooltip>
          </div>
          <div className="right-tool">
            <Tooltip title="放大">
              <ZoomInOutlined onClick={this.setZoomIn}/>
            </Tooltip>
            <Tooltip title="缩小">
              <ZoomOutOutlined onClick={this.setZoomOut}/>
            </Tooltip>
            <Tooltip title="1:1">
              <OneToOneOutlined onClick={this.setZoomInt}/>
            </Tooltip>
          </div>
        </div>
        <div className="canvas" style={{width: '100%', minHeight: card.plot.height, height: card.plot.height}} id={card.uuid + 'canvas'}>
          <div id={card.uuid + 'stencil'} className={'mk-stencil ' + (toolunfold ? '' : 'merge')}>
            <div className="tool-control" onClick={() => this.setState({toolunfold: !toolunfold})}>
              <DoubleLeftOutlined />
            </div>
          </div>
          <div id={card.uuid + 'container'} className="mk-container"></div>
          <div className={'mk-node-edit ' + (nodeunfold ? '' : 'merge')}>
            <div className="tool-control" onClick={() => this.setState({nodeunfold: !nodeunfold})}>
              <DoubleLeftOutlined />
            </div>
            <div className="header">设置</div>
            {!node ? <div className="empty">未选中</div> : <NodeUpdate node={node} onChange={this.changeProps}/>}
          </div>
        </div>
        <div className="component-name">
          <div className="center">
            <div className="title">{card.name}</div>
            <div className="content">
              {card.errors && card.errors.map((err, index) => {
                if (err.level === 0) {
                  return <span key={index} className="error">{err.detail}</span>
                } else {
                  return <span key={index} className="waring">{err.detail};</span>
                }
              })}
            </div>
          </div>
        </div>
      </div>