206 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			206 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
<template>
 | 
						|
  <section class="AiFlow">
 | 
						|
    <div ref="lfIns" :style="{height}"/>
 | 
						|
  </section>
 | 
						|
</template>
 | 
						|
 | 
						|
<script>
 | 
						|
 | 
						|
export default {
 | 
						|
  name: "AiFlow",
 | 
						|
  model: {
 | 
						|
    prop: "value",
 | 
						|
    event: "change"
 | 
						|
  },
 | 
						|
  props: {
 | 
						|
    value: String,
 | 
						|
    config: Object,
 | 
						|
    height: {default: '400px'},
 | 
						|
    readonly: Boolean,
 | 
						|
    process: {default: null}
 | 
						|
  },
 | 
						|
  computed: {
 | 
						|
    dndPanelConfigs: () => [
 | 
						|
      {
 | 
						|
        type: 'start',
 | 
						|
        text: '开始',
 | 
						|
        label: '开始',
 | 
						|
        icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAnBJREFUOBGdVL1rU1EcPfdGBddmaZLiEhdx1MHZQXApraCzQ7GKLgoRBxMfcRELuihWKcXFRcEWF8HBf0DdDCKYRZpnl7p0svLe9Zzbd29eQhTbC8nv+9zf130AT63jvooOGS8Vf9Nt5zxba7sXQwODfkWpkbjTQfCGUd9gIp3uuPP8bZ946g56dYQvnBg+b1HB8VIQmMFrazKcKSvFW2dQTxJnJdQ77urmXWOMBCmXM2Rke4S7UAW+/8ywwFoewmBps2tu7mbTdp8VMOkIRAkKfrVawalJTtIliclFbaOBqa0M2xImHeVIfd/nKAfVq/LGnPss5Kh00VEdSzfwnBXPUpmykNss4lUI9C1ga+8PNrBD5YeqRY2Zz8PhjooIbfJXjowvQJBqkmEkVnktWhwu2SM7SMx7Cj0N9IC0oQXRo8xwAGzQms+xrB/nNSUWVveI48ayrFGyC2+E2C+aWrZHXvOuz+CiV6iycWe1Rd1Q6+QUG07nb5SbPrL4426d+9E1axKjY3AoRrlEeSQo2Eu0T6BWAAr6COhTcWjRaYfKG5csnvytvUr/WY4rrPMB53Uo7jZRjXaG6/CFfNMaXEu75nG47X+oepU7PKJvvzGDY1YLSKHJrK7vFUwXKkaxwhCW3u+sDFMVrIju54RYYbFKpALZAo7sB6wcKyyrd+aBMryMT2gPyD6GsQoRFkGHr14TthZni9ck0z+Pnmee460mHXbRAypKNy3nuMdrWgVKj8YVV8E7PSzp1BZ9SJnJAsXdryw/h5ctboUVi4AFiCd+lQaYMw5z3LGTBKjLQOeUF35k89f58Vv/tGh+l+PE/wG0rgfIUbZK5AAAAABJRU5ErkJggg==',
 | 
						|
      },
 | 
						|
      {
 | 
						|
        type: 'task',
 | 
						|
        label: '流程节点',
 | 
						|
        icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAEFVwZaAAAABGdBTUEAALGPC/xhBQAAAqlJREFUOBF9VM9rE0EUfrMJNUKLihGbpLGtaCOIR8VjQMGDePCgCCIiCNqzCAp2MyYUCXhUtF5E0D+g1t48qAd7CCLqQUQKEWkStcEfVGlLdp/fm3aW2QQdyLzf33zz5m2IsAZ9XhDpyaaIZkTS4ASzK41TFao88GuJ3hsr2pAbipHxuSYyKRugagICGANkfFnNh3HeE2N0b3nN2cgnpcictw5veJIzxmDamSlxxQZicq/mflxhbaH8BLRbuRwNtZp0JAhoplVRUdzmCe/vO27wFuuA3S5qXruGdboy5/PRGFsbFGKo/haRtQHIrM83bVeTrOgNhZReWaYGnE4aUQgTJNvijJFF4jQ8BxJE5xfKatZWmZcTQ+BVgh7s8SgPlCkcec4mGTmieTP4xd7PcpIEg1TX6gdeLW8rTVMVLVvb7ctXoH0Cydl2QOPJBG21STE5OsnbweVYzAnD3A7PVILuY0yiiyDwSm2g441r6rMSgp6iK42yqroI2QoXeJVeA+YeZSa47gZdXaZWQKTrG93rukk/l2Al6Kzh5AZEl7dDQy+JjgFahQjRopSxPbrbvK7GRe9ePWBo1wcU7sYrFZtavXALwGw/7Dnc50urrHJuTPSoO2IMV3gUQGNg87IbSOIY9BpiT9HV7FCZ94nPXb3MSnwHn/FFFE1vG6DTby+r31KAkUktB3Qf6ikUPWxW1BkXSPQeMHHiW0+HAd2GelJsZz1OJegCxqzl+CLVHa/IibuHeJ1HAKzhuDR+ymNaRFM+4jU6UWKXorRmbyqkq/D76FffevwdCp+jN3UAN/C9JRVTDuOxC/oh+EdMnqIOrlYteKSfadVRGLJFJPSB/ti/6K8f0CNymg/iH2gO/f0DwE0yjAFO6l8JaR5j0VPwPwfaYHqOqrCI319WzwhwzNW/aQAAAABJRU5ErkJggg==',
 | 
						|
      },
 | 
						|
      {
 | 
						|
        type: 'gateway',
 | 
						|
        label: '条件判断',
 | 
						|
        icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAAHeEJUAAAAABGdBTUEAALGPC/xhBQAAAvVJREFUOBGNVEFrE0EU/mY3bQoiFlOkaUJrQUQoWMGePLX24EH0IIoHKQiCV0G8iE1covgLiqA/QTzVm1JPogc9tIJYFaQtlhQxqYjSpunu+L7JvmUTU3AgmTfvffPNN++9WSA1DO182f6xwILzD5btfAoQmwL5KJEwiQyVbSVZ0IgRyV6PTpIJ81E5ZvqfHQR0HUOBHW4L5Et2kQ6Zf7iAOhTFAA8s0pEP7AXO1uAA52SbqGk6h/6J45LaLhO64ByfcUzM39V7ZiAdS2yCePPEIQYvTUHqM/n7dgQNfBKWPjpF4ISk8q3J4nB11qw6X8l+FsF3EhlkEMfrjIer3wJTLwS2aCNcj4DbGxXTw00JmAuO+Ni6bBxVUCvS5d9aa04+so4pHW5jLTywuXAL7jJ+D06sl82Sgl2JuVBQn498zkc2bGKxULHjCnSMadBKYDYYHAtsby1EQ5lNGrQd4Y3v4Zo0XdGEmDno46yCM9Tk+RiJmUYHS/aXHPNTcjxcbTFna000PFJHIVZ5lFRqRpJWk9/+QtlOUYJj9HG5pVFEU7zqIYDVsw2s+AJaD8wTd2umgSCCyUxgGsS1Y6TBwXQQTFuZaHcd8gAGioE90hlsY+wMcs30RduYtxanjMGal8H5dMW67dmT1JFtYUEe8LiQLRsPZ6IIc7A4J5tqco3T0pnv/4u0kyzrYUq7gASuEyI8VXKvB9Odytv6jS/PNaZBln0nioJG/AVQRZvApOdhjj3Jt8QC8Im09SafwdBdvIpztpxWxpeKCC+EsFdS8DCyuCn2munFpL7ctHKp+Xc5cMybeIyMAN33SPL3ZR9QV1XVwLyzHm6Iv0/yeUuUb7PPlZC4D4HZkeu6dpF4v9j9MreGtMbxMMRLIcjJic9yHi7WQ3yVKzZVWUr5UrViJvn1FfUlwe/KYVfYyWRLSGNu16hR01U9IacajXPei0wx/5BqgInvJN+MMNtNme7ReU9SBbgntovn0kKHpFg7UogZvaZiOue/q1SBo9ktHzQAAAAASUVORK5CYII=',
 | 
						|
      },
 | 
						|
      {
 | 
						|
        type: 'end',
 | 
						|
        text: '结束',
 | 
						|
        label: '结束',
 | 
						|
        icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAA1BJREFUOBFtVE1IVUEYPXOf+tq40Y3vPcmFIdSjIorWoRG0ERWUgnb5FwVhYQSl72oUoZAboxKNFtWiwKRN0M+jpfSzqJAQclHo001tKkjl3emc8V69igP3znzfnO/M9zcDcKT67azmjYWTwl9Vn7Vumeqzj1DVb6cleQY4oAVnIOPb+mKAGxQmKI5CWNJ2aLPatxWa3aB9K7/fB+/Z0jUF6TmMlFLQqrkECWQzOZxYGjTlOl8eeKaIY5yHnFn486xBustDjWT6dG7pmjHOJd+33t0iitTPkK6tEvjxq4h2MozQ6WFSX/LkDUGfFwfhEZj1Auz/U4pyAi5Sznd7uKzznXeVHlI/Aywmk6j7fsUsEuCGADrWARXXwjxWQsUbIupDHJI7kF5dRktg0eN81IbiZXiTESic50iwS+t1oJgL83jAiBupLDCQqwziaWSoAFSeIR3P5Xv5az00wyIn35QRYTwdSYbz8pH8fxUUAtxnFvYmEmgI0wYXUXcCCSpeEVpXlsRhBnCEATxWylL9+EKCAYhe1NGstUa6356kS9NVvt3DU2fd+Wtbm/+lSbylJqsqkSm9CRhvoJVlvKPvF1RKY/FcPn5j4UfIMLn8D4UYb54BNsilTDXKnF4CfTobA0FpoW/LSp306wkXM+XaOJhZaFkcNM82ASNAWMrhrUbRfmyeI1FvRBTpN06WKxa9BK0o2E4Pd3zfBBEwPsv9sQBnmLVbLEIZ/Xe9LYwJu/Er17W6HYVBc7vmuk0xUQ+pqxdom5Fnp55SiytXLPYoMXNM4u4SNSCFWnrVIzKG3EGyMXo6n/BQOe+bX3FClY4PwydVhthOZ9NnS+ntiLh0fxtlUJHAuGaFoVmttpVMeum0p3WEXbcll94l1wM/gZ0Ccczop77VvN2I7TlsZCsuXf1WHvWEhjO8DPtyOVg2/mvK9QqboEth+7pD6NUQC1HN/TwvydGBARi9MZSzLE4b8Ru3XhX2PBxf8E1er2A6516o0w4sIA+lwURhAON82Kwe2iDAC1Watq4XHaGQ7skLcFOtI5lDxuM2gZe6WFIotPAhbaeYlU4to5cuarF1QrcZ/lwrLaCJl66JBocYZnrNlvm2+MBCTmUymPrYZVbjdlr/BxlMjmNmNI3SAAAAAElFTkSuQmCC',
 | 
						|
      }
 | 
						|
    ],
 | 
						|
  },
 | 
						|
  data() {
 | 
						|
    return {
 | 
						|
      flow: null,
 | 
						|
      configWatch: null
 | 
						|
    }
 | 
						|
  },
 | 
						|
  methods: {
 | 
						|
    loadLib() {
 | 
						|
      this.$injectCss("https://cdn.cunwuyun.cn/logicflow/index.css")
 | 
						|
      const load = url => new Promise(resolve => this.$injectLib(url, () => resolve()))
 | 
						|
      let libs = ["https://cdn.cunwuyun.cn/logicflow/logic-flow.js", "https://cdn.jsdelivr.net/npm/@logicflow/extension/lib/BpmnElement.js"]
 | 
						|
      if (!this.readonly) {
 | 
						|
        this.$injectCss("https://cdn.jsdelivr.net/npm/@logicflow/extension/lib/style/index.css")
 | 
						|
        libs = [
 | 
						|
          libs,
 | 
						|
          "https://cdn.jsdelivr.net/npm/@logicflow/extension/lib/Menu.js",
 | 
						|
          "https://cdn.jsdelivr.net/npm/@logicflow/extension/lib/DndPanel.js"
 | 
						|
        ].flat()
 | 
						|
      }
 | 
						|
      return Promise.all(libs.map(e => load(e)))
 | 
						|
    },
 | 
						|
    initModels(lf) {
 | 
						|
      const {StartEventModel, StartEventView, UserTaskView, UserTaskModel, EndEventModel, EndEventView, ExclusiveGatewayModel, ExclusiveGatewayView} = window
 | 
						|
 | 
						|
      class CustomTaskModel extends UserTaskModel {
 | 
						|
        getNodeStyle() {
 | 
						|
          const style = super.getNodeStyle();
 | 
						|
          const props = this.properties
 | 
						|
          if (props.status == 'pass') {
 | 
						|
            style.stroke = '#2ea222'
 | 
						|
          } else if (props.status == 'current') {
 | 
						|
            style.stroke = '#46f'
 | 
						|
          } else if (props.status == 'reject') {
 | 
						|
            style.stroke = '#f46'
 | 
						|
          }
 | 
						|
          return style;
 | 
						|
        }
 | 
						|
 | 
						|
        getTextStyle() {
 | 
						|
          const style = super.getTextStyle();
 | 
						|
          const props = this.properties
 | 
						|
          if (props.status == 'current') {
 | 
						|
            style.fontSize = 14;
 | 
						|
            style.fontWeight = 'bold'
 | 
						|
          } else if (props.status == 'pass') {
 | 
						|
            style.color = '#2ea222'
 | 
						|
          } else if (props.status == 'reject') {
 | 
						|
            style.color = '#f46'
 | 
						|
            style.fontWeight = 'bold'
 | 
						|
          }
 | 
						|
          return style;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      class StartModel extends StartEventModel {
 | 
						|
        getNodeStyle() {
 | 
						|
          const style = super.getNodeStyle();
 | 
						|
          const props = this.properties
 | 
						|
          if (!!props.isStart) {
 | 
						|
            style.stroke = '#2ea222'
 | 
						|
            style.fill = 'rgb(46,162,34,.6)'
 | 
						|
          }
 | 
						|
          return style;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      class EndModel extends EndEventModel {
 | 
						|
        getNodeStyle() {
 | 
						|
          const style = super.getNodeStyle();
 | 
						|
          const props = this.properties
 | 
						|
          if (!!props.isFinished) {
 | 
						|
            style.stroke = '#2ea222'
 | 
						|
          }
 | 
						|
          return style;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      class GatewayModel extends ExclusiveGatewayModel {
 | 
						|
        getNodeStyle() {
 | 
						|
          const style = super.getNodeStyle();
 | 
						|
          const props = this.properties
 | 
						|
          if (props.status == 'pass') {
 | 
						|
            style.stroke = '#2ea222'
 | 
						|
          } else if (props.status == 'current') {
 | 
						|
            style.stroke = '#46f'
 | 
						|
          } else if (props.status == 'reject') {
 | 
						|
            style.stroke = '#f46'
 | 
						|
          }
 | 
						|
          return style;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      lf?.batchRegister([
 | 
						|
        {type: 'task', view: UserTaskView, model: CustomTaskModel},
 | 
						|
        {type: 'start', view: StartEventView, model: StartModel},
 | 
						|
        {type: 'end', view: EndEventView, model: EndModel},
 | 
						|
        {type: 'gateway', view: ExclusiveGatewayView, model: GatewayModel},
 | 
						|
      ])
 | 
						|
    },
 | 
						|
    initFlow() {
 | 
						|
      const {LogicFlow, Menu, DndPanel, BpmnElement} = window
 | 
						|
      let plugins = [BpmnElement, this.readonly ? [] : [Menu, DndPanel]].flat()
 | 
						|
      this.$load(!!LogicFlow && this.$refs.lfIns && plugins.reduce((r, e) => r && !!e, true), 200, "logicFlow").then(() => {
 | 
						|
        this.flow = new LogicFlow({
 | 
						|
          container: this.$refs.lfIns, plugins,
 | 
						|
          animation: true,
 | 
						|
          isSilentMode: this.readonly,
 | 
						|
          stopScrollGraph: this.readonly,
 | 
						|
          stopZoomGraph: this.readonly,
 | 
						|
          stopMoveGraph: this.readonly,
 | 
						|
          hoverOutline: !this.readonly,
 | 
						|
          edgeSelectedOutline: !this.readonly,
 | 
						|
          style: {
 | 
						|
            outline: {
 | 
						|
              fill: 'transparent',
 | 
						|
              stroke: '#26f',
 | 
						|
            },
 | 
						|
          },
 | 
						|
        })
 | 
						|
        this.$emit("update:process", this.flow);
 | 
						|
        this.initModels(this.flow)
 | 
						|
        this.flow.extension.dndPanel?.setPatternItems(this.dndPanelConfigs)
 | 
						|
        this.flow.render()
 | 
						|
        this.initValue()
 | 
						|
        this.flow.on('history:change', evt => {
 | 
						|
          this.configWatch?.()
 | 
						|
          const conf = this.$copy(evt.data?.undos || null).pop()
 | 
						|
          this.$emit("update:config", conf)
 | 
						|
        })
 | 
						|
      })
 | 
						|
    },
 | 
						|
    initValue() {
 | 
						|
      this.configWatch = this.$watch('config', v => {
 | 
						|
        if (v?.nodes?.length > 0) {
 | 
						|
          this.flow?.render(v || {})
 | 
						|
          this.initCurrent()
 | 
						|
          this.configWatch?.()
 | 
						|
        }
 | 
						|
      }, {immediate: true, deep: true})
 | 
						|
    },
 | 
						|
    initCurrent() {
 | 
						|
      if (this.value) {
 | 
						|
        const curs = this.value?.split(",")
 | 
						|
        curs.map(id => this.flow?.setProperties(id, {status: 'current'}))
 | 
						|
        this.flow?.focusOn({id: curs?.[0]})
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
  mounted() {
 | 
						|
    this.$nextTick(() => this.loadLib().then(() => this.initFlow()))
 | 
						|
  }
 | 
						|
}
 | 
						|
</script>
 | 
						|
 | 
						|
<style lang="scss" scoped>
 | 
						|
.AiFlow {
 | 
						|
}
 | 
						|
</style>
 |