工作流:复杂树状结构,动态添加组件

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>

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值