<!-- 
    filename: ProcessGraph.vue
    author: liubin
    date: 2023-10-20 15:00:58
    description: 
-->

<template>
	<section class="process-graph">
		<SearchBar :formConfigs="searchBarFormConfig" ref="search-bar" />

		<div class="btns" style="text-align: right; position: absolute; top: 20px; right: 20px">
			<el-button type="warning" @click="undo" plain v-if="allowUndo" :disabled="!allowUndo" icon="el-icon-back">
				撤销
			</el-button>
			<el-button type="warning" @click="redo" plain v-if="allowRedo" :disabled="!allowRedo">
				下一步
				<i class="el-icon-right el-icon--right"></i>
			</el-button>
			<el-button class="btn-refresh" @click="handleUpdateLayout" icon="el-icon-refresh">
				刷新布局</el-button>
			<el-button type="primary" plain class="btn-create" icon="el-icon-plus" @click="handleAdd">
				新建工序
			</el-button>
			<el-button class="btn-edit" :disabled="currentDet == null" @click="handleEdit">编辑</el-button>
		</div>

		<div class="process-graph__panel" ref="panel"></div>

		<base-dialog :dialogTitle="title" :dialogVisible="open" width="35%" @close="cancel" @cancel="cancel"
			@confirm="submitForm">
			<DialogForm v-if="open" ref="form" v-model="form" :rows="rows" />
		</base-dialog>
	</section>
</template>

<script>
import { Graph } from '@antv/x6';
import ProcessNode, { createProcessNode, CACHE_NAME, getSectionFrom } from './ProcessNode';
import DialogForm from '@/components/DialogForm';
// import { IdToName } from '@/utils'

Graph.registerNode('process-node', ProcessNode);

export default {
	name: 'ProcessGraph',
	components: { DialogForm },
	props: {},
	inject: ['getFlowId'],
	data() {
		return {
			allowRedo: false,
			allowUndo: false,
			graph: null,
			searchBarFormConfig: [{ label: '工序列表' }],
			title: '',
			open: false,
			form: {
				name: '', // 工序名称
				sectionId: '', // 工段id
				remark: '', // 描述
			},
			rows: [
				[
					{
						input: true,
						label: '工序名称',
						prop: 'name',
						rules: [{ required: true, message: '不能为空', trigger: 'blur' }],
					},
				],
				[
					{
						select: true,
						label: '工段',
						prop: 'sectionId',
						url: '/base/core-workshop-section/listAll',
						rules: [{ required: true, message: '不能为空', trigger: 'blur' }],
						bind: {
							filterable: true,
						},
						cache: CACHE_NAME
					},
				],
				[
					{
						textarea: true,
						label: '工序说明',
						prop: 'remark',
					},
				],
			],
			updateUrl: '/extend/process-flow-det/update',
			deleteUrl: '/extend/process-flow-det/delete',
			addUrl: '/extend/process-flow-det/create',
			// pageUrl: '/extend/process-flow-det/get',
			infoUrl: '/extend/process-flow-view/getByFlowId',
			layout: {
				id: null,
				flowId: null,
				content: '',
				createTime: null
			},
			currentDet: null,
			currentNode: null

		};
	},
	watch: {
		'form.sectionId': {
			handler(id) {
			},
			immediate: false,
		},
		currentDet: {
			handler(val) {
				this.$emit('det-selected', val)
			},
			deep: true,
			immediate: true
		}
	},
	activated() {
		this.loadLayout().then(json => {
			this.initGraph(json)
		})
	},
	deactivated() {
		this.graph.dispose();
		this.$nextTick(() => {
			this.resetLayout();
			this.graph = null;
		})
	},
	computed: {},
	methods: {

		initGraph(json) {
			const graph = new Graph({
				container: this.$refs.panel,
				grid: {
					size: 10,
					visible: true,
				},
				history: true,
				selecting: {
					className: 'my-select'
				},
				connecting: {
					snap: true,
					allowBlank: false,
					allowLoop: false,
					allowNode: false,
					allowPort: true,
					allowEdge: false,
				},
				panning: true,
				// scroller: {
				// 	enabled: true,
				// 	pannable: true,
				// 	cursor: '',
				// 	width: 800,
				// 	height: 200
				// },
				mousewheel: {
					enabled: true,
					modifiers: ['ctrl', 'meta']
				}
			});

			graph.fromJSON(json)
			this.graph = graph;
			this.$nextTick(() => {
				this.registerGraphEvents();
			})
		},

		registerGraphEvents() {
			const reset = () => {
				const nodes = this.graph.getNodes();
				const edges = this.graph.getEdges();
				this.currentDet = null;
				this.currentNode = null;

				nodes.forEach(node => {
					node.attr('container/stroke', '#ccc');
				});
				edges.forEach(edge => {
					edge.attr('line/stroke', '#ccc')
				})
			}

			this.graph.on('node:click', ({ e, x, y, node, view }) => {
				reset();
				node.attr('container/stroke', '#0b58ff');
				const { detId, detName, detDesc, processId, sectionId, sectionName } = node.attrs;
				this.currentDet = {}
				this.$set(this.currentDet, 'detId', detId.text)
				this.$set(this.currentDet, 'sectionId', sectionId.text)
				this.$set(this.currentDet, 'detName', detName.text)
				this.$set(this.currentDet, 'detDesc', detDesc.text)
				this.$set(this.currentDet, 'flowId', processId.text)
				this.$set(this.currentDet, 'sectionName', sectionName.text)
				this.currentNode = node
			});
			this.graph.on('edge:click', ({ e, x, y, edge, view }) => {
				// console.log('edge clicked!', edge)
				reset();
				edge.attr('line/stroke', '#0b58ff')
			});
			this.graph.on('blank:click', ({ e, x, y }) => {
				reset();
			});
			this.graph.on('node:mouseenter', ({ node }) => {
				node.addTools({
					name: 'button-remove',
					args: {
						x: '100%',
						y: 0,
						offset: { x: 0, y: 0 },
						onClick: ({ e, cell, view }) => {
							this.$confirm(
								'确定删除这个工序吗?',
								'提示',
								{
									confirmButtonText: '确定',
									cancelButtonText: '取消',
									type: 'warning'
								}
							).then(() => {
								view.cell.remove()
							}).catch(err => {
								return;
							})
						}
					}
				})
			});
			this.graph.on('node:mouseleave', ({ node }) => {
				node.removeTools();
			})
		},

		resetLayout() {
			this.layout = {
				id: null,
				flowId: null,
				content: '',
				createTime: null
			}
		},

		async loadLayout() {
			const flowId = this.$route.params.id;
			if (!flowId) return { cells: [] }
			const { code, data } = await this.info({ id: flowId });
			if (code == 0) {
				if (data) {
					this.layout = data;
					return JSON.parse(data?.content) || { cells: [] };
				}
				return { cells: [] };
			}
			this.resetLayout();
			return Promise.reject(this.infoUrl + ' 接口出错!');
		},

		handleToJson() { },

		handleLoadJson() { },

		handleDumpJson() {
			if (this.graph) {
				console.log(JSON.stringify(this.graph.toJSON(), null, 2));
			}
		},

		async handleUpdateLayout() {
			this.layout.content = JSON.stringify(this.graph.toJSON());
			let code, data;
			console.table([this.layout, this.$route.params.id])
			// 手动刷新布局
			if (this.layout.id) {
				({ code, data } = await this.http('/extend/process-flow-view/update', 'put', this.layout));
			} else {
				this.layout.flowId = this.$route.params.id;
				({ code, data } = await this.http('/extend/process-flow-view/create', 'post', this.layout));
			}

			if (code == 0) {
				this.$modal.msgSuccess('布局已刷新!')
			}
		},

		reset() {
			this.form = {
				name: '', // 工序名称
				sectionId: '', // 工段id
				remark: '', // 描述
			};
			this.resetForm('form');
		},

		/** 取消按钮 */
		cancel() {
			this.open = false;
			this.reset();
		},

		handleAdd() {
			this.reset();
			this.open = true;
			this.title = '添加工序';
		},

		handleEdit() {
			this.form.name = this.currentDet.detName;
			this.form.sectionId = this.currentDet.sectionId;
			this.form.remark = this.currentDet.detDesc;
			this.form.id = this.currentDet.detId;
			this.title = '编辑工序';
			this.$nextTick(() => {
				this.open = true;
			})
		},

		async handleDelete(id) {
			const { code, data } = await this.delete({ id });
			if (code == 0) {
				this.$msgSuccess('成功删除一个工序!');
				return true;
			}
			return false;
		},

		/** 提交按钮 */
		submitForm() {
			this.$refs['form'].validate((valid) => {
				if (!valid) {
					return;
				}
				// 修改的提交
				if (this.form.id != null) {
					this.updateProcess()
						.then((form) => {
							const { name, sectionId, remark } = form;
							getSectionFrom(sectionId).then(sectionName => {
								// 修改当前node的信息 
								this.currentNode.setAttrs({
									detName: { text: name },
									sectionId: { text: sectionId },
									sectionName: { text: sectionName },
									detDesc: { text: remark }
								})
							})
						})
						.catch(err => { });
					return;
				}

				this.createProcess()
					.then(({ id, name, sectionId, remark, flowId }) => {
						if (!id) return null;
						return createProcessNode({
							flowId: flowId,
							name, sectionId, remark,
							id,
						})
					}).then(node => {
						if (!node) {
							this.$modal.msgError('创建节点失败');
							return;
						};
						this.graph.addNode(node);
					}).catch(err => {
						return;
					});

			});
		},

		updateProcess() {
			const flowId = this.getFlowId();
			if (!flowId) {
				this.$modal.msgError('工艺ID不能为空');
				return Promise.reject('工艺ID不能为空');
			}
			return this.put({ flowId, ...this.form })
				.then(({ code, data }) => {
					if (code == 0) {
						this.$modal.msgSuccess('修改成功');
					} else {
						this.$modal.msgError('修改失败');
					}
					const formCopy = { ...this.form }
					this.open = false;
					return formCopy;
				});
		},

		createProcess() {
			// const flowId = this.$route.params.id;
			const flowId = this.getFlowId(); // it also works
			if (!flowId) {
				this.$modal.msgError('工艺ID不能为空');
				return Promise.reject('工艺ID不能为空');
			}
			console.log('create process', this.form)
			// 添加的提交
			return this.post({ flowId, ...this.form }).then(
				({ code, data }) => {
					this.$modal.msgSuccess('新增成功');
					this.open = false;
					// this.getList();
					return {
						id: data, // 服务器返回的新建的工段id
						...this.form,  // 保存一份 this.form 副本，当 open->false 时 this.form 里的信息就清空了
						flowId
					};
				}
			).catch(err => {
				this.$msgError(err)
			});
		},

		put(payload) {
			return this.http(this.updateUrl, 'put', payload);
		},
		post(payload) {
			return this.http(this.addUrl, 'post', payload);
		},
		recv(payload) {
			return this.http(this.pageUrl, 'get', payload);
		},
		info(payload) {
			return this.http(this.infoUrl, 'get', payload);
		},
		delete(payload) {
			return this.http(this.deleteUrl, 'delete', payload);
		},
		http(url, method, payload) {
			return this.$axios({
				url,
				method,
				params: method === 'get' ? payload : null,
				data: method !== 'get' ? payload : null,
			})
		},
	},
};
</script>

<style scoped lang="scss">
.process-graph {
	padding: 12px 20px 20px;
	background: #fff;
	border-radius: 8px;
	position: relative;
}

.process-graph__panel {
	height: 300px;
}
</style>

<style>
.x6-widget-selection-selected {
	border: 1px solid red;
}

.my-select {
	border: 1px solid red;
}
</style>
