mirror of
https://gitee.com/xiangheng/x_admin.git
synced 2025-11-03 10:31:00 +08:00
流程部分setup
This commit is contained in:
@@ -48,13 +48,13 @@
|
|||||||
v-show="activeStep === 'formDesign'"
|
v-show="activeStep === 'formDesign'"
|
||||||
tabName="formDesign"
|
tabName="formDesign"
|
||||||
/>
|
/>
|
||||||
<Diagram
|
<FlowEdit
|
||||||
ref="processDesign"
|
ref="flowEdit"
|
||||||
v-show="activeStep === 'processDesign'"
|
v-show="activeStep === 'flowEdit'"
|
||||||
tabName="processDesign"
|
tabName="flowEdit"
|
||||||
:fieldList="fieldList"
|
:fieldList="fieldList"
|
||||||
:conf="mockData.flowProcessData"
|
:conf="mockData.flowProcessData"
|
||||||
></Diagram>
|
></FlowEdit>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, shallowRef, watch } from 'vue'
|
import { ref, shallowRef, watch } from 'vue'
|
||||||
import XForm from './XForm/index.vue'
|
import XForm from './XForm/index.vue'
|
||||||
import Diagram from './flowEdit/Diagram.vue'
|
import FlowEdit from './flowEdit/index.vue'
|
||||||
import BasicSetting from './BasicSetting/index.vue'
|
import BasicSetting from './BasicSetting/index.vue'
|
||||||
|
|
||||||
import feedback from '@/utils/feedback'
|
import feedback from '@/utils/feedback'
|
||||||
@@ -89,7 +89,7 @@ const props = defineProps({
|
|||||||
|
|
||||||
const basicSetting = shallowRef<InstanceType<typeof BasicSetting>>()
|
const basicSetting = shallowRef<InstanceType<typeof BasicSetting>>()
|
||||||
const formDesign = shallowRef<InstanceType<typeof XForm>>()
|
const formDesign = shallowRef<InstanceType<typeof XForm>>()
|
||||||
const processDesign = shallowRef<InstanceType<typeof Diagram>>()
|
const flowEdit = shallowRef<InstanceType<typeof FlowEdit>>()
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const activeStep = ref('basicSetting')
|
const activeStep = ref('basicSetting')
|
||||||
const mockData = ref({
|
const mockData = ref({
|
||||||
@@ -102,7 +102,7 @@ const fieldList = ref([])
|
|||||||
const steps = [
|
const steps = [
|
||||||
{ label: '基础设置', key: 'basicSetting' },
|
{ label: '基础设置', key: 'basicSetting' },
|
||||||
{ label: '表单设计', key: 'formDesign' },
|
{ label: '表单设计', key: 'formDesign' },
|
||||||
{ label: '流程设计', key: 'processDesign' }
|
{ label: '流程设计', key: 'flowEdit' }
|
||||||
]
|
]
|
||||||
watch(
|
watch(
|
||||||
() => dialogVisible.value,
|
() => dialogVisible.value,
|
||||||
@@ -151,7 +151,7 @@ function changeSteps(item) {
|
|||||||
function publish() {
|
function publish() {
|
||||||
const p1 = basicSetting.value.getData()
|
const p1 = basicSetting.value.getData()
|
||||||
const p2 = formDesign.value.getData()
|
const p2 = formDesign.value.getData()
|
||||||
const p3 = processDesign.value.getData()
|
const p3 = flowEdit.value.getData()
|
||||||
Promise.all([p1, p2, p3])
|
Promise.all([p1, p2, p3])
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
console.log('res', res)
|
console.log('res', res)
|
||||||
|
|||||||
@@ -1,338 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="diagram">
|
|
||||||
<diagram-toolbar
|
|
||||||
v-if="lf"
|
|
||||||
class="diagram-toolbar"
|
|
||||||
:lf="lf"
|
|
||||||
:active-edges="activeEdges"
|
|
||||||
@saveGraph="$_saveGraph"
|
|
||||||
@importData="importData"
|
|
||||||
/>
|
|
||||||
<div class="diagram-main">
|
|
||||||
<diagram-sidebar class="diagram-sidebar" @dragInNode="$_dragInNode" />
|
|
||||||
<div ref="container" class="diagram-container">
|
|
||||||
<div class="diagram-wrapper">
|
|
||||||
<div ref="diagram" class="lf-diagram"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 右侧属性面板 -->
|
|
||||||
<PropertyPanel ref="panelRef" @setProperties="$setProperties" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import LogicFlow from '@logicflow/core'
|
|
||||||
import { SelectionSelect, Menu, BpmnElement } from '@logicflow/extension'
|
|
||||||
|
|
||||||
import '@logicflow/core/dist/style/index.css'
|
|
||||||
import '@logicflow/extension/lib/style/index.css'
|
|
||||||
import DiagramToolbar from './DiagramToolbar.vue'
|
|
||||||
import DiagramSidebar from './DiagramSidebar.vue'
|
|
||||||
import PropertyPanel from './PropertyPanel.vue'
|
|
||||||
import { registerCustomElement } from './node'
|
|
||||||
|
|
||||||
// import { Control } from "@logicflow/extension";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Diagram',
|
|
||||||
components: {
|
|
||||||
DiagramToolbar,
|
|
||||||
DiagramSidebar,
|
|
||||||
PropertyPanel
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
tabName: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
fieldList: {
|
|
||||||
type: Array,
|
|
||||||
default: () => {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
conf: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
sidebarWidth: 200,
|
|
||||||
diagramWidth: 0,
|
|
||||||
diagramHeight: 0,
|
|
||||||
lf: null,
|
|
||||||
filename: '',
|
|
||||||
activeEdges: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// const data = ''
|
|
||||||
|
|
||||||
this.filename = 'export.json'
|
|
||||||
// const d = window.sessionStorage.getItem(this.filename)
|
|
||||||
// if (d) {
|
|
||||||
// data = JSON.parse(d)
|
|
||||||
// }
|
|
||||||
|
|
||||||
this.initLogicFlow(this.conf)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initLogicFlow(data) {
|
|
||||||
// 引入框选插件
|
|
||||||
LogicFlow.use(SelectionSelect)
|
|
||||||
LogicFlow.use(Menu)
|
|
||||||
LogicFlow.use(BpmnElement)
|
|
||||||
|
|
||||||
const lf = new LogicFlow({
|
|
||||||
container: this.$refs.diagram,
|
|
||||||
overlapMode: 1,
|
|
||||||
autoWrap: true,
|
|
||||||
metaKeyMultipleSelected: true,
|
|
||||||
keyboard: {
|
|
||||||
enabled: true
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
size: 10,
|
|
||||||
type: 'dot'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
lf.setTheme({
|
|
||||||
baseEdge: { strokeWidth: 1 },
|
|
||||||
baseNode: { strokeWidth: 1 },
|
|
||||||
nodeText: { overflowMode: 'autoWrap', lineHeight: 1.5 },
|
|
||||||
edgeText: { overflowMode: 'autoWrap', lineHeight: 1.5 }
|
|
||||||
})
|
|
||||||
// 注册自定义元素
|
|
||||||
registerCustomElement(lf)
|
|
||||||
lf.setDefaultEdgeType('pro-polyline')
|
|
||||||
lf.render(data)
|
|
||||||
this.lf = lf
|
|
||||||
|
|
||||||
this.lf.on('node:click', (e) => {
|
|
||||||
console.log('点击节点', e.data)
|
|
||||||
this.$refs.panelRef.open(e.data, this.fieldList)
|
|
||||||
})
|
|
||||||
|
|
||||||
// lf.renderRawData( )
|
|
||||||
},
|
|
||||||
|
|
||||||
$_dragInNode(type, text = '') {
|
|
||||||
this.lf.dnd.startDrag({
|
|
||||||
type,
|
|
||||||
text
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// 获取可以进行设置的属性
|
|
||||||
$_getProperty() {
|
|
||||||
// this.lf.getProperties(node.id)
|
|
||||||
},
|
|
||||||
$setProperties(node, item) {
|
|
||||||
this.lf.setProperties(node.id, item)
|
|
||||||
},
|
|
||||||
$_setZIndex(node, type) {
|
|
||||||
this.lf.setElementZIndex(node.id, type)
|
|
||||||
},
|
|
||||||
importData(text) {
|
|
||||||
this.lf.renderRawData(text)
|
|
||||||
},
|
|
||||||
$_saveGraph() {
|
|
||||||
const data = this.lf.getGraphData()
|
|
||||||
this.download(this.filename, JSON.stringify(data))
|
|
||||||
},
|
|
||||||
download(filename, text) {
|
|
||||||
window.sessionStorage.setItem(filename, text)
|
|
||||||
const element = document.createElement('a')
|
|
||||||
element.setAttribute(
|
|
||||||
'href',
|
|
||||||
'data:text/plain;charset=utf-8,' + encodeURIComponent(text)
|
|
||||||
)
|
|
||||||
element.setAttribute('download', filename)
|
|
||||||
element.style.display = 'none'
|
|
||||||
document.body.appendChild(element)
|
|
||||||
element.click()
|
|
||||||
document.body.removeChild(element)
|
|
||||||
},
|
|
||||||
getData() {
|
|
||||||
/**
|
|
||||||
* 校验目标
|
|
||||||
* 1. 必须存在开始节点和结束节点
|
|
||||||
* 2. 连线方向正确
|
|
||||||
* 3. 多余的节点(不重要)
|
|
||||||
* 4. 所有分支必须结束节点
|
|
||||||
* 5. 检查审批节点设置情况
|
|
||||||
* 6. 一个节点可以有多个子网关,子网关只能通过一个,不能有多个普通子节点
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const data = this.lf.getGraphData()
|
|
||||||
|
|
||||||
const nodes = data.nodes
|
|
||||||
const edges = data.edges
|
|
||||||
|
|
||||||
let haveMoreChildNode = false
|
|
||||||
const sourceNodeIdSum = {} //节点id->[子节点id]
|
|
||||||
|
|
||||||
edges.forEach((edge) => {
|
|
||||||
const targetNode = nodes.find((item) => item.id == edge.targetNodeId)
|
|
||||||
if (sourceNodeIdSum[edge.sourceNodeId]) {
|
|
||||||
sourceNodeIdSum[edge.sourceNodeId].push(targetNode)
|
|
||||||
|
|
||||||
for (const n of sourceNodeIdSum[edge.sourceNodeId]) {
|
|
||||||
if (n.type != 'bpmn:exclusiveGateway') {
|
|
||||||
haveMoreChildNode = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sourceNodeIdSum[edge.sourceNodeId] = [targetNode]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
console.log('sourceNodeIdSum', sourceNodeIdSum)
|
|
||||||
if (haveMoreChildNode) {
|
|
||||||
// 如果都是网关就可以,等优化
|
|
||||||
return reject({
|
|
||||||
target: this.tabName,
|
|
||||||
message: '流程设计-一个节点只能有一个子节点,可以有多个网关'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (data.nodes.length != data.edges.length + 1) {
|
|
||||||
// return reject({
|
|
||||||
// message: "流程设计-节点之间必须连线,可以清理多余的节点",//不是很重要
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// 检查开始节点和结束节点是否存在
|
|
||||||
const findStartNode = nodes.filter((item) => item.type == 'bpmn:startEvent')
|
|
||||||
const findEndNode = nodes.filter((item) => item.type == 'bpmn:endEvent')
|
|
||||||
if (findStartNode.length != 1 || findEndNode.length != 1) {
|
|
||||||
return reject({
|
|
||||||
target: this.tabName,
|
|
||||||
message: '流程设计-流程必须有且只有一个开始节点和结束节点'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function handel(arr, pid) {
|
|
||||||
const newArr = []
|
|
||||||
arr.forEach((node) => {
|
|
||||||
// console.log('sourceNodeIdSum', sourceNodeIdSum, node.id)
|
|
||||||
const newNode = {
|
|
||||||
id: node.id,
|
|
||||||
pid: pid,
|
|
||||||
label: node?.text?.value,
|
|
||||||
type: node.type,
|
|
||||||
fieldAuth: node?.properties?.fieldAuth,
|
|
||||||
gateway: node?.properties?.gateway,
|
|
||||||
|
|
||||||
userType: node?.properties?.userType || 0,
|
|
||||||
userId: node?.properties?.userId || 0,
|
|
||||||
deptId: node?.properties?.deptId || 0,
|
|
||||||
postId: node?.properties?.postId || 0
|
|
||||||
}
|
|
||||||
if (sourceNodeIdSum[node.id]) {
|
|
||||||
newNode.children = handel(sourceNodeIdSum[node.id], node.id)
|
|
||||||
}
|
|
||||||
newArr.push(newNode)
|
|
||||||
})
|
|
||||||
return newArr
|
|
||||||
}
|
|
||||||
const TreeNode = handel(findStartNode, 0)
|
|
||||||
|
|
||||||
// tree转list
|
|
||||||
function treeToList(tree) {
|
|
||||||
const arr = []
|
|
||||||
tree.forEach((item) => {
|
|
||||||
arr.push(item)
|
|
||||||
if (item.children) {
|
|
||||||
arr.push(...treeToList(item.children))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('TreeNode', TreeNode)
|
|
||||||
console.log('treeToList', treeToList(TreeNode))
|
|
||||||
// 检查连线方向是否正确;
|
|
||||||
resolve({ formData: data, treeToList: treeToList(TreeNode) })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.diagram {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-toolbar {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 20px;
|
|
||||||
height: 40px;
|
|
||||||
padding: 0 10px;
|
|
||||||
/* width: 310px; */
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
/* border-bottom: 1px solid #e5e5e5; */
|
|
||||||
z-index: 10;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0px 0px 4px rgba($color: #000000, $alpha: 0.5);
|
|
||||||
}
|
|
||||||
.diagram-main {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.diagram-sidebar {
|
|
||||||
position: absolute;
|
|
||||||
left: 20px;
|
|
||||||
z-index: 10;
|
|
||||||
top: 50px;
|
|
||||||
width: 60px;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0px 0px 4px rgba($color: #000000, $alpha: 0.5);
|
|
||||||
}
|
|
||||||
.diagram-container {
|
|
||||||
height: 100%;
|
|
||||||
.diagram-wrapper {
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.lf-diagram {
|
|
||||||
// box-shadow: 0px 0px 4px #838284;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* 由于背景图和gird不对齐,需要css处理一下 */
|
|
||||||
.diagram-container :v-deep .lf-background {
|
|
||||||
left: -9px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 9px;
|
|
||||||
height: 9px;
|
|
||||||
background: white;
|
|
||||||
border-left: 1px solid #e8e8e8;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
border-width: 1px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: #fff;
|
|
||||||
border-radius: 6px;
|
|
||||||
background: #c9c9c9;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #b5b5b5;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-drawer
|
|
||||||
v-model="drawerVisible"
|
|
||||||
size="600px"
|
|
||||||
:title="'节点:' + node?.text?.value"
|
|
||||||
@close="close"
|
|
||||||
>
|
|
||||||
<!-- fieldList:{{ fieldList }}
|
|
||||||
<div>properties:{{ properties }}</div> -->
|
|
||||||
|
|
||||||
<!-- 开始节点 -->
|
|
||||||
<!-- {{ node }} -->
|
|
||||||
|
|
||||||
<div v-if="node.type == 'bpmn:startEvent'">
|
|
||||||
开始节点
|
|
||||||
<FieldAuth :node="node" :properties="properties" :fieldList="fieldList"></FieldAuth>
|
|
||||||
</div>
|
|
||||||
<div v-if="node.type == 'bpmn:userTask'">
|
|
||||||
<UserTask :node="node" :properties="properties" :fieldList="fieldList"></UserTask>
|
|
||||||
<FieldAuth :node="node" :properties="properties" :fieldList="fieldList"></FieldAuth>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="node.type == 'bpmn:serviceTask'">
|
|
||||||
<div>系统任务</div>
|
|
||||||
<div>抄送</div>
|
|
||||||
<div>发送邮件</div>
|
|
||||||
<div>发送短信</div>
|
|
||||||
<div>发送站内消息</div>
|
|
||||||
<div>数据入库</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="node.type == 'bpmn:exclusiveGateway'">
|
|
||||||
<Gateway :node="node" :properties="properties" :fieldList="fieldList"></Gateway>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="node.type == 'bpmn:endEvent'">结束</div>
|
|
||||||
</el-drawer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import UserTask from './PropertyPanel/UserTask.vue'
|
|
||||||
import FieldAuth from './PropertyPanel/FieldAuth.vue'
|
|
||||||
import Gateway from './PropertyPanel/Gateway.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'PropertyPanel',
|
|
||||||
props: {},
|
|
||||||
components: {
|
|
||||||
UserTask,
|
|
||||||
FieldAuth,
|
|
||||||
Gateway
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
drawerVisible: false,
|
|
||||||
|
|
||||||
node: {},
|
|
||||||
properties: {
|
|
||||||
userType: '',
|
|
||||||
userId: '', //审批人id
|
|
||||||
deptId: '', //审批部门id
|
|
||||||
postId: '', //岗位id
|
|
||||||
|
|
||||||
fieldAuth: {}, // 字段权限
|
|
||||||
gateway: [] //网关条件列表
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 表单列表
|
|
||||||
* [{
|
|
||||||
* id: 1,
|
|
||||||
* label: '表单1',
|
|
||||||
* auth: 1,
|
|
||||||
* }]
|
|
||||||
*/
|
|
||||||
fieldList: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
open(node, fieldList) {
|
|
||||||
this.node = node
|
|
||||||
|
|
||||||
this.properties.userType = node?.properties?.userType || ''
|
|
||||||
this.properties.userId = node?.properties?.userId || ''
|
|
||||||
this.properties.deptId = node?.properties?.deptId || ''
|
|
||||||
this.properties.postId = node?.properties?.postId || ''
|
|
||||||
|
|
||||||
this.properties.gateway = node?.properties?.gateway || []
|
|
||||||
|
|
||||||
this.properties.fieldAuth = node?.properties?.fieldAuth
|
|
||||||
? { ...node?.properties?.fieldAuth }
|
|
||||||
: {}
|
|
||||||
|
|
||||||
this.fieldList = fieldList.map((item) => {
|
|
||||||
let auth = 1
|
|
||||||
const formId = item?.field?.id
|
|
||||||
if (node?.properties?.fieldAuth?.[formId]) {
|
|
||||||
auth = node.properties.fieldAuth[formId]
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
id: formId,
|
|
||||||
label: item?.field?.options?.label,
|
|
||||||
auth: auth
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.drawerVisible = true
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
const fieldAuth = {}
|
|
||||||
this.fieldList.forEach((item) => {
|
|
||||||
fieldAuth[item.id] = item.auth
|
|
||||||
})
|
|
||||||
this.$emit('setProperties', this.node, { ...this.properties })
|
|
||||||
|
|
||||||
// this.setProperties('fieldAuth', {
|
|
||||||
// ...fieldAuth
|
|
||||||
// })
|
|
||||||
// this.setProperties('userType', this.properties.userType)
|
|
||||||
// this.setProperties('userId', this.properties.userId)
|
|
||||||
// this.setProperties('deptId', this.properties.deptId)
|
|
||||||
// this.setProperties('postId', this.properties.postId)
|
|
||||||
// this.setProperties('gateway', this.properties.gateway)
|
|
||||||
},
|
|
||||||
|
|
||||||
setProperties(key, val) {
|
|
||||||
this.$emit('setProperties', this.node, {
|
|
||||||
[key]: val
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
v-model="drawerVisible"
|
||||||
|
size="600px"
|
||||||
|
:title="'节点:' + node?.text?.value"
|
||||||
|
@close="close"
|
||||||
|
>
|
||||||
|
<!-- fieldList:{{ fieldList }}
|
||||||
|
<div>properties:{{ properties }}</div> -->
|
||||||
|
|
||||||
|
<!-- 开始节点 -->
|
||||||
|
<!-- {{ node }} -->
|
||||||
|
|
||||||
|
<div v-if="node.type == 'bpmn:startEvent'">
|
||||||
|
开始节点
|
||||||
|
<FieldAuth :node="node" :properties="properties" :fieldList="fieldList"></FieldAuth>
|
||||||
|
</div>
|
||||||
|
<div v-if="node.type == 'bpmn:userTask'">
|
||||||
|
<UserTask :node="node" :properties="properties" :fieldList="fieldList"></UserTask>
|
||||||
|
<FieldAuth :node="node" :properties="properties" :fieldList="fieldList"></FieldAuth>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="node.type == 'bpmn:serviceTask'">
|
||||||
|
<div>系统任务</div>
|
||||||
|
<div>抄送</div>
|
||||||
|
<div>发送邮件</div>
|
||||||
|
<div>发送短信</div>
|
||||||
|
<div>发送站内消息</div>
|
||||||
|
<div>数据入库</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="node.type == 'bpmn:exclusiveGateway'">
|
||||||
|
<Gateway :node="node" :properties="properties" :fieldList="fieldList"></Gateway>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="node.type == 'bpmn:endEvent'">结束</div>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import UserTask from './UserTask.vue'
|
||||||
|
import FieldAuth from './FieldAuth.vue'
|
||||||
|
import Gateway from './Gateway.vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['setProperties'])
|
||||||
|
|
||||||
|
const drawerVisible = ref(false)
|
||||||
|
const node = ref<{
|
||||||
|
type?: string
|
||||||
|
text?: {
|
||||||
|
value?: string
|
||||||
|
}
|
||||||
|
}>({})
|
||||||
|
const properties = reactive({
|
||||||
|
userType: '',
|
||||||
|
userId: '',
|
||||||
|
deptId: '',
|
||||||
|
postId: '',
|
||||||
|
fieldAuth: {},
|
||||||
|
gateway: []
|
||||||
|
})
|
||||||
|
const fieldList = ref([])
|
||||||
|
|
||||||
|
const open = (newNode, newFieldList) => {
|
||||||
|
node.value = newNode
|
||||||
|
|
||||||
|
properties.userType = newNode?.properties?.userType || ''
|
||||||
|
properties.userId = newNode?.properties?.userId || ''
|
||||||
|
properties.deptId = newNode?.properties?.deptId || ''
|
||||||
|
properties.postId = newNode?.properties?.postId || ''
|
||||||
|
properties.gateway = newNode?.properties?.gateway || []
|
||||||
|
|
||||||
|
properties.fieldAuth = newNode?.properties?.fieldAuth
|
||||||
|
? { ...newNode?.properties?.fieldAuth }
|
||||||
|
: {}
|
||||||
|
|
||||||
|
fieldList.value = newFieldList.map((item) => ({
|
||||||
|
id: item?.field?.id,
|
||||||
|
label: item?.field?.options?.label,
|
||||||
|
auth: newNode?.properties?.fieldAuth?.[item?.field?.id] || 1
|
||||||
|
}))
|
||||||
|
drawerVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
const fieldAuth = {}
|
||||||
|
fieldList.value.forEach((item) => {
|
||||||
|
fieldAuth[item.id] = item.auth
|
||||||
|
})
|
||||||
|
|
||||||
|
emit('setProperties', node.value, { ...properties })
|
||||||
|
drawerVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
open
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
323
admin/src/components/flow/flowEdit/index.vue
Normal file
323
admin/src/components/flow/flowEdit/index.vue
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
<template>
|
||||||
|
<div class="diagram">
|
||||||
|
<diagram-toolbar
|
||||||
|
v-if="lf"
|
||||||
|
class="diagram-toolbar"
|
||||||
|
:lf="lf"
|
||||||
|
:active-edges="activeEdges"
|
||||||
|
@saveGraph="saveGraph"
|
||||||
|
@importData="importData"
|
||||||
|
/>
|
||||||
|
<div class="diagram-main">
|
||||||
|
<diagram-sidebar class="diagram-sidebar" @dragInNode="dragInNode" />
|
||||||
|
<div ref="container" class="diagram-container">
|
||||||
|
<div class="diagram-wrapper">
|
||||||
|
<div ref="diagramRef" class="lf-diagram"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Right-side property panel -->
|
||||||
|
<PropertyPanel ref="PropertyPanelRef" @setProperties="setProperties" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// Importing necessary functions and components
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import LogicFlow from '@logicflow/core'
|
||||||
|
import { SelectionSelect, Menu, BpmnElement } from '@logicflow/extension'
|
||||||
|
|
||||||
|
import '@logicflow/core/dist/style/index.css'
|
||||||
|
import '@logicflow/extension/lib/style/index.css'
|
||||||
|
import DiagramToolbar from './DiagramToolbar.vue'
|
||||||
|
import DiagramSidebar from './DiagramSidebar.vue'
|
||||||
|
import PropertyPanel from './PropertyPanel/PropertyPanel.vue'
|
||||||
|
import { registerCustomElement } from './node'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'flowEdit'
|
||||||
|
})
|
||||||
|
// Define component props
|
||||||
|
const props = defineProps({
|
||||||
|
tabName: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
fieldList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
conf: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Define refs for reactive data and component references
|
||||||
|
const lf = ref(null) // Reference to LogicFlow instance
|
||||||
|
const activeEdges = ref([]) // Reactive array for active edges
|
||||||
|
const diagramRef = ref(null) // Reference to the diagram container
|
||||||
|
const PropertyPanelRef = ref(null) // Reference to the PropertyPanel component
|
||||||
|
|
||||||
|
// Lifecycle hook to initialize LogicFlow when the component is mounted
|
||||||
|
onMounted(() => {
|
||||||
|
initLogicFlow(props.conf)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Function to initialize LogicFlow
|
||||||
|
function initLogicFlow(data) {
|
||||||
|
// 引入框选插件
|
||||||
|
LogicFlow.use(SelectionSelect)
|
||||||
|
LogicFlow.use(Menu)
|
||||||
|
LogicFlow.use(BpmnElement)
|
||||||
|
|
||||||
|
// Creating a new LogicFlow instance
|
||||||
|
const logicFlowInstance = new LogicFlow({
|
||||||
|
container: diagramRef.value, // Setting the container where LogicFlow will be rendered
|
||||||
|
overlapMode: 1,
|
||||||
|
autoWrap: true,
|
||||||
|
metaKeyMultipleSelected: true,
|
||||||
|
keyboard: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
size: 10,
|
||||||
|
type: 'dot'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Setting theme for LogicFlow
|
||||||
|
logicFlowInstance.setTheme({
|
||||||
|
baseEdge: { strokeWidth: 1 },
|
||||||
|
baseNode: { strokeWidth: 1 },
|
||||||
|
nodeText: { overflowMode: 'autoWrap', lineHeight: 1.5 },
|
||||||
|
edgeText: { overflowMode: 'autoWrap', lineHeight: 1.5 }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Registering custom elements for LogicFlow
|
||||||
|
registerCustomElement(logicFlowInstance)
|
||||||
|
|
||||||
|
// Setting default edge type and rendering initial data
|
||||||
|
logicFlowInstance.setDefaultEdgeType('pro-polyline')
|
||||||
|
logicFlowInstance.render(data)
|
||||||
|
|
||||||
|
// Assigning the LogicFlow instance to the 'lf' ref
|
||||||
|
lf.value = logicFlowInstance
|
||||||
|
|
||||||
|
// Event listener for node clicks
|
||||||
|
lf.value.on('node:click', (e) => {
|
||||||
|
console.log('Click on node', e.data)
|
||||||
|
PropertyPanelRef.value.open(e.data, props.fieldList)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to handle dragging nodes into the diagram
|
||||||
|
function dragInNode(type, text = '') {
|
||||||
|
lf.value.dnd.startDrag({
|
||||||
|
type,
|
||||||
|
text
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to set properties of a node
|
||||||
|
function setProperties(node, item) {
|
||||||
|
lf.value.setProperties(node.id, item)
|
||||||
|
}
|
||||||
|
// function setZIndex(node, type) {
|
||||||
|
// lf.value.setElementZIndex(node.id, type)
|
||||||
|
// }
|
||||||
|
// Function to import data into the LogicFlow instance
|
||||||
|
function importData(text) {
|
||||||
|
lf.value.renderRawData(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to save the graph data
|
||||||
|
function saveGraph() {
|
||||||
|
const data = lf.value.getGraphData()
|
||||||
|
download('export.json', JSON.stringify(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to download the graph data as a file
|
||||||
|
function download(filename, text) {
|
||||||
|
window.sessionStorage.setItem(filename, text)
|
||||||
|
const element = document.createElement('a')
|
||||||
|
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text))
|
||||||
|
element.setAttribute('download', filename)
|
||||||
|
element.style.display = 'none'
|
||||||
|
document.body.appendChild(element)
|
||||||
|
element.click()
|
||||||
|
document.body.removeChild(element)
|
||||||
|
}
|
||||||
|
async function getData() {
|
||||||
|
/**
|
||||||
|
* 校验目标
|
||||||
|
* 1. 必须存在开始节点和结束节点
|
||||||
|
* 2. 连线方向正确
|
||||||
|
* 3. 多余的节点(不重要)
|
||||||
|
* 4. 所有分支必须结束节点
|
||||||
|
* 5. 检查审批节点设置情况
|
||||||
|
* 6. 一个节点可以有多个子网关,子网关只能通过一个,不能有多个普通子节点
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
return new Promise<{
|
||||||
|
formData: any
|
||||||
|
treeToList: any
|
||||||
|
}>((resolve, reject) => {
|
||||||
|
const data = lf.value.getGraphData()
|
||||||
|
const nodes = data.nodes
|
||||||
|
const edges = data.edges
|
||||||
|
|
||||||
|
let haveMoreChildNode = false
|
||||||
|
const sourceNodeIdSum = {} // Node ID -> child nodes mapping
|
||||||
|
|
||||||
|
edges.forEach((edge) => {
|
||||||
|
const targetNode = nodes.find((item) => item.id === edge.targetNodeId)
|
||||||
|
if (sourceNodeIdSum[edge.sourceNodeId]) {
|
||||||
|
sourceNodeIdSum[edge.sourceNodeId].push(targetNode)
|
||||||
|
for (const n of sourceNodeIdSum[edge.sourceNodeId]) {
|
||||||
|
if (n.type !== 'bpmn:exclusiveGateway') {
|
||||||
|
haveMoreChildNode = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sourceNodeIdSum[edge.sourceNodeId] = [targetNode]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (haveMoreChildNode) {
|
||||||
|
return reject({
|
||||||
|
target: props.tabName,
|
||||||
|
message: '流程设计-一个节点只能有一个子节点,可以有多个网关'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 检查开始节点和结束节点是否存在
|
||||||
|
const findStartNode = nodes.filter((item) => item.type === 'bpmn:startEvent')
|
||||||
|
const findEndNode = nodes.filter((item) => item.type === 'bpmn:endEvent')
|
||||||
|
if (findStartNode.length !== 1 || findEndNode.length !== 1) {
|
||||||
|
return reject({
|
||||||
|
target: props.tabName,
|
||||||
|
message: '流程设计-流程必须有且只有一个开始节点和结束节点'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const TreeNode = handel(nodes, 0)
|
||||||
|
|
||||||
|
function handel(arr, pid) {
|
||||||
|
const newArr = []
|
||||||
|
arr.forEach((node) => {
|
||||||
|
const newNode = {
|
||||||
|
id: node.id,
|
||||||
|
pid: pid,
|
||||||
|
label: node?.text?.value,
|
||||||
|
type: node.type,
|
||||||
|
fieldAuth: node?.properties?.fieldAuth,
|
||||||
|
gateway: node?.properties?.gateway,
|
||||||
|
|
||||||
|
userType: node?.properties?.userType || 0,
|
||||||
|
userId: node?.properties?.userId || 0,
|
||||||
|
deptId: node?.properties?.deptId || 0,
|
||||||
|
postId: node?.properties?.postId || 0,
|
||||||
|
children: null
|
||||||
|
}
|
||||||
|
if (sourceNodeIdSum[node.id]) {
|
||||||
|
newNode.children = handel(sourceNodeIdSum[node.id], node.id)
|
||||||
|
}
|
||||||
|
newArr.push(newNode)
|
||||||
|
})
|
||||||
|
return newArr
|
||||||
|
}
|
||||||
|
|
||||||
|
function treeToList(tree) {
|
||||||
|
const arr = []
|
||||||
|
tree.forEach((item) => {
|
||||||
|
arr.push(item)
|
||||||
|
if (item.children) {
|
||||||
|
arr.push(...treeToList(item.children))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
// 检查连线方向是否正确;
|
||||||
|
resolve({ formData: data, treeToList: treeToList(TreeNode) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getData
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.diagram {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagram-toolbar {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 20px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 10px;
|
||||||
|
/* width: 310px; */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* border-bottom: 1px solid #e5e5e5; */
|
||||||
|
z-index: 10;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0px 0px 4px rgba($color: #000000, $alpha: 0.5);
|
||||||
|
}
|
||||||
|
.diagram-main {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.diagram-sidebar {
|
||||||
|
position: absolute;
|
||||||
|
left: 20px;
|
||||||
|
z-index: 10;
|
||||||
|
top: 50px;
|
||||||
|
width: 60px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0px 0px 4px rgba($color: #000000, $alpha: 0.5);
|
||||||
|
}
|
||||||
|
.diagram-container {
|
||||||
|
height: 100%;
|
||||||
|
.diagram-wrapper {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.lf-diagram {
|
||||||
|
// box-shadow: 0px 0px 4px #838284;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 由于背景图和gird不对齐,需要css处理一下 */
|
||||||
|
.diagram-container :v-deep .lf-background {
|
||||||
|
left: -9px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
background: white;
|
||||||
|
border-left: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #c9c9c9;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #b5b5b5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user