workflow-engine-web/flowable-engine-web/src/views/admin/layout/process/ProcessTree.vue

703 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script>
//导入所有节点组件
import Approval from '@/views/common/process/nodes/ApprovalNode.vue'
import Cc from '@/views/common/process/nodes/CcNode.vue'
import Concurrent from '@/views/common/process/nodes/ConcurrentNode.vue'
import Condition from '@/views/common/process/nodes/ConditionNode.vue'
import Trigger from '@/views/common/process/nodes/TriggerNode.vue'
import Delay from '@/views/common/process/nodes/DelayNode.vue'
import Empty from '@/views/common/process/nodes/EmptyNode.vue'
import Root from '@/views/common/process/nodes/RootNode.vue'
import Node from '@/views/common/process/nodes/Node.vue'
import DefaultProps from "./DefaultNodeProps"
export default {
name: "ProcessTree",
components: {Node, Root, Approval, Cc, Trigger, Concurrent, Condition, Delay, Empty},
data() {
return {
valid: true
}
},
computed: {
nodeMap() {
return this.$store.state.nodeMap;
},
parentMap() {
return this.$store.state.parentMap;
},
dom() {
return this.$store.state.design.process;
}
},
render(h, ctx) {
console.log("渲染流程树")
this.nodeMap.clear()
this.parentMap.clear()
this.initMapping(this.dom)
console.log(this.dom)
console.log(this.nodeMap, this.parentMap)
let processTrees = this.getDomTree(h, "admin")
//插入末端节点
processTrees.push(h('div', {style: {'text-align': 'center'}}, [
h('div', {class: {'process-end': true}, domProps: {innerHTML: '流程结束'}})
]))
return h('div', {class: {'_root': true}, ref: '_root'}, processTrees)
},
methods: {
// 获取demo的树形结构
getDomTree(h, id) {
let node = this.parentMap.get(id)
if (!(node && node.id)) {
return []
}
console.log(node.name, "node.name", node.id, "node.id")
if (this.isPrimaryNode(node)) {
//普通业务节点
console.log("node name", node.name, "id", node.id, "parentId", node.parentId)
let childDoms = this.getDomTree(h, node.id)
this.decodeAppendDom(h, node, childDoms)
return [h('div', {'class': {'primary-node': true}}, childDoms)];
} else if (this.isBranchNode(node)) {
let index = 0;
//遍历分支节点,包含并行及条件节点
let branchItems = node.branchs.map(branchNode => {
//处理每个分支内子节点
this.toMapping(branchNode)
let childDoms = this.getDomTree(h, branchNode.id)
this.decodeAppendDom(h, branchNode, childDoms, {level: index + 1, size: node.branchs.length})
//插入4条横线遮挡掉条件节点左右半边线条
this.insertCoverLine(h, index, childDoms, node.branchs)
//遍历子分支尾部分支
index++;
return h('div', {'class': {'branch-node-item': true}}, childDoms);
})
//插入添加分支/条件的按钮
branchItems.unshift(h('div', {'class': {'add-branch-btn': true}}, [
h('el-button', {
'class': {'add-branch-btn-el': true},
props: {size: 'small', round: true},
on: {click: () => this.addBranchNode(node)},
domProps: {innerHTML: `添加${this.isConditionNode(node) ? '条件' : '分支'}`},
}, [])
]));
let bchDom = [h('div', {'class': {'branch-node': true}}, branchItems)]
//继续遍历分支后的节点
let afterChildDoms = this.getDomTree(h, node.id)
return [h('div', {}, [bchDom, afterChildDoms])]
} else if (this.isEmptyNode(node)) {
//空节点,存在于分支尾部
let childDoms = this.getDomTree(h, node.id)
this.decodeAppendDom(h, node, childDoms)
return [h('div', {'class': {'empty-node': true}}, childDoms)];
}
return []
},
getDomTreeOld(h, node) {
this.toMapping(node);
if (this.isPrimaryNode(node)) {
//普通业务节点
let childDoms = this.getDomTreeOld(h, node.children)
this.decodeAppendDom(h, node, childDoms)
return [h('div', {'class': {'primary-node': true}}, childDoms)];
} else if (this.isBranchNode(node)) {
let index = 0;
//遍历分支节点,包含并行及条件节点
let branchItems = node.branchs.map(branchNode => {
//处理每个分支内子节点
this.toMapping(branchNode);
let childDoms = this.getDomTreeOld(h, branchNode.children)
this.decodeAppendDom(h, branchNode, childDoms, {level: index + 1, size: node.branchs.length})
//插入4条横线遮挡掉条件节点左右半边线条
this.insertCoverLine(h, index, childDoms, node.branchs)
//遍历子分支尾部分支
index++;
return h('div', {'class': {'branch-node-item': true}}, childDoms);
})
//插入添加分支/条件的按钮
branchItems.unshift(h('div', {'class': {'add-branch-btn': true}}, [
h('el-button', {
'class': {'add-branch-btn-el': true},
props: {size: 'small', round: true},
on: {click: () => this.addBranchNode(node)},
domProps: {innerHTML: `添加${this.isConditionNode(node) ? '条件' : '分支'}`},
}, [])
]));
let bchDom = [h('div', {'class': {'branch-node': true}}, branchItems)]
//继续遍历分支后的节点
let afterChildDoms = this.getDomTreeOld(h, node.children)
return [h('div', {}, [bchDom, afterChildDoms])]
} else if (this.isEmptyNode(node)) {
//空节点,存在于分支尾部
let childDoms = this.getDomTreeOld(h, node.children)
this.decodeAppendDom(h, node, childDoms)
return [h('div', {'class': {'empty-node': true}}, childDoms)];
} else {
//遍历到了末端,无子节点
return [];
}
},
//解码渲染的时候插入dom到同级
decodeAppendDom(h, node, dom, props = {}) {
props.config = node
dom.unshift(h(node.type.toLowerCase(), {
props: props,
ref: node.id,
key: node.id,
//定义事件,插入节点,删除节点,选中节点,复制/移动
on: {
insertNode: type => this.insertNode(type, node),
delNode: () => this.delNode(node),
selected: () => this.selectNode(node),
copy: () => this.copyBranch(node),
leftMove: () => this.branchMove(node, -1),
rightMove: () => this.branchMove(node, 1)
}
}, []))
},
// 初始化map集合,以便数据整理
initMapping(node) {
node.forEach(node => {
this.nodeMap.set(node.id, node)
this.parentMap.set(node.parentId, node)
})
},
//id映射到map用来向上遍历
toMapping(node) {
if (node && node.id) {
//console.log("node=> " + node.id + " name:" + node.name + " type:" + node.type)
let newNode = {
...node
}
newNode.children = []
this.nodeMap.set(newNode.id, newNode)
}
},
// 新增线条
insertCoverLine(h, index, doms, branchs) {
if (index === 0) {
//最左侧分支
doms.unshift(h('div', {'class': {'line-top-left': true}}, []))
doms.unshift(h('div', {'class': {'line-bot-left': true}}, []))
}
if (index === branchs.length - 1) {
//最右侧分支
doms.unshift(h('div', {'class': {'line-top-right': true}}, []))
doms.unshift(h('div', {'class': {'line-bot-right': true}}, []))
}
},
copyBranch(node) {
let parentNode = this.nodeMap.get(node.parentId)
let branchNode = this.$deepCopy(node)
branchNode.name = branchNode.name + '-copy'
this.forEachNode(parentNode, branchNode, (parent, node) => {
let id = this.getRandomId()
console.log(node, '新id =>' + id, '老nodeId:' + node.id)
node.id = id
node.parentId = parent.id
})
parentNode.branchs.splice(parentNode.branchs.indexOf(node), 0, branchNode)
this.$forceUpdate()
},
//移动节点
branchMove(node, offset) {
let parentNode = this.nodeMap.get(node.parentId)
let index = parentNode.branchs.indexOf(node)
let branch = parentNode.branchs[index + offset]
parentNode.branchs[index + offset] = parentNode.branchs[index]
parentNode.branchs[index] = branch
this.$forceUpdate()
},
//判断是否为主要业务节点
isPrimaryNode(node) {
return node &&
(node.type === 'ROOT' || node.type === 'APPROVAL'
|| node.type === 'CC' || node.type === 'DELAY'
|| node.type === 'TRIGGER');
},
isBranchNode(node) {
return node && (node.type === 'CONDITIONS' || node.type === 'CONCURRENTS');
},
isEmptyNode(node) {
return node && (node.type === 'EMPTY')
},
//是分支节点
isConditionNode(node) {
return node.type === 'CONDITIONS';
},
//是分支节点
isBranchSubNode(node) {
return node && (node.type === 'CONDITION' || node.type === 'CONCURRENT');
},
isConcurrentNode(node) {
return node.type === 'CONCURRENTS'
},
getRandomId() {
return `node_${new Date().getTime().toString().substring(5)}${Math.round(Math.random() * 9000 + 1000)}`
},
//选中一个节点
selectNode(node) {
this.$store.commit('selectedNode', node)
this.$emit('selectedNode', node)
},
//处理节点插入逻辑
insertNode(type, parentNode) {
this.$refs['_root'].click()
//缓存一下后面的节点
console.log(parentNode)
//插入新节点
let id = this.getRandomId();
this.updateParentId(id, parentNode.id)
let children = {
id: id,
parentId: parentNode.id,
// props: {},
type: type,
}
switch (type) {
case 'APPROVAL':
this.insertApprovalNode(children);
break;
case 'CC':
this.insertCcNode(children);
break;
case 'DELAY':
this.insertDelayNode(children);
break;
case 'TRIGGER':
this.insertTriggerNode(children);
break;
case 'CONDITIONS':
this.insertConditionsNode(children);
break;
case 'CONCURRENTS':
this.insertConcurrentsNode(children);
break;
default:
break;
}
this.$forceUpdate()
},
/**
* 更新父id
* @param newId
* @param oldId
*/
updateParentId(newId, oldId) {
this.dom.map(node => {
if (node.parentId === oldId) {
node.parentId = newId
}
})
},
/**
* 审批人
* @param parentNode
*/
insertApprovalNode(parentNode) {
let node = {
...parentNode,
name: "审批人",
props: this.$deepCopy(DefaultProps.APPROVAL_PROPS)
}
this.dom.push(node)
},
/**
* 抄送人
* @param node
*/
insertCcNode(node) {
let newNode = {
...node,
name: "抄送人",
props: this.$deepCopy(DefaultProps.CC_PROPS)
}
this.dom.push(newNode)
},
/**
* 延时处理
* @param node
*/
insertDelayNode(node) {
let newNode = {
...node,
name: "延时处理",
props: this.$deepCopy(DefaultProps.DELAY_PROPS)
}
this.dom.push(newNode)
},
/**
* 触发器
* @param node
*/
insertTriggerNode(node) {
let newNode = {
...node,
name: "触发器",
props: this.$deepCopy(DefaultProps.TRIGGER_PROPS)
}
this.dom.push(newNode)
},
/**
* 新增条件分支
* @param node
*/
insertConditionsNode(node) {
let newNode = {
...node,
name: "条件分支",
branchs: [
{
id: this.getRandomId(),
parentId: node.id,
type: "CONDITION",
props: this.$deepCopy(DefaultProps.CONDITION_PROPS),
name: "条件1",
children: {}
}, {
id: this.getRandomId(),
parentId: node.id,
type: "CONDITION",
props: this.$deepCopy(DefaultProps.CONDITION_PROPS),
name: "条件2",
children: {}
}
]
}
this.dom.push(newNode)
let emptyNode = {
id: this.getRandomId(),
parentId: node.id,
type: "EMPTY"
}
this.updateParentId(emptyNode.id, newNode.id)
this.dom.push(emptyNode)
},
/**
* 新增同步运行节点
* @param node
*/
insertConcurrentsNode(node) {
let newNode = {
...node,
name: "并行分支",
branchs: [
{
id: this.getRandomId(),
parentId: node.id,
type: "CONDITION",
props: this.$deepCopy(DefaultProps.CONDITION_PROPS),
name: "条件1",
children: {}
}, {
id: this.getRandomId(),
parentId: node.id,
type: "CONDITION",
props: this.$deepCopy(DefaultProps.CONDITION_PROPS),
name: "条件2",
children: {}
}
]
}
this.dom.push(newNode)
let emptyNode = {
id: this.getRandomId(),
parentId: node.id,
type: "EMPTY"
}
this.updateParentId(emptyNode.id, newNode.id)
this.dom.push(emptyNode)
},
getBranchEndNode(conditionNode) {
if (!conditionNode.children || !conditionNode.children.id) {
return conditionNode;
}
return this.getBranchEndNode(conditionNode.children);
},
addBranchNode(node) {
if (node.branchs.length < 8) {
node.branchs.push({
id: this.getRandomId(),
parentId: node.id,
name: (this.isConditionNode(node) ? '条件' : '分支') + (node.branchs.length + 1),
props: this.isConditionNode(node) ? this.$deepCopy(DefaultProps.CONDITION_PROPS) : {},
type: this.isConditionNode(node) ? "CONDITION" : "CONCURRENT",
children: {}
})
} else {
this.$message.warning("最多只能添加 8 项😥")
}
},
//删除当前节点
delNode(node) {
//获取该节点的父节点
let parentNode = this.nodeMap.get(node.parentId)
if (parentNode) {
if (this.isBranchNode(parentNode)) {
this.delBranchNode(parentNode, node)
} else {
this.delNodeInDomChange(node.id, parentNode.id)
}
} else {
this.$message.warning("出现错误,找不到上级节点😥")
}
},
//删除分支
delBranchNode(parentNode, node) {
let sunNode = this.parentMap.get(node.id)
//判断当前节点下有没有字节点,有则需要提示
if (sunNode) {
this.$confirm('当前分支下有子节点,是否继续?', '提示', {
confirmButtonText: '确 定',
cancelButtonText: '取 消',
type: 'warning'
}).then(() => {
//确认后进行子节点的操作
this.delBranchSunNode(sunNode.id)
this.doDelBranchNode(parentNode, node)
})
return
}else {
// 没有直接开始删除
this.doDelBranchNode(parentNode, node)
}
},
//删除分支的子节点
delBranchSunNode(id) {
let node = this.parentMap.get(id)
this.delNodeInDomChange(id)
if (node) {
this.delBranchSunNode(node.id)
}
},
//删除分支节点
doDelBranchNode(parentNode, node){
//判断当前分时是否为2
if (parentNode.branchs.length === 2){
let nodeList = [...parentNode.branchs]
nodeList.splice(nodeList.indexOf(node), 1)
//查看另外一个分支上是否有节点
let sunNode = this.parentMap.get(nodeList[0].id)
//有则需要放到主分支上
if (sunNode){
//更改分支上第一个节点的父id
this.updateParentId(parentNode.parentId,sunNode.parentId)
//找到最后一个节点
let lastNode = this.getLastNode(sunNode.id)
let emptyNode = this.parentMap.get(parentNode.id)
//更新空节点下的第一个节点的id为当前分支最后一个节点
this.updateParentId(lastNode.id,emptyNode.id)
//删除分支的主节点
this.delNodeInDom(parentNode)
//删除分支的空节点
this.delNodeInDom(emptyNode)
}else {
//没有则直接删除
this.delEntireBranch(parentNode)
}
}else {
parentNode.branchs.splice(parentNode.branchs.indexOf(node), 1)
}
},
//获取最后一个节点
getLastNode(id){
let node = this.parentMap.get(id)
if (node){
return this.getLastNode(node.id)
}else {
return this.nodeMap.get(id)
}
},
delEntireBranch(node){
//删除分支节点和空节点
let emptyNode = this.parentMap.get(node.id)
this.delNodeInDomChange(node.id,node.parentId)
this.delNodeInDomChange(emptyNode.id,emptyNode.parentId)
},
/**
* 从dom中删除
* @param delId
* @param parentId
*/
delNodeInDomChange(delId, parentId) {
this.updateParentId(parentId, delId)
let delNode = this.nodeMap.get(delId)
this.dom.splice(this.dom.indexOf(delNode),1)
},
delNodeInDom(delNode){
this.dom.splice(this.dom.indexOf(delNode),1)
},
validateProcess() {
this.valid = true
let err = []
this.validate(err, this.dom)
return err
},
validateNode(err, node) {
if (this.$refs[node.id].validate) {
this.valid = this.$refs[node.id].validate(err)
}
},
//更新指定节点的dom
nodeDomUpdate(node) {
this.$refs[node.id].$forceUpdate()
},
//给定一个起始节点,遍历内部所有节点
forEachNode(parent, node, callback) {
if (this.isBranchNode(node)) {
callback(parent, node)
this.forEachNode(node, node.children, callback)
node.branchs.map(branchNode => {
callback(node, branchNode)
this.forEachNode(branchNode, branchNode.children, callback)
})
} else if (this.isPrimaryNode(node) || this.isEmptyNode(node) || this.isBranchSubNode(node)) {
callback(parent, node)
this.forEachNode(node, node.children, callback)
}
},
//校验所有节点设置
validate(err, nodeList) {
nodeList.map(node=>{
if (this.isPrimaryNode(node)){
//校验条件节点
this.validateNode(err, node)
}else if (this.isBranchNode(node)){
node.branchs.map(branchNode => {
//校验条件节点
this.validateNode(err, branchNode)
})
}
})
},
validateOld(err, node) {
if (this.isPrimaryNode(node)) {
this.validateNode(err, node)
this.validate(err, node.children)
} else if (this.isBranchNode(node)) {
//校验每个分支
node.branchs.map(branchNode => {
//校验条件节点
this.validateNode(err, branchNode)
//校验条件节点后面的节点
this.validate(err, branchNode.children)
})
this.validate(err, node.children)
} else if (this.isEmptyNode(node)) {
this.validate(err, node.children)
}
}
},
watch: {}
}
</script>
<style lang="less" scoped>
._root {
margin: 0 auto;
}
.process-end {
width: 60px;
margin: 0 auto;
margin-bottom: 20px;
border-radius: 15px;
padding: 5px 10px;
font-size: small;
color: #747474;
background-color: #f2f2f2;
box-shadow: 0 0 10px 0 #bcbcbc;
}
.primary-node {
display: flex;
align-items: center;
flex-direction: column;
}
.branch-node {
display: flex;
justify-content: center;
/*border-top: 2px solid #cccccc;
border-bottom: 2px solid #cccccc;*/
}
.branch-node-item {
position: relative;
display: flex;
background: #f5f6f6;
flex-direction: column;
align-items: center;
border-top: 2px solid #cccccc;
border-bottom: 2px solid #cccccc;
&:before {
content: "";
position: absolute;
top: 0;
left: calc(50% - 1px);
margin: auto;
width: 2px;
height: 100%;
background-color: #CACACA;
}
.line-top-left, .line-top-right, .line-bot-left, .line-bot-right {
position: absolute;
width: 50%;
height: 4px;
background-color: #f5f6f6;
}
.line-top-left {
top: -2px;
left: -1px;
}
.line-top-right {
top: -2px;
right: -1px;
}
.line-bot-left {
bottom: -2px;
left: -1px;
}
.line-bot-right {
bottom: -2px;
right: -1px;
}
}
.add-branch-btn {
position: absolute;
width: 80px;
.add-branch-btn-el {
z-index: 999;
position: absolute;
top: -15px;
}
}
.empty-node {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
</style>