<template>
  <div v-loading="loading" v-if="easyFlowVisible" class="box">
    <div id="flowContainer" class="container">
      <template v-for="node in data.nodeList">
        <flow-node
          v-show="node.show"
          :id="node.id"
          :node="node"
          :key="node.id"
          :nodeData="nodeData"
          @deleteNode="deleteNode"
          @changeNodeSite="changeNodeSite"
          @nodeRightMenu="nodeRightMenu"
          @editNode="editNode"
        ></flow-node>
      </template>
    </div>

    <flow-info v-if="flowInfoVisible" ref="flowInfo" :data="data"></flow-info>
    <flow-node-form v-if="nodeFormVisible" ref="nodeForm"></flow-node-form>

    <div class="mask"></div>
  </div>
</template>

<script>
import draggable from 'vuedraggable'
import { jsPlumb } from 'jsplumb'
import flowNode from '@/components/flow/node'
import flowTool from '@/components/flow/tool'
import FlowInfo from '@/components/flow/info'
import FlowNodeForm from './node_form'
import lodash from 'lodash'
import API from '@/api/build-management/index'
import { SvgTemperature } from '@hui/svg-icon'

export default {
  name: 'easyFlow',
  data () {
    return {
      loading: false.jsPlumb: null./ / jsPlumb instance
      easyFlowVisible: true.flowInfoVisible: false.nodeFormVisible: false.index: 1.// Default Settings
      jsplumbSetting: {
        // Dynamic anchor point, position adaptive
        Anchors: [
          'Top'.'TopCenter'.'TopRight'.'TopLeft'.'Right'.'RightMiddle'.'Bottom'.'BottomCenter'.'BottomRight'.'BottomLeft'.'Left'.'LeftMiddle'].Container: 'flowContainer'.Flowchart; StateMachine; Flowchart
        Connector: 'Flowchart'.// The mouse cannot drag the stripper
        ConnectionsDetachable: false.// The node is not deleted when the line is deleted
        DeleteEndpointsOnDetach: false.// The endpoint of the wire
        // Endpoint: ["Dot", {radius: 5}],
        Endpoint: ['Rectangle', { height: 10.width: 10}].// Line end style
        EndpointStyle: { fill: 'rgba(255,255,255,0)'.outlineWidth: 1 },
        LogEnabled: true.// Whether to enable jsPlumb internal logging
        / / draw the line
        PaintStyle: { stroke: 'black'.strokeWidth: 3 },
        // Draw arrows
        Overlays: [['Arrow', { width: 12.length: 12.location: 1 }]],
        RenderMode: 'svg'
      },
      // jsplumb connection parameters
      jsplumbConnectOptions: {
        isSource: true.isTarget: true
        // static static static static static static static static static static static static static static static static static
        // anchor: 'Continuous'
        // anchor: 'AutoDefault'
      },
      jsplumbSourceOptions: {
        filter:
          '.flow-node-drag' /* "span" for tag, ".classname "for class, "#id" for element ID */.filterExclude: false.anchor: 'Continuous'.allowLoopback: false
      },
      jsplumbTargetOptions: {
        filter:
          '.flow-node-drag' /* "span" for tag, ".classname "for class, "#id" for element ID */.filterExclude: false.anchor: 'Continuous'.allowLoopback: false
      },
      // Check whether the load is complete
      loadEasyFlowFinish: false./ / data
      data: {},
      nodeData: {},
      levelTreeData: []
      // exampleGreyEndpointOptions: {
      // endpoint: 'Rectangle',
      // paintStyle: { width: 25, height: 21, fillStyle: '#666' },
      // isSource: true,
      // connectorStyle: { strokeStyle: '#666' },
      // isTarget: true
      // }}},props: {
    flowData: Object
  },
  components: {
    draggable,
    flowNode,
    flowTool,
    FlowInfo,
    FlowNodeForm
  },
  watch: {
    flowData: {
      // Changing buildLevel or idAndNameDtos updates the legend when it is displayed
      handler: async function (val, oldVal) {
        this.loading = true
        const levelTreeRes = await API.levelTree()
        this.levelTreeData = levelTreeRes.data
        this.dataReloadC(this.transformData(val))
        this.nodeData = val
        setTimeout(() = > {
          this.loading = false
        }, 500)},deep: true.immediate: true
    }
  },
  created () {
    this.loading = true
  },
  async mounted () {
    const levelTreeRes = await API.levelTree()
    this.levelTreeData = levelTreeRes.data
    this.jsPlumb = jsPlumb.getInstance()
    this.$nextTick(() = > {
      // const data = {
      // name: 'process ',
      // nodeList: [
      / / {
      // id: '1',
      // name: 'park ',
      // left: '100px',
      // top: '15px',
      // ico: 'h-icon-user',
      // show: true
      / /},
      / / {
      // id: '2',
      // name: 'school ',
      // left: '100px',
      // top: '140px',
      // ico: 'h-icon-user',
      // show: true
      / /},
      / / {
      // id: '3',
      // name: 'building ',
      // left: '100px',
      // top: '265px',
      // ico: 'h-icon-user',
      // show: true
      / /},
      / / {
      // id: '4',
      // name: 'building ',
      // left: '100px',
      // top: '390px',
      // ico: 'h-icon-user',
      // show: true
      / /},
      / / {
      // id: '5',
      // name: 'floor ',
      // left: '100px',
      // top: '515px',
      // ico: 'h-icon-user',
      // show: true
      / /},
      / / {
      // id: '6',
      // name: 'room ',
      // left: '100px',
      // top: '640px',
      // ico: 'h-icon-user',
      // show: true
      / /}
      / /,
      // lineList: [
      / / {
      // from: '1',
      // to: '2'
      / /},
      / / {
      // from: '2',
      // to: '3'
      / /},
      / / {
      // from: '3',
      // to: '4'
      / /},
      / / {
      // from: '4',
      // to: '5'
      / /},
      / / {
      // from: '5',
      // to: '6'
      / /},
      / / {
      // from: '3',
      // to: '6',
      // anchor: ['Right', 'Left']
      / /},
      / / {
      // from: '3',
      // to: '5',
      // anchor: ['Right', 'Left']
      / /}
      / /]
      // }
      this.nodeData = this.flowData
      this.dataReloadC(this.transformData(this.flowData))
      this.loading = false})},methods: {
    transformData (data) {
      // const data = this.flowData
      // const buildLevelListTemp = this.levelTreeData
      const buildLevelListTemp = this.levelTreeData
      const sortFun = (a, b) = > a.buildLevel - b.buildLevel
      const buildLevelList = buildLevelListTemp.sort(sortFun)
      // Generate a node
      const res = {
        name: 'process'.nodeList: [].lineList: []
      }
      res.nodeList = buildLevelList.map((item, index) = > {
        return {
          id: item.buildTypeId,
          name: item.buildName,
          left: '100px'.top: `The ${15 + 125 * (item.buildLevel - 1)}px`.ico: 'h-icon-user'.show: true.buildLevel: item.buildLevel
        }
      })
      res.nodeList.forEach((item, index) = > {
        if (index + 1 < res.nodeList.length) {
          res.lineList.push({
            from: item.id,
            to: res.nodeList[index + 1].id
          })
        }
      })
      if (
        Array.isArray(data.hierarchyForm.idAndNameDtos) &&
        data.hierarchyForm.idAndNameDtos.length > 0
      ) {
        data.hierarchyForm.idAndNameDtos.forEach((item, index) = > {
          if (
            this.levelTreeData[data.hierarchyForm.buildLevel - 1]! = =undefined
          ) {
            res.lineList.push({
              from: this.levelTreeData[data.hierarchyForm.buildLevel - 1]
                .buildTypeId,
              to: item,
              anchor: ['Right'.'Left'].paintStyle: { stroke: '#E72528'.strokeWidth: 2}})}})}return res
    },
    jsPlumbInit () {
      const _this = this
      this.jsPlumb.ready(function () {
        // Import default configurations
        _this.jsPlumb.importDefaults(_this.jsplumbSetting)
        // Causes the entire jsPlumb to be redrawn immediately.
        _this.jsPlumb.setSuspendDrawing(false.true)
        // Initialize the node
        _this.loadEasyFlow()

        // Single click the connection line,
        _this.jsPlumb.bind('click'.function (conn, originalEvent) {
          console.log('click', conn)

          _this
            .$confirm('Are you sure to delete the clicked line? '.'tip', {
              confirmButtonText: 'sure'.cancelButtonText: 'cancel'.type: 'warning'
            })
            .then(() = > {
              _this.jsPlumb.deleteConnection(conn)
            })
            .catch(() = >{})})/ / the attachment
        _this.jsPlumb.bind('connection'.function (evt) {
          console.log('connection', evt)
          const from = evt.source.id
          const to = evt.target.id
          if (_this.loadEasyFlowFinish) {
            _this.lineList.push({
              from: from.to: to
            })
          }
        })

        // Delete the connection
        _this.jsPlumb.bind('connectionDetached'.function (evt) {
          console.log('connectionDetached', evt)
          _this.deleteLine(evt.sourceId, evt.targetId)
        })

        // Change the connection node of the line
        _this.jsPlumb.bind('connectionMoved'.function (evt) {
          console.log('connectionMoved', evt)
          _this.changeLine(evt.originalSourceId, evt.originalTargetId)
        })

        / / click the endpoint
        // jsPlumb.bind("endpointClick", function (evt) {
        // console.log('endpointClick', evt)
        // })
        //
        // // Double-click Endpoint
        // jsPlumb.bind("endpointDblClick", function (evt) {
        // console.log('endpointDblClick', evt)
        // })

        // contextmenu
        _this.jsPlumb.bind('contextmenu'.function (evt) {
          console.log('contextmenu', evt)
        })

        // beforeDrop
        _this.jsPlumb.bind('beforeDrop'.function (evt) {
          console.log('beforeDrop', evt)
          _this.$message.error('beforeDrop')
          _this.$message({
            message: 'Congratulations, this is a success.'.type: 'success'
          })
          const from = evt.sourceId
          const to = evt.targetId
          if (from === to) {
            _this.$message.error('Cannot connect yourself')
            return false
          }
          if (_this.hasLine(from, to)) {
            _this.$message.error('Cannot repeat connection')
            return false
          }
          if (_this.hashOppositeLine(from, to)) {
            _this.$message.error('No loopback.')
            return false
          }
          return true
        })

        // beforeDetach
        _this.jsPlumb.bind('beforeDetach'.function (evt) {
          console.log('beforeDetach', evt)
        })
      })
    },
    // Load flowchart
    loadEasyFlow () {
      // Initialize the node
      for (var i = 0; i < this.data.nodeList.length; i++) {
        const node = this.data.nodeList[i]
        console.log(node)
        // Set the source point, which can be dragged out to connect to other nodes
        this.jsPlumb.makeSource(node.id, this.jsplumbSourceOptions)
        // // sets the target point, which can be connected by lines drawn from other source points
        this.jsPlumb.makeTarget(node.id, this.jsplumbTargetOptions)
        // this.jsPlumb.addEndpoint(node.id, this.exampleGreyEndpointOptions)
        // Set draggable
        // jsPlumb.draggable(node.id, {
        // containment: 'parent',
        // grid: [10, 10]
        // })

        this.jsPlumb.draggable(node.id, {
          containment: 'parent'
        })

        // jsPlumb.draggable(node.id)
      }

      // Initialize the connection
      for (var i = 0; i < this.data.lineList.length; i++) {
        const line = this.data.lineList[i]
        this.jsPlumb.connect(
          {
            source: line.from,
            target: line.to
          },
          Object.assign({ ... this.jsplumbConnectOptions,anchor: line.anchor,
            paintStyle:
              line.paintStyle === undefined
                ? { stroke: 'lightgray'.strokeWidth: 1 }
                : line.paintStyle
            // connector: ['Bezier']}}))this.$nextTick(function () {
        this.loadEasyFlowFinish = true
      })
    },
    getNodes () {
      console.log(jsPlumb)
      console.log(jsPlumb.Defaults)
    },
    getLines () {
      console.log('line', jsPlumb.getConnections())
    },
    / / delete line
    deleteLine (from, to) {
      this.data.lineList = this.data.lineList.filter(function (line) {
        returnline.from ! = =from&& line.to ! == to }) },// Change the connection
    changeLine (oldFrom, oldTo) {
      this.deleteLine(oldFrom, oldTo)
    },
    // Change the position of the node
    changeNodeSite (data) {
      for (var i = 0; i < this.data.nodeList.length; i++) {
        const node = this.data.nodeList[i]
        if (node.id === data.nodeId) {
          node.left = data.left
          node.top = data.top
        }
      }
    },
    // Add a new node
    addNode (evt, nodeMenu, mousePosition) {
      console.log('Add node', evt, nodeMenu)
      const width = this.$refs.flowTool.$el.clientWidth
      const index = this.index++
      const nodeId = 'node' + index
      var left = mousePosition.left
      var top = mousePosition.top
      if (mousePosition.left < 0) {
        left = evt.originalEvent.layerX - width
      }
      if (mousePosition.top < 0) {
        top = evt.originalEvent.clientY - 50
      }
      var node = {
        id: 'node' + index,
        name: 'nodes' + index,
        left: left + 'px'.top: top + 'px'.ico: nodeMenu.ico,
        show: true
      }
      this.data.nodeList.push(node)
      this.$nextTick(function () {
        this.jsPlumb.makeSource(nodeId, this.jsplumbSourceOptions)

        this.jsPlumb.makeTarget(nodeId, this.jsplumbTargetOptions)

        this.jsPlumb.draggable(nodeId, {
          containment: 'parent'})})},// Whether the line exists
    hasLine (from, to) {
      for (var i = 0; i < this.data.lineList.length; i++) {
        var line = this.data.lineList[i]
        if (line.from === from && line.to === to) {
          return true}}return false
    },
    // Does it contain opposite lines
    hashOppositeLine (from, to) {
      return this.hasLine(to, from)
    },
    nodeRightMenu (nodeId, evt) {
      this.menu.show = true
      this.menu.curNodeId = nodeId
      this.menu.left = evt.x + 'px'
      this.menu.top = evt.y + 'px'
    },
    deleteNode (nodeId) {
      this.$confirm('Sure to delete node' + nodeId + '? '.'tip', {
        confirmButtonText: 'sure'.cancelButtonText: 'cancel'.type: 'warning'.closeOnClickModal: false
      })
        .then(() = > {
          this.data.nodeList = this.data.nodeList.filter(function (node) {
            // return node.id ! == nodeId
            if (node.id === nodeId) {
              node.show = false
            }
            return true
          })
          this.$nextTick(function () {
            console.log('delete' + nodeId)
            this.jsPlumb.removeAllEndpoints(nodeId)
          })
        })
        .catch(() = > {})
      return true
    },
    editNode (nodeId) {
      console.log('Edit node', nodeId)
      this.nodeFormVisible = true
      this.$nextTick(function () {
        this.$refs.nodeForm.init(this.data, nodeId)
      })
    },
    // Process data information
    dataInfo () {
      this.flowInfoVisible = true
      this.$nextTick(function () {
        this.$refs.flowInfo.init()
      })
    },
    dataReload (data) {
      this.easyFlowVisible = false
      this.data.nodeList = []
      this.data.lineList = []
      this.$nextTick(() = > {
        // Get the data in the background and load it
        data = lodash.cloneDeep(data)
        this.easyFlowVisible = true
        this.data = data
        this.$nextTick(() = > {
          this.jsPlumb = jsPlumb.getInstance()
          this.$nextTick(() = > {
            this.jsPlumbInit()
          })
        })
      })
    },
    // Data is reloaded
    dataReloadC (data) {
      this.dataReload(data)
    }
  }
}
</script>

<style lang="scss" scoped>
.box {
  height: calc(100% - 35px);
  position: relative;
  .mask {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    z-index: 1000;
  }
  .container {
    // background-image: linear-gradient(
    //     90deg, / /rgba(0.0.0.0.15) 10%, / /rgba(0.0.0.0) 10%/ / / /)linear-gradient(rgba(0.0.0.0.15) 10%.rgba(0.0.0.0) 10%);
    background-size: 10px 10px;
    height: 100%;
    background-color: rgb(251.251.251);
    /*background-color: #42b983; * /
    position: relative;
  }
  .labelClass {
    background-color: white;
    padding: 5px;
    opacity: 0.7;
    border: 1px solid # 346789;
    /*border-radius: 10px; * /
    cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; }}</style>

Copy the code

flowNode

<template>
  <div
    ref="node"
    :style="flowNodeContainer"
    @mouseenter="showDelete"
    @mouseleave="hideDelete"
    @mouseup="changeNodeSite"
  >
    <! -- <div class="flow-node-header"> -->
    <! -- The icon style in the upper left corner -->
    <! -- <i :class="nodeClass"></i> -->
    <! -- The "Edit" and "Delete" buttons in the upper right corner of the node when the mouse cursor is moved into the node -->
    <! -- <div style="position: absolute; top: 0px; right: 0px; line-height: 25px" v-show="mouseEnter"> <a href="#" style @click="editNode"> <img src="@/assets/edit.png" /> </a>&nbsp; <a href="#" style @click="deleteNode"> <img src="@/assets/delete.png" /> </a> &nbsp; </div>-->
    <! -- </div> -->
    <! -- The body of the node -->
    <! -- <div class="flow-node-body">{{node.name}}</div> -->
    <div class="box">
      <div :class="['flow-node',activeStyle]">
        <div class="flow-node-left">
          <img src="@/assets/img_ Layer ID.png" alt />
          <span>{{node.buildLevel}}</span>
        </div>
        <div class="flow-node-right">
          <span>{{node.name}}</span>
        </div>
      </div>
      <div v-if="activeDot" class="dot"></div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    node: Object.nodeData: Object
  },
  data () {
    return {
      // Control node operation display
      mouseEnter: false}},computed: {
    // Node container style
    flowNodeContainer: {
      get () {
        return {
          position: 'absolute'.width: '274px'.top: this.node.top,
          left: this.node.left,
          boxShadow: this.mouseEnter ? '#66a6e0 0px 0px 12px 0px' : ' '.backgroundColor: 'transparent'
        }
      }
    },
    nodeClass () {
      var nodeclass = {}
      nodeclass[this.node.ico] = true
      nodeclass['flow-node-drag'] = true
      return nodeclass
    },
    activeStyle () {
      return this.node.buildLevel === this.nodeData.hierarchyForm.buildLevel ? 'active' : ' '
    },
    activeDot () {
      return (this.nodeData.hierarchyForm.idAndNameDtos.length > 0) && (
        this.node.buildLevel === this.nodeData.hierarchyForm.buildLevel || this.nodeData.hierarchyForm.idAndNameDtos.find(
          id= > {
            return id === this.node.id
          }
        )
      )
    }
  },
  watch: {
  },
  mounted () {
    console.log(this.node)
    console.log(this.nodeData)
  },
  methods: {
    // Delete a node
    deleteNode () {
      this.$emit('deleteNode'.this.node.id)
    },
    // Edit the node
    editNode () {
      this.$emit('editNode'.this.node.id)
    },
    // Mouse entry
    showDelete () {
      this.mouseEnter = true
    },
    // Mouse away
    hideDelete () {
      this.mouseEnter = false
    },
    // The mouse moves and lifts
    changeNodeSite () {
      // Avoid jitter
      if (this.node.left == this.$refs.node.style.left && this.node.top == this.$refs.node.style.top) {
        return
      }
      this.$emit('changeNodeSite', {
        nodeId: this.node.id,
        left: this.$refs.node.style.left,
        top: this.$refs.node.style.top
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.box {
  width: 100%;
  padding-right: 18px;
  position: relative;
  .dot {
    position: absolute;
    right: 0px;
    top: 18px;
    width: 14px;
    height: 14px;
    border-radius: 7px;
    background: #e72528;
    &:after {
      content: ' ';
      width: 4px;
      height: 4px;
      border-radius: 2px;
      background: #fff;
      position: absolute;
      right: 5px;
      top: 5px; }}.flow-node {
    width: 100%;
    height: 50px;
    background: #ffffff;
    border: 1px solid rgba(0.0.0.0.12);
    border-radius: 2px;
    overflow: hidden;
    position: relative;
    .flow-node-left {
      width: 48px;
      height: 48px;
      // background: rgba(0.0.0.0.9);
      border-radius: 2px NaNpx 2px;
      float: left;
      position: relative;
      img {
        position: relative;
        // top: 2px;
      }
      span {
        position: absolute;
        left: 18px;
        top: 11px;
        width: 8px;
        height: 26px;
        font-family: PingFangSC-Medium;
        font-size: 18px;
        color: rgba(255.255.255.0.9);
        line-height: 26px;
        font-weight: 500; }}.flow-node-right {
      height: 48px;
      line-height: 50px;
      font-family: PingFangSC-Regular;
      font-size: 14px;
      color: rgba(0.0.0.0.7);
      line-height: 20px;
      font-weight: 400;
      float: left;
      span {
        display: inline-block;
        height: 48px;
        line-height: 48px;
        margin-left: 16px;
        max-width: 188px;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis; }}}.active {
    background: rgba(231.37.40.0.2);
    border: 2px solid #e72528;
    box-shadow: 0 3px 6px -4px rgba(0.0.0.0.12),
      0 6px 16px 0 rgba(0.0.0.0.08), 0 9px 28px 8px rgba(0.0.0.0.05);
    border-radius: 2px;
    position: relative; / /}}.flow-node-drag {
//   width: 25px;
//   height: 25px; / / / /}.flow-node-header {
//   background-color: #66a6e0;
//   height: 25px;
//   cursor: pointer;
//   border-top-left-radius: 6px;
//   border-top-right-radius: 6px; / / / /}.flow-node-header a {
//   text-decoration: none;
//   line-height: 25px;
//   vertical-align: middle; / / / /}.flow-node-header a img {
//   line-height: 25px;
//   vertical-align: middle;
//   margin-bottom: 5px; / / / /}.flow-node-body {
//   /*background-color: beige; * /
//   background-color: white;
//   text-align: center;
//   cursor: pointer;
//   height: 25px;
//   line-height: 25px;
//   border-bottom-left-radius: 6px;
//   border-bottom-right-radius: 6px;
// }
</style>

Copy the code