mirror of
https://gitee.com/xiangheng/x_admin.git
synced 2025-11-01 20:12:50 +08:00
流程部分setup
This commit is contained in:
@@ -48,13 +48,13 @@
|
||||
v-show="activeStep === 'formDesign'"
|
||||
tabName="formDesign"
|
||||
/>
|
||||
<Diagram
|
||||
ref="processDesign"
|
||||
v-show="activeStep === 'processDesign'"
|
||||
tabName="processDesign"
|
||||
<FlowEdit
|
||||
ref="flowEdit"
|
||||
v-show="activeStep === 'flowEdit'"
|
||||
tabName="flowEdit"
|
||||
:fieldList="fieldList"
|
||||
:conf="mockData.flowProcessData"
|
||||
></Diagram>
|
||||
></FlowEdit>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
@@ -62,7 +62,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, shallowRef, watch } from '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 feedback from '@/utils/feedback'
|
||||
@@ -89,7 +89,7 @@ const props = defineProps({
|
||||
|
||||
const basicSetting = shallowRef<InstanceType<typeof BasicSetting>>()
|
||||
const formDesign = shallowRef<InstanceType<typeof XForm>>()
|
||||
const processDesign = shallowRef<InstanceType<typeof Diagram>>()
|
||||
const flowEdit = shallowRef<InstanceType<typeof FlowEdit>>()
|
||||
const dialogVisible = ref(false)
|
||||
const activeStep = ref('basicSetting')
|
||||
const mockData = ref({
|
||||
@@ -102,7 +102,7 @@ const fieldList = ref([])
|
||||
const steps = [
|
||||
{ label: '基础设置', key: 'basicSetting' },
|
||||
{ label: '表单设计', key: 'formDesign' },
|
||||
{ label: '流程设计', key: 'processDesign' }
|
||||
{ label: '流程设计', key: 'flowEdit' }
|
||||
]
|
||||
watch(
|
||||
() => dialogVisible.value,
|
||||
@@ -151,7 +151,7 @@ function changeSteps(item) {
|
||||
function publish() {
|
||||
const p1 = basicSetting.value.getData()
|
||||
const p2 = formDesign.value.getData()
|
||||
const p3 = processDesign.value.getData()
|
||||
const p3 = flowEdit.value.getData()
|
||||
Promise.all([p1, p2, p3])
|
||||
.then((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