703 lines
21 KiB
Vue
703 lines
21 KiB
Vue
<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>
|