Browse Source

基于logicflow 封装流程组件

zhangchong 2 năm trước cách đây
mục cha
commit
1213c78612

+ 72 - 0
package-lock.json

@@ -10,6 +10,8 @@
 			"license": "MIT",
 			"dependencies": {
 				"@element-plus/icons-vue": "^2.0.10",
+				"@logicflow/core": "^1.1.31",
+				"@logicflow/extension": "^1.1.31",
 				"@microsoft/signalr": "^7.0.0",
 				"@wangeditor/editor": "^5.1.23",
 				"@wangeditor/editor-for-vue": "^5.1.12",
@@ -253,6 +255,26 @@
 				"@jridgewell/sourcemap-codec": "1.4.14"
 			}
 		},
+		"node_modules/@logicflow/core": {
+			"version": "1.1.31",
+			"resolved": "https://registry.npmmirror.com/@logicflow/core/-/core-1.1.31.tgz",
+			"integrity": "sha512-TQYJoxhqSzWBvw/NkI5hNZ8WkfS7JDLrvp56EU/OYo6Nbusj2n8/ya8XaMM50+GXqnp6+38t3RAGCC9Dk9cmhA==",
+			"dependencies": {
+				"@types/mousetrap": "^1.6.4",
+				"mousetrap": "^1.6.5",
+				"preact": "^10.4.8"
+			}
+		},
+		"node_modules/@logicflow/extension": {
+			"version": "1.1.31",
+			"resolved": "https://registry.npmmirror.com/@logicflow/extension/-/extension-1.1.31.tgz",
+			"integrity": "sha512-MUDdP7Anf3OmIEVmJTlIH8e5h2yrdqlwWYCDjDoh9R1IOfzR4wCvEiH4HgOkadTsGtfaihFRyFnPIYT1cu7oUA==",
+			"dependencies": {
+				"@logicflow/core": "^1.1.31",
+				"ids": "^1.0.0",
+				"preact": "^10.4.8"
+			}
+		},
 		"node_modules/@microsoft/signalr": {
 			"version": "7.0.0",
 			"resolved": "https://registry.npmmirror.com/@microsoft/signalr/-/signalr-7.0.0.tgz",
@@ -364,6 +386,11 @@
 				"@types/lodash": "*"
 			}
 		},
+		"node_modules/@types/mousetrap": {
+			"version": "1.6.11",
+			"resolved": "https://registry.npmmirror.com/@types/mousetrap/-/mousetrap-1.6.11.tgz",
+			"integrity": "sha512-F0oAily9Q9QQpv9JKxKn0zMKfOo36KHCW7myYsmUyf2t0g+sBTbG3UleTPoguHdE1z3GLFr3p7/wiOio52QFjQ=="
+		},
 		"node_modules/@types/node": {
 			"version": "18.11.9",
 			"resolved": "https://registry.npmmirror.com/@types/node/-/node-18.11.9.tgz",
@@ -2690,6 +2717,11 @@
 				"@babel/runtime": "^7.12.0"
 			}
 		},
+		"node_modules/ids": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmmirror.com/ids/-/ids-1.0.0.tgz",
+			"integrity": "sha512-Zvtq1xUto4LttpstyOlFum8lKx+i1OmRfg+6A9drFS9iSZsDPMHG4Sof/qwNR4kCU7jBeWFPrY2ocHxiz7cCRw=="
+		},
 		"node_modules/ignore": {
 			"version": "5.2.0",
 			"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.2.0.tgz",
@@ -3108,6 +3140,11 @@
 			"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.0.tgz",
 			"integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ=="
 		},
+		"node_modules/mousetrap": {
+			"version": "1.6.5",
+			"resolved": "https://registry.npmmirror.com/mousetrap/-/mousetrap-1.6.5.tgz",
+			"integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA=="
+		},
 		"node_modules/ms": {
 			"version": "2.1.2",
 			"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",
@@ -4694,6 +4731,26 @@
 				"@jridgewell/sourcemap-codec": "1.4.14"
 			}
 		},
+		"@logicflow/core": {
+			"version": "1.1.31",
+			"resolved": "https://registry.npmmirror.com/@logicflow/core/-/core-1.1.31.tgz",
+			"integrity": "sha512-TQYJoxhqSzWBvw/NkI5hNZ8WkfS7JDLrvp56EU/OYo6Nbusj2n8/ya8XaMM50+GXqnp6+38t3RAGCC9Dk9cmhA==",
+			"requires": {
+				"@types/mousetrap": "^1.6.4",
+				"mousetrap": "^1.6.5",
+				"preact": "^10.4.8"
+			}
+		},
+		"@logicflow/extension": {
+			"version": "1.1.31",
+			"resolved": "https://registry.npmmirror.com/@logicflow/extension/-/extension-1.1.31.tgz",
+			"integrity": "sha512-MUDdP7Anf3OmIEVmJTlIH8e5h2yrdqlwWYCDjDoh9R1IOfzR4wCvEiH4HgOkadTsGtfaihFRyFnPIYT1cu7oUA==",
+			"requires": {
+				"@logicflow/core": "^1.1.31",
+				"ids": "^1.0.0",
+				"preact": "^10.4.8"
+			}
+		},
 		"@microsoft/signalr": {
 			"version": "7.0.0",
 			"resolved": "https://registry.npmmirror.com/@microsoft/signalr/-/signalr-7.0.0.tgz",
@@ -4795,6 +4852,11 @@
 				"@types/lodash": "*"
 			}
 		},
+		"@types/mousetrap": {
+			"version": "1.6.11",
+			"resolved": "https://registry.npmmirror.com/@types/mousetrap/-/mousetrap-1.6.11.tgz",
+			"integrity": "sha512-F0oAily9Q9QQpv9JKxKn0zMKfOo36KHCW7myYsmUyf2t0g+sBTbG3UleTPoguHdE1z3GLFr3p7/wiOio52QFjQ=="
+		},
 		"@types/node": {
 			"version": "18.11.9",
 			"resolved": "https://registry.npmmirror.com/@types/node/-/node-18.11.9.tgz",
@@ -6540,6 +6602,11 @@
 				"@babel/runtime": "^7.12.0"
 			}
 		},
+		"ids": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmmirror.com/ids/-/ids-1.0.0.tgz",
+			"integrity": "sha512-Zvtq1xUto4LttpstyOlFum8lKx+i1OmRfg+6A9drFS9iSZsDPMHG4Sof/qwNR4kCU7jBeWFPrY2ocHxiz7cCRw=="
+		},
 		"ignore": {
 			"version": "5.2.0",
 			"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.2.0.tgz",
@@ -6887,6 +6954,11 @@
 			"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.0.tgz",
 			"integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ=="
 		},
+		"mousetrap": {
+			"version": "1.6.5",
+			"resolved": "https://registry.npmmirror.com/mousetrap/-/mousetrap-1.6.5.tgz",
+			"integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA=="
+		},
 		"ms": {
 			"version": "2.1.2",
 			"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",

+ 2 - 0
package.json

@@ -14,6 +14,8 @@
 	},
 	"dependencies": {
 		"@element-plus/icons-vue": "^2.0.10",
+		"@logicflow/core": "^1.1.31",
+		"@logicflow/extension": "^1.1.31",
 		"@microsoft/signalr": "^7.0.0",
 		"@wangeditor/editor": "^5.1.23",
 		"@wangeditor/editor-for-vue": "^5.1.12",

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 331 - 0
src/components/LogicFlow/index.vue


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 47 - 0
src/components/LogicFlow/snakerflow/custom/index.ts


+ 70 - 0
src/components/LogicFlow/snakerflow/decision/index.ts

@@ -0,0 +1,70 @@
+import { GraphModel, h, NodeConfig, PolygonNode, PolygonNodeModel } from '@logicflow/core'
+import { nodeStyleHandle } from '../tool'
+
+class DecisionModel extends PolygonNodeModel {
+  static extendKey = 'DecisionModel';
+  constructor (data: NodeConfig, graphModel: GraphModel) {
+    if (!data.text) {
+      data.text = ''
+    }
+    if (data.text && typeof data.text === 'string') {
+      data.text = {
+        value: data.text,
+        x: data.x,
+        y: data.y + 40
+      }
+    }
+    super(data, graphModel)
+    this.points = [
+      [25, 0],
+      [50, 25],
+      [25, 50],
+      [0, 25]
+    ]
+  }
+
+  getNodeStyle (): {
+    [x: string]: any;
+    fill?: string;
+    stroke?: string;
+    strokeWidth?: number;
+    } {
+    const style = super.getNodeStyle()
+    return nodeStyleHandle(this, style)
+  }
+}
+
+class DecisionView extends PolygonNode {
+  static extendKey = 'DecisionNode';
+  getShape (): h.JSX.Element {
+    const { model } = this.props
+    const { x, y, width, height, points } = model
+    const style = model.getNodeStyle()
+    return h(
+      'g',
+      {
+        transform: `matrix(1 0 0 1 ${x - width / 2} ${y - height / 2})`
+      },
+      h('polygon', {
+        ...style,
+        x,
+        y,
+        points
+      }),
+      h('path', {
+        d:
+          'm 16,15 7.42857142857143,9.714285714285715 -7.42857142857143,9.714285714285715 3.428571428571429,0 5.714285714285715,-7.464228571428572 5.714285714285715,7.464228571428572 3.428571428571429,0 -7.42857142857143,-9.714285714285715 7.42857142857143,-9.714285714285715 -3.428571428571429,0 -5.714285714285715,7.464228571428572 -5.714285714285715,-7.464228571428572 -3.428571428571429,0 z',
+        ...style
+      })
+    )
+  }
+}
+
+const Decision = {
+  type: 'hotline:decision',
+  view: DecisionView,
+  model: DecisionModel
+}
+
+export { DecisionView, DecisionModel }
+export default Decision

+ 80 - 0
src/components/LogicFlow/snakerflow/end/index.ts

@@ -0,0 +1,80 @@
+import { CircleNode, CircleNodeModel, ConnectRule, GraphModel, h, NodeConfig } from '@logicflow/core'
+import { nodeStyleHandle } from '../tool'
+
+class EndModel extends CircleNodeModel {
+  static extendKey = 'EndModel';
+  constructor (data: NodeConfig, graphModel: GraphModel) {
+    if (!data.text) {
+      data.text = ''
+    }
+    if (data.text && typeof data.text === 'string') {
+      data.text = {
+        value: data.text,
+        x: data.x,
+        y: data.y + 40
+      }
+    }
+    super(data, graphModel)
+  }
+
+  setAttributes ():void {
+    this.r = 18
+  }
+
+  getConnectedSourceRules (): ConnectRule[] {
+    const rules = super.getConnectedSourceRules()
+    const notAsSource = {
+      message: '结束节点不能作为边的起点',
+      validate: () => false
+    }
+    rules.push(notAsSource)
+    return rules
+  }
+
+  getNodeStyle () : {
+    [x: string]: any;
+    r?: number;
+    fill?: string;
+    stroke?: string;
+    strokeWidth?: number;
+    } {
+    const style = super.getNodeStyle()
+    return nodeStyleHandle(this, style)
+  }
+}
+
+class EndView extends CircleNode {
+  static extendKey = 'EndView';
+  getAnchorStyle (): {} {
+    return {
+      visibility: 'hidden'
+    }
+  }
+
+  getShape ():h.JSX.Element {
+    const { model } = this.props
+    const style = model.getNodeStyle()
+    const { x, y, r } = model
+    const outCircle = super.getShape()
+    return h(
+      'g',
+      {},
+      outCircle,
+      h('circle', {
+        ...style,
+        cx: x,
+        cy: y,
+        r: r - 5
+      })
+    )
+  }
+}
+
+const End = {
+  type: 'hotline:end',
+  view: EndView,
+  model: EndModel
+}
+
+export { EndView, EndModel }
+export default End

+ 79 - 0
src/components/LogicFlow/snakerflow/fork/index.ts

@@ -0,0 +1,79 @@
+import { GraphModel, h, NodeConfig, PolygonNode, PolygonNodeModel } from '@logicflow/core'
+import { nodeStyleHandle } from '../tool'
+
+class ForkModel extends PolygonNodeModel {
+  static extendKey = 'ForkModel';
+  constructor (data: NodeConfig, graphModel: GraphModel) {
+    if (!data.text) {
+      data.text = ''
+    }
+    if (data.text && typeof data.text === 'string') {
+      data.text = {
+        value: data.text,
+        x: data.x,
+        y: data.y + 40
+      }
+    }
+    super(data, graphModel)
+    this.points = [
+      [25, 0],
+      [50, 25],
+      [25, 50],
+      [0, 25]
+    ]
+  }
+
+  getNodeStyle (): {
+    [x: string]: any;
+    fill?: string;
+    stroke?: string;
+    strokeWidth?: number;
+    } {
+    const style = super.getNodeStyle()
+    return nodeStyleHandle(this, style)
+  }
+}
+
+class ForkView extends PolygonNode {
+  static extendKey = 'ForkNode';
+  getShape (): h.JSX.Element {
+    const { model } = this.props
+    const { x, y, width, height, points } = model
+    const style = model.getNodeStyle()
+    return h(
+      'g',
+      {
+        transform: `matrix(1 0 0 1 ${x - width / 2} ${y - height / 2})`
+      },
+      h('polygon', {
+        ...style,
+        x,
+        y,
+        points
+      }),
+      h(
+        'svg',
+        {
+          x: (width - 28) / 2,
+          y: (height - 28) / 2,
+          width: 28,
+          height: 28,
+          viewBox: '0 0 1024 1024'
+        },
+        h('path', {
+          fill: style.stroke,
+          d: 'M779.733333 64c2.474667 0 4.864 0.768 6.826667 2.176l192.042667 137.173333a11.733333 11.733333 0 0 1 0 19.114667l-192 137.173333A11.733333 11.733333 0 0 1 768 350.08L767.957333 256h-237.994666c-6.826667 0-14.250667 7.466667-15.786667 18.901333l-0.298667 4.48v465.237334c0 12.373333 6.613333 21.248 13.482667 23.04l2.56 0.341333H768L768 674.346667a11.733333 11.733333 0 0 1 18.56-9.557334l192.042667 137.173334a11.733333 11.733333 0 0 1 0 19.114666l-192 137.173334a11.733333 11.733333 0 0 1-18.602667-9.557334L767.957333 853.333333h-237.994666c-54.912 0-97.877333-45.824-101.205334-101.717333l-0.213333-6.997333L428.501333 554.666667H291.370667a128.042667 128.042667 0 1 1 0-85.333334h137.130666V279.381333c0-56.618667 40.789333-104.704 94.677334-108.458666L529.92 170.666667h237.994667L768 75.733333c0-6.485333 5.248-11.733333 11.733333-11.733333z'
+        })
+      )
+    )
+  }
+}
+
+const Fork = {
+  type: 'hotline:fork',
+  view: ForkView,
+  model: ForkModel
+}
+
+export { ForkView, ForkModel }
+export default Fork

+ 88 - 0
src/components/LogicFlow/snakerflow/index.ts

@@ -0,0 +1,88 @@
+import Custom from './custom'
+import Decision from './decision'
+import End from './end'
+import Fork from './fork'
+import Join from './join'
+import Start from './start'
+import Task from './task'
+import Transtion from './transition'
+import SubProcess from './subProcess'
+import { logicFlowJsonToSnakerXml, snakerXml2LogicFlowJson } from './tool'
+
+/**
+ * snakerflow组件注册
+ */
+class SnakerFlowElement {
+  static pluginName = 'snakerFlowElement';
+  constructor (data:any) {
+    data.lf.register(Custom)
+    data.lf.register(Decision)
+    data.lf.register(End)
+    data.lf.register(Fork)
+    data.lf.register(Join)
+    data.lf.register(Start)
+    data.lf.register(Task)
+    data.lf.register(Transtion)
+    data.lf.register(SubProcess)
+  }
+}
+/**
+ * snakerflow给你LogicFlow数据转换器
+ */
+class SnakerFlowAdapter {
+  static pluginName = 'snakerFlowAdapter';
+  constructor ({ lf } : { lf: any }) {
+    lf.adapterIn = (userData: any) => {
+      if (typeof userData === 'string') {
+        userData = snakerXml2LogicFlowJson(userData)
+      }
+      userData = userData.json ? userData.json : userData
+      // 对入参数据进行简单处理
+      if (userData.nodes) {
+        userData.nodes.forEach((node: any) => {
+          if (typeof node.properties.field === 'object') {
+            node.properties.field = JSON.stringify(node.properties.field)
+          }
+        })
+      }
+      // 将流程扩展属性绑定给lf图模型对象
+      lf.graphModel.name = userData.name
+      lf.graphModel.displayName = userData.displayName
+      lf.graphModel.expireTime = userData.expireTime
+      lf.graphModel.instanceUrl = userData.instanceUrl
+      lf.graphModel.instanceNoClass = userData.instanceNoClass
+      return userData
+    }
+    lf.adapterOut = (logicFlowData: any) => {
+      const graphData = {
+        name: lf.graphModel.name,
+        displayName: lf.graphModel.displayName,
+        expireTime: lf.graphModel.expireTime,
+        instanceUrl: lf.graphModel.instanceUrl,
+        instanceNoClass: lf.graphModel.instanceNoClass,
+        ...logicFlowData
+      }
+      Object.keys(graphData).forEach((key: string) => {
+        // 删除undefined的值
+        if (graphData[key] === undefined) {
+          delete graphData[key]
+        }
+      })
+      // 处理一下field数据-stringjson==>obj
+      graphData.nodes.forEach((node: any) => {
+        if (node.type === 'hotline:task' && node.properties.field) {
+          node.properties.field = JSON.parse(node.properties.field)
+        }
+      })
+      const xml = logicFlowJsonToSnakerXml(graphData)
+      return {
+        json: graphData,
+        xml
+      }
+    }
+  }
+}
+export {
+  SnakerFlowElement,
+  SnakerFlowAdapter
+}

+ 87 - 0
src/components/LogicFlow/snakerflow/join/index.ts

@@ -0,0 +1,87 @@
+import { GraphModel, h, NodeConfig, PolygonNode, PolygonNodeModel } from '@logicflow/core'
+import { nodeStyleHandle } from '../tool'
+
+class JoinModel extends PolygonNodeModel {
+  static extendKey = 'JoinModel';
+  constructor (data: NodeConfig, graphModel: GraphModel) {
+    if (!data.text) {
+      data.text = ''
+    }
+    if (data.text && typeof data.text === 'string') {
+      data.text = {
+        value: data.text,
+        x: data.x,
+        y: data.y + 40
+      }
+    }
+    super(data, graphModel)
+    this.points = [
+      [25, 0],
+      [50, 25],
+      [25, 50],
+      [0, 25]
+    ]
+  }
+
+  getNodeStyle (): {
+    [x: string]: any;
+    fill?: string;
+    stroke?: string;
+    strokeWidth?: number;
+    } {
+    const style = super.getNodeStyle()
+    return nodeStyleHandle(this, style)
+  }
+}
+
+class JoinView extends PolygonNode {
+  static extendKey = 'JoinNode';
+  getShape ():h.JSX.Element {
+    const { model } = this.props
+    const { x, y, width, height, points } = model
+    const style = model.getNodeStyle()
+    return h(
+      'g',
+      {
+        transform: `matrix(1 0 0 1 ${x - width / 2} ${y - height / 2})`
+      },
+      h('polygon', {
+        ...style,
+        x,
+        y,
+        points
+      }),
+      h(
+        'svg',
+        {
+          x: (width - 28) / 2,
+          y: (height - 28) / 2,
+          width: 28,
+          height: 28,
+          viewBox: '0 0 1024 1024'
+        },
+        h('path', {
+          fill: style.stroke,
+          d: 'M256 298.666667a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m0 85.333333a128 128 0 1 0 0-256 128 128 0 0 0 0 256zM256 810.666667a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m0 85.333333a128 128 0 1 0 0-256 128 128 0 0 0 0 256zM768 810.666667a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m0 85.333333a128 128 0 1 0 0-256 128 128 0 0 0 0 256z'
+        }),
+        h('path', {
+          fill: style.stroke,
+          d: 'M213.333333 341.333333h85.333334v341.333334H213.333333V341.333333z'
+        }),
+        h('path', {
+          fill: style.stroke,
+          d: 'M213.333333 341.333333h85.333334a128 128 0 0 0 128 128h170.666666a213.333333 213.333333 0 0 1 213.333334 213.333334h-85.333334a128 128 0 0 0-128-128h-170.666666a213.333333 213.333333 0 0 1-213.333334-213.333334z'
+        })
+      )
+    )
+  }
+}
+
+const Join = {
+  type: 'hotline:join',
+  view: JoinView,
+  model: JoinModel
+}
+
+export { JoinView, JoinModel }
+export default Join

+ 56 - 0
src/components/LogicFlow/snakerflow/start/index.ts

@@ -0,0 +1,56 @@
+import { CircleNode, CircleNodeModel, ConnectRule, GraphModel, NodeConfig } from '@logicflow/core'
+import { nodeStyleHandle } from '../tool'
+class StartModel extends CircleNodeModel {
+  static extendKey = 'StartModel';
+  constructor (data: NodeConfig, graphModel: GraphModel) {
+    if (!data.text) {
+      data.text = ''
+    }
+    if (data.text && typeof data.text === 'string') {
+      data.text = {
+        value: data.text,
+        x: data.x,
+        y: data.y + 40
+      }
+    }
+    super(data, graphModel)
+  }
+
+  setAttributes (): void {
+    this.r = 18
+  }
+
+  getConnectedTargetRules (): ConnectRule[] {
+    const rules = super.getConnectedTargetRules()
+    const notAsTarget = {
+      message: '起始节点不能作为边的终点',
+      validate: () => false
+    }
+    rules.push(notAsTarget)
+    return rules
+  }
+
+  getNodeStyle (): {
+    [x: string]: any;
+    r?: number;
+    fill?: string;
+    stroke?: string;
+    strokeWidth?: number;
+    } {
+    const style = super.getNodeStyle()
+    return nodeStyleHandle(this, style)
+  }
+}
+
+class StartView extends CircleNode {
+  static extendKey = 'StartNode';
+}
+
+const Start = {
+  type: 'hotline:start',
+  view: StartView,
+  model: StartModel
+}
+
+export { StartModel, StartView }
+export default Start

+ 25 - 0
src/components/LogicFlow/snakerflow/subProcess/index.ts

@@ -0,0 +1,25 @@
+import { GroupNode } from '@logicflow/extension'
+
+class SubProcessView extends GroupNode.view {
+}
+class SubProcessModel extends GroupNode.model {
+  initNodeData (data: any) {
+    super.initNodeData(data)
+    this.isRestrict = true
+    this.resizable = true
+    this.foldable = false
+    this.width = 500
+    this.height = 300
+    this.foldedWidth = 50
+    this.foldedHeight = 50
+  }
+}
+
+const SubProcess = {
+  type: 'hotline:subProcess',
+  view: SubProcessView,
+  model: SubProcessModel
+}
+
+export { SubProcess, SubProcessModel }
+export default SubProcess

+ 77 - 0
src/components/LogicFlow/snakerflow/task/index.ts

@@ -0,0 +1,77 @@
+import { GraphModel, h, NodeConfig, RectNode, RectNodeModel } from '@logicflow/core'
+import { nodeStyleHandle } from '../tool'
+class TaskModel extends RectNodeModel {
+  static extendKey = 'TaskModel';
+  constructor (data: NodeConfig, graphModel: GraphModel) {
+    super(data, graphModel)
+    if (data.properties) {
+      this.width = (data.properties.width ? data.properties.width : 120) as number
+      this.height = (data.properties.height ? data.properties.height : 80) as number
+    }
+  }
+
+  getNodeStyle ():{
+    [x: string]: any;
+    width?: number;
+    height?: number;
+    radius?: number;
+    fill?: string;
+    stroke?: string;
+    strokeWidth?: number;
+    } {
+    const style = super.getNodeStyle()
+    return nodeStyleHandle(this, style)
+  }
+}
+
+class TaskView extends RectNode {
+  static extendKey = 'TaskNode';
+  getLabelShape ():h.JSX.Element {
+    const { model } = this.props
+    const { x, y, width, height } = model
+    const style = model.getNodeStyle()
+    return h(
+      'svg',
+      {
+        x: x - width / 2 + 5,
+        y: y - height / 2 + 5,
+        width: 25,
+        height: 25,
+        viewBox: '0 0 1274 1024'
+      },
+      h('path', {
+        fill: style.stroke,
+        d:
+          'M655.807326 287.35973m-223.989415 0a218.879 218.879 0 1 0 447.978829 0 218.879 218.879 0 1 0-447.978829 0ZM1039.955839 895.482975c-0.490184-212.177424-172.287821-384.030443-384.148513-384.030443-211.862739 0-383.660376 171.85302-384.15056 384.030443L1039.955839 895.482975z'
+      })
+    )
+  }
+
+  getShape ():h.JSX.Element {
+    const { model } = this.props
+    const { x, y, width, height, radius } = model
+    const style = model.getNodeStyle()
+    // todo: 将basic-shape对外暴露,在这里可以直接用。现在纯手写有点麻烦。
+    return h('g', {}, [
+      h('rect', {
+        ...style,
+        x: x - width / 2,
+        y: y - height / 2,
+        rx: radius,
+        ry: radius,
+        width,
+        height
+      }),
+      this.getLabelShape()
+    ])
+  }
+}
+
+const Task = {
+  type: 'hotline:task',
+  view: TaskView,
+  model: TaskModel
+}
+
+export { TaskView, TaskModel }
+export default Task

+ 409 - 0
src/components/LogicFlow/snakerflow/tool.ts

@@ -0,0 +1,409 @@
+import { BaseEdgeModel, BaseNodeModel } from '@logicflow/core'
+
+/**
+ * 节点样式处理方法
+ * @param {*}} _this
+ * @param {*} style
+ * @returns
+ */
+export const nodeStyleHandle = (_this: BaseNodeModel, style: {
+  [x: string]: any;
+  r?: number;
+  fill?: string;
+  stroke?: string;
+  strokeWidth?: number;
+  }) : {
+    [x: string]: any;
+    r?: number;
+    fill?: string;
+    stroke?: string;
+    strokeWidth?: number;
+    } => {
+  if (_this.properties.state === 'active') {
+    style.stroke = '#00ff00'
+  } else if (_this.properties.state === 'history') {
+    style.stroke = '#ff0000'
+  }
+  return style
+}
+/**
+ * 边样式处理方法
+ * @param {*}} _this
+ * @param {*} style
+ * @returns
+ */
+export const edgeStyleHandle = (_this: BaseEdgeModel, style: {
+  [x: string]: any;
+  r?: number;
+  fill?: string;
+  stroke?: string;
+  strokeWidth?: number;
+  }):{
+    [x: string]: any;
+    r?: number;
+    fill?: string;
+    stroke?: string;
+    strokeWidth?: number;
+    } => {
+  if (_this.properties.state === 'active') {
+    style.stroke = '#00ff00'
+  } else if (_this.properties.state === 'history') {
+    style.stroke = '#ff0000'
+  }
+  return style
+}
+/**
+ * 解析xml成Dom对象
+ * @param {} xml
+ * @returns
+ */
+export const parseXml2Dom = (xml:string): any => {
+  let xmlDoc = null
+  if (window.DOMParser) {
+    const parser = new DOMParser()
+    xmlDoc = parser.parseFromString(xml, 'text/xml')
+  } else { // Internet Explorer
+    // eslint-disable-next-line no-undef
+    xmlDoc = new ActiveXObject('Microsoft.XMLDOM')
+    xmlDoc.async = false
+    xmlDoc.loadXML(xml)
+  }
+  return xmlDoc
+}
+// 节点标签
+const NODE_NAMES = ['start', 'task', 'decision', 'end', 'custom', 'join', 'fork', 'subProcess']
+// 流程节点属性
+const PROCESS_ATTR_KEYS = ['name', 'displayName', 'instanceUrl', 'expireTime', 'instanceNoClass']
+// 节点属性
+const NODE_ATTR_KEYS = ['name', 'displayName', 'form', 'assignee', 'assignmentHandler', 'taskType', 'performType',
+  'preInterceptors', 'postInterceptors', 'reminderTime', 'reminderRepeat',
+  'expireTime', 'autoExecute', 'callback', 'expr', 'handleClass',
+  'clazz', 'methodName', 'args', 'layout', 'g']
+// 变迁节点属性
+const TRANSITION_ATTR_KEYS = ['name', 'displayName', 'to', 'expr', 'g']
+
+/**
+ * 将snaker的定义文件转成LogicFlow支持的数据格式
+ * @param {*} xml
+ * @returns
+ */
+export const snakerXml2LogicFlowJson = (xml: string): any => {
+  const graphData = {
+    nodes: [] as BaseNodeModel[],
+    edges: [] as BaseNodeModel[]
+  } as any
+  const xmlDoc = parseXml2Dom(xml)
+  const processDom = xmlDoc.getElementsByTagName('process')
+  if (!processDom.length) {
+    return graphData
+  }
+  let value = null
+  // 解析process属性
+  PROCESS_ATTR_KEYS.forEach(key => {
+    value = processDom[0].getAttribute(key)
+    if (value) {
+      graphData[key] = value
+    }
+  })
+  let nodeEles: any = null
+  let node: any = null
+  let lfNode: any = {}
+  // 解析节点
+  NODE_NAMES.forEach(key => {
+    nodeEles = processDom[0].getElementsByTagName(key)
+    if (nodeEles.length) {
+      for (let i = 0, len = nodeEles.length; i < len; i++) {
+        node = nodeEles[i]
+        lfNode = {
+          type: 'hotline:' + key,
+          properties: {}
+        }
+        // 处理节点
+        NODE_ATTR_KEYS.forEach(attrKey => {
+          value = node?.getAttribute(attrKey)
+          if (value) {
+            if (attrKey === 'name') {
+              lfNode.id = value
+            } else if (attrKey === 'layout') {
+              const attr = value.split(',')
+              if (attr.length === 4) {
+                lfNode.x = attr[0]
+                lfNode.y = attr[1]
+                lfNode.properties.width = attr[2] <= 0 ? 100 : attr[2]
+                lfNode.properties.height = attr[3] <= 0 ? 50 : attr[3]
+              }
+            } else if (attrKey === 'displayName') {
+              lfNode.text = value
+            } else {
+              lfNode.properties[attrKey] = value
+            }
+          }
+        })
+        // 处理扩展属性
+        if (key === 'task') {
+          const fieldEles = node.getElementsByTagName('field')
+          if (fieldEles.length) {
+            const fieldEle = fieldEles[0]
+            const field = <any>null
+            const attr = fieldEle.getElementsByTagName('attr')
+            for (let iii = 0; iii < attr.length; iii++) {
+              field[attr[iii].getAttribute('name')] = attr[iii].getAttribute('value')
+            }
+            lfNode.properties.field = field
+          }
+        }
+        graphData.nodes.push(lfNode)
+        // 处理边
+        let transitionEles = null
+        let transitionEle = null
+        let edge :any = {}
+        if (key !== 'end') {
+          transitionEles = node.getElementsByTagName('transition')
+          for (let j = 0, lenn = transitionEles.length; j < lenn; j++) {
+            transitionEle = transitionEles[j]
+            edge = {}
+            edge.id = transitionEle.getAttribute('name')
+            edge.type = 'hotline:transition'
+            edge.sourceNodeId = lfNode.id
+            edge.targetNodeId = transitionEle.getAttribute('to')
+            edge.text = {
+              value: transitionEle.getAttribute('displayName') ? transitionEle.getAttribute('displayName') : ''
+            }
+            edge.properties = {}
+            const expr = transitionEle.getAttribute('expr')
+            if (expr) {
+              edge.properties.expr = expr
+            }
+            const g = transitionEle.getAttribute('g')
+            if (g) {
+              const points = g.split(';')
+              if (points.length >= 2) {
+                edge.pointsList = []
+                points.forEach((item: any) => {
+                  const pointArr = item.split(',')
+                  edge.pointsList.push({
+                    x: Number(pointArr[0]),
+                    y: Number(pointArr[1])
+                  })
+                })
+                edge.startPoint = edge.pointsList[0]
+                edge.endPoint = edge.pointsList[edge.pointsList.length - 1]
+              }
+            }
+            graphData.edges.push(edge)
+          }
+        }
+      }
+    }
+  })
+
+  return graphData
+}
+/**
+ * 将LogicFlow的数据转成snaker的定义文件
+ * @param {*} data(...processInfo,nodes,edges)
+ * @returns
+ */
+export const logicFlowJsonToSnakerXml = (data: any): string => {
+  let xml = ''
+  // data的数据由流程定义文件信息+logicFlow数据构成
+  // 先构建成流程对象
+  const processObj = {
+    name: data.name, // 流程定义名称
+    displayName: data.displayName, // 流程定义显示名称
+    instanceUrl: data.instanceUrl, // 实例启动Url
+    expireTime: data.expireTime, // 期望完成时间
+    instanceNoClass: data.instanceNoClass // 实例编号生成类
+  }
+  /**
+   * 获取所有子流程
+   */
+  const getSubProcessNodes = () => {
+    return data.nodes.filter((node:any) => {
+      return node.type === 'hotline:subProcess'
+    })
+  }
+  /**
+   * 获取开始节点
+   * @returns
+   */
+  const getStartNode = () => {
+    const subProcessNodes = getSubProcessNodes()
+    return data.nodes.find((node:any) => {
+      // 是开始节点,且非子流程中的开始节点
+      return node.type === 'hotline:start' && !subProcessNodes.some((item: any) => {
+        return item.children.includes(node.id)
+      })
+    })
+  }
+  /**
+   * 获取当前节点的所有下一个节点集合
+   * @param {*} id 当前节点名称
+   * @returns
+   */
+  const getNextNodes = (id:string) => {
+    return data.edges.filter((edge:any) => {
+      return edge.sourceNodeId === id
+    }).map((edge:any) => {
+      return data.nodes.find((node:any) => {
+        return node.id === edge.targetNodeId
+      })
+    })
+  }
+  /**
+   * 获取节点所有输出边
+   * @param {*} id
+   * @returns
+   */
+  const getTransitions = (id:string) => {
+    return data.edges.filter((edge:any) => {
+      return edge.sourceNodeId === id
+    }).map(edge => {
+      return {
+        name: edge.id,
+        displayName: (edge.text instanceof String || edge.text === undefined) ? edge.text : edge.text.value,
+        to: edge.targetNodeId, // 目地节点id
+        expr: edge.properties.expr, // 表达式
+        g: edge.pointsList.map((point: { x: string; y: string; }) => { // 转换点集合
+          return point.x + ',' + point.y
+        }).join(';')
+      }
+    })
+  }
+  /**
+   * 构建节点属性
+   * @param {}} node
+   * @returns
+   */
+  const buildNode = (node: any) => {
+    let field
+    if (node.properties.field && Object.keys(node.properties.field).length) {
+      field = {
+        name: 'ext',
+        displayName: '扩展属性',
+        attr: Object.keys(node.properties.field).map(key => {
+          return {
+            name: key,
+            value: node.properties.field[key]
+          }
+        })
+      }
+    }
+    return {
+      name: node.id,
+      displayName: (node.text instanceof String || node.text === undefined) ? node.text : node.text.value,
+      layout: node.x + ',' + node.y + ',' + (node.properties.nodeSize?.width || node.properties.width || (node.type === 'hotline:subProcess' ? '500' : '120')) + ',' + (node.properties.nodeSize?.height || node.properties.height || (node.type === 'hotline:subProcess' ? '300' : '80')),
+      ...node.properties,
+      transition: getTransitions(node.id),
+      field
+    }
+  }
+  /**
+   * 特殊字符转义
+   * @param {*} text
+   * @returns
+   */
+  const textEncode = (text: string) => {
+    text = text.replace(/&/g, '&amp;')
+      .replace(/"/g, '&quot;')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;')
+    return text
+  }
+  /**
+   * 递归构建节点属性
+   * @param {} node
+   */
+  const recursionBuildNode = (node: any) => {
+    const nodeName = node.type.replace('hotline:', '')
+    if (!processObj[nodeName + '_' + node.id]) {
+      processObj[nodeName + '_' + node.id] = buildNode(node)
+      const nextNodes = getNextNodes(node.id)
+      nextNodes.forEach(nextNode => {
+        recursionBuildNode(nextNode)
+      })
+    }
+  }
+  const startNode = getStartNode()
+  if (!startNode) {
+    // 开始节点不存在,xml不合法
+    return ''
+  }
+  recursionBuildNode(startNode)
+  xml = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'
+  xml += '<process '
+  Object.keys(processObj).forEach(key => {
+    const value = processObj[key]
+    if (PROCESS_ATTR_KEYS.includes(key) && value) {
+      xml += ' ' + key + '=' + '"' + textEncode(value) + '"'
+    }
+  })
+  xml += '>\n'
+  // 生成节点xml
+  Object.keys(processObj).forEach(key => {
+    const value = processObj[key]
+    const nodeName = key.split('_')[0]
+    if (NODE_NAMES.includes(nodeName)) {
+      xml += '\t<' + nodeName
+      // 构造属性
+      Object.keys(value).forEach(nodeAttrKey => {
+        if (NODE_ATTR_KEYS.includes(nodeAttrKey) && value[nodeAttrKey]) {
+          xml += ' ' + nodeAttrKey + '=' + '"' + textEncode(value[nodeAttrKey]) + '"'
+        }
+      })
+      xml += '>\n\t'
+      // 构建transition
+      if (value.transition) {
+        value.transition.forEach((tran: any) => {
+          xml += '\t<transition'
+          // transition属性
+          Object.keys(tran).forEach(tranAttrKey => {
+            if (TRANSITION_ATTR_KEYS.includes(tranAttrKey) && tran[tranAttrKey]) {
+              xml += ' ' + tranAttrKey + '=' + '"' + textEncode(tran[tranAttrKey]) + '"'
+            }
+          })
+          xml += '>'
+          xml += '</transition>\n'
+        })
+      }
+      if (value.field && nodeName === 'task') {
+        xml += '\t<field name="' + value.field.name + '" displayName="' + value.field.displayName + '">\n'
+        value.field.attr.forEach((attrItem: any) => {
+          xml += '\t\t<attr name="' + attrItem.name + '" value="' + textEncode(attrItem.value) + '"></attr>\n'
+        })
+        xml += '\t</field>\n'
+      }
+      xml += '\t</' + nodeName + '>\n'
+    }
+  })
+  xml += '</process>'
+  // 构建子流程模型数据
+  const buildSubProcessList = () => {
+    const subProcessNodes = getSubProcessNodes()
+    const subProcessModel = subProcessNodes.map(n => {
+      const subModel = {
+        name: n.id, // 流程定义名称
+        displayName: n.properties.displayName, // 流程定义显示名称
+        instanceUrl: n.properties.instanceUrl, // 实例启动Url
+        expireTime: n.properties.expireTime, // 期望完成时间
+        instanceNoClass: n.properties.instanceNoClass, // 实例编号生成类
+        nodes: data.nodes.filter((item: any) => n.children.includes(item.id)),
+        edges: data.edges.filter((item: any) => n.children.includes(item.id))
+      }
+      subModel.nodes.forEach((item: any) => {
+        data.edges.forEach((edge: any) => {
+          if (edge.sourceNodeId === item.id) {
+            subModel.edges.push(edge)
+          }
+        })
+      })
+      return subModel
+    })
+    return subProcessModel
+  }
+  const subProcessList = buildSubProcessList()
+  subProcessList.forEach((processModel: any) => {
+    xml += logicFlowJsonToSnakerXml(processModel).replace('<?xml version="1.0" encoding="UTF-8" standalone="no"?>', '')
+  })
+  return xml
+}

+ 32 - 0
src/components/LogicFlow/snakerflow/transition/index.ts

@@ -0,0 +1,32 @@
+import { PolylineEdge, PolylineEdgeModel } from '@logicflow/core'
+
+class TransitionModel extends PolylineEdgeModel {
+  static extendKey = 'TransitionModel';
+  getEdgeStyle (): {
+    [x: string]: any;
+    fill?: string;
+    stroke?: string;
+    strokeWidth?: number;
+    } {
+    const style = super.getEdgeStyle()
+    if (this.properties.state === 'active') {
+      style.stroke = '#00ff00'
+    } else if (this.properties.state === 'history') {
+      style.stroke = '#ff0000'
+    }
+    return style
+  }
+}
+
+class TransitionView extends PolylineEdge {
+  static extendKey = 'TransitionEdge';
+}
+
+const Transition = {
+  type: 'hotline:transition',
+  view: TransitionView,
+  model: TransitionModel
+}
+
+export { TransitionView, TransitionModel }
+export default Transition

+ 6 - 4
src/utils/request.ts

@@ -4,16 +4,16 @@
  * @version: 
  * @Date: 2022-08-09 16:19:57
  * @LastEditors: Please set LastEditors
- * @LastEditTime: 2022-11-23 14:47:56
+ * @LastEditTime: 2022-11-28 13:22:57
  */
 import axios, {AxiosInstance, AxiosResponse} from 'axios';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { Session } from '/@/utils/storage';
+import router from "/@/router/index"
 //exception log
 import { Exceptionless } from "/@/utils/exceptionless.js";
 // 全局配置
 import appConfig from "/@/utils/appConfig";
-
 // 配置新建一个 axios 实例
 const service:AxiosInstance = axios.create({
 	baseURL: appConfig.apiUrl,
@@ -51,7 +51,8 @@ service.interceptors.response.use(
 				if(dom.length === 0){// 解决重复提示的问题
 					ElMessageBox.alert('你已被登出,请重新登录', '提示', {type: 'warning'}).then(() => {
 						Session.clear(); // 清除浏览器全部临时缓存
-						window.location.href = '/'; // 去登录页
+						// window.location.href = '/'; // 去登录页
+						router.replace(`/login?redirect=${router.currentRoute.value.path}&params=${JSON.stringify(router.currentRoute.value.query ? router.currentRoute.value.query : router.currentRoute.value.params)}`);
 					}).catch((): void => {});
 				}
 			}
@@ -75,7 +76,8 @@ service.interceptors.response.use(
 				if(dom.length === 0){// 解决重复提示的问题
 					ElMessageBox.alert('你已被登出,请重新登录', '提示', {type: 'warning'}).then(() => {
 						Session.clear(); // 清除浏览器全部临时缓存
-						window.location.href = '/'; // 去登录页
+						// window.location.href = '/'; // 去登录页
+						router.replace(`/login?redirect=${router.currentRoute.value.path}&params=${JSON.stringify(router.currentRoute.value.query ? router.currentRoute.value.query : router.currentRoute.value.params)}`);
 					}).catch((): void => {});
 				}
 		}else{

+ 1 - 0
src/views/home/index.vue

@@ -374,6 +374,7 @@ const { entranceList, noticeList, active, activities,loading,entranceLoading,lis
 					align-items: center;
 					color: var(--el-text-color-regular);
 					cursor: pointer;
+					font-weight: normal;
 				}
 			}
 			&-list {

+ 293 - 0
src/views/system/public/data.json

@@ -0,0 +1,293 @@
+{
+    "name": "leave",
+    "displayName": "请假",
+    "instanceUrl": "leaveForm",
+    "nodes": [
+      {
+        "id": "start",
+        "type": "hotline:start",
+        "x": 340,
+        "y": 160,
+        "properties": {
+          "width": "120",
+          "height": "80"
+        },
+        "text": {
+          "x": 340,
+          "y": 200,
+          "value": "开始"
+        }
+      },
+      {
+        "id": "apply",
+        "type": "hotline:task",
+        "x": 520,
+        "y": 160,
+        "properties": {
+          "assignee": "approve.operator",
+          "taskType": "Major",
+          "performType": "ANY",
+          "autoExecute": "N",
+          "width": "120",
+          "height": "80",
+          "field": {
+            "userKey": "1"
+          }
+        },
+        "text": {
+          "x": 520,
+          "y": 160,
+          "value": "请假申请"
+        }
+      },
+      {
+        "id": "approveDept",
+        "type": "hotline:task",
+        "x": 740,
+        "y": 160,
+        "properties": {
+          "assignmentHandler": "com.mldong.config.FlowAssignmentHandler",
+          "taskType": "Major",
+          "performType": "ANY",
+          "autoExecute": "N",
+          "width": "120",
+          "height": "80"
+        },
+        "text": {
+          "x": 740,
+          "y": 160,
+          "value": "部门领导审批"
+        }
+      },
+      {
+        "id": "approveBoss",
+        "type": "hotline:task",
+        "x": 900,
+        "y": 480,
+        "properties": {
+          "assignmentHandler": "com.mldong.config.FlowAssignmentHandler",
+          "taskType": "Major",
+          "performType": "ANY",
+          "autoExecute": "N",
+          "width": "120",
+          "height": "80"
+        },
+        "text": {
+          "x": 900,
+          "y": 480,
+          "value": "公司领导审批"
+        }
+      },
+      {
+        "id": "2c75eebf-5baf-4cd0-a7b3-05466be13634",
+        "type": "hotline:decision",
+        "x": 740,
+        "y": 340,
+        "properties": {
+          "width": "120",
+          "height": "80"
+        }
+      },
+      {
+        "id": "end",
+        "type": "hotline:end",
+        "x": 1080,
+        "y": 160,
+        "properties": {
+          "width": "120",
+          "height": "80"
+        },
+        "text": {
+          "x": 1080,
+          "y": 200,
+          "value": "结束"
+        }
+      }
+    ],
+    "edges": [
+      {
+        "id": "3037be41-5682-4344-b94a-9faf5c3e62ba",
+        "type": "hotline:transition",
+        "sourceNodeId": "start",
+        "targetNodeId": "apply",
+        "startPoint": {
+          "x": 358,
+          "y": 160
+        },
+        "endPoint": {
+          "x": 460,
+          "y": 160
+        },
+        "properties": {},
+        "pointsList": [
+          {
+            "x": 358,
+            "y": 160
+          },
+          {
+            "x": 460,
+            "y": 160
+          }
+        ]
+      },
+      {
+        "id": "c79642ae-9f28-4213-8cdf-0e0d6467b1b9",
+        "type": "hotline:transition",
+        "sourceNodeId": "apply",
+        "targetNodeId": "approveDept",
+        "startPoint": {
+          "x": 580,
+          "y": 160
+        },
+        "endPoint": {
+          "x": 680,
+          "y": 160
+        },
+        "properties": {},
+        "pointsList": [
+          {
+            "x": 580,
+            "y": 160
+          },
+          {
+            "x": 680,
+            "y": 160
+          }
+        ]
+      },
+      {
+        "id": "09d9b143-9473-4a0f-8287-9abf6f65baf5",
+        "type": "hotline:transition",
+        "sourceNodeId": "approveDept",
+        "targetNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634",
+        "startPoint": {
+          "x": 740,
+          "y": 200
+        },
+        "endPoint": {
+          "x": 740,
+          "y": 315
+        },
+        "properties": {},
+        "pointsList": [
+          {
+            "x": 740,
+            "y": 200
+          },
+          {
+            "x": 740,
+            "y": 315
+          }
+        ]
+      },
+      {
+        "id": "a64348ec-4168-4f36-8a61-15cf12c710b9",
+        "type": "hotline:transition",
+        "sourceNodeId": "approveBoss",
+        "targetNodeId": "end",
+        "startPoint": {
+          "x": 960,
+          "y": 480
+        },
+        "endPoint": {
+          "x": 1080,
+          "y": 142
+        },
+        "properties": {},
+        "pointsList": [
+          {
+            "x": 960,
+            "y": 480
+          },
+          {
+            "x": 1140,
+            "y": 480
+          },
+          {
+            "x": 1140,
+            "y": 112
+          },
+          {
+            "x": 1080,
+            "y": 112
+          },
+          {
+            "x": 1080,
+            "y": 142
+          }
+        ]
+      },
+      {
+        "id": "517ef2c7-3486-4992-b554-0f538ab91751",
+        "type": "hotline:transition",
+        "sourceNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634",
+        "targetNodeId": "end",
+        "startPoint": {
+          "x": 764,
+          "y": 339
+        },
+        "endPoint": {
+          "x": 1080,
+          "y": 178
+        },
+        "properties": {
+          "expr": "#f_day<3"
+        },
+        "text": {
+          "x": 922,
+          "y": 339,
+          "value": "请假天数小于3"
+        },
+        "pointsList": [
+          {
+            "x": 764,
+            "y": 339
+          },
+          {
+            "x": 1080,
+            "y": 339
+          },
+          {
+            "x": 1080,
+            "y": 178
+          }
+        ]
+      },
+      {
+        "id": "d7ec4166-f3fc-4fd6-a2ac-a6c4d509c4dd",
+        "type": "hotline:transition",
+        "sourceNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634",
+        "targetNodeId": "approveBoss",
+        "startPoint": {
+          "x": 740,
+          "y": 365
+        },
+        "endPoint": {
+          "x": 840,
+          "y": 480
+        },
+        "properties": {
+          "expr": "#f_day>=3"
+        },
+        "text": {
+          "x": 740,
+          "y": 422.5,
+          "value": "请假天数大于等于3"
+        },
+        "pointsList": [
+          {
+            "x": 740,
+            "y": 365
+          },
+          {
+            "x": 740,
+            "y": 480
+          },
+          {
+            "x": 840,
+            "y": 480
+          }
+        ]
+      }
+    ]
+  }

+ 16 - 11
src/views/system/public/index.vue

@@ -1,19 +1,24 @@
-<!--
- * @Author: zc
- * @Description: 
- * @version: 
- * @Date: 2022-11-15 16:38:59
- * @LastEditors: Please set LastEditors
- * @LastEditTime: 2022-11-17 14:20:05
--->
 <template>
-    <div  layout-pd>
-
+    <div class="layout-padding">
+        <div  class="layout-padding-auto layout-padding-view pd20">
+            <LogicFlow style="flex:1" ref='designerRef' v-model="flowData" @on-save="handleSave"/>
+        </div>
     </div>
 </template>
 
 <script setup lang="ts">
-
+import {defineAsyncComponent, ref} from "vue";
+import demoData from "./data.json";
+import subProcessData from "./subProcessData.json";
+const LogicFlow = defineAsyncComponent(() => import('/@/components/LogicFlow/index.vue'))
+const designerRef = ref();
+const flowData = ref(demoData);
+/**
+ * 保存事件处理
+ */
+const handleSave = (data: any) => {
+    console.log(data)
+}
 </script>
 
 <style scoped>

+ 432 - 0
src/views/system/public/subProcessData.json

@@ -0,0 +1,432 @@
+{
+  "name": "leave",
+  "displayName": "请假",
+  "instanceUrl": "leaveForm",
+  "nodes": [
+    {
+      "id": "start",
+      "type": "hotline:start",
+      "x": 340,
+      "y": 160,
+      "properties": {
+        "width": "120",
+        "height": "80"
+      },
+      "text": {
+        "x": 340,
+        "y": 200,
+        "value": "开始"
+      }
+    },
+    {
+      "id": "apply",
+      "type": "hotline:task",
+      "x": 520,
+      "y": 160,
+      "properties": {
+        "assignee": "approve.operator",
+        "taskType": "Major",
+        "performType": "ANY",
+        "autoExecute": "N",
+        "width": "120",
+        "height": "80",
+        "field": {
+          "userKey": "1"
+        }
+      },
+      "text": {
+        "x": 520,
+        "y": 160,
+        "value": "请假申请"
+      }
+    },
+    {
+      "id": "approveDept",
+      "type": "hotline:task",
+      "x": 740,
+      "y": 160,
+      "properties": {
+        "assignmentHandler": "com.mldong.config.FlowAssignmentHandler",
+        "taskType": "Major",
+        "performType": "ANY",
+        "autoExecute": "N",
+        "width": "120",
+        "height": "80"
+      },
+      "text": {
+        "x": 740,
+        "y": 160,
+        "value": "部门领导审批"
+      }
+    },
+    {
+      "id": "approveBoss",
+      "type": "hotline:task",
+      "x": 900,
+      "y": 480,
+      "properties": {
+        "assignmentHandler": "com.mldong.config.FlowAssignmentHandler",
+        "taskType": "Major",
+        "performType": "ANY",
+        "autoExecute": "N",
+        "width": "120",
+        "height": "80"
+      },
+      "text": {
+        "x": 900,
+        "y": 480,
+        "value": "公司领导审批"
+      }
+    },
+    {
+      "id": "2c75eebf-5baf-4cd0-a7b3-05466be13634",
+      "type": "hotline:decision",
+      "x": 740,
+      "y": 340,
+      "properties": {
+        "width": "120",
+        "height": "80"
+      }
+    },
+    {
+      "id": "end",
+      "type": "hotline:end",
+      "x": 1080,
+      "y": 160,
+      "properties": {
+        "width": "120",
+        "height": "80"
+      },
+      "text": {
+        "x": 1080,
+        "y": 200,
+        "value": "结束"
+      }
+    },
+    {
+      "id": "b8ac8be8-165f-48c0-8f2e-f0f6e3d39edd",
+      "type": "hotline:subProcess",
+      "x": 520,
+      "y": 720,
+      "properties": {},
+      "children": [
+        "be2b3e28-2079-43f5-97b6-09ac0b9c379f",
+        "8776b95f-5ad5-49c1-bf11-b7771e86f653",
+        "9ab1a4bb-b9da-434a-93ec-cfccafd22473"
+      ]
+    },
+    {
+      "id": "8776b95f-5ad5-49c1-bf11-b7771e86f653",
+      "type": "hotline:start",
+      "x": 340,
+      "y": 720,
+      "properties": {},
+      "text": {
+        "x": 340,
+        "y": 760,
+        "value": "开始"
+      }
+    },
+    {
+      "id": "9ab1a4bb-b9da-434a-93ec-cfccafd22473",
+      "type": "hotline:task",
+      "x": 520,
+      "y": 720,
+      "properties": {},
+      "text": {
+        "x": 520,
+        "y": 720,
+        "value": "用户任务"
+      }
+    },
+    {
+      "id": "be2b3e28-2079-43f5-97b6-09ac0b9c379f",
+      "type": "hotline:end",
+      "x": 680,
+      "y": 720,
+      "properties": {},
+      "text": {
+        "x": 680,
+        "y": 760,
+        "value": "结束"
+      }
+    }
+  ],
+  "edges": [
+    {
+      "id": "3037be41-5682-4344-b94a-9faf5c3e62ba",
+      "type": "hotline:transition",
+      "sourceNodeId": "start",
+      "targetNodeId": "apply",
+      "startPoint": {
+        "x": 358,
+        "y": 160
+      },
+      "endPoint": {
+        "x": 460,
+        "y": 160
+      },
+      "properties": {},
+      "pointsList": [
+        {
+          "x": 358,
+          "y": 160
+        },
+        {
+          "x": 460,
+          "y": 160
+        }
+      ]
+    },
+    {
+      "id": "c79642ae-9f28-4213-8cdf-0e0d6467b1b9",
+      "type": "hotline:transition",
+      "sourceNodeId": "apply",
+      "targetNodeId": "approveDept",
+      "startPoint": {
+        "x": 580,
+        "y": 160
+      },
+      "endPoint": {
+        "x": 680,
+        "y": 160
+      },
+      "properties": {},
+      "pointsList": [
+        {
+          "x": 580,
+          "y": 160
+        },
+        {
+          "x": 680,
+          "y": 160
+        }
+      ]
+    },
+    {
+      "id": "09d9b143-9473-4a0f-8287-9abf6f65baf5",
+      "type": "hotline:transition",
+      "sourceNodeId": "approveDept",
+      "targetNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634",
+      "startPoint": {
+        "x": 740,
+        "y": 200
+      },
+      "endPoint": {
+        "x": 740,
+        "y": 315
+      },
+      "properties": {},
+      "pointsList": [
+        {
+          "x": 740,
+          "y": 200
+        },
+        {
+          "x": 740,
+          "y": 315
+        }
+      ]
+    },
+    {
+      "id": "a64348ec-4168-4f36-8a61-15cf12c710b9",
+      "type": "hotline:transition",
+      "sourceNodeId": "approveBoss",
+      "targetNodeId": "end",
+      "startPoint": {
+        "x": 960,
+        "y": 480
+      },
+      "endPoint": {
+        "x": 1080,
+        "y": 142
+      },
+      "properties": {},
+      "pointsList": [
+        {
+          "x": 960,
+          "y": 480
+        },
+        {
+          "x": 1140,
+          "y": 480
+        },
+        {
+          "x": 1140,
+          "y": 112
+        },
+        {
+          "x": 1080,
+          "y": 112
+        },
+        {
+          "x": 1080,
+          "y": 142
+        }
+      ]
+    },
+    {
+      "id": "517ef2c7-3486-4992-b554-0f538ab91751",
+      "type": "hotline:transition",
+      "sourceNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634",
+      "targetNodeId": "end",
+      "startPoint": {
+        "x": 764,
+        "y": 339
+      },
+      "endPoint": {
+        "x": 1080,
+        "y": 178
+      },
+      "properties": {
+        "expr": "#f_day<3"
+      },
+      "text": {
+        "x": 922,
+        "y": 339,
+        "value": "请假天数小于3"
+      },
+      "pointsList": [
+        {
+          "x": 764,
+          "y": 339
+        },
+        {
+          "x": 1080,
+          "y": 339
+        },
+        {
+          "x": 1080,
+          "y": 178
+        }
+      ]
+    },
+    {
+      "id": "d7ec4166-f3fc-4fd6-a2ac-a6c4d509c4dd",
+      "type": "hotline:transition",
+      "sourceNodeId": "2c75eebf-5baf-4cd0-a7b3-05466be13634",
+      "targetNodeId": "approveBoss",
+      "startPoint": {
+        "x": 740,
+        "y": 365
+      },
+      "endPoint": {
+        "x": 840,
+        "y": 480
+      },
+      "properties": {
+        "expr": "#f_day>=3"
+      },
+      "text": {
+        "x": 740,
+        "y": 422.5,
+        "value": "请假天数大于等于3"
+      },
+      "pointsList": [
+        {
+          "x": 740,
+          "y": 365
+        },
+        {
+          "x": 740,
+          "y": 480
+        },
+        {
+          "x": 840,
+          "y": 480
+        }
+      ]
+    },
+    {
+      "id": "33527dd6-c20b-41e5-8ec4-4adc601d49f4",
+      "type": "hotline:transition",
+      "sourceNodeId": "8776b95f-5ad5-49c1-bf11-b7771e86f653",
+      "targetNodeId": "9ab1a4bb-b9da-434a-93ec-cfccafd22473",
+      "startPoint": {
+        "x": 358,
+        "y": 720
+      },
+      "endPoint": {
+        "x": 460,
+        "y": 720
+      },
+      "properties": {},
+      "pointsList": [
+        {
+          "x": 358,
+          "y": 720
+        },
+        {
+          "x": 460,
+          "y": 720
+        }
+      ]
+    },
+    {
+      "id": "7083e5f2-5d2e-43e3-ad9c-8cb63be7cac2",
+      "type": "hotline:transition",
+      "sourceNodeId": "9ab1a4bb-b9da-434a-93ec-cfccafd22473",
+      "targetNodeId": "be2b3e28-2079-43f5-97b6-09ac0b9c379f",
+      "startPoint": {
+        "x": 580,
+        "y": 720
+      },
+      "endPoint": {
+        "x": 662,
+        "y": 720
+      },
+      "properties": {},
+      "pointsList": [
+        {
+          "x": 580,
+          "y": 720
+        },
+        {
+          "x": 662,
+          "y": 720
+        }
+      ]
+    },
+    {
+      "id": "d10998da-24c8-4df4-9c3d-96e752893641",
+      "type": "hotline:transition",
+      "sourceNodeId": "apply",
+      "targetNodeId": "b8ac8be8-165f-48c0-8f2e-f0f6e3d39edd",
+      "startPoint": {
+        "x": 520,
+        "y": 200
+      },
+      "endPoint": {
+        "x": 520,
+        "y": 570
+      },
+      "properties": {},
+      "pointsList": [
+        {
+          "x": 520,
+          "y": 200
+        },
+        {
+          "x": 520,
+          "y": 230
+        },
+        {
+          "x": 520,
+          "y": 230
+        },
+        {
+          "x": 520,
+          "y": 540
+        },
+        {
+          "x": 520,
+          "y": 540
+        },
+        {
+          "x": 520,
+          "y": 570
+        }
+      ]
+    }
+  ]
+}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác