1. 实现效果
2. 实现代码
<template>
<!-- 工作流设计页面 -->
<div class="container-wrapper">
<div class="zoom-btns">
<el-button icon="el-icon-minus" size="mini" :disabled="minusDisabled" @click="minus"></el-button>
<span>{{ (size * 100).toFixed(0) }}%</span>
<el-button icon="el-icon-plus" size="mini" :disabled="plusDisabled" @click="plus"></el-button>
</div>
<!-- 左侧展示 -->
<div class="container-left" v-loading="loading" v-if="!loading">
<div :style="{ transform: scale }">
<div v-for="(item, index) in workFlowList" :key="index" style="display: flex; flex-direction: column; align-items: center" :style="{ width: width }">
<!-- 表单触发事件 -->
<div class="node" v-if="item.type == 'trigger'">
<div class="node-top">
<div class="node-icon"><i class="el-icon-tickets"></i></div>
<div class="node-name">{{ item.name }}</div>
<div class="node-more"><!-- <i class="el-icon-more"></i> --></div>
</div>
<div class="node-content" @click="getContent(item)" :class="item.id == contentData.id ? 'node-content-active' : ''">
<div class="node-empty" v-if="item.triggerEvent == ''"><span>请设置触发方式</span></div>
<span v-else class="node-empty-span">
<span v-if="item.triggerEvent == 'C'">仅新建数据时触发</span>
<span v-if="item.triggerEvent == 'U'">仅编辑数据时触发</span>
<span v-if="item.triggerEvent == 'CU'">新建数据时或是编辑数据时触发</span>
<span v-if="item.triggerEvent == 'D'">删除数据时触发</span>
</span>
</div>
<el-tooltip class="item" effect="dark" content="请设置触发方式" placement="bottom" v-if="item.triggerEvent == ''">
<div class="error-block"><i class="el-icon-warning"></i></div>
</el-tooltip>
</div>
<div class="add-node" v-if="item.type == 'trigger'">
<div class="add-node-btn">
<div class="add-node-button" @click="addNode(item, 'trigger')"><i class="el-icon-plus"></i></div>
</div>
</div>
<!-- 审批节点 -->
<template v-if="item.type == 'approval'">
<div class="node">
<div class="node-top">
<div class="node-icon"><i class="el-icon-user-solid"></i></div>
<div class="node-name">{{ item.name }}</div>
<div class="node-more"><i class="el-icon-delete" @click="nodeDelete(item)"></i></div>
</div>
<div class="node-content" @click="getContent(item)" :class="item.id == contentData.id ? 'node-content-active' : ''">
<div class="node-empty" v-if="item.personInCharge.name == undefined || item.personInCharge.name == ''"><span>请设置负责人</span></div>
<span v-else class="node-empty-span">审批人:{{ item.personInCharge.name }}</span>
</div>
<el-tooltip
class="item"
effect="dark"
content="请设置负责人"
placement="bottom"
v-if="item.personInCharge.name == undefined || item.personInCharge.name == ''"
>
<div class="error-block"><i class="el-icon-warning"></i></div>
</el-tooltip>
</div>
<div class="add-node">
<div class="add-node-btn">
<div class="add-node-button" @click="addNode(item)"><i class="el-icon-plus"></i></div>
</div>
</div>
</template>
<!-- 抄送节点 -->
<template v-if="item.type == 'giveList'">
<div class="node">
<div class="node-top">
<div class="node-icon"><i class="el-icon-s-promotion"></i></div>
<div class="node-name">{{ item.name }}</div>
<div class="node-more"><i class="el-icon-delete" @click="nodeDelete(item)"></i></div>
</div>
<div class="node-content" @click="getContent(item)" :class="item.id == contentData.id ? 'node-content-active' : ''">
<div class="node-empty" v-if="item.personInCharge.name == undefined || item.personInCharge.name == ''"><span>请设置负责人</span></div>
<span v-else class="node-empty-span">抄送人:{{ item.personInCharge.name }}</span>
</div>
<el-tooltip
class="item"
effect="dark"
content="请设置抄送人"
placement="bottom"
v-if="item.personInCharge.name == undefined || item.personInCharge.name == ''"
>
<div class="error-block"><i class="el-icon-warning"></i></div>
</el-tooltip>
</div>
<div class="add-node">
<div class="add-node-btn">
<div class="add-node-button" @click="addNode(item)"><i class="el-icon-plus"></i></div>
</div>
</div>
</template>
<!-- 条件分支 -->
<template v-if="item.children != undefined && item.children.length > 0">
<branch
:style="{ width: width }"
:model="item.children"
:activeId="activeId"
@addNodeChange="addNodeChange"
@addConditionChange="addConditionChange"
@addNodeDelete="addNodeDelete"
@addNodeGetContent="addNodeGetContent"
></branch>
</template>
</div>
<!-- 结束节点 -->
<div class="end-node" :style="{ width: width }">
<div class="end-node-circle"></div>
<div class="end-node-text">结束流程</div>
</div>
</div>
</div>
<div class="container-border"></div>
<!-- 右侧配置 -->
<div v-if="formFile.length > 0" class="container-right" :class="isOpen ? 'container-right-open' : ''">
<rightPanel
:formFile="formFile"
:contentData="contentData"
:contentForm="contentForm"
@configurationSave="configurationSave"
@configurationCancel="configurationCancel"
></rightPanel>
</div>
<!-- 添加节点抽屉 -->
<el-drawer :visible.sync="drawer" :modal-append-to-body="false" direction="rtl" :before-close="handleClose">
<div slot="title"><span style="font-size: 20px; color: #333333">添加一个动作</span></div>
<div class="drawer-wrapper">
<div class="drawer-title">人工节点</div>
<div class="drawer-content">
<div class="drawer-content-item" @click="addNodeDrawer('approval')"><span>审批节点</span></div>
<div class="drawer-content-item" @click="addNodeDrawer('giveList')"><span>抄送节点</span></div>
</div>
<div class="drawer-title">构件</div>
<div class="drawer-content">
<div class="drawer-content-item" @click="addNodeDrawer('branch')"><span>条件分支</span></div>
</div>
</div>
</el-drawer>
</div>
</template>
<script>
import branch from './branch';
import rightPanel from './rightPanel.vue';
import { enterpriseFormGetByFormCode, enterpriseWorkflowEdit, enterpriseWorkflow, enterpriseWorkflowGetByCod } from '../../../api/workflows/index.js';
export default {
name: 'workFlow',
props: ['formCode', 'workListId', 'workListCode'],
components: {
branch,
rightPanel
},
data() {
return {
formFile: [], // 动态表单字段
drawer: false, // 节点抽屉
// 工作流展示及节点属性配置
workFlowList: [
{
id: this.generateUUID(),
name: '表单触发事件',
type: 'trigger',
triggerEvent: 'C',
triggerEventForm: '',
triggerConditions: []
}
],
// 流程属性
formWork: {
withdraw: true, // 是否允许工作流撤回
approvalHistory: false, // 是否允许查看审批历史
approveEdit: false, // 审批完成后是否可以编辑
noApprover: '', // 无需审批人
emptyApprover: 'PASS', // 审批人为空时
heavyApprover: '', // 审批人去重
ownApproval: true // 发起人审批自动通过
},
workFlowIndex: 0, // 下标记录
width: '', // 宽度
scale: '', // 放大,缩小
size: 1, // 数值
minusDisabled: false, // 缩小禁用
plusDisabled: false, // 放大禁用
contentData: {}, // 左侧选择数据
contentForm: {}, // 左侧选择数据 流程属性
isOpen: false, // 右侧配置是否显示
drawerType: 'add', // 区分是否为条件分支添加
branchModel: '', // 条件分支传来的参数 存放
branchConfig: '', // 条件分支传来的参数 存放
activeId: '', // 条件分支部分选中
loading: false,
idList: [] // 存放父级id
};
},
computed: {},
created() {
this.getFormFile();
this.getWork();
},
methods: {
// 查询工作流内容
getWork() {
this.loading = true;
enterpriseWorkflow(this.workListId).then(response => {
this.loading = false;
if (response.data.defineJson != null) {
this.formWork = [];
this.workFlowList = [];
this.formWork = JSON.parse(response.data.defineJson).formWork;
this.workFlowList = JSON.parse(response.data.defineJson).workFlowList;
this.refresh();
}
});
},
// 刷新
refresh() {
this.loading = true;
this.formWork = [];
this.workFlowList = [];
enterpriseWorkflowGetByCod({ code: this.workListCode }).then(response => {
this.loading = false;
if (response.data.defineJson != '') {
this.formWork = JSON.parse(response.data.defineJson).formWork;
this.workFlowList = JSON.parse(response.data.defineJson).workFlowList;
}
setTimeout(() => {
this.width = document.querySelector('.branch-box').clientWidth + 'px';
}, 500);
});
},
// 获取动态表单字段
getFormFile() {
enterpriseFormGetByFormCode({ formCode: this.formCode }).then(response => {
if (response.data) {
this.formFile = JSON.parse(response.data.defineJson);
for(var i = 0; i < this.formFile.length; i++){
if(this.formFile[i].componentType == 'tables' || this.formFile[i].componentType == 'upload'){
this.formFile.splice(i,1);
i--
}
};
}
});
},
// 点击加号 添加节点
addNode(config, trigger) {
// 获取当前元素在数据内的下标位置
this.workFlowIndex = this.workFlowList.map(item => item).indexOf(config);
this.idList = [];
if (trigger == undefined) {
this.idList.push(config.id);
}
this.drawer = true;
this.drawerType = 'add';
},
// 保存id 作为parentId使用
addNodeParList(list){
for (var m = 0; m < list.length; m++) {
if(list[m].children == undefined || list[m].children.length == 0){
this.idList.push(list[m].id)
}else {
this.addNodeParList(list[m].children)
}
}
},
// 点击抽屉内容添加节点
addNodeDrawer(config) {
if (this.drawerType == 'add') {
switch (config) {
case 'approval':
var approvalConfigureData = {
id: this.generateUUID(),
name: '审批节点',
type: 'approval',
personInCharge: '',
enable: false,
personInChargeCC: '',
nodeTitle: [],
opinion: '禁用',
circulationRules: 'ANYONE',
parentId: this.idList,
nodeOperation: [
{ buttonText: '同意', type: 'agree', isTrue: true },
{ buttonText: '回退', type: 'retreat', isTrue: false },
{
buttonText: '转交',
type: 'forward',
isTrue: false,
candidate: ''
},
{ buttonText: '拒绝', type: 'refuse', isTrue: true }
]
};
this.workFlowList.splice(this.workFlowIndex + 1, 0, approvalConfigureData);
this.drawer = false;
this.saveEdit();
break;
case 'giveList':
var CCConfigureData = {
id: this.generateUUID(),
name: '抄送节点',
type: 'giveList',
personInCharge: '',
nodeTitle: [],
parentId: this.idList
};
this.workFlowList.splice(this.workFlowIndex + 1, 0, CCConfigureData);
this.drawer = false;
this.saveEdit();
break;
case 'branch':
var branchConfigureData = {
children: [
{
id: this.generateUUID(),
name: '条件1',
type: 'branch',
condition: 'custom',
editing: 'BEFOREUPDATE',
triggerConditions: [],
children: [],
parentId: this.idList
},
{
name: '条件2',
type: 'branch',
id: this.generateUUID(),
condition: 'custom',
editing: 'BEFOREUPDATE',
triggerConditions: [],
children: [],
parentId: this.idList
}
]
};
this.workFlowList.splice(this.workFlowIndex + 1, 0, branchConfigureData);
this.drawer = false;
this.saveEdit();
break;
}
} else {
this.myFilter(this.branchModel, this.branchConfig, config);
}
},
// 左侧点击
getContent(config) {
this.isOpen = true;
this.activeId = '';
this.contentData = JSON.parse(JSON.stringify(config));
this.contentForm = JSON.parse(JSON.stringify(this.formWork));
},
// 条件分支组件 传来的
addNodeGetContent(config) {
this.isOpen = true;
this.activeId = config.id;
this.contentData = JSON.parse(JSON.stringify(config));
this.contentForm = JSON.parse(JSON.stringify(this.formWork));
},
// 关闭节点抽屉
handleClose() {
this.drawer = false;
},
// 缩小
minus() {
this.size = parseFloat((this.size - 0.1).toFixed(1));
if (this.size <= 0.5) {
// 禁用
this.minusDisabled = true;
} else {
this.scale = `scale(${this.size})`;
this.plusDisabled = false;
}
},
// 放大
plus() {
this.size = parseFloat((this.size + 0.1).toFixed(1));
if (this.size >= 1.5) {
// 禁用
this.plusDisabled = true;
} else {
this.scale = `scale(${this.size})`;
this.minusDisabled = false;
}
},
// 条件分支 抽屉选择添加节点
myFilter(arr, config, type) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].id == config.id) {
switch (type) {
case 'approval':
var approvalConfigureData = {
id: this.generateUUID(),
name: '审批节点',
type: 'approval',
personInCharge: '',
enable: false,
personInChargeCC: '',
nodeTitle: [],
opinion: '禁用',
parentId: this.idList,
circulationRules: 'ANYONE',
nodeOperation: [
{ buttonText: '同意', type: 'agree', isTrue: true },
{ buttonText: '回退', type: 'retreat', isTrue: false },
{
buttonText: '转交',
type: 'forward',
isTrue: false,
candidate: ''
},
{ buttonText: '拒绝', type: 'refuse', isTrue: true }
],
children: []
};
arr[i].children.push(approvalConfigureData);
this.drawer = false;
this.saveEdit();
break;
case 'giveList':
var CCConfigureData = {
id: this.generateUUID(),
name: '抄送节点',
type: 'giveList',
parentId: this.idList,
personInCharge: '',
nodeTitle: [],
children: []
};
arr[i].children.push(CCConfigureData);
this.drawer = false;
this.saveEdit();
break;
case 'branch':
arr[i].children.push(
{
id: this.generateUUID(),
name: '条件1',
type: 'branch',
parentId: this.idList,
condition: 'custom',
editing: 'BEFOREUPDATE',
triggerConditions: [],
children: []
},
{
name: '条件2',
type: 'branch',
condition: 'custom',
parentId: this.idList,
editing: 'BEFOREUPDATE',
id: this.generateUUID(),
triggerConditions: [],
children: []
}
);
this.drawer = false;
this.saveEdit();
setTimeout(() => {
this.width = document.querySelector('.branch-box').clientWidth + 'px';
}, 500);
break;
}
break;
} else {
this.myFilter(arr[i].children, config);
}
}
},
// 条件分支组件 添加节点
addNodeChange(model, config) {
this.idList = [];
if (config != undefined) {
this.idList.push(config.id);
this.drawerType = 'branchAdd';
this.drawer = true;
this.branchModel = model;
this.branchConfig = config;
} else {
let index = 0;
for (var i = 0; i < this.workFlowList.length; i++) {
if (this.workFlowList[i].children != undefined && model.length > 0) {
if (this.workFlowList[i].children[0].id == model[0].id) {
index = i;
}
}
}
this.drawer = true;
this.drawerType = 'add';
this.workFlowIndex = index;
}
setTimeout(() => {
this.width = document.querySelector('.branch-box').clientWidth + 'px';
}, 500);
},
// 条件分支组件 添加单个条件
addConditionChange(config) {
config.push({
id: this.generateUUID(),
name: '条件' + (config.length + 1),
condition: 'custom',
editing: 'BEFOREUPDATE',
children: [],
triggerConditions: [],
type: 'branch',
parentId: config[0].parentId
});
setTimeout(() => {
this.saveEdit();
this.width = document.querySelector('.branch-box').clientWidth + 'px';
}, 500);
},
// 条件分支 删除
myFilterDelete(arr, config) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].id == config.id) {
if (arr.length == 2) {
arr.splice(0, 2);
} else {
arr.splice(i, 1);
}
break;
} else {
this.myFilterDelete(arr[i].children, config);
}
}
},
// 条件分支组件 删除
addNodeDelete(model, config) {
this.isOpen = false;
if (!Array.isArray(config)) {
// 不是数组 是分支
this.myFilterDelete(model, config);
}
for (var i = 0; i < this.workFlowList.length; i++) {
if (this.workFlowList[i].id == undefined && this.workFlowList[i].children.length == 0) {
this.workFlowList.splice(i, 1);
i -= 1;
}
}
this.saveEdit();
},
// 审批节点 抄送节点删除
nodeDelete(config) {
this.isOpen = false;
this.$confirm('删除节点后将无法恢复(不影响此节点下所有节点)', '是否删除该节点?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
var index = this.workFlowList.map(item => item).indexOf(config); // 获取下标
if (index + 1 < this.workFlowList.length) {
if (index - 1 >= 1) {
if (this.workFlowList[index - 1].children != undefined) {
// 分支节点
let list = this.workFlowList[index - 1].children;
this.workFlowList[index + 1].parentId = [];
list.forEach(item => {
this.workFlowList[index + 1].parentId.push(item.id);
});
} else {
this.workFlowList[index + 1].parentId = [];
this.workFlowList[index + 1].parentId.push(this.workFlowList[index - 1].id);
}
} else {
this.workFlowList[index + 1].parentId = [];
}
}
this.workFlowList.splice(index, 1);
this.saveEdit();
})
.catch(() => {});
},
// 右侧配置保存 循环
configFilter(arr, config) {
for (var i = 0; i < arr.length; i++) {
if (config.id == arr[i].id) {
arr[i] = config;
} else {
if (arr[i].children != undefined) {
this.configFilter(arr[i].children, config);
}
}
}
},
// 保存节点
saveEdit() {
this.idList = [];
for(var i = 0; i < this.workFlowList.length; i++){
if(i == 1){ // 根据业务去除第二个集合的 parentId
if(this.workFlowList[i].children == undefined){ // 当前不是分支节点
this.workFlowList[i].parentId = [];
}else {
for(var n = 0; n < this.workFlowList[i].children.length; n++ ){
this.workFlowList[i].children[n].parentId = []
}
}
}
if(i > 1){
if(this.workFlowList[i].children == undefined){ // 当前不是分支节点
this.workFlowList[i].parentId = [];
if(this.workFlowList[i - 1].children == undefined){ // 上一个节点不是条件分支
this.workFlowList[i].parentId.push(this.workFlowList[i - 1].id);
}else { // 分支节点
let list = this.workFlowList[i - 1].children;
this.idList = [];
this.addNodeParList(list);
this.workFlowList[i].parentId = this.idList;
}
}else { // 当前节点为分支节点
let arr = this.workFlowList[i].children;
if(this.workFlowList[i - 1].children == undefined) { // 上一个节点不是条件分支
for(var j = 0; j < arr.length; j++ ){
arr[j].parentId = [];
arr[j].parentId.push(this.workFlowList[i - 1].id);
}
}else { // 是分支节点
let list = this.workFlowList[i - 1].children;
this.idList = [];
this.addNodeParList(list);
for(var j = 0; j < arr.length; j++ ){
arr[j].parentId = [];
arr[j].parentId = this.idList;
}
}
};
}
}
let list = {
id: this.workListId,
defineJson: {
workFlowList: this.workFlowList,
formWork: this.formWork
}
};
enterpriseWorkflowEdit(list).then(response => {
this.$modal.msgSuccess('操作成功');
this.refresh();
});
},
// 右侧配置 保存
configurationSave(config, model) {
this.isOpen = false;
this.formWork = model;
if (config.children == undefined) {
for (var i = 0; i < this.workFlowList.length; i++) {
if (config.id == this.workFlowList[i].id) {
this.workFlowList[i] = config;
}
}
} else {
this.configFilter(this.workFlowList, config);
}
this.saveEdit();
},
// 右侧配置取消
configurationCancel() {
this.isOpen = false;
}
}
};
</script>
<style scoped lang="scss">
.container-wrapper {
width: 100%;
height: calc(100vh - 60px);
display: flex;
flex-direction: row;
position: relative;
}
.container-left {
flex-grow: 1;
height: calc(100 - 60px);
min-width: calc(100% - 420px );
display: flex;
flex-direction: column;
padding-top: 30px;
background: #f0f2f5;
overflow: scroll;
}
.container-border {
height: 100%;
width: 2px;
border: 2px solid #f1e8e8;
}
.container-right {
width: 0px;
height: 100%;
}
.container-right-open {
width: 420px;
flex-shrink: 0;
}
.node {
width: 250px;
position: relative;
}
.node-top {
width: 250px;
height: 60px;
/* background: rgb(255, 169, 81); */
background: #1677ff;
border-radius: 8px 8px 0px 0px;
display: flex;
flex-direction: row;
align-items: center;
font-size: 18px;
color: #fff;
.node-icon {
box-sizing: border-box;
width: 38px;
height: 38px;
margin-right: 12px;
line-height: 36px;
color: #fff;
text-align: center;
border: 1px solid #ffffff;
border-radius: 50%;
margin-left: 15px;
}
.node-name {
width: 150px;
height: 22px;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.node-content {
width: 250px;
min-height: 70px;
background: #fff;
border-radius: 0px 0px 8px 8px;
box-shadow: 0 0 10px 0 rgb(0 0 0 / 10%);
padding: 5px;
.node-empty {
display: flex;
justify-content: center;
padding: 12px 15px 10px 15px;
font-size: 14px;
cursor: pointer;
span {
display: block;
padding: 6px 16px;
color: #1677ff;
border: 1px solid #1677ff;
border-radius: 16px;
}
}
.node-empty-span {
display: block;
width: 100%;
padding: 10px 8px;
line-height: 22px;
background-color: #f2f2f2;
border-radius: 4px;
min-height: 70px;
}
}
.node-content-active {
border: 2px solid #1677ff;
}
.add-node {
position: relative;
display: inline-flex;
flex-shrink: 0;
width: 250px;
min-height: 85px;
.add-node-btn {
display: flex;
flex-grow: 1;
flex-shrink: 0;
justify-content: center;
width: 220px;
padding: 20px 0 32px;
.add-node-button {
width: 28px;
height: 28px;
padding: 0;
font-size: 14px;
line-height: 28px;
color: #1677ff;
text-align: center;
background-color: #ffffff;
border-width: 0;
border-radius: 4px;
z-index: 2;
cursor: pointer;
box-shadow: 0 0 7px 2px rgb(0 0 0 / 10%);
}
}
}
.add-node::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
width: 2px;
height: 100%;
margin: auto;
content: '';
background-color: #cacaca;
}
.end-node {
font-size: 14px;
color: rgba(25, 31, 37, 0.4);
text-align: left;
border-radius: 50%;
.end-node-circle {
width: 10px;
height: 10px;
margin: auto;
background: #dbdcdc;
border-radius: 50%;
}
.end-node-text {
margin-top: 5px;
text-align: center;
}
}
.drawer-wrapper {
padding: 20px;
.drawer-title {
font-size: 16px;
color: #666666;
}
.drawer-content {
display: flex;
flex-direction: row;
align-items: center;
margin-top: 30px;
margin-right: 20px;
.drawer-content-item {
display: flex;
flex-direction: column;
align-items: center;
width: 100px;
height: 100px;
cursor: pointer;
span {
margin-top: 12px;
font-weight: bolder;
}
}
}
}
// 缩放
.zoom-btns {
position: absolute;
top: 35px;
left: 80px;
z-index: 10;
span {
font-size: 16px;
margin: 0 15px;
}
}
.node-more {
cursor: pointer;
padding: 0px 20px 25px 0px;
}
.error-block {
position: absolute;
top: 30px;
right: -60px;
width: 40px;
height: 40px;
font-size: 39px;
color: #1677ff;
cursor: help;
}
</style>
3. 条件分支子组件
<template>
<div class="new-node-wrap">
<div class="new-branch-wrap">
<div class="branch-box-wrap">
<div class="branch-box">
<el-button class="add-branch list-complete-item" v-if="model[0].type == 'branch'" @click="addCondition(model)">添加条件</el-button>
<div class="add-branch list-complete-item" style="display: none;" v-else></div>
<div
class="col-box list-complete-item"
style="min-width: 400px"
v-for="(item, index) in model" :key="index"
>
<div class="condition-node">
<div class="condition-node-box" :class="[item.children != undefined && item.children.length> 0 ? 'has-children-condition': '']">
<div class="condition-node__block" :class="item.id == myActiveId ? 'node-content-act' : ''" v-if="item.type == 'branch'">
<div class="block-top">
<div class="block-top-name"><span class="top-name">{{item.name}}</span><span class="top-names">优先级{{index + 1}}</span></div>
<div class="block-top-icon">
<i class="el-icon-delete" @click="nodeDelete(model,item)"></i>
</div>
</div>
<div class="block-set" @click="getContent(item)">
设置触发方式
</div>
</div>
<div class="add-node" v-if="item.type == 'branch'">
<div class="add-node-btn">
<div class="add-node-button" @click="addNode(model,item)">
<i class="el-icon-plus"></i>
</div>
</div>
</div>
<!-- 审批节点 -->
<template v-if="item.type == 'approval'">
<div class="node">
<div class="node-top">
<div class="node-icon">
<i class="el-icon-user-solid"></i>
</div>
<div class="node-name">{{ item.name }}</div>
<div class="node-more">
<i class="el-icon-delete" @click="nodeDelete(model,item)"></i>
</div>
</div>
<div
class="node-content"
@click="getContent(item)"
:class="item.id == myActiveId ? 'node-content-active' : ''"
>
<div
class="node-empty"
v-if="
item.personInCharge.name == undefined ||
item.personInCharge.name == ''
"
>
<span>请设置负责人</span>
</div>
<span v-else class="node-empty-span">
审批人:{{ item.personInCharge.name }}
</span>
</div>
<el-tooltip
class="item"
effect="dark"
content="请设置负责人"
placement="bottom"
v-if="
item.personInCharge.name == undefined ||
item.personInCharge.name == ''
"
>
<div class="error-block">
<i class="el-icon-warning"></i>
</div>
</el-tooltip>
</div>
<div class="add-node">
<div class="add-node-btn">
<div class="add-node-button" @click="addNode(model,item)">
<i class="el-icon-plus"></i>
</div>
</div>
</div>
</template>
<!-- 抄送节点 -->
<template v-if="item.type == 'giveList'">
<div class="node">
<div class="node-top">
<div class="node-icon">
<i class="el-icon-s-promotion"></i>
</div>
<div class="node-name">{{item.name}}</div>
<div class="node-more">
<i class="el-icon-delete" @click="nodeDelete(model,item)"></i>
</div>
</div>
<div
class="node-content"
@click="getContent(item)"
:class="item.id == myActiveId ? 'node-content-active' : ''"
>
<div
class="node-empty"
v-if="
item.personInCharge.name == undefined ||
item.personInCharge.name == ''
"
>
<span>请设置负责人</span>
</div>
<span v-else class="node-empty-span">
抄送人:{{ item.personInCharge.name }}
</span>
</div>
<el-tooltip
class="item"
effect="dark"
content="请设置抄送人"
placement="bottom"
v-if="
item.personInCharge.name == undefined ||
item.personInCharge.name == ''
"
>
<div class="error-block">
<i class="el-icon-warning"></i>
</div>
</el-tooltip>
</div>
<div class="add-node">
<div class="add-node-btn">
<div class="add-node-button" @click="addNode(model,item)">
<i class="el-icon-plus"></i>
</div>
</div>
</div>
</template>
<!--跨层级监听事件 v-on="$listeners" 递归渲染 -->
<branch v-if="item.children != undefined && item.children.length > 0" :activeId="myActiveId" :model="item.children" v-on="$listeners"/>
</div>
</div>
<div class="conditionLine" v-if="item.type == 'branch' || model.length > 1"></div>
</div>
</div>
<div class="add-node-btn-box">
<div class="add-node-btn" v-if="model[0].type == 'branch'">
<div class="add-node-btn add-node-button" @click="addNode(model)">
<i class="el-icon-plus"></i>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "branch",
props: ["model",'activeId'],
computed: {},
data(){
return {
myActiveId: '',
}
},
inject: ['activeId'],
watch: {
activeId: {
handler(newVal, oldVal) {
this.myActiveId = newVal;
},
deep: true, // 深度监听
},
},
methods: {
// 左侧点击
getContent(config) {
this.myActiveId = config.id;
this.$emit('addNodeGetContent',config)
},
// 添加分支
addNode(model,config){
this.$emit('addNodeChange',model,config)
},
addCondition(config){
this.$emit('addConditionChange',config)
},
// 分支节点删除
nodeDelete(model,config){
this.$confirm('删除分支节点后将无法恢复(同时删除分支下所有节点)', '是否删除该节点?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$emit('addNodeDelete',model,config)
}).catch(() => {
});
},
},
};
</script>
<style lang="scss">
.new-node-wrap {
position: relative;
display: inline-flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-start;
width: 100%;
}
.new-branch-wrap {
display: inline-flex;
width: 100%;
}
.new-branch-wrap > .branch-box-wrap > .branch-box > .add-branch {
position: absolute;
top: -16px;
left: 50%;
z-index: 10;
display: inline-flex;
align-items: center;
justify-content: center;
height: 30px;
padding: 0 10px;
font-size: 12px;
line-height: 30px;
color: #3296fa;
user-select: none;
background: #fff;
border: none;
border-radius: 15px;
outline: none;
box-shadow: 0 2px 4px 0 rgb(0 0 0 / 10%);
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transform: translateX(-50%);
}
.branch-box-wrap {
display: flex;
flex-direction: column;
flex-shrink: 0;
flex-wrap: wrap;
align-items: center;
width: 100%;
min-height: 270px;
}
.branch-box {
position: relative;
display: flex;
height: auto;
min-height: 180px;
overflow: visible;
transition: all 1s;
}
.new-branch-wrap > .branch-box-wrap > .branch-box > .col-box {
position: relative;
display: inline-flex;
flex-direction: column;
align-items: center;
border-top: 2px solid #ccc;
border-bottom: 2px solid #ccc;
transition: all 0.4s;
background: #f0f2f5; // 盖掉中间的线
}
.condition-node {
position: relative;
z-index: 1;
display: inline-flex;
flex-direction: column;
}
.condition-node > .condition-node-box {
position: relative;
display: inline-flex;
flex-direction: column;
flex-grow: 1;
align-items: center;
justify-content: center;
padding-top: 30px;
padding-right: 50px;
padding-left: 50px;
}
.condition-node > .condition-node-box::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 2px;
height: 100%;
margin: auto;
content: '';
background-color: #cacaca;
}
.condition-node > .condition-node-box > .condition-node__block {
position: relative;
box-sizing: border-box;
width: 320px;
min-height: 118px;
padding: 12px 24px;
background: #fff;
border-radius: 8px;
outline: none;
box-shadow: 0 0 10px 0 rgb(0 0 0 / 10%);
}
.new-branch-wrap > .branch-box-wrap > .branch-box > div:nth-child(2) {
border: none;
}
.new-branch-wrap > .branch-box-wrap > .branch-box > div:last-child {
border: none;
}
.new-branch-wrap > .branch-box-wrap > .branch-box > .col-box::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
width: 2px;
height: 100%;
margin: auto;
content: '';
background-color: #cacaca;
}
.new-branch-wrap > .branch-box-wrap > .branch-box > div:nth-child(2) > .conditionLine {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 50%;
border-top: 2px solid #ccc;
border-bottom: 2px solid #ccc;
}
.new-branch-wrap > .branch-box-wrap > .branch-box > div:last-child > .conditionLine {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 50%;
border-top: 2px solid #ccc;
border-bottom: 2px solid #ccc;
}
.condition-node > .has-children-condition {
padding-right: 0 !important;
padding-left: 0 !important;
}
.add-node {
position: relative;
display: inline-flex;
flex-shrink: 0;
width: 250px;
min-height: 85px;
.add-node-btn {
display: flex;
flex-grow: 1;
flex-shrink: 0;
justify-content: center;
width: 220px;
padding: 20px 0 32px;
.add-node-button {
width: 28px;
height: 28px;
padding: 0;
font-size: 14px;
line-height: 28px;
color: #1677ff;
text-align: center;
background-color: #ffffff;
border-width: 0;
border-radius: 4px;
z-index: 3;
cursor: pointer;
box-shadow: 0 0 7px 2px rgb(0 0 0 / 10%);
}
}
}
.new-branch-wrap > .branch-box-wrap > .add-node-btn-box {
position: relative;
display: inline-flex;
flex-shrink: 0;
width: 240px;
min-height: 2px;
}
.new-branch-wrap > .branch-box-wrap > .add-node-btn-box::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
width: 2px;
height: 100%;
margin: auto;
content: '';
background-color: #cacaca;
}
.new-branch-wrap > .branch-box-wrap > .add-node-btn-box > .add-node-btn {
display: flex;
flex-grow: 1;
flex-shrink: 0;
justify-content: center;
width: 240px;
padding: 20px 0 32px;
user-select: none;
}
.add-node-button {
width: 28px;
height: 28px;
padding: 0;
font-size: 14px;
line-height: 28px;
color: #1677ff;
text-align: center;
background-color: #ffffff;
border-width: 0;
border-radius: 4px;
z-index: 3;
cursor: pointer;
box-shadow: 0 0 7px 2px rgb(0 0 0 / 10%);
}
.block-top {
height: 40px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.block-top-name {
display: flex;
flex-direction: row;
align-items: center;
.top-name {
display: inline-block;
max-width: 146px;
height: 22px;
margin-right: 8px;
font-size: 18px;
line-height: 22px;
color: #333333;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.top-names {
font-size: 12px;
color: #999999;
}
}
.block-top-icon {
color: #c3c3c3;
font-size: 16px;
cursor: pointer;
}
}
.block-set {
position: absolute;
bottom: 28px;
left: 99px;
width: 122px;
height: 24px;
padding: 0;
font-size: 14px;
line-height: 21px;
color: #1677ff;
text-align: center;
border: 1px solid #1677ff;
border-radius: 21px;
cursor: pointer;
}
.node {
width: 250px;
position: relative;
}
.node-top {
width: 250px;
height: 60px;
background: #1677ff;
border-radius: 8px 8px 0px 0px;
display: flex;
flex-direction: row;
align-items: center;
font-size: 18px;
color: #fff;
.node-icon {
box-sizing: border-box;
width: 38px;
height: 38px;
margin-right: 12px;
line-height: 36px;
color: #fff;
text-align: center;
border: 1px solid #ffffff;
border-radius: 50%;
margin-left: 15px;
}
.node-name {
width: 150px;
height: 22px;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.node-content {
width: 250px;
min-height: 70px;
background: #fff;
border-radius: 0px 0px 8px 8px;
box-shadow: 0 0 10px 0 rgb(0 0 0 / 10%);
padding: 5px;
.node-empty {
display: flex;
justify-content: center;
padding: 12px 15px 10px 15px;
font-size: 14px;
cursor: pointer;
span {
display: block;
padding: 6px 16px;
color: #1677ff;
border: 1px solid #1677ff;
border-radius: 16px;
}
}
.node-empty-span {
display: block;
width: 100%;
padding: 10px 8px;
line-height: 22px;
background-color: #f2f2f2;
border-radius: 4px;
min-height: 70px;
}
}
.node-content-active {
border: 2px solid #1677ff;
}
.node-content-act {
border: 2px solid #8564f2;
}
.error-block {
position: absolute;
top: 30px;
right: -60px;
width: 40px;
height: 40px;
font-size: 39px;
color: #1677ff;
cursor: help;
}
</style>