基于antd+vue2来实现一个简单的绘画流程图功能

简单流程图的实现(基于antd+vue2的)代码很多哦~

实现页面如下

1.简单操作如下

2.弹框中使用组件:

javascript 复制代码
 	<vfd
      ref="vfd"
      style="background-color: white;"
      :needShow="true"
      :fieldNames="fieldNames"
      @openUser="openUser"
      @openRole="openRole"
    ></vfd>

3.组件的文件结构:


FlowDesigner.vue代码如下

javascript 复制代码
<template>
  <div style="height: 60vh">
    <a-layout class="container">
      <a-layout-sider
        v-show="needShow"
        width="200"
        theme="light"
        class="select-area"
      >
        <a-row style="padding:5px">
          <a-checkable-tag
            v-model="tag.checked0"
            @change="toggleNodeShow0"
            class="tag"
          >工具</a-checkable-tag>
          <div align="center">
            <a-list
              :grid="{ gutter: 8, column: 1 }"
              v-if="tag.toolShow"
            >
              <a-list-item>
                <a-button-group>
                  <a-button
                    v-for="(tool, index) in field.tools"
                    :key="index"
                    :icon="tool.icon"
                    :type="currentTool.type == tool.type ? 'primary': 'default'"
                    @click="selectTool(tool.type)"
                  >
                  </a-button>
                </a-button-group>
              </a-list-item>
            </a-list>
          </div>
        </a-row>
        <a-row style="padding:5px">
          <a-checkable-tag
            v-model="tag.checked1"
            @change="toggleNodeShow1"
            class="tag"
          >基础节点</a-checkable-tag>
          <div align="center">
            <a-list
              :grid="{ gutter: 8, column: 2 }"
              v-if="tag.commonNodeShow"
            >
              <a-list-item
                v-for="(commonNode, index) in field.commonNodes"
                :key="index"
              >
                <div
                  class="node-item"
                  :type="commonNode.type"
                  belongto="commonNodes"
                >
                  <a-icon :type="commonNode.icon" /> {{ commonNode.name }}
                </div>
              </a-list-item>
            </a-list>
          </div>
        </a-row>
        <a-row style="padding:5px">
          <a-checkable-tag
            v-model="tag.checked3"
            @change="toggleNodeShow3"
            class="tag"
          >泳道节点</a-checkable-tag>
          <div align="center">
            <a-list
              :grid="{ gutter: 8, column: 2 }"
              v-if="tag.laneNodeShow"
            >
              <a-list-item
                v-for="(laneNode, index) in field.laneNodes"
                :key="index"
              >
                <div
                  class="node-item"
                  :type="laneNode.type"
                  belongto="laneNodes"
                >
                  <a-icon :type="laneNode.icon" /> {{ laneNode.name }}
                </div>
              </a-list-item>
            </a-list>
          </div>
        </a-row>
      </a-layout-sider>
      <a-layout>
        <a-layout-header
          v-show="needShow"
          class="header-option"
          style="background-color:#fff;padding-right: 10px;"
        >
          <a-popconfirm
            title="确认要重新绘制吗?"
            placement="bottom"
            okText="确认"
            cancelText="取消"
            @confirm="clear"
          >
            <a-tooltip
              title="重新绘制"
              placement="bottom"
            >
              <a-button
                class="header-option-button"
                size="small"
                icon="sync"
              ></a-button>
            </a-tooltip>
          </a-popconfirm>
          <a-tooltip
            :title="flowData.config.showGridText"
            placement="bottom"
          >
            <a-button
              @click="toggleShowGrid"
              class="header-option-button"
              size="small"
              :icon="flowData.config.showGridIcon"
            >
            </a-button>
          </a-tooltip>
          <a-tooltip
            title="设置"
            placement="bottom"
          >
            <a-button
              @click="setting"
              class="header-option-button"
              size="small"
              icon="setting"
            ></a-button>
          </a-tooltip>
          <a-tooltip
            title="JSON"
            placement="bottom"
          >
            <a-button
              @click="openTest"
              class="header-option-button"
              size="small"
              icon="save"
            ></a-button>
          </a-tooltip>
          <a-popconfirm
            title="请选择帮助项:"
            placement="bottom"
            okType="default"
            okText="快捷键大全"
            cancelText="使用文档"
            @confirm="shortcutHelper"
            @cancel="usingDoc"
          >
            <a-icon
              slot="icon"
              type="question-circle-o"
              style="color: red"
            />
            <a-tooltip
              title="帮助"
              placement="bottom"
            >
              <a-button
                class="header-option-button"
                size="small"
                icon="book"
              ></a-button>
            </a-tooltip>
          </a-popconfirm>
        </a-layout-header>
        <a-layout-content class="flowContent">
          <flow-area
            ref="flowArea"
            :browserType="browserType"
            :flowData="flowData"
            :select.sync="currentSelect"
            :selectGroup.sync="currentSelectGroup"
            :plumb="plumb"
            :currentTool="currentTool"
            :activityId="activityId"
            @findNodeConfig="findNodeConfig"
            @selectTool="selectTool"
            @getShortcut="getShortcut"
            @saveFlow="saveFlow"
          >
          </flow-area>
          <vue-context-menu
            class="customMenuClass"
            :contextMenuData="linkContextMenuData"
            @deleteLink="deleteLink"
          >
          </vue-context-menu>
        </a-layout-content>
      </a-layout>
      <a-layout-sider
        width="300"
        theme="light"
        class="attr-area"
        @mousedown.stop="loseShortcut"
      >
        <flow-attr
          ref="flowAttrForm"
          :plumb="plumb"
          :flowData="flowData"
          :needShow="needShow"
          :fieldNames.sync="fieldNames"
          :select.sync="currentSelect"
          @openUser="openUser"
          @openRole="openRole"
        ></flow-attr>
      </a-layout-sider>
    </a-layout>
    <setting-modal ref="settingModal"></setting-modal>
    <shortcut-modal ref="shortcutModal"></shortcut-modal>
    <test-modal
      ref="testModal"
      @loadFlow="loadFlow"
      @clear123="clear()"
    ></test-modal>
  </div>
</template>

<script>
import jsplumb from 'jsplumb'
import { tools, commonNodes, laneNodes } from './config/basic-node-config.js'
import { flowConfig } from './config/args-config.js'
import $ from 'jquery'
import 'jquery-ui/ui/widgets/draggable'
import 'jquery-ui/ui/widgets/droppable'
import 'jquery-ui/ui/widgets/resizable'
import { ZFSN } from './util/ZFSN.js'
import FlowArea from './modules/FlowArea'
import FlowAttr from './modules/FlowAttr'
import SettingModal from './modules/SettingModal'
import ShortcutModal from './modules/ShortcutModal'
import UsingDocModal from './modules/UsingDocModal'
import TestModal from './modules/TestModal'

export default {
  name: 'vfd',
  components: {
    jsplumb,
    flowConfig,
    FlowArea,
    FlowAttr,
    SettingModal,
    ShortcutModal,
    UsingDocModal,
    TestModal,
  },
  //条件选择字段
  props: ['fieldNames', 'needShow', 'activityId'],
  mounted() {
    const that = this
    that.dealCompatibility()
    that.initNodeSelectArea()
    that.initJsPlumb()
    that.listenShortcut()
    that.initFlow()
    that.listenPage()
  },
  data() {
    return {
      tag: {
        checked0: true,
        checked1: true,
        checked2: true,
        checked3: true,
        toolShow: true,
        commonNodeShow: true,
        highNodeShow: true,
        laneNodeShow: true,
      },
      browserType: 3,
      plumb: {},
      field: {
        tools: tools,
        commonNodes: commonNodes,
        laneNodes: laneNodes,
      },
      flowData: {
        nodeList: [],
        linkList: [],
        attr: {
          id: '',
        },
        config: {
          showGrid: true,
          showGridText: '隐藏网格',
          showGridIcon: 'eye',
        },
        status: flowConfig.flowStatus.CREATE,
        remarks: [],
      },
      currentTool: {
        type: 'drag',
        icon: 'drag',
        name: '拖拽',
      },
      currentSelect: {},
      currentSelectGroup: [],
      activeShortcut: true,
      linkContextMenuData: flowConfig.contextMenu.link,
      flowPicture: {
        url: '',
        modalVisible: false,
        closable: false,
        maskClosable: false,
      },
      flowLineAdditions: flowConfig.flowLineAdditions,
    }
  },
  methods: {
    //用户选择界面
    openUser(value) {
      this.$emit('openUser', value)
    },
    //角色选择界面
    openRole(value) {
      this.$emit('openRole', value)
    },
    //角色用户设置必须包含id、name属性的数组
    setFlowAttrForm(value, type) {
      this.$refs.flowAttrForm.setFlowAttrForm(value, type)
    },
    toggleNodeShow0(flag) {
      if (!flag) {
        this.tag.toolShow = false
      } else {
        this.tag.toolShow = true
      }
    },
    toggleNodeShow1(flag) {
      if (!flag) {
        this.tag.commonNodeShow = false
      } else {
        this.tag.commonNodeShow = true
      }
    },
    toggleNodeShow2(flag) {
      if (!flag) {
        this.tag.highNodeShow = false
      } else {
        this.tag.highNodeShow = true
      }
    },
    toggleNodeShow3(flag) {
      if (!flag) {
        this.tag.laneNodeShow = false
      } else {
        this.tag.laneNodeShow = true
      }
    },
    getBrowserType() {
      let userAgent = navigator.userAgent
      let isOpera = userAgent.indexOf('Opera') > -1
      if (isOpera) {
        return 1
      }
      if (userAgent.indexOf('Firefox') > -1) {
        return 2
      }
      if (userAgent.indexOf('Chrome') > -1) {
        return 3
      }
      if (userAgent.indexOf('Safari') > -1) {
        return 4
      }
      if (userAgent.indexOf('compatible') > -1 && userAgent.indexOf('MSIE') > -1 && !isOpera) {
        alert('IE浏览器支持性较差,推荐使用Firefox或Chrome')
        return 5
      }
      if (userAgent.indexOf('Trident') > -1) {
        alert('Edge浏览器支持性较差,推荐使用Firefox或Chrome')
        return 6
      }
    },
    dealCompatibility() {
      const that = this

      that.browserType = that.getBrowserType()
      if (that.browserType == 2) {
        flowConfig.shortcut.scaleContainer = {
          code: 16,
          codeName: 'SHIFT(chrome下为ALT)',
          shortcutName: '画布缩放',
        }
      }
    },
    initJsPlumb() {
      const that = this

      that.plumb = jsPlumb.getInstance(flowConfig.jsPlumbInsConfig)

      that.plumb.bind('beforeDrop', function (info) {
        let sourceId = info.sourceId
        let targetId = info.targetId

        if (sourceId == targetId) return false
        let filter = that.flowData.linkList.filter((link) => link.sourceId == sourceId && link.targetId == targetId)
        if (filter.length > 0) {
          that.$message.error('同方向的两节点连线只能有一条!')
          return false
        }
        return true
      })

      that.plumb.bind('connection', function (conn, e) {
        let connObj = conn.connection.canvas
        let o = {},
          id,
          label
        if (
          that.flowData.status == flowConfig.flowStatus.CREATE ||
          that.flowData.status == flowConfig.flowStatus.MODIFY
        ) {
          id = 'link-' + ZFSN.getId()
          label = ''
        } else if (that.flowData.status == flowConfig.flowStatus.LOADING) {
          let l = that.flowData.linkList[that.flowData.linkList.length - 1]
          id = l.id
          label = l.label
        }
        connObj.id = id
        o.type = 'link'
        o.id = id
        o.sourceId = conn.sourceId
        o.targetId = conn.targetId
        o.label = label
        o.cls = {
          linkType: flowConfig.jsPlumbInsConfig.Connector[0],
          linkColor: flowConfig.jsPlumbInsConfig.PaintStyle.stroke,
          linkThickness: flowConfig.jsPlumbInsConfig.PaintStyle.strokeWidth,
        }
        $('#' + id).bind('contextmenu', function (e) {
          that.showLinkContextMenu(e)
          that.currentSelect = that.flowData.linkList.filter((l) => l.id == id)[0]
        })
        $('#' + id).bind('click', function (e) {
          let event = window.event || e
          event.stopPropagation()
          that.currentSelect = that.flowData.linkList.filter((l) => l.id == id)[0]
        })
        if (that.flowData.status != flowConfig.flowStatus.LOADING) that.flowData.linkList.push(o)
      })

      that.plumb.importDefaults({
        ConnectionsDetachable: flowConfig.jsPlumbConfig.conn.isDetachable,
      })

      ZFSN.consoleLog(['实例化JsPlumb成功...'])
    },
    initNodeSelectArea() {
      $(document).ready(function () {
        $('.node-item').draggable({
          opacity: flowConfig.defaultStyle.dragOpacity,
          helper: 'clone',
          cursorAt: {
            top: 16,
            left: 60,
          },
          containment: 'window',
          revert: 'invalid',
        })
        ZFSN.consoleLog(['初始化节点选择列表成功...'])
      })
    },
    listenShortcut() {
      const that = this
      document.onkeydown = function (e) {
        let event = window.event || e

        if (!that.activeShortcut) return
        let key = event.keyCode

        switch (key) {
          case flowConfig.shortcut.multiple.code:
            that.$refs.flowArea.rectangleMultiple.flag = true
            break
          case flowConfig.shortcut.dragContainer.code:
            that.$refs.flowArea.container.dragFlag = true
            break
          case flowConfig.shortcut.scaleContainer.code:
            that.$refs.flowArea.container.scaleFlag = true
            break
          case flowConfig.shortcut.dragTool.code:
            that.selectTool('drag')
            break
          case flowConfig.shortcut.connTool.code:
            that.selectTool('connection')
            break
          case flowConfig.shortcut.zoomInTool.code:
            that.selectTool('zoom-in')
            break
          case flowConfig.shortcut.zoomOutTool.code:
            that.selectTool('zoom-out')
            break
          case 37:
            that.moveNode('left')
            break
          case 38:
            that.moveNode('up')
            break
          case 39:
            that.moveNode('right')
            break
          case 40:
            that.moveNode('down')
            break
        }
      }
      document.onkeyup = function (e) {
        let event = window.event || e

        let key = event.keyCode
        if (key == flowConfig.shortcut.dragContainer.code) {
          that.$refs.flowArea.container.dragFlag = false
        } else if (key == flowConfig.shortcut.scaleContainer.code) {
          event.preventDefault()
          that.$refs.flowArea.container.scaleFlag = false
        } else if (key == flowConfig.shortcut.multiple.code) {
          that.$refs.flowArea.rectangleMultiple.flag = false
        }
      }

      ZFSN.consoleLog(['初始化快捷键成功...'])
    },
    listenPage() {
      window.onbeforeunload = function (e) {
        e = e || window.event
        if (e) {
          e.returnValue = '关闭提示'
        }
        return '关闭提示'
      }
    },
    initFlow() {
      const that = this
      if (that.flowData.status == flowConfig.flowStatus.CREATE) {
        that.flowData.attr.id = 'flow-' + ZFSN.getId()
      } else {
        that.loadFlow()
      }
      ZFSN.consoleLog(['初始化流程图成功...'])
    },
    loadFlow(json) {
      const that = this
      setTimeout(() => {
        that.flowLineAdditions.forEach((item) => {
          that.fieldNames.push(item)
        })
        const map = new Map()
        const list = that.fieldNames.filter((key) => !map.has(key.id) && map.set(key.id, 1))
        that.$emit('update:fieldNames', list)
      }, 100)
      that.clear()
      let loadData = JSON.parse(json)
      that.flowData.attr = loadData.attr
      that.flowData.config = loadData.config
      that.flowData.status = flowConfig.flowStatus.LOADING
      that.plumb.batch(function () {
        let nodeList = loadData.nodeList
        let areaList = loadData.areaList
        nodeList.forEach(function (node, index) {
          that.flowData.nodeList.push(node)
        })
        if (!!areaList && areaList.length > 0) {
          areaList.forEach(function (node, index) {
            that.flowData.nodeList.push(node)
          })
        }
        let linkList = loadData.linkList
        that.$nextTick(() => {
          linkList.forEach(function (link, index) {
            that.flowData.linkList.push(link)
            let conn = that.plumb.connect({
              source: link.sourceId,
              target: link.targetId,
              anchor: flowConfig.jsPlumbConfig.anchor.default,
              connector: [
                link.cls.linkType,
                {
                  gap: 5,
                  cornerRadius: 8,
                  alwaysRespectStubs: true,
                },
              ],
              paintStyle: {
                stroke: link.cls.linkColor,
                strokeWidth: link.cls.linkThickness,
              },
            })
            if (link.label != '') {
              conn.setLabel({
                label: link.label,
                cssClass: 'linkLabel',
              })
            }
          })
          that.currentSelect = {}
          that.currentSelectGroup = []
          that.flowData.status = flowConfig.flowStatus.MODIFY
        })
      }, true)
    },
    findNodeConfig(belongto, type, callback) {
      let node = null
      switch (belongto) {
        case 'commonNodes':
          node = commonNodes.filter((n) => n.type == type)
          break
        case 'laneNodes':
          node = laneNodes.filter((n) => n.type == type)
          break
      }
      if (node && node.length >= 0) node = node[0]
      callback(node)
    },
    selectTool(type) {
      let tool = tools.filter((t) => t.type == type)
      if (tool && tool.length >= 0) this.currentTool = tool[0]

      switch (type) {
        case 'drag':
          this.changeToDrag()
          break
        case 'connection':
          this.changeToConnection()
          break
        case 'zoom-in':
          this.changeToZoomIn()
          break
        case 'zoom-out':
          this.changeToZoomOut()
          break
      }
    },
    changeToDrag() {
      const that = this

      that.flowData.nodeList.forEach(function (node, index) {
        let f = that.plumb.toggleDraggable(node.id)
        if (!f) {
          that.plumb.toggleDraggable(node.id)
        }
        if (node.type != 'x-lane' && node.type != 'y-lane') {
          that.plumb.unmakeSource(node.id)
          that.plumb.unmakeTarget(node.id)
        }
      })
    },
    changeToConnection() {
      const that = this

      that.flowData.nodeList.forEach(function (node, index) {
        let f = that.plumb.toggleDraggable(node.id)
        if (f) {
          that.plumb.toggleDraggable(node.id)
        }
        if (node.type != 'x-lane' && node.type != 'y-lane') {
          that.plumb.makeSource(node.id, flowConfig.jsPlumbConfig.makeSourceConfig)
          that.plumb.makeTarget(node.id, flowConfig.jsPlumbConfig.makeTargetConfig)
        }
      })

      that.currentSelect = {}
      that.currentSelectGroup = []
    },
    changeToZoomIn() {
      console.log('切换到放大工具')
    },
    changeToZoomOut() {
      console.log('切换到缩小工具')
    },
    checkFlow() {
      const that = this
      let nodeList = that.flowData.nodeList
      let linkList = that.flowData.linkList
      let areaList = []
      for (let index = nodeList.length - 1; index >= 0; index--) {
        const item = nodeList[index]
        if (item.type == 'x-lane' || item.type == 'y-lane') {
          nodeList.splice(index, 1)
          areaList.push(item)
        }
        if (!!item.setInfo) {
          if (
            (item.setInfo.nodeDesignate == 'SPECIAL_USER' || item.setInfo.nodeDesignate == 'SPECIAL_ROLE') &&
            item.setInfo.nodeDesignateData.length == 0
          ) {
            this.$message.error('节点:' + item.setInfo.nodeName + ',执行权限需要配置!')
            return false
          }
        }
      }
      that.flowData.areaList = areaList
      linkList.forEach((item) => {
        if (!!item.compares) {
          for (let index = item.compares.length - 1; index >= 0; index--) {
            const compare = item.compares[index]
            //这些字段没有就去掉条件
            if (!compare.operation || !compare.fieldName || !compare.value) {
              item.compares.splice(index, 1)
            }
          }
        }
      })
      if (nodeList.length <= 0) {
        this.$message.error('流程图中无任何节点!')
        return false
      }
      return true
    },
    saveFlow() {
      const that = this
      if (!that.checkFlow()) return
      let flowObj = Object.assign({}, that.flowData)
      flowObj.status = flowConfig.flowStatus.SAVE
      let d = JSON.stringify(flowObj)
      //this.$message.success('保存流程成功!请查看控制台。');
      return d
    },
    cancelDownLoadFlowPicture() {
      this.flowPicture.url = ''
      this.flowPicture.modalVisible = false
    },
    clear() {
      const that = this
      that.flowData.nodeList.forEach(function (node, index) {
        that.plumb.remove(node.id)
      })
      that.currentSelect = {}
      that.currentSelectGroup = []
      that.flowData.nodeList = []
      that.flowData.linkList = []
      that.flowData.remarks = []
    },
    toggleShowGrid() {
      let flag = this.flowData.config.showGrid
      if (flag) {
        this.flowData.config.showGrid = false
        this.flowData.config.showGridText = '显示网格'
        this.flowData.config.showGridIcon = 'eye-invisible'
      } else {
        this.flowData.config.showGrid = true
        this.flowData.config.showGridText = '隐藏网格'
        this.flowData.config.showGridIcon = 'eye'
      }
    },
    setting() {
      this.$refs.settingModal.open()
    },
    shortcutHelper() {
      this.$refs.shortcutModal.open()
    },
    usingDoc() {
      window.open('https://gitee.com/yjblogs/VFD?_from=gitee_search')
    },
    exit() {
      alert('退出流程设计器...')
    },
    showLinkContextMenu(e) {
      let event = window.event || e

      event.preventDefault()
      event.stopPropagation()
      $('.vue-contextmenuName-flow-menu').css('display', 'none')
      $('.vue-contextmenuName-node-menu').css('display', 'none')
      let x = event.clientX
      let y = event.clientY
      this.linkContextMenuData.axis = { x, y }
    },
    deleteLink() {
      const that = this
      let sourceId = that.currentSelect.sourceId
      let targetId = that.currentSelect.targetId
      that.plumb.deleteConnection(
        that.plumb.getConnections({
          source: sourceId,
          target: targetId,
        })[0]
      )
      let linkList = that.flowData.linkList
      linkList.splice(
        linkList.findIndex((link) => link.sourceId == sourceId || link.targetId == targetId),
        1
      )
      that.currentSelect = {}
    },
    loseShortcut() {
      this.activeShortcut = false
    },
    getShortcut() {
      this.activeShortcut = true
    },
    openTest() {
      const that = this

      let flowObj = Object.assign({}, that.flowData)
      that.$refs.testModal.flowData = flowObj
      that.$refs.testModal.testVisible = true
    },
    moveNode(type) {
      const that = this

      let m = flowConfig.defaultStyle.movePx,
        isX = true
      switch (type) {
        case 'left':
          m = -m
          break
        case 'up':
          m = -m
          isX = false
          break
        case 'right':
          break
        case 'down':
          isX = false
      }

      if (that.currentSelectGroup.length > 0) {
        that.currentSelectGroup.forEach(function (node, index) {
          if (isX) {
            node.x += m
          } else {
            node.y += m
          }
        })
        that.plumb.repaintEverything()
      } else if (that.currentSelect.id) {
        if (isX) {
          that.currentSelect.x += m
        } else {
          that.currentSelect.y += m
        }
        that.plumb.repaintEverything()
      }
    },
  },
}
</script>

<style lang="less" scoped>
@import './style/flow-designer.less';
</style>

4.modules文件中的所有文件代码

FlowArea.vue代码如下

javascript 复制代码
<template>
  <div style="width: 100%; height: 100%; overflow: hidden; position: relative;">
    <div
      v-if="container.auxiliaryLine.isOpen && container.auxiliaryLine.isShowXLine"
      class="auxiliary-line-x"
      :style="{ top: auxiliaryLinePos.y + 'px' }"
    ></div>
    <div
      v-if="container.auxiliaryLine.isOpen && container.auxiliaryLine.isShowYLine"
      class="auxiliary-line-y"
      :style="{ left: auxiliaryLinePos.x + 'px' }"
    ></div>
    <div
      id="flowContainer"
      class="flow-container"
      :class="{ grid: flowData.config.showGrid, zoomIn: currentTool.type == 'zoom-in', zoomOut: currentTool.type == 'zoom-out', canScale: container.scaleFlag, canDrag: container.dragFlag, canMultiple: rectangleMultiple.flag }"
      :style="{ top: container.pos.top + 'px', left: container.pos.left + 'px', transform: 'scale(' + container.scale + ')', transformOrigin: container.scaleOrigin.x + 'px ' + container.scaleOrigin.y + 'px' }"
      @click.stop="containerHandler"
      @mousedown="mousedownHandler"
      @mousemove="mousemoveHandler"
      @mouseup="mouseupHandler"
      @mousewheel="scaleContainer"
      @DOMMouseScroll="scaleContainer"
      @contextmenu="showContainerContextMenu"
    >
      <flow-node
        v-for="(node, index) in flowData.nodeList"
        :key="index"
        :node="node"
        :plumb="plumb"
        :select.sync="currentSelect"
        :selectGroup.sync="currentSelectGroup"
        :currentTool="currentTool"
        :activityId="activityId"
        @showNodeContextMenu="showNodeContextMenu"
        @isMultiple="isMultiple"
        @updateNodePos="updateNodePos"
        @alignForLine="alignForLine"
        @hideAlignLine="hideAlignLine"
      >
      </flow-node>
      <div
        class="rectangle-multiple"
        v-if="rectangleMultiple.flag && rectangleMultiple.multipling"
        :style="{ top: rectangleMultiple.position.top + 'px', left: rectangleMultiple.position.left + 'px', width: rectangleMultiple.width + 'px', height: rectangleMultiple.height + 'px' }"
      >
      </div>
    </div>
    <!-- <div class="container-scale">
			缩放倍数:{{ container.scaleShow }}%
		</div>
		<div class="mouse-position">
			x: {{ mouse.position.x }}, y: {{ mouse.position.y }}
		</div> -->
    <vue-context-menu
      class="customMultiMenuClass"
      :contextMenuData="containerContextMenuData"
      @flowInfo="flowInfo"
      @paste="paste"
      @selectAll="selectAll"
      @saveFlow="saveFlow"
      @verticaLeft="verticaLeft"
      @verticalCenter="verticalCenter"
      @verticalRight="verticalRight"
      @levelUp="levelUp"
      @levelCenter="levelCenter"
      @levelDown="levelDown"
      @addRemark="addRemark"
    >
    </vue-context-menu>
    <!-- 节点右键操作 -->
    <vue-context-menu
      class="customMultiMenuClass"
      :contextMenuData="nodeContextMenuData"
      @copyNode="copyNode"
      @deleteNode="deleteNode"
    >
    </vue-context-menu>
  </div>
</template>

<script>
import jsplumb from 'jsplumb'
import { flowConfig } from '../config/args-config.js'
import $ from 'jquery'
import 'jquery-ui/ui/widgets/draggable'
import 'jquery-ui/ui/widgets/droppable'
import 'jquery-ui/ui/widgets/resizable'
import { ZFSN } from '../util/ZFSN.js'
import FlowNode from './FlowNode'

export default {
  props: ['browserType', 'flowData', 'plumb', 'select', 'selectGroup', 'currentTool', 'activityId'],
  components: {
    jsplumb,
    FlowNode,
  },
  mounted() {
    this.initFlowArea()
  },
  data() {
    return {
      ctx: null,
      currentSelect: this.select,
      currentSelectGroup: this.selectGroup,
      container: {
        pos: {
          //每个框架不同,不能用-3000
          top: -500,
          left: -500,
        },
        dragFlag: false,
        draging: false,
        scale: flowConfig.defaultStyle.containerScale.init,
        scaleFlag: false,
        scaleOrigin: {
          x: 0,
          y: 0,
        },
        scaleShow: ZFSN.mul(flowConfig.defaultStyle.containerScale.init, 100),
        auxiliaryLine: {
          isOpen: flowConfig.defaultStyle.isOpenAuxiliaryLine,
          isShowXLine: false,
          isShowYLine: false,
          controlFnTimesFlag: true,
        },
      },
      auxiliaryLinePos: {
        x: 0,
        y: 0,
      },
      mouse: {
        position: {
          x: 0,
          y: 0,
        },
        tempPos: {
          x: 0,
          y: 0,
        },
      },
      rectangleMultiple: {
        flag: false,
        multipling: false,
        position: {
          top: 0,
          left: 0,
        },
        height: 0,
        width: 0,
      },
      containerContextMenuData: flowConfig.contextMenu.container,
      nodeContextMenuData: flowConfig.contextMenu.node,
      tempLinkId: '',
      clipboard: [],
    }
  },
  methods: {
    initFlowArea() {
      const that = this
      that.ctx = document.getElementById('flowContainer').parentNode
      $('.flow-container').droppable({
        accept: function (t) {
          if (t[0].className.indexOf('node-item') != -1) {
            let event = window.event || 'firefox'
            if (that.ctx.contains(event.srcElement) || event == 'firefox') {
              return true
            }
          }
          return false
        },
        hoverClass: 'flow-container-active',
        drop: function (event, ui) {
          let belongto = ui.draggable.attr('belongto')
          let type = ui.draggable.attr('type')
          that.$emit('selectTool', 'drag')
          that.$emit('findNodeConfig', belongto, type, (node) => {
            if (!node) {
              that.$message.error('未知的节点类型!')
              return
            }
            that.addNewNode(node)
          })
        },
      })
    },
    mousedownHandler(e) {
      const that = this

      let event = window.event || e

      if (event.button == 0) {
        if (that.container.dragFlag) {
          that.mouse.tempPos = that.mouse.position
          that.container.draging = true
        }

        that.currentSelectGroup = []
        if (that.rectangleMultiple.flag) {
          that.mouse.tempPos = that.mouse.position
          that.rectangleMultiple.multipling = true
        }
      }
    },
    mousemoveHandler(e) {
      const that = this

      let event = window.event || e

      if (event.target.id == 'flowContainer') {
        that.mouse.position = {
          x: event.offsetX,
          y: event.offsetY,
        }
      } else {
        let cn = event.target.className
        let tn = event.target.tagName
        if (cn != 'lane-text' && cn != 'lane-text-div' && tn != 'svg' && tn != 'path' && tn != 'I') {
          that.mouse.position.x = event.target.offsetLeft + event.offsetX
          that.mouse.position.y = event.target.offsetTop + event.offsetY
        }
      }
      if (that.container.draging) {
        let nTop = that.container.pos.top + (that.mouse.position.y - that.mouse.tempPos.y)
        let nLeft = that.container.pos.left + (that.mouse.position.x - that.mouse.tempPos.x)
        if (nTop >= 0) nTop = 0
        if (nLeft >= 0) nLeft = 0
        that.container.pos = {
          top: nTop,
          left: nLeft,
        }
      }
      if (that.rectangleMultiple.multipling) {
        let h = that.mouse.position.y - that.mouse.tempPos.y
        let w = that.mouse.position.x - that.mouse.tempPos.x
        let t = that.mouse.tempPos.y
        let l = that.mouse.tempPos.x
        if (h >= 0 && w < 0) {
          w = -w
          l -= w
        } else if (h < 0 && w >= 0) {
          h = -h
          t -= h
        } else if (h < 0 && w < 0) {
          h = -h
          w = -w
          t -= h
          l -= w
        }
        that.rectangleMultiple.height = h
        that.rectangleMultiple.width = w
        that.rectangleMultiple.position.top = t
        that.rectangleMultiple.position.left = l
      }
    },
    mouseupHandler() {
      const that = this

      if (that.container.draging) that.container.draging = false
      if (that.rectangleMultiple.multipling) {
        that.judgeSelectedNode()
        that.rectangleMultiple.multipling = false
        that.rectangleMultiple.width = 0
        that.rectangleMultiple.height = 0
      }
    },
    judgeSelectedNode() {
      const that = this

      let ay = that.rectangleMultiple.position.top
      let ax = that.rectangleMultiple.position.left
      let by = ay + that.rectangleMultiple.height
      let bx = ax + that.rectangleMultiple.width

      let nodeList = that.flowData.nodeList
      nodeList.forEach(function (node, index) {
        if (node.y >= ay && node.x >= ax && node.y <= by && node.x <= bx) {
          that.plumb.addToDragSelection(noaddToDragSelectionde.id)
          that.currentSelectGroup.push(node)
        }
      })
    },
    scaleContainer(e) {
      const that = this

      let event = window.event || e

      if (that.container.scaleFlag) {
        if (that.browserType == 2) {
          if (event.detail < 0) {
            that.enlargeContainer()
          } else {
            that.narrowContainer()
          }
        } else {
          if (event.deltaY < 0) {
            that.enlargeContainer()
          } else if (that.container.scale) {
            that.narrowContainer()
          }
        }
      }
    },
    enlargeContainer() {
      const that = this
      that.container.scaleOrigin.x = that.mouse.position.x
      that.container.scaleOrigin.y = that.mouse.position.y
      let newScale = ZFSN.add(that.container.scale, flowConfig.defaultStyle.containerScale.onceEnlarge)
      if (newScale <= flowConfig.defaultStyle.containerScale.max) {
        that.container.scale = newScale
        that.container.scaleShow = ZFSN.mul(that.container.scale, 100)
        that.plumb.setZoom(that.container.scale)
      }
    },
    narrowContainer() {
      const that = this
      that.container.scaleOrigin.x = that.mouse.position.x
      that.container.scaleOrigin.y = that.mouse.position.y
      let newScale = ZFSN.sub(that.container.scale, flowConfig.defaultStyle.containerScale.onceNarrow)
      if (newScale >= flowConfig.defaultStyle.containerScale.min) {
        that.container.scale = newScale
        that.container.scaleShow = ZFSN.mul(that.container.scale, 100)
        that.plumb.setZoom(that.container.scale)
      }
    },
    showContainerContextMenu(e) {
      let event = window.event || e

      event.preventDefault()
      $('.vue-contextmenuName-node-menu').css('display', 'none')
      $('.vue-contextmenuName-link-menu').css('display', 'none')
      this.selectContainer()
      let x = event.clientX
      let y = event.clientY
      this.containerContextMenuData.axis = { x, y }
    },
    showNodeContextMenu(e) {
      let event = window.event || e

      event.preventDefault()
      $('.vue-contextmenuName-flow-menu').css('display', 'none')
      $('.vue-contextmenuName-link-menu').css('display', 'none')
      let x = event.clientX
      let y = event.clientY
      this.nodeContextMenuData.axis = { x, y }
    },
    flowInfo() {
      const that = this

      let nodeList = that.flowData.nodeList
      let linkList = that.flowData.linkList
      alert('当前流程图中有 ' + nodeList.length + ' 个节点,有 ' + linkList.length + ' 条连线。')
    },
    paste() {
      const that = this
      let dis = 0
      that.clipboard.forEach(function (node, index) {
        let newNode = Object.assign({}, node)
        newNode.id = newNode.type + '-' + ZFSN.getId()
        let nodePos = that.computeNodePos(that.mouse.position.x + dis, that.mouse.position.y + dis)
        newNode.x = nodePos.x
        newNode.y = nodePos.y
        dis += 20
        that.flowData.nodeList.push(newNode)
      })
    },
    selectAll() {
      const that = this
      that.flowData.nodeList.forEach(function (node, index) {
        that.plumb.addToDragSelection(node.id)
        that.currentSelectGroup.push(node)
      })
    },
    saveFlow() {
      this.$emit('saveFlow')
    },
    checkAlign() {
      if (this.currentSelectGroup.length < 2) {
        this.$message.error('请选择至少两个节点!')
        return false
      }
      return true
    },
    verticaLeft() {
      const that = this

      if (!that.checkAlign()) return
      let nodeList = that.flowData.nodeList
      let selectGroup = that.currentSelectGroup
      let baseX = selectGroup[0].x
      let baseY = selectGroup[0].y
      for (let i = 1; i < selectGroup.length; i++) {
        baseY = baseY + selectGroup[i - 1].height + flowConfig.defaultStyle.alignSpacing.vertical
        let f = nodeList.filter((n) => n.id == selectGroup[i].id)[0]
        f.tx = baseX
        f.ty = baseY
        that.plumb.animate(
          selectGroup[i].id,
          { top: baseY, left: baseX },
          {
            duration: flowConfig.defaultStyle.alignDuration,
            complete: function () {
              f.x = f.tx
              f.y = f.ty
            },
          }
        )
      }
    },
    verticalCenter() {
      const that = this

      if (!that.checkAlign()) return
      let nodeList = that.flowData.nodeList
      let selectGroup = that.currentSelectGroup
      let baseX = selectGroup[0].x
      let baseY = selectGroup[0].y
      let firstX = baseX
      for (let i = 1; i < selectGroup.length; i++) {
        baseY = baseY + selectGroup[i - 1].height + flowConfig.defaultStyle.alignSpacing.vertical
        baseX = firstX + ZFSN.div(selectGroup[0].width, 2) - ZFSN.div(selectGroup[i].width, 2)
        let f = nodeList.filter((n) => n.id == selectGroup[i].id)[0]
        f.tx = baseX
        f.ty = baseY
        that.plumb.animate(
          selectGroup[i].id,
          { top: baseY, left: baseX },
          {
            duration: flowConfig.defaultStyle.alignDuration,
            complete: function () {
              f.x = f.tx
              f.y = f.ty
            },
          }
        )
      }
    },
    verticalRight() {
      const that = this

      if (!that.checkAlign()) return
      let nodeList = that.flowData.nodeList
      let selectGroup = that.currentSelectGroup
      let baseX = selectGroup[0].x
      let baseY = selectGroup[0].y
      let firstX = baseX
      for (let i = 1; i < selectGroup.length; i++) {
        baseY = baseY + selectGroup[i - 1].height + flowConfig.defaultStyle.alignSpacing.vertical
        baseX = firstX + selectGroup[0].width - selectGroup[i].width
        let f = nodeList.filter((n) => n.id == selectGroup[i].id)[0]
        f.tx = baseX
        f.ty = baseY
        that.plumb.animate(
          selectGroup[i].id,
          { top: baseY, left: baseX },
          {
            duration: flowConfig.defaultStyle.alignDuration,
            complete: function () {
              f.x = f.tx
              f.y = f.ty
            },
          }
        )
      }
    },
    levelUp() {
      const that = this

      if (!that.checkAlign()) return
      let nodeList = that.flowData.nodeList
      let selectGroup = that.currentSelectGroup
      let baseX = selectGroup[0].x
      let baseY = selectGroup[0].y
      for (let i = 1; i < selectGroup.length; i++) {
        baseX = baseX + selectGroup[i - 1].width + flowConfig.defaultStyle.alignSpacing.level
        let f = nodeList.filter((n) => n.id == selectGroup[i].id)[0]
        f.tx = baseX
        f.ty = baseY
        that.plumb.animate(
          selectGroup[i].id,
          { top: baseY, left: baseX },
          {
            duration: flowConfig.defaultStyle.alignDuration,
            complete: function () {
              f.x = f.tx
              f.y = f.ty
            },
          }
        )
      }
    },
    levelCenter() {
      const that = this

      if (!that.checkAlign()) return
      let nodeList = that.flowData.nodeList
      let selectGroup = that.currentSelectGroup
      let baseX = selectGroup[0].x
      let baseY = selectGroup[0].y
      let firstY = baseY
      for (let i = 1; i < selectGroup.length; i++) {
        baseY = firstY + ZFSN.div(selectGroup[0].height, 2) - ZFSN.div(selectGroup[i].height, 2)
        baseX = baseX + selectGroup[i - 1].width + flowConfig.defaultStyle.alignSpacing.level
        let f = nodeList.filter((n) => n.id == selectGroup[i].id)[0]
        f.tx = baseX
        f.ty = baseY
        that.plumb.animate(
          selectGroup[i].id,
          { top: baseY, left: baseX },
          {
            duration: flowConfig.defaultStyle.alignDuration,
            complete: function () {
              f.x = f.tx
              f.y = f.ty
            },
          }
        )
      }
    },
    levelDown() {
      const that = this

      if (!that.checkAlign()) return
      let nodeList = that.flowData.nodeList
      let selectGroup = that.currentSelectGroup
      let baseX = selectGroup[0].x
      let baseY = selectGroup[0].y
      let firstY = baseY
      for (let i = 1; i < selectGroup.length; i++) {
        baseY = firstY + selectGroup[0].height - selectGroup[i].height
        baseX = baseX + selectGroup[i - 1].width + flowConfig.defaultStyle.alignSpacing.level
        let f = nodeList.filter((n) => n.id == selectGroup[i].id)[0]
        f.tx = baseX
        f.ty = baseY
        that.plumb.animate(
          selectGroup[i].id,
          { top: baseY, left: baseX },
          {
            duration: flowConfig.defaultStyle.alignDuration,
            complete: function () {
              f.x = f.tx
              f.y = f.ty
            },
          }
        )
      }
    },
    addRemark() {
      const that = this
      alert('添加备注(待完善)...')
    },
    copyNode() {
      const that = this

      that.clipboard = []
      if (that.currentSelectGroup.length > 0) {
        that.clipboard = Object.assign([], that.currentSelectGroup)
      } else if (that.currentSelect.id) {
        that.clipboard.push(that.currentSelect)
      }
    },
    getConnectionsByNodeId(nodeId) {
      const that = this
      let conns1 = that.plumb.getConnections({
        source: nodeId,
      })
      let conns2 = that.plumb.getConnections({
        target: nodeId,
      })
      return conns1.concat(conns2)
    },
    deleteNode() {
      const that = this
      let nodeList = that.flowData.nodeList
      let linkList = that.flowData.linkList
      let arr = []

      arr.push(Object.assign({}, that.currentSelect))

      arr.forEach(function (c, index) {
        let conns = that.getConnectionsByNodeId(c.id)
        conns.forEach(function (conn, index) {
          linkList.splice(
            linkList.findIndex((link) => link.sourceId == conn.sourceId || link.targetId == conn.targetId),
            1
          )
        })
        that.plumb.deleteEveryEndpoint()
        let inx = nodeList.findIndex((node) => node.id == c.id)
        nodeList.splice(inx, 1)
        that.$nextTick(() => {
          linkList.forEach(function (link, index) {
            let conn = that.plumb.connect({
              source: link.sourceId,
              target: link.targetId,
              anchor: flowConfig.jsPlumbConfig.anchor.default,
              connector: [
                link.cls.linkType,
                {
                  gap: 5,
                  cornerRadius: 8,
                  alwaysRespectStubs: true,
                },
              ],
              paintStyle: {
                stroke: link.cls.linkColor,
                strokeWidth: link.cls.linkThickness,
              },
            })
            if (link.label != '') {
              conn.setLabel({
                label: link.label,
                cssClass: 'linkLabel',
              })
            }
          })
        })
      })
      that.selectContainer()
    },
    addNewNode(node) {
      const that = this

      let x = that.mouse.position.x
      let y = that.mouse.position.y
      let nodePos = that.computeNodePos(x, y)
      x = nodePos.x
      y = nodePos.y

      let newNode = Object.assign({}, node)
      newNode.id = newNode.type + '-' + ZFSN.getId()
      newNode.height = 50
      if (newNode.type == 'start' || newNode.type == 'end' || newNode.type == 'event' || newNode.type == 'gateway') {
        newNode.x = x - 25
        newNode.width = 50
      } else {
        newNode.x = x - 60
        newNode.width = 120
      }
      newNode.y = y - 25
      if (newNode.type == 'x-lane') {
        newNode.height = 200
        newNode.width = 400
      } else if (newNode.type == 'y-lane') {
        newNode.height = 400
        newNode.width = 200
      }
      that.flowData.nodeList.push(newNode)
    },
    computeNodePos(x, y) {
      const pxx = flowConfig.defaultStyle.alignGridPX[0]
      const pxy = flowConfig.defaultStyle.alignGridPX[1]
      if (x % pxx) x = pxx - (x % pxx) + x
      if (y % pxy) y = pxy - (y % pxy) + y
      return {
        x: x,
        y: y,
      }
    },
    containerHandler() {
      const that = this

      that.selectContainer()
      let toolType = that.currentTool.type
      if (toolType == 'zoom-in') {
        that.enlargeContainer()
      } else if (toolType == 'zoom-out') {
        that.narrowContainer()
      }
    },
    selectContainer() {
      this.currentSelect = {}
      this.$emit('getShortcut')
    },
    isMultiple(callback) {
      callback(this.rectangleMultiple.flag)
    },
    updateNodePos() {
      const that = this

      let nodeList = that.flowData.nodeList
      that.currentSelectGroup.forEach(function (node, index) {
        let l = parseInt($('#' + node.id).css('left'))
        let t = parseInt($('#' + node.id).css('top'))
        let f = nodeList.filter((n) => n.id == node.id)[0]
        f.x = l
        f.y = t
      })
    },
    alignForLine(e) {
      const that = this

      if (that.selectGroup.length > 1) return
      if (that.container.auxiliaryLine.controlFnTimesFlag) {
        let elId = e.el.id
        let nodeList = that.flowData.nodeList
        nodeList.forEach(function (node, index) {
          if (elId != node.id) {
            let dis = flowConfig.defaultStyle.showAuxiliaryLineDistance,
              elPos = e.pos,
              elH = e.el.offsetHeight,
              elW = e.el.offsetWidth,
              disX = elPos[0] - node.x,
              disY = elPos[1] - node.y
            if ((disX >= -dis && disX <= dis) || (disX + elW >= -dis && disX + elW <= dis)) {
              that.container.auxiliaryLine.isShowYLine = true
              that.auxiliaryLinePos.x = node.x + that.container.pos.left
              let nodeMidPointX = node.x + node.width / 2
              if (nodeMidPointX == elPos[0] + elW / 2) {
                that.auxiliaryLinePos.x = nodeMidPointX + that.container.pos.left
              }
            }
            if ((disY >= -dis && disY <= dis) || (disY + elH >= -dis && disY + elH <= dis)) {
              that.container.auxiliaryLine.isShowXLine = true
              that.auxiliaryLinePos.y = node.y + that.container.pos.top
              let nodeMidPointY = node.y + node.height / 2
              if (nodeMidPointY == elPos[1] + elH / 2) {
                that.auxiliaryLinePos.y = nodeMidPointY + that.container.pos.left
              }
            }
          }
        })
        that.container.auxiliaryLine.controlFnTimesFlag = false
        setTimeout(function () {
          that.container.auxiliaryLine.controlFnTimesFlag = true
        }, 200)
      }
    },
    hideAlignLine() {
      if (this.container.auxiliaryLine.isOpen) {
        this.container.auxiliaryLine.isShowXLine = false
        this.container.auxiliaryLine.isShowYLine = false
      }
    },
  },
  watch: {
    select(val) {
      this.currentSelect = val
      if (this.tempLinkId != '') {
        $('#' + this.tempLinkId).removeClass('link-active')
        this.tempLinkId = ''
      }
      if (this.currentSelect.type == 'link') {
        this.tempLinkId = this.currentSelect.id
        $('#' + this.currentSelect.id).addClass('link-active')
      }
    },
    currentSelect: {
      handler(val) {
        this.$emit('update:select', val)
      },
      deep: true,
    },
    selectGroup(val) {
      this.currentSelectGroup = val
      if (this.currentSelectGroup.length <= 0) this.plumb.clearDragSelection()
    },
    currentSelectGroup: {
      handler(val) {
        this.$emit('update:selectGroup', val)
      },
      deep: true,
    },
  },
}
</script>

<style lang="less" scoped>
@import '../style/flow-area.less';
</style>

modules文件下的FlowAttr.vue文件代码如下

javascript 复制代码
<template>
  <div>
    <a-tabs
      size="small"
      defaultActiveKey="flow-attr"
      :activeKey="activeKey"
    >
      <a-tab-pane key="flow-attr">
        <span slot="tab">
          <a-icon type="cluster" />
          流程属性
        </span>
        <a-form layout="horizontal">
          <a-form-item
            label="流程id"
            :label-col="formItemLayout.labelCol"
            :wrapper-col="formItemLayout.wrapperCol"
          >
            <a-input
              :value="flowData.attr.id"
              disabled
            />
          </a-form-item>
        </a-form>
      </a-tab-pane>
      <a-tab-pane key="node-attr">
        <span slot="tab">
          <a-icon type="profile" />
          节点属性
        </span>
        <template v-if="currentSelect.type == 'start round mix'">
          <a-form layout="horizontal">
            <a-form-item
              label="类型"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-tag color="purple">{{ currentSelect.type }}</a-tag>
            </a-form-item>
            <a-form-item
              label="id"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-input
                :value="currentSelect.id"
                disabled
              />
            </a-form-item>
            <a-form-item
              label="名称"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-input
                placeholder="请输入节点名称"
                :value="currentSelect.name"
                @change="nameChange"
              />
            </a-form-item>
          </a-form>
        </template>
        <template v-if="currentSelect.type == 'end round'">
          <a-form layout="horizontal">
            <a-form-item
              label="类型"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-tag color="purple">{{ currentSelect.type }}</a-tag>
            </a-form-item>
            <a-form-item
              label="id"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-input
                :value="currentSelect.id"
                disabled
              />
            </a-form-item>
            <a-form-item
              label="名称"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-input
                placeholder="请输入节点名称"
                :value="currentSelect.name"
                @change="nameChange"
              />
            </a-form-item>
          </a-form>
        </template>
        <template v-if="currentSelect.type == 'node'">
          <a-form layout="horizontal">
            <a-form-item
              label="类型"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-tag color="purple">{{ currentSelect.type }}</a-tag>
            </a-form-item>
            <a-form-item
              label="id"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-input
                :value="currentSelect.id"
                disabled
              />
            </a-form-item>
            <a-form-item
              label="名称"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-input
                placeholder="请输入节点名称"
                :value="currentSelect.name"
                @change="nameChange"
              />
            </a-form-item>
            <!-- <a-form-item
              label="驳回类型"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-select
                v-model="setInfo.nodeRejectType"
                @change="e => nodeRejectTypeChange(e)"
              >
                <a-select-option
                  v-for="(item,index) in nodeRejectType"
                  :key="index"
                  :value="item.id"
                >{{ item.name }}</a-select-option>
              </a-select>
            </a-form-item> -->
            <a-form-item
              label="驳回节点"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-select
                v-model="setInfo.nodeRejectStep"
                @change="e => nodeRejectStepChange(e)"
              >
                <a-select-option
                  v-for="(item,index) in allNodeList"
                  :key="index"
                  :value="item.id"
                >{{ item.name }}</a-select-option>
              </a-select>
            </a-form-item>
            <!--  -->
            <a-form-item
              label="会签方式"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-select
                v-model="setInfo.nodeConfluenceType"
                @change="e => nodeConfluenceTypeChange(e)"
              >
                <a-select-option
                  v-for="(item,index) in nodeConfluenceType"
                  :key="index"
                  :value="item.id"
                >{{ item.name }}</a-select-option>
              </a-select>
            </a-form-item>
            <a-form-item
              label="回调URL"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-input
                :value="setInfo.thirdPartyUrl "
                @change="linkThirdPartyUrlChange"
              />
            </a-form-item>
            <a-form-item
              label="执行权限"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-select
                :default-value="setInfo.nodeDesignate"
                v-model="setInfo.nodeDesignate"
                @change="e => nodeDesignateChange(e)"
              >
                <a-select-option
                  v-for="(item,index) in nodeDesignateData"
                  :key="index"
                  :value="item.id"
                >{{ item.name }}</a-select-option>
              </a-select>
            </a-form-item>
            <a-form-item
              v-show="specialShow"
              v-if="setInfo.nodeDesignate=='SPECIAL_USER'"
              label="指定用户"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-col
                :md="18"
                :sm="18"
              >
                <a-input
                  disabled="disabled"
                  v-model="specialName"
                />
              </a-col>
              <a-col
                :md="6"
                :sm="6"
              >
                <a-button
                  icon="search"
                  @click="setUser()"
                />
              </a-col>
            </a-form-item>
            <a-form-item
              v-show="specialShow"
              v-else
              label="指定角色"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-col
                :md="18"
                :sm="18"
              >
                <a-input
                  disabled="disabled"
                  v-model="specialName"
                />
              </a-col>
              <a-col
                :md="6"
                :sm="6"
              >
                <a-button
                  icon="search"
                  @click="setRole()"
                />
              </a-col>
            </a-form-item>
            <!-- <a-form-item
              label="当前部门"
              v-if="setInfo.nodeDesignate=='SPECIAL_ROLE'"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-switch
                checkedChildren="是"
                unCheckedChildren="否"
                v-model="currentDepart"
                @click="currentDepartChange"
              />
            </a-form-item> -->
          </a-form>
        </template>
        <template v-else-if="currentSelect.type == 'fork'">
          <a-form layout="horizontal">
            <a-form-item
              label="类型"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-tag color="purple">{{ currentSelect.type }}</a-tag>
            </a-form-item>
            <a-form-item
              label="id"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-input
                :value="currentSelect.id"
                disabled
              />
            </a-form-item>
            <a-form-item
              label="名称"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-input
                placeholder="请输入节点名称"
                :value="currentSelect.name"
                @change="nameChange"
              />
            </a-form-item>
          </a-form>
        </template>
        <template v-else-if="currentSelect.type == 'join'">
          <a-form layout="horizontal">
            <a-form-item
              label="类型"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-tag color="purple">{{ currentSelect.type }}</a-tag>
            </a-form-item>
            <a-form-item
              label="id"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-input
                :value="currentSelect.id"
                disabled
              />
            </a-form-item>
            <a-form-item
              label="名称"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-input
                placeholder="请输入节点名称"
                :value="currentSelect.name"
                @change="nameChange"
              />
            </a-form-item>
            <!-- <a-form-item
              label="驳回类型"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-select
                v-model="setInfo.nodeRejectType"
                @change="e => nodeRejectTypeChange(e)"
              >
                <a-select-option
                  v-for="(item,index) in nodeRejectType"
                  :key="index"
                  :value="item.id"
                >{{ item.name }}</a-select-option>
              </a-select>
            </a-form-item> -->
            <a-form-item
              label="驳回节点"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-select
                v-model="setInfo.nodeRejectStep"
                @change="e => nodeRejectStepChange(e)"
              >
                <a-select-option
                  v-for="(item,index) in allNodeList"
                  :key="index"
                  :value="item.id"
                >{{ item.name }}</a-select-option>
              </a-select>
            </a-form-item>
            <a-form-item
              label="会签方式"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-select
                v-model="setInfo.nodeConfluenceType"
                @change="e => nodeConfluenceTypeChange(e)"
              >
                <a-select-option
                  v-for="(item,index) in nodeConfluenceType"
                  :key="index"
                  :value="item.id"
                >{{ item.name }}</a-select-option>
              </a-select>
            </a-form-item>
            <a-form-item
              label="回调URL"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-input
                :value="setInfo.thirdPartyUrl"
                @change="linkThirdPartyUrlChange"
              />
            </a-form-item>
            <a-form-item
              label="执行权限"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-select
                :default-value="setInfo.nodeDesignate"
                v-model="setInfo.nodeDesignate"
                @change="e => nodeDesignateChange(e)"
              >
                <a-select-option
                  v-for="(item,index) in nodeDesignateData"
                  :key="index"
                  :value="item.id"
                >{{ item.name }}</a-select-option>
              </a-select>
            </a-form-item>
            <a-form-item
              v-show="specialShow"
              v-if="setInfo.nodeDesignate=='SPECIAL_USER'"
              label="指定用户"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-col
                :md="18"
                :sm="18"
              >
                <a-input
                  disabled="disabled"
                  v-model="specialName"
                />
              </a-col>
              <a-col
                :md="6"
                :sm="6"
              >
                <a-button
                  icon="search"
                  @click="setUser()"
                />
              </a-col>
            </a-form-item>
            <a-form-item
              v-show="specialShow"
              v-else
              label="指定角色"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-col
                :md="18"
                :sm="18"
              >
                <a-input
                  disabled="disabled"
                  v-model="specialName"
                />
              </a-col>
              <a-col
                :md="6"
                :sm="6"
              >
                <a-button
                  icon="search"
                  @click="setRole()"
                />
              </a-col>
            </a-form-item>
            <!-- <a-form-item
              label="当前部门"
              v-if="setInfo.nodeDesignate=='SPECIAL_ROLE'"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-switch
                checkedChildren="是"
                unCheckedChildren="否"
                v-model="currentDepart"
                @click="currentDepartChange"
              />
            </a-form-item> -->
          </a-form>
        </template>
        <template v-else-if="currentSelect.type == 'x-lane' || currentSelect.type == 'y-lane'">
          <a-form layout="horizontal">
            <a-form-item
              label="类型"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-tag color="purple">{{ currentSelect.type }}</a-tag>
            </a-form-item>
            <a-form-item
              label="id"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-input
                :value="currentSelect.id"
                disabled
              />
            </a-form-item>
            <a-form-item
              label="名称"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-input
                placeholder="请输入节点名称"
                :value="currentSelect.name"
                @change="nameChange"
              />
            </a-form-item>
          </a-form>
        </template>
      </a-tab-pane>
      <a-tab-pane key="link-attr">
        <span slot="tab">
          <a-icon type="branches" />
          连线属性
        </span>
        <a-form layout="horizontal">
          <a-form-item
            label="id"
            :label-col="formItemLayout.labelCol"
            :wrapper-col="formItemLayout.wrapperCol"
          >
            <a-input
              :value="currentSelect.id"
              disabled
            />
          </a-form-item>
          <a-form-item
            label="源节点"
            :label-col="formItemLayout.labelCol"
            :wrapper-col="formItemLayout.wrapperCol"
          >
            <a-input
              :value="currentSelect.sourceId"
              disabled
            />
          </a-form-item>
          <a-form-item
            label="目标节点"
            :label-col="formItemLayout.labelCol"
            :wrapper-col="formItemLayout.wrapperCol"
          >
            <a-input
              :value="currentSelect.targetId"
              disabled
            />
          </a-form-item>
          <a-form-item
            label="文本"
            :label-col="formItemLayout.labelCol"
            :wrapper-col="formItemLayout.wrapperCol"
          >
            <a-input
              :value="currentSelect.label"
              @change="linkLabelChange"
            />
          </a-form-item>
          <a-form-item
            label="添加条件"
            :label-col="formItemLayout.labelCol"
            :wrapper-col="formItemLayout.wrapperCol"
          >
            <a-button
              icon="plus"
              @click="addList()"
            />
          </a-form-item>
          <div
            :key="i"
            v-for="(item,i) in compares"
          >
            <a-form-item
              :label="'条件'+i"
              :label-col="formItemLayout.labelCol"
              :wrapper-col="formItemLayout.wrapperCol"
            >
              <a-col
                :md="10"
                :sm="10"
              >
                <a-select
                  placeholder="关系"
                  v-model="compares[i].condition"
                  @change="e => conditionChange(i,e)"
                >
                  <a-select-option
                    v-for="(condition,index) in conditions "
                    :key="index"
                    :value="condition.id"
                  >{{ condition.name }}</a-select-option>
                </a-select>
              </a-col>
              <a-col
                :md="10"
                :sm="10"
              >
                <a-select
                  placeholder="属性"
                  v-model="compares[i].fieldName"
                  @change="e => fieldNameChange(i,e)"
                >
                  <a-select-option
                    v-for="(fieldName,index) in fieldNames "
                    :key="index"
                    :value="fieldName.id"
                  >{{ fieldName.name }}</a-select-option>
                </a-select>
              </a-col>
              <a-col
                :md="4"
                :sm="4"
              >
                <a-button
                  icon="minus"
                  @click="subList(i)"
                  v-if="compares.length>0"
                />
              </a-col>
              <a-col
                :md="10"
                :sm="10"
              >
                <a-select
                  placeholder="比较"
                  v-model="compares[i].operation"
                  @change="e => operationChange(i,e)"
                >
                  <a-select-option
                    v-for="(operation,index) in operations"
                    :key="index"
                    :value="operation.id"
                  >{{ operation.name }}</a-select-option>
                </a-select>
              </a-col>
              <a-col
                :md="10"
                :sm="10"
                v-if="compares[i].fieldName=='CreatedUserId'||compares[i].fieldName=='CreatedOrgId'"
              >
                <a-tooltip placement="topLeft">
                  <span
                    v-if="compares[i].valueName"
                    slot="title"
                  >
                    {{ compares[i].valueName }}
                  </span>
                  <template
                    v-else
                    slot="title"
                  >
                    值
                  </template>
                  <div>
                    <a-input
                      :disabled="true"
                      v-model="compares[i].valueName"
                      clearable
                      placeholder="值"
                    />
                  </div>
                </a-tooltip>
              </a-col>
              <a-col
                :md="10"
                :sm="10"
                v-else
              >
                <a-input
                  v-model="compares[i].value"
                  clearable
                  placeholder="值"
                  @change="e => valueChange(i,e)"
                />
              </a-col>
              <a-col
                :md="4"
                :sm="4"
                v-if="compares[i].fieldName=='CreatedUserId'"
              >
                <a-button
                  icon="search"
                  @click="setUser(i)"
                  v-if="compares.length>0"
                />
              </a-col>
              <a-col
                :md="4"
                :sm="4"
                v-if="compares[i].fieldName=='CreatedOrgId'"
              >
                <a-button
                  icon="search"
                  @click="setRole(i)"
                  v-if="compares.length>0"
                />
              </a-col>
            </a-form-item>
          </div>
        </a-form>
      </a-tab-pane>
    </a-tabs>
  </div>
</template>

<script>
import jsplumb from 'jsplumb'

export default {
  props: ['plumb', 'flowData', 'select', 'fieldNames'],
  components: {
    jsplumb,
  },
  data() {
    return {
      currentSelect: this.select,
      formItemLayout: {
        labelCol: { span: 6 },
        wrapperCol: { span: 16 },
      },
      compares: this.select.compares,
      setInfo: this.select.setInfo,
      specialName: '',
      // currentDepart: false,
      operations: [
        { id: '>', name: '>' },
        { id: '<', name: '<' },
        { id: '>=', name: '>=' },
        { id: '<=', name: '<=' },
        { id: '=', name: '=' },
        { id: '!=', name: '!=' },
        { id: 'in', name: 'in' },
        { id: 'not in', name: 'not in' },
      ],
      conditions: [
        { id: 'and', name: '并且' },
        { id: 'or', name: '或者' },
      ],
      specialShow: false,
      nodeDesignateData: [
        { id: 'ALL_USER', name: '所有用户' },
        { id: 'SPECIAL_USER', name: '指定用户' },
        { id: 'SPECIAL_ROLE', name: '指定角色' },
        // { id: 'RUNTIME_SPECIAL_ROLE', name: '运行时指定角色' },
        // { id: 'RUNTIME_SPECIAL_USER', name: '运行时指定用户' },
      ],
      // 驳回类型
      // nodeRejectType: [
      //   { id: '0', name: '前一步' },
      //   { id: '1', name: '第一步' },
      // ],
      nodeConfluenceType: [
        { id: 'all', name: '全部通过' },
        { id: 'one', name: '至少有一个通过' },
      ],
      activeKey: 'flow-attr',
      currentCompare: null,
    }
  },
  methods: {
    nameChange(e) {
      this.currentSelect.name = e.target.value
      this.currentSelect.setInfo.nodeName = this.currentSelect.name
      this.currentSelect.setInfo.nodeCode = this.currentSelect.name
    },
    linkLabelChange(e) {
      const that = this
      let label = e.target.value

      that.currentSelect.label = label
      let conn = that.plumb.getConnections({
        source: that.currentSelect.sourceId,
        target: that.currentSelect.targetId,
      })[0]
      if (label != '') {
        conn.setLabel({
          label: label,
          cssClass: 'linkLabel',
        })
      } else {
        let labelOverlay = conn.getLabelOverlay()
        if (labelOverlay) conn.removeOverlay(labelOverlay.id)
      }
    },
    linkThirdPartyUrlChange(e) {
      const that = this
      let thirdPartyUrl = e.target.value
      that.currentSelect.setInfo.thirdPartyUrl = thirdPartyUrl
    },
    // 驳回类型切换
    // nodeRejectTypeChange(e) {
    //   const that = this
    //   let nodeRejectType = e
    //   that.setInfo.nodeRejectType = nodeRejectType
    //   that.currentSelect.setInfo.nodeRejectType = nodeRejectType
    // },
    nodeRejectStepChange(e) {
      const that = this
      let nodeRejectStep = e
      that.setInfo.nodeRejectStep = nodeRejectStep
      that.currentSelect.setInfo.nodeRejectStep = nodeRejectStep
    },
    nodeConfluenceTypeChange(e) {
      const that = this
      let nodeConfluenceType = e
      that.setInfo.nodeConfluenceType = nodeConfluenceType
      that.currentSelect.setInfo.nodeConfluenceType = nodeConfluenceType
    },
    addList() {
      const that = this
      let compares = that.compares
      compares.push({ fieldType: 'form', value: '', name: '', condition: 'and', valueName: '', operation: '=' })
      that.compares = compares
      that.currentSelect.compares = compares
    },
    subList(e) {
      const that = this
      let compares = that.compares
      if (compares.length == 1) {
        compares = [{ fieldType: 'form', value: '', name: '', condition: 'and', valueName: '', operation: '=' }]
      } else {
        compares.splice(e, 1)
      }
      that.compares = compares
      that.currentSelect.compares = compares
    },
    fieldNameChange(i, e) {
      const that = this
      const compares = that.compares
      compares[i].fieldName = e
      that.currentSelect.compares = compares
    },
    conditionChange(i, e) {
      const that = this
      const compares = that.compares
      compares[i].condition = e
      that.currentSelect.compares = compares
    },
    operationChange(i, e) {
      const that = this
      const compares = that.compares
      compares[i].operation = e
      that.currentSelect.compares = compares
    },
    valueChange(i, e) {
      const that = this
      const compares = that.compares
      that.currentSelect.compares = compares
    },
    //打开选择用户界面
    setUser(value) {
      const that = this
      if (that.currentSelect.type == 'link') {
        that.currentCompare = value
        this.$emit('openUser', null)
      } else {
        that.currentCompare = null
        this.$emit('openUser', {
          rowKeyList: that.setInfo.nodeDesignateData,
          rowDataList: this.setInfo.selectRows || [],
        })
      }
    },
    //打开选择角色界面
    setRole(value) {
      const that = this
      if (that.currentSelect.type == 'link') {
        that.currentCompare = value
        this.$emit('openUser', null)
      } else {
        that.currentCompare = null
        this.$emit('openRole', {
          rowKeyList: that.setInfo.nodeDesignateData,
          rowDataList: this.setInfo.selectRows || [],
        })
      }
    },
    setFlowAttrForm(record, type) {
      const that = this
      const nodeDesignateData = []
      const nodeDesignateName = []
      if (record.length) {
        record.forEach((item) => {
          nodeDesignateData.push(item.id)
          nodeDesignateName.push(item.name)
        })
      }
      if (that.currentSelect.type == 'link') {
        that.compares[that.currentCompare].value = nodeDesignateData.join(',')
        that.compares[that.currentCompare].valueName = nodeDesignateName.join(',')
        that.currentSelect.compares = that.compares
      } else {
        that.setInfo.selectRows = record.length > 0 ? record : []
        that.setInfo.nodeDesignateData = nodeDesignateData
        that.setInfo.nodeDesignateName = nodeDesignateName
        that.currentSelect.setInfo.nodeDesignateName = nodeDesignateName
        that.currentSelect.setInfo.nodeDesignateData = nodeDesignateData
        if (nodeDesignateName.length > 0) {
          that.specialName = nodeDesignateName.join(',')
        } else {
          that.specialName = ''
          that.setInfo.nodeDesignate = 'ALL_USER'
          this.currentSelect.setInfo.nodeDesignate = 'ALL_USER'
          this.specialShow = false
        }
      }
    },
    // currentDepartChange(e) {
    //   const that = this
    //   let currentDepart = e
    //   that.currentDepart = currentDepart
    //   that.setInfo.currentDepart = currentDepart
    //   that.currentSelect.setInfo = that.setInfo
    // },
    nodeDesignateChange(e) {
      const that = this
      let nodeDesignate = e
      that.setInfo.nodeDesignate = nodeDesignate
      that.setInfo.nodeDesignateData = []
      that.setInfo.nodeDesignateName = []
      that.setInfo.selectRows = []
      // that.setInfo.currentDepart = false
      // that.currentDepart = false
      that.specialName = ''
      if (nodeDesignate == 'SPECIAL_USER') {
        that.specialShow = true
      } else if (nodeDesignate == 'SPECIAL_ROLE') {
        that.specialShow = true
      } else {
        that.specialShow = false
      }
      that.currentSelect.setInfo.nodeDesignate = nodeDesignate
      that.currentSelect.setInfo.selectRows = []
    },

    // 线数据进行排序
    sortLinkList(linkList) {
      linkList.sort((next, pre) => {
        if (next.sourceId.includes('start')) {
          return -1 //next排到前面
        } else if (pre.targetId.includes('end')) {
          return -1 // pre排到后面
        } else if (next.targetId === pre.sourceId) {
          return -1 //pre排到前面
        } else {
          return 1 // 保持原有顺序
        }
      })
      return linkList
    },

    // 根据线数据顺序进行节点顺序排列
    sortNodeList(nodeList, linkList) {
      if (linkList.length) {
        const _newLinkList = linkList.map((item) => item.sourceId)
        _newLinkList.push(linkList[linkList.length - 1].targetId)
        nodeList.sort((next, pre) => {
          return _newLinkList.indexOf(next.id) - _newLinkList.indexOf(pre.id)
        })
        return nodeList
      }
    },
  },
  watch: {
    select(val) {
      console.log(val, '当前选择的节点')
      this.currentSelect = val
      if (this.currentSelect.type == 'link') {
        this.activeKey = 'link-attr'
        if (!this.currentSelect.compares || this.currentSelect.compares.length == 0) {
          this.currentSelect.compares = [
            { fieldType: 'form', value: '', name: '', condition: 'and', valueName: '', operation: '=' },
          ]
        }
        this.compares = this.currentSelect.compares
      } else if (!this.currentSelect.type) {
        this.activeKey = 'flow-attr'
      } else {
        this.activeKey = 'node-attr'
        if (this.currentSelect.type == 'node' || this.currentSelect.type == 'join') {
          if (!this.currentSelect.setInfo) {
            this.currentSelect.setInfo = {}
            this.currentSelect.setInfo.nodeCode = this.currentSelect.name
            this.currentSelect.setInfo.nodeName = this.currentSelect.name
          }
          if (!this.currentSelect.setInfo.nodeDesignate || this.currentSelect.setInfo.nodeDesignate == 'ALL_USER') {
            this.currentSelect.setInfo.nodeDesignate = 'ALL_USER'
            this.specialShow = false
            this.specialName = ''
          }

          // 驳回节点没有值 ---给默认值
          if (!this.currentSelect.setInfo.nodeRejectStep && this.allNodeList && this.allNodeList.length) {
            this.currentSelect.setInfo.nodeRejectStep = this.allNodeList[this.allNodeList.length - 1].id
          }
          if (!this.currentSelect.setInfo.nodeConfluenceType) {
            this.currentSelect.setInfo.nodeConfluenceType = 'all'
          }
          if (
            this.currentSelect.setInfo.nodeDesignate == 'SPECIAL_USER' ||
            this.currentSelect.setInfo.nodeDesignate == 'SPECIAL_ROLE'
          ) {
            this.specialShow = true
            this.specialName = this.currentSelect.setInfo.nodeDesignateName.join(',')
          }
          this.setInfo = this.currentSelect.setInfo
        }
      }
    },
    currentSelect: {
      handler(val) {
        this.$emit('update:select', val)
      },
      deep: true,
    },
  },
  computed: {
    allNodeList() {
      console.log(this.flowData, 'this.flowData---this.flowData')
      const linkList = this.sortLinkList(this.flowData.linkList)
      let nodeList = this.flowData.nodeList
      if (linkList.length) {
        const _newLinkList = linkList.map((item) => item.sourceId)
        _newLinkList.push(linkList[linkList.length - 1].targetId)
        nodeList.sort((next, pre) => {
          return _newLinkList.indexOf(next.id) - _newLinkList.indexOf(pre.id)
        })
        if (this.select && this.select.id) {
          const _index = nodeList.findIndex((v) => v.id === this.select.id)
          nodeList = nodeList.slice(0, _index)
        }
        console.log(nodeList, 'nodeList----nodeList')
        return nodeList
      }
    },
  },
}
</script>

<style lang="less" scoped>
@import '../style/flow-attr.less';
</style>

modules文件下的FlowNode.vue文件代码如下

javascript 复制代码
<template>
  <div
    v-if="node.type == 'start round mix'"
    :id="node.id"
    class="common-circle-node"
    :class="{ active: isActive() }"
    :style="{ top: node.y + 'px', left: node.x + 'px',
    	cursor: currentTool.type == 'drag' ? 'move' : (currentTool.type == 'connection' ? 'crosshair' :
    																								(currentTool.type == 'zoom-in' ? 'zoom-in' :
    																								(currentTool.type == 'zoom-out' ? 'zoom-out' : 'default'))),
      background:verificationStyle[!!activityId?'1':'4'] }"
    @click.stop="selectNode"
    @contextmenu.stop="showNodeContextMenu"
  >
    <a-icon type="play-circle" />
    {{ node.name }}
  </div>

  <div
    v-else-if="node.type == 'end round'"
    :id="node.id"
    class="common-circle-node"
    :class="{ active: isActive() }"
    :style="{ top: node.y + 'px', left: node.x + 'px',
    	cursor: currentTool.type == 'drag' ? 'move' : (currentTool.type == 'connection' ? 'crosshair' :
    																								(currentTool.type == 'zoom-in' ? 'zoom-in' :
    																								(currentTool.type == 'zoom-out' ? 'zoom-out' : 'default'))),
      background:verificationStyle[!!activityId&&activityId==node.id?'0':(!!node.setInfo&&!!node.setInfo.Taged?node.setInfo.Taged.toString():'4')] }"
    @click.stop="selectNode"
    @contextmenu.stop="showNodeContextMenu"
  >
    <a-icon type="close-circle" />
    {{ node.name }}
  </div>

  <div
    v-else-if="node.type == 'node'"
    :id="node.id"
    class="common-rectangle-node"
    :class="{ active: isActive() }"
    :style="{ top: node.y + 'px', left: node.x + 'px',
    		cursor: currentTool.type == 'drag' ? 'move' : (currentTool.type == 'connection' ? 'crosshair' :
    																								(currentTool.type == 'zoom-in' ? 'zoom-in' :
    																								(currentTool.type == 'zoom-out' ? 'zoom-out' : 'default'))),
      background:verificationStyle[!!activityId&&activityId==node.id?'0':(!!node.setInfo&&!!node.setInfo.Taged?node.setInfo.Taged.toString():'4')] }"
    @click.stop="selectNode"
    @contextmenu.stop="showNodeContextMenu"
  >
    <a-icon type="setting" />
    {{ node.name }}
  </div>

  <div
    v-else-if="node.type == 'fork'"
    :id="node.id"
    class="common-rectangle-node"
    :class="{ active: isActive() }"
    :style="{ top: node.y + 'px', left: node.x + 'px',
    		cursor: currentTool.type == 'drag' ? 'move' : (currentTool.type == 'connection' ? 'crosshair' :
    																								(currentTool.type == 'zoom-in' ? 'zoom-in' :
    																								(currentTool.type == 'zoom-out' ? 'zoom-out' : 'default'))),
      background:verificationStyle[!!activityId&&activityId==node.id?'0':(!!node.setInfo&&!!node.setInfo.Taged?node.setInfo.Taged.toString():'4')] }"
    @click.stop="selectNode"
    @contextmenu.stop="showNodeContextMenu"
  >
    <a-icon type="fullscreen" />
    {{ node.name }}
  </div>

  <div
    v-else-if="node.type == 'join'"
    :id="node.id"
    class="common-rectangle-node"
    :class="{ active: isActive() }"
    :style="{ top: node.y + 'px', left: node.x + 'px',
    		cursor: currentTool.type == 'drag' ? 'move' : (currentTool.type == 'connection' ? 'crosshair' :
    																								(currentTool.type == 'zoom-in' ? 'zoom-in' :
    																								(currentTool.type == 'zoom-out' ? 'zoom-out' : 'default'))),
      background:verificationStyle[!!activityId&&activityId==node.id?'0':(!!node.setInfo&&!!node.setInfo.Taged?node.setInfo.Taged.toString():'4')] }"
    @click.stop="selectNode"
    @contextmenu.stop="showNodeContextMenu"
  >
    <a-icon type="fullscreen-exit" />
    {{ node.name }}
  </div>

  <div
    v-else-if="node.type == 'x-lane'"
    :id="node.id"
    class="common-x-lane-node"
    :class="{ active: isActive() }"
    :style="{ top: node.y + 'px', left: node.x + 'px', height: node.height + 'px', width: node.width + 'px',
    		cursor: currentTool.type == 'zoom-in' ? 'zoom-in' : (currentTool.type == 'zoom-out' ? 'zoom-out' : 'default') }"
  >
    <div
      class="lane-text-div"
      :style="{ cursor: currentTool.type == 'drag' ? 'move' : 'default' }"
      @click.stop="selectNode"
      @contextmenu.stop="showNodeContextMenu"
    >
      <span class="lane-text">{{ node.name }}</span>
    </div>
  </div>

  <div
    v-else-if="node.type == 'y-lane'"
    :id="node.id"
    class="common-y-lane-node"
    :class="{ active: isActive() }"
    :style="{ top: node.y + 'px', left: node.x + 'px', height: node.height + 'px', width: node.width + 'px',
    		cursor: currentTool.type == 'zoom-in' ? 'zoom-in' : (currentTool.type == 'zoom-out' ? 'zoom-out' : 'default') }"
  >
    <div
      class="lane-text-div"
      :style="{ cursor: currentTool.type == 'drag' ? 'move' : 'default' }"
      @click.stop="selectNode"
      @contextmenu.stop="showNodeContextMenu"
    >
      <span class="lane-text">{{ node.name }}</span>
    </div>
  </div>

  <div v-else></div>
</template>


<script>
import jsplumb from 'jsplumb'
import { flowConfig } from '../config/args-config.js'
import $ from 'jquery'
import 'jquery-ui/ui/widgets/draggable'
import 'jquery-ui/ui/widgets/droppable'
import 'jquery-ui/ui/widgets/resizable'
import { ZFSN } from '../util/ZFSN.js'

export default {
  props: ['select', 'selectGroup', 'node', 'plumb', 'currentTool', 'activityId'],
  components: {
    jsplumb,
  },
  mounted() {
    this.registerNode()
  },
  data() {
    return {
      currentSelect: this.select,
      currentSelectGroup: this.selectGroup,
      verificationStyle: flowConfig.verificationStyle,
    }
  },
  methods: {
    registerNode() {
      const that = this
      that.plumb.draggable(that.node.id, {
        containment: 'parent',
        handle: function (e, el) {
          var possibles = el.parentNode.querySelectorAll(
            '.common-circle-node,.common-rectangle-node,.common-diamond-node,.lane-text-div'
          )
          for (var i = 0; i < possibles.length; i++) {
            if (possibles[i] === el || e.target.className == 'lane-text') return true
          }
          return false
        },
        grid: flowConfig.defaultStyle.alignGridPX,
        drag: function (e) {
          if (flowConfig.defaultStyle.isOpenAuxiliaryLine) {
            that.$emit('alignForLine', e)
          }
        },
        stop: function (e) {
          that.node.x = e.pos[0]
          that.node.y = e.pos[1]
          if (that.currentSelectGroup.length > 1) {
            that.$emit('updateNodePos')
          }
          that.$emit('hideAlignLine')
        },
      })

      if (that.node.type == 'x-lane' || that.node.type == 'y-lane') {
        $('#' + that.node.id).resizable({
          minHeight: 200,
          minWidth: 200,
          maxHeight: 2000,
          maxWidth: 2000,
          stop: function (event, ui) {
            that.node.height = ui.size.height
            that.node.width = ui.size.width
          },
        })
      }
      that.currentSelect = that.node
      that.currentSelectGroup = []
    },
    selectNode() {
      const that = this
      that.currentSelect = this.node
      that.$emit('isMultiple', (flag) => {
        if (!flag) {
          that.currentSelectGroup = []
        } else {
          let f = that.currentSelectGroup.filter((s) => s.id == that.node.id)
          if (f.length <= 0) {
            that.plumb.addToDragSelection(that.node.id)
            that.currentSelectGroup.push(that.node)
          }
        }
      })
    },
    showNodeContextMenu(e) {
      this.$emit('showNodeContextMenu', e)
      this.selectNode()
    },
    isActive() {
      const that = this
      if (that.currentSelect.id == that.node.id) return true
      let f = that.currentSelectGroup.filter((n) => n.id == that.node.id)
      if (f.length > 0) return true
      return false
    },
  },
  watch: {
    select(val) {
      this.currentSelect = val
    },
    currentSelect: {
      handler(val) {
        this.$emit('update:select', val)
      },
      deep: true,
    },
    selectGroup(val) {
      this.currentSelectGroup = val
    },
    currentSelectGroup: {
      handler(val) {
        this.$emit('update:selectGroup', val)
      },
      deep: true,
    },
  },
}
</script>

<style lang="less" scoped>
@import '../style/flow-node.less';
</style>

modules文件下Settingmodal.vue文件代码如下

javascript 复制代码
<template>
	<div>
		<a-drawer
			title="设置"
			placement="left"
			:width="600"
			:visible="settingVisible"
			@close="close">
			
			<a-form 
				:form="settingForm"
				layout="horizontal">
				
				<a-divider orientation="left">画布</a-divider>
				<a-form-item label="缩小比例" :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol">
					<a-slider 
						:min="0.05" 
						:max="0.5"
						:step="0.05" 
						:tipFormatter="formatterContainerOnceNarrow" 
						v-decorator="['containerOnceNarrow', {}]"
						@afterChange="setContainerOnceNarrow" />
				</a-form-item>
				<a-form-item label="放大比例" :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol">
					<a-slider 
						:min="0.05" 
						:max="0.5"
						:step="0.05" 
						:tipFormatter="formatterContainerOnceEnlarge" 
						v-decorator="['containerOnceEnlarge', {}]"
						@afterChange="setContainerOnceEnlarge" />
				</a-form-item>
				
				<a-divider orientation="left">连线</a-divider>
				<a-form-item label="类型" :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol">
					<a-select v-decorator="['linkType', {}]" @change="setFlowType">
						<a-select-option value="Bezier">贝塞尔曲线</a-select-option>
						<a-select-option value="Straight">直线</a-select-option>
						<a-select-option value="Flowchart">流程图线</a-select-option>
						<a-select-option value="StateMachine">状态线</a-select-option>
					</a-select>
				</a-form-item>
				<a-form-item label="颜色" :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol">
					<colorPicker v-model="linkColor" @change="setLinkColor" />
				</a-form-item>
				<a-form-item label="粗细" :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol">
					<a-slider 
						:min="1" 
						:max="10"
						v-decorator="['linkThickness', {}]"
						@afterChange="setStrokeWidth" />
				</a-form-item>
				
				<a-divider orientation="left">默认样式</a-divider>
				<a-form-item label="辅助线" :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol">
					<a-switch 
						:checked="isOpenAuxiliaryLine"
						v-decorator="['isOpenAuxiliaryLine', {}]" 
						checkedChildren="开" 
						unCheckedChildren="关" 
						@change='toggleOpenAuxiliaryLine'/>
				</a-form-item>
				<a-form-item label="自动对齐水平间距" :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol">
					<a-slider 
						:min="10" 
						:max="800" 
						:step="5" 
						v-decorator="['alignLevelDistance', {}]" 
						@afterChange="setAlignLevelDistance" />
				</a-form-item>
				<a-form-item label="自动对齐垂直间距" :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol">
					<a-slider 
						:min="10" 
						:max="800" 
						:step="5" 
						v-decorator="['alignVerticalDistance', {}]" 
						@afterChange="setAlignVerticalDistance" />
				</a-form-item>
				<a-form-item label="微移距离" :label-col="formItemLayout.labelCol" :wrapper-col="formItemLayout.wrapperCol">
					<a-slider 
						:min="1" 
						v-decorator="['movePx', {}]" 
						@afterChange="setMovePx" />
				</a-form-item>
			</a-form>
		</a-drawer>
	</div>
</template>

<script>
	import { flowConfig } from '../config/args-config.js'
	
	export default {
		data () {
			return {
				settingVisible: false,
				formItemLayout: {
					labelCol: { span: 6 },
					wrapperCol: { span: 15 }
				},
				initFlag: false,
				settingForm: this.$form.createForm(this),
				isOpenAuxiliaryLine: flowConfig.defaultStyle.isOpenAuxiliaryLine,
				linkColor: flowConfig.jsPlumbInsConfig.PaintStyle.stroke
			}
		},
		methods: {
			init () {
				const that = this;
				that.$nextTick(() => {
					that.settingForm.setFieldsValue({
						movePx: flowConfig.defaultStyle.movePx,
						linkType: flowConfig.jsPlumbInsConfig.Connector[0],
						linkThickness: flowConfig.jsPlumbInsConfig.PaintStyle.strokeWidth,
						alignLevelDistance: flowConfig.defaultStyle.alignSpacing.level,
						alignVerticalDistance: flowConfig.defaultStyle.alignSpacing.vertical,
						containerOnceNarrow: flowConfig.defaultStyle.containerScale.onceNarrow,
						containerOnceEnlarge: flowConfig.defaultStyle.containerScale.onceEnlarge
					});
				});
			},
			open () {
				this.settingVisible = true;
				if (!this.initFlag) {
					this.init();
					this.initFlag = true;
				}
			},
			close () {
				this.settingVisible = false;
			},
			setFlowType (v) {
				flowConfig.jsPlumbInsConfig.Connector[0] = v;
			},
			toggleOpenAuxiliaryLine (flag) {
				this.isOpenAuxiliaryLine = flag;
				flowConfig.defaultStyle.isOpenAuxiliaryLine = flag;
			},
			setMovePx (v) {
				flowConfig.defaultStyle.movePx = v;
			},
			setLinkColor (v) {
				this.linkColor = v;
				flowConfig.jsPlumbInsConfig.PaintStyle.stroke = v;
			},
			setStrokeWidth (v) {
				flowConfig.jsPlumbInsConfig.PaintStyle.strokeWidth = v;
			},
			setAlignLevelDistance (v) {
				flowConfig.defaultStyle.alignSpacing.level = v;
			},
			setAlignVerticalDistance (v) {
				flowConfig.defaultStyle.alignSpacing.vertical = v;
			},
			formatterContainerOnceNarrow (v) {
				return `${v*100}%`;
			},
			setContainerOnceNarrow (v) {
				flowConfig.defaultStyle.containerScale.onceNarrow = v;
			},
			formatterContainerOnceEnlarge (v) {
				return `${v*100}%`;
			},
			setContainerOnceEnlarge (v) {
				flowConfig.defaultStyle.containerScale.onceEnlarge = v;
			}
		}
	}
</script>

<style>
	.m-colorPicker .box {
		z-index: 9999 !important;
		width: 220px !important;
	}
	
	.ant-divider-horizontal.ant-divider-with-text, .ant-divider-horizontal.ant-divider-with-text-left, .ant-divider-horizontal.ant-divider-with-text-right {
		font-weight: 800;
		margin: 24px 0 4px;
	}
</style>

modules文件下ShortcutModal.vue文件代码如下

javascript 复制代码
<template>
	<a-modal 
		title="快捷键大全"
		width="50%"
		:visible="modalVisible"
		okText="确认"
		cancelText="取消"
		@ok="saveSetting"
		@cancel="cancel">
		<a-table
			rowKey="code"
			:columns="columns"
			:dataSource="dataSource">
		</a-table>
	</a-modal>
</template>

<script>
	import { flowConfig } from '../config/args-config.js'
	
	export default {
		data () {
			return {
				modalVisible: false,
				columns: [
					{
						title: '功能',
						align: 'center',
						key: 'shortcutName',
						dataIndex: 'shortcutName',
						width: '50%'
					},
					{
						title: '快捷键',
						align: 'center',
						key: 'codeName',
						dataIndex: 'codeName',
						width: '50%'
					}
				],
				dataSource: []
			}
		},
		methods: {
			open () {
				const that = this;
				
				that.modalVisible = true;
				let obj = Object.assign({}, flowConfig.shortcut);
				for (let k in obj) {
					that.dataSource.push(obj[k]);
				}
			},
			close () {
				this.dataSource = [];
				this.modalVisible = false;
			},
			saveSetting () {
				this.close();
			},
			cancel () {
				this.close();
			}
		}
	}
</script>

<style>
</style>

modules文件下TestModal.vue文件代码如下

javascript 复制代码
<template>
	<div>
		<a-drawer
			title="JSON"
			placement="right"
			:width="600"
			:visible="testVisible"
			@close="onClose">
			
			<div>当前的flowData:</div>
			<json-view 
				:value="flowData"
				:expand-depth=3
				boxed
				copyable/>
			
			<div style="margin-top: 12px;">暂存:</div>
			<a-textarea :autosize="{ minRows: 10, maxRows: 100 }" :value="flowDataJson" @change="editFlowDataJson" />
			
			<a-divider />
			<a-button @click="onLoad">加载</a-button>
			<a-button @click="tempSave" :style="{ marginRight: '8px' }" type="primary">暂存</a-button>
		</a-drawer>
	</div>
</template>

<script>
	import JsonView from 'vue-json-viewer'
	import { flowConfig } from '../config/args-config.js'
	
	export default {
		components: {
			JsonView
		},
		data () {
			return {
				testVisible: false,
				flowData: null,
				flowDataJson: ''
			}
		},
		methods: {
			onClose () {
				this.testVisible = false;
			},
			editFlowDataJson (e) {
				this.flowDataJson = e.target.value;
			},
			tempSave () {
				let tempObj = Object.assign({}, this.flowData);
				tempObj.status = flowConfig.flowStatus.SAVE;
				this.flowDataJson = JSON.stringify(tempObj);
			},		
			clearData () {
				this.$emit('clear123',this.flowDataJson);
			},
			onLoad () {
				this.clearData();
				setTimeout(() => {
					this.$emit('loadFlow', this.flowDataJson);
					this.onClose();
				}, 100)
			}
		}
	}
</script>

<style>
</style>

config文件下args-config.js文件代码如下

javascript 复制代码
export let flowConfig = {
	jsPlumbInsConfig: {
		Connector: [
			"Flowchart",
			{
				gap: 5,
				cornerRadius: 8,
				alwaysRespectStubs: true
			}
		],
		ConnectionOverlays: [
			[
				'Arrow',
				{
					width: 10, 
					length: 10, 
					location: 1
				}
			]
		],
		PaintStyle: {
			stroke: "#2a2929",
			strokeWidth: 2
		},
		HoverPaintStyle: {
			stroke: "#409EFF",
			strokeWidth: 3
		},
		EndpointStyle: {
			fill: "#456",
			stroke: "#2a2929",
			strokeWidth: 1,
			radius: 3
		},
		EndpointHoverStyle: {
			fill: "pink"
		}
	},
	jsPlumbConfig: {
		anchor: {
			default: ["Bottom", "Right", "Top", "Left"]
		},
		conn: {
			isDetachable: false
		},
		makeSourceConfig: {
			filter: "a",
            filterExclude: true,
            maxConnections: -1,
            endpoint: [ "Dot", { radius: 7 } ],
            anchor: ["Bottom", "Right", "Top", "Left"]
		},
		makeTargetConfig: {
			filter: "a",
            filterExclude: true,
            maxConnections: -1,
            endpoint: [ "Dot", { radius: 7 } ],
            anchor: ["Bottom", "Right", "Top", "Left"]
		}
	},
	defaultStyle: {
		dragOpacity: 0.7,
		alignGridPX: [5, 5],
		alignSpacing: {
			level: 100,
			vertical: 100
		},
		alignDuration: 300,
		containerScale: {
			init: 1,
			min: 0.5,
			max: 3,
			onceNarrow: 0.1,
			onceEnlarge: 0.1
		},
		isOpenAuxiliaryLine: true,
		showAuxiliaryLineDistance: 20,
		movePx: 5,
		photoBlankDistance: 200
	},
	// ID的生成类型。1.uuid uuid 2.time_stamp 时间戳 3.sequence 序列 4.time_stamp_and_sequence 时间戳加序列 5.custom 自定义
	idType: 'uuid',
	flowStatus: {
		CREATE: '0',
		SAVE: '1',
		MODIFY: '2',
		LOADING: '3'
	},
	shortcut: {
		multiple: {
			code: 17,
			codeName: 'CTRL',
			shortcutName: '多选',
		},
		dragContainer: {
			code: 32,
			codeName: 'SPACE',
			shortcutName: '画布拖拽',
		},
		scaleContainer: {
			code: 18,
			codeName: 'ALT(firefox下为SHIFT)',
			shortcutName: '画布缩放',
		},
		dragTool: {
			code: 68,
			codeName: 'D',
			shortcutName: '拖拽工具',
		},
		connTool: {
			code: 76,
			codeName: 'L',
			shortcutName: '连线工具',
		},
		zoomInTool: {
			code: 190,
			codeName: '<',
			shortcutName: '放大工具',
		},
		zoomOutTool: {
			code: 188,
			codeName: '>',
			shortcutName: '缩小工具',
		},
		leftMove: {
			code: 37,
			codeName: '←',
			shortcutName: '左移',
		},
		upMove: {
			code: 38,
			codeName: '↑',
			shortcutName: '上移',
		},
		rightMove: {
			code: 39,
			codeName: '→',
			shortcutName: '右移',
		},
		downMove: {
			code: 40,
			codeName: '↓',
			shortcutName: '下移',
		},
		testModal: {
			code: 84,
			codeName: 'CTRL+ALT+P',
			shortcutName: '打开测试页面',
		}
	},
	contextMenu: {
		container: {
			menuName: 'flow-menu',
			axis: {
				x: null,
				y: null
			},
			menulists: [
				// {
				// 	fnHandler: 'flowInfo',
				// 	icoName: 'edit',
				// 	btnName: '流程图信息'
				// },
				{
					fnHandler: 'paste',
					icoName: 'edit',
					btnName: '粘贴'
		        },
		        {
		        	fnHandler: 'selectAll',
					icoName: 'edit',
					btnName: '全选'
		        },
		        // {
		        // 	fnHandler: 'saveFlow',
				// 	icoName: 'edit',
				// 	btnName: '保存流程'
		        // },
		        // {
		        // 	iconName: 'edit',
		        // 	fnHandler: 'addRemark',
		        // 	btnName: '添加备注'
		        // },
		        {
					icoName: 'edit',
					btnName: '对齐方式',
					children: [
						{
							icoName: 'edit',
	                        fnHandler: 'verticaLeft',
	                        btnName: '垂直左对齐'
						},
						{
							icoName: 'edit',
	                        fnHandler: 'verticalCenter',
	                        btnName: '垂直居中'
						},
						{
							icoName: 'edit',
	                        fnHandler: 'verticalRight',
	                        btnName: '垂直右对齐'
						},
						{
							icoName: 'edit',
	                        fnHandler: 'levelUp',
	                        btnName: '水平上对齐'
						},
						{
							icoName: 'edit',
	                        fnHandler: 'levelCenter',
	                        btnName: '水平居中'
						},
						{
							icoName: 'edit',
	                        fnHandler: 'levelDown',
	                        btnName: '水平下对齐'
						}
					]
		        }
			]
		},
		node: {
			menuName: 'node-menu',
			axis: {
				x: null,
				y: null
			},
			menulists: [
				{
					fnHandler: 'copyNode',
					icoName: 'edit',
					btnName: '复制节点'
				},
				{
					fnHandler: 'deleteNode',
					icoName: 'edit',
					btnName: '删除节点'
				}
			]
		},
		link: {
			menuName: 'link-menu',
			axis: {
				x: null,
				y: null
			},
			menulists: [
				{
					fnHandler: 'deleteLink',
					icoName: 'edit',
					btnName: '删除连线'
				}
			]
		}
	},
	verificationStyle:{0:"#5bc0de",//正在处理
								1:"#7ccb7c",//已完成
								2:"#d9534f",//不同意
								3:"#faad14",//驳回或者撤回
								4:"#f4f6fc"},//还未处理
	flowLineAdditions:[{id:'CreatedUserId',name:'申请人'},
						{id:'CreatedOrgId',name:'所属组织'}]//连线条件额外参数
};

config文件下basic-node-config.js文件代码如下

javascript 复制代码
export const tools = [
	{
		type: 'drag',
		icon: 'drag',
		name: '拖拽'
	},
	{
		type: 'connection',
		icon: 'fork',
		name: '连线'
	},
	{
		type: 'zoom-in',
		icon: 'zoom-in',
		name: '放大'
	},
	{
		type: 'zoom-out',
		icon: 'zoom-out',
		name: '缩小'
	}
];

export const commonNodes = [
	{
		type: 'start round mix',
		name: '开始',
		icon: 'play-circle'
	},
	{
		type: 'end round',
		name: '结束',
		icon: 'close-circle'
	},
	{
		type: 'node',
		name: '任务节点',
		icon: 'setting'
	},
	{
		type: 'fork',
		name: '会签开始',
		icon: 'fullscreen'
	},
	{
		type: 'join',
		name: '会签结束',
		icon: 'fullscreen-exit'
	}
];

export const laneNodes = [
	{
		type: 'x-lane',
		name: '横向泳道',
		icon: 'column-width'
	},
	{
		type: 'y-lane',
		name: '纵向泳道',
		icon: 'column-height'
	}
];

util文件下ZFSN.js文件代码如下

javascript 复制代码
import { flowConfig } from '../config/args-config.js'

export let ZFSN = {
	seqNo: 1,
	consoleLog: function(strArr) {
		let log = '';
		for (let i = 0, len = strArr.length; i < len; i++) {
			log += strArr[i] + '\n';
		}
		//console.log('%c' + log, 'color: red; font-weight: bold;');
	},
	getId: function() {
		let idType = flowConfig.idType;
		if (typeof idType == 'string') {
			if (idType == 'uuid') {
				return this.getUUID();
			} else if (idType == 'time_stamp') {
				return this.getTimeStamp();
			}
		} else if (idType instanceof Array) {
			if (idType[0] == 'time_stamp_and_sequence') {
				return this.getSequence(idType[1]);
			} else if (idType[0] == 'time_stamp_and_sequence') {
				return this.getTimeStampAndSequence(idType[1]);
			} else if (idType[0] == 'custom') {
				return idType[1]();
			}
		}
	},
	getUUID: function() {
		let s = [];
		let hexDigits = "0123456789abcdef";
		for(let i = 0; i < 36; i++) {
			s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
		}
		s[14] = "4";
		s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
		s[8] = s[13] = s[18] = s[23] = "-";

		let uuid = s.join("");
		return uuid.replace(/-/g, '');
	},
	getTimeStamp: function() {
		return new Date().getTime();
	},
	getSequence: function(seqNoLength) {
		let zeroStr = new Array(seqNoLength).fill('0').join('');
		return (zeroStr + (this.seqNo++)).slice(-seqNoLength);
	},
	getTimeStampAndSequence: function(seqNoLength) {
		return this.getTimeStamp() + this.getSequence(seqNoLength);
	},
	add: function(a, b) {
		let c, d, e;
		try {
			c = a.toString().split(".")[1].length;
		} catch (f) {
			c = 0;
		}
		try {
			d = b.toString().split(".")[1].length;
		} catch (f) {
			d = 0;
		}
		return e = Math.pow(10, Math.max(c, d)), (this.mul(a, e) + this.mul(b, e)) / e;
	},
	sub: function(a, b) {
		let c, d, e;
		try {
			c = a.toString().split(".")[1].length;
		} catch (f) {
			c = 0;
		}
		try {
			d = b.toString().split(".")[1].length;
		} catch (f) {
			d = 0;
		}
		return e = Math.pow(10, Math.max(c, d)), (this.mul(a, e) - this.mul(b, e)) / e;
	},
	mul: function(a, b) {
		let c = 0, d = a.toString(), e = b.toString();
		try {
			c += d.split(".")[1].length;
		} catch (f) {}
		try {
			c += e.split(".")[1].length;
		} catch (f) {}
		return Number(d.replace(".", "")) * Number(e.replace(".", "")) / Math.pow(10, c);
	},
	div: function(a, b) {
		let c, d, e = 0, f = 0;
		try {
			e = a.toString().split(".")[1].length;
		} catch (g) {}
		try {
			f = b.toString().split(".")[1].length;
		} catch (g) {}
		return c = Number(a.toString().replace(".", "")), d = Number(b.toString().replace(".", "")), this.mul(c / d, Math.pow(10, f - e));
	}
};

style文件下flow-area.less文件代码如下

javascript 复制代码
@active-color: #409EFF;

.btn-wrapper-simple {
    height: 24px !important;
    line-height: 24px !important;
}
.vue-contextmenu-listWrapper {
    padding-left: 1px !important;
}
.child-ul-wrapper {
    padding-left: 1px !important;
}

.flow-container {
    width: 1000%;
    height: 1000%;
    position: relative;
    transition: transform 0.5s ease 0s,transform-origin 0.5s ease 0s;
    
    &.grid {
        background-image: -webkit-linear-gradient(90deg, rgba(235, 235, 235, 1) 5%, rgba(0, 0, 0, 0) 5%);
        background-image: -moz-linear-gradient(90deg, rgba(235, 235, 235, 1) 5%, rgba(0, 0, 0, 0) 5%);
        background-image: -o-linear-gradient(90deg, rgba(235, 235, 235, 1) 5%, rgba(0, 0, 0, 0) 5%);
        background-image: -webkit-gradient(linear, 0 100%, 0 0, color-stop(0.05, rgba(235, 235, 235, 1)), color-stop(0.05, rgba(0, 0, 0, 0)));
        background-image: linear-gradient(90deg, rgba(235, 235, 235, 1) 5%, rgba(0, 0, 0, 0) 5%),linear-gradient(rgba(235, 235, 235, 1) 5%, rgba(0, 0, 0, 0) 5%);
        background-size: 1rem 1rem;
    }
    
    &.zoomIn {
        cursor: zoom-in;
    }
    &.zoomOut {
        cursor: zoom-out;
    }
    &.canScale {
        cursor: url(../assets/search.png), default;
    }
    &.canDrag {
        cursor: grab;
    }
    &.canMultiple {
        cursor: url(../assets/multip-pointer.png), default;
    }
}

.rectangle-multiple {
    position: absolute;
    border: 1px dashed #31676f;
    background-color: #0cceea29;
}

.flow-container-active {
    background-color: #e4e4e438;
    cursor: crosshair;
}

.auxiliary-line-x {
    position: absolute;
    border: 0.5px solid @active-color;
    width: 100%;
    z-index: 9999;
}
.auxiliary-line-y {
    position: absolute;
    border: 0.5px solid @active-color;
    height: 100%;
    z-index: 9999;
}

.link-active {
    outline: 2px dashed @active-color;
}

.container-scale {
    position: absolute;
    top: 0;
    left: 5px;
}

.mouse-position {
    position: absolute;
    bottom: 0;
    right: 5px;
}

.common-remarks {
    width: 100px;
    height: 150px;
    position: absolute;
    background-color: #ffffaa;
}
::v-deep.customMultiMenuClass {
  .context-menu-list{
    .btn-wrapper-simple{
      height: auto;
    }
    .has-child{
      .float-status-2{
        padding-left: 0;
      }
    }
  }
  // .float-status-2 {
  //   padding-left: 0;
  //   .btn-wrapper-simple {
  //     height: auto;
  //   }
  // }
}

style文件下flow-designer.less文件代码如下

javascript 复制代码
@primary-color: #409EFF;

.container {
    border: 2px solid #e4e7ed;
    height: 100%;
    
    moz-user-select: -moz-none;
    -moz-user-select: none;
    -o-user-select:none;
    -khtml-user-select:none;
    -webkit-user-select:none;
    -ms-user-select:none;
    user-select:none;
}

.select-area {
    border-right: 1px solid #e4e7ed;
}

.header-option {
    background: white;
    height: 36px;
    line-height: 36px;
    border-bottom: 2px solid #e4e7ed;
    text-align: right;
}

.header-option-button {
    border: 0;
    margin-left: 8px;
}

.flowContent {
    background: #fafafa;
    height: 100%;
    border: 2px dashed rgba(170,170,170,0.7);
}

.ant-layout-footer {
    padding: 4px 8px;
}
.foot {
    height: auto;
    text-align: center;
}

.attr-area {
    border-left: 1px solid #e4e7ed;
    min-height:350px;
}

.tag {
    margin: 6px;
}

.tool-item {
    background: #f4f6fc;
    height: 32px;
    line-height: 32px;
    margin: 5px;
    padding-left: 8px;
    text-align: center;
    cursor: pointer;
    
    &:hover{
        background: #d2d3d6;
    }
    
    &.active {
        background: black;
    }
}

.node-item {
    background: #f4f6fc;
    height: 32px;
    line-height: 32px;
    margin: 5px;
    padding-left: 8px;
    text-align: left;
    cursor: move;
    min-width: 80px;
    
    &:hover{
        color: @primary-color;
        outline: 1px dashed @primary-color;
    }
}

.ant-list-grid .ant-list-item {
    margin-bottom: 8px;
}

.linkLabel {
    background-color: white;
    padding: 1px;
    border: 1px solid #346789;
    border-radius: 5px;
    opacity: 0.8;
    z-index: 3;
}
::v-deep.customMenuClass {
  padding-left: 0;

  .context-menu-list {
    .btn-wrapper-simple {
      height: auto;
    }
  }
}

style文件下flow-node.less文件代码如下

javascript 复制代码
@common-node-bg: #f4f6fc;
@common-node-bg-hover: #f4f6fcb0;
@common-node-active: #409EFF;

.common-circle-node {
    position: absolute;
    height: 50px;
    width: 100px;
    line-height: 50px;
    text-align: center;
    border: 1px solid #777;
    border-radius: 100px;
    background-color: @common-node-bg;
    white-space: nowrap;
    &:hover{
        background-color: @common-node-bg-hover;
        z-index: 2;
    }

    &.active {
        outline: 2px dashed @common-node-active;
        outline-offset: 0px;
    }
}

.common-rectangle-node {
    position: absolute;
    height: 50px;
    width: 120px;
    line-height: 50px;
    text-align: center;
    border: 1px solid #777;
    border-radius: 5px;
    background-color: @common-node-bg;
    white-space: nowrap;

    &:hover {
        background-color: @common-node-bg-hover;
        z-index: 2;
    }

    &.active {
        outline: 2px dashed @common-node-active;
        outline-offset: 0px;
    }
}

.common-diamond-node {
    position: absolute;
    height: 50px;
    width: 50px;
    line-height: 50px;
    text-align: center;
    border: 1px solid #777;
    border-radius: 3px;
    background-color: @common-node-bg;
    transform: rotate(45deg);
    white-space: nowrap;

    &:before {
        position: absolute;
        content: '网关';
        transform: rotate(-45deg);
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
    }

    &:hover {
        background-color: @common-node-bg-hover;
        z-index: 2;
    }

    &.active {
        outline: 2px dashed @common-node-active;
        outline-offset: 0px;
    }
}

.common-x-lane-node {
    position: absolute;
    text-align: center;
    border: 1px solid #777;
    border-radius: 2px;
    z-index: -1;

    &.active {
        outline: 2px dashed @common-node-active;
        outline-offset: 0px;
        .ui-resizable-se{
            z-index: 90;
            width: 8px;
            height: 8px;
            border: 1px;
            right: -5px;
            bottom: -5px;
            cursor: se-resize;
            position:absolute; 
        }
        .ui-resizable-s{
            z-index: 90;
            width: 100%;
            height: 15px;
            border: 1px;
            right: 5px;
            bottom: -5px;
            cursor: s-resize;
            position:absolute; 
        }
        .ui-resizable-e{
            z-index: 90;
            width: 15px;
            height: 100%;
            border: 1px;
            right: -5px;
            bottom: 5px;
            cursor: e-resize;
            position:absolute; 
        }
    }

    .lane-text-div {
        width: 18px;
        height: 100%;
        position: absolute;
        display: table;
        border-right: 1px solid #777;
        background-color: @common-node-bg;

        &:hover {
            z-index: 2;
        }

        .lane-text {
            word-wrap: break-word;
            display: table-cell;
            vertical-align: middle;
            font-size: 0.8em;
        }
    }
}

.common-y-lane-node {
    position: absolute;
    text-align: center;
    border: 1px solid #777;
    border-radius: 2px;
    z-index: -1;

    &.active {
        outline: 2px dashed @common-node-active;
        outline-offset: 0px;
        .ui-resizable-se{
            z-index: 90;
            width: 8px;
            height: 8px;
            border: 1px;
            right: -5px;
            bottom: -5px;
            cursor: se-resize;
            position:absolute; 
        }
        .ui-resizable-s{
            z-index: 90;
            width: 100%;
            height: 15px;
            border: 1px;
            right: 5px;
            bottom: -5px;
            cursor: s-resize;
            position:absolute; 
        }
        .ui-resizable-e{
            z-index: 90;
            width: 15px;
            height: 100%;
            border: 1px;
            right: -5px;
            bottom: 5px;
            cursor: e-resize;
            position:absolute; 
        }
    }

    .lane-text-div {
        width: 100%;
        height: 18px;
        position: absolute;
        display: table;
        border-bottom: 1px solid #777;
        background-color: @common-node-bg;

        &:hover {
            z-index: 2;
        }

        .lane-text {
            word-wrap: break-word;
            display: table-cell;
            font-size: 0.8em;
        }
    }
}

.node-icon {
    position: absolute;
    top: 3px;
    left: 3px;
}
相关推荐
TMS320VC5257H10 分钟前
通义灵码生成的流程图是黑色背景怎么办
流程图·通义灵码·mermaid
TttHhhYy1 天前
vue写后台管理系统,有个需求将所有的$message消息提示换成确认框来增强消息提示效果,遇到嵌套过多的情况,出现某些问题
前端·javascript·vue.js·anti-design-vue
安的列斯凯奇2 天前
苍穹外卖的分层所用到的技术以及工具+jwt令牌流程图(jwt验证)
流程图
小阮的学习笔记6 天前
Vue3中使用LogicFlow实现简单流程图
javascript·vue.js·流程图
爱分享的淘金达人8 天前
2025年山东省考报名流程图解
java·考研·spring·eclipse·tomcat·流程图
烟雨国度11 天前
Spring MVC 完整生命周期和异常处理流程图
spring·mvc·流程图
冰淇淋噢!12 天前
一般公司流程图详情版
流程图
半块菠萝16 天前
html简易流程图
css·html·流程图
寰梦20 天前
上传Gitee仓库流程图
gitee·流程图
技术路上的苦行僧20 天前
Spring源码解析(35)之Spring全体系源码流程图
java·spring·流程图·1024程序员节