yudao-dev/src/views/extend/processFlowView/components/ProcessDetail.vue
2023-11-22 11:15:39 +08:00

520 lines
11 KiB
Vue

<!--
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, true);
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,
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;
console.log('node clicked!', node)
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(async () => {
const id = node.attrs.detId.text;
const status = await this.handleDelete(id);
if (status) {
view.cell.remove();
// 更新布局,不然下次打开删除的节点还在
this.handleUpdateLayout();
}
})
.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.$modal.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.$modal.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({ id }) {
return this.$axios({
url: this.deleteUrl + `?id=${id}`,
method: 'delete',
});
},
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>