<template lang=""> <div class="vueFlow"> <div id="container"></div> <div id="stencil"></div> <div id="graph-container"></div> </div> <el-button @click="submitAll" type="primary">保存</el-button> <el-dialog :title="modalType == 'add' ? '新增' : '编辑'" v-model="dialogVisible" width="500" v-loading="loading" > <byForm :formConfig="formConfig" :formOption="formOption" v-model="formData.data" :rules="rules" ref="byform" > </byForm> <template #footer> <el-button @click="dialogVisible = false" size="large" >取 消</el-button > <el-button type="danger" @click="deleteFlowDefinitionNodeObj()" size="large" :loading="submitLoading" > 删 除 </el-button> <el-button type="primary" @click="submitForm('byform')" size="large" :loading="submitLoading" > 确 定 </el-button> </template> </el-dialog> </template> <script lang="ts" setup> import { defineComponent, ref, onMounted, onUnmounted, watch, reactive, toRefs, computed, nextTick, getCurrentInstance, } from 'vue' import byForm from '@/components/byForm/index' import { Graph, Shape } from '@antv/x6' import { Stencil } from '@antv/x6-plugin-stencil' import { Transform } from '@antv/x6-plugin-transform' import { Selection } from '@antv/x6-plugin-selection' import { Snapline } from '@antv/x6-plugin-snapline' import { Keyboard } from '@antv/x6-plugin-keyboard' import { Clipboard } from '@antv/x6-plugin-clipboard' import { History } from '@antv/x6-plugin-history' import Cookies from 'js-cookie' import { ElMessage, ElMessageBox } from "element-plus"; defineProps({ title: { type: Object, default: '', }, }); const { proxy } = getCurrentInstance() const internalInstance = getCurrentInstance() const dialogVisible = ref(false) const modalType = ref('add') const loading = ref(false) const submitLoading = ref(false) const formData = reactive({ data: { userName: '', password: '', }, }) const byform = ref(null) const flowDefinitionNodeObj = ref({}) const rules = reactive({ nodeName: [ { required: true, message: '请输入节点名称', trigger: 'blur', }, ], handleObjectType: [ { required: true, message: '办理人类型不能为空', trigger: 'blur', }, ], handleObjectId: [ { required: true, message: '办理人不能为空', trigger: 'blur', }, ], }) const formConfig = computed(() => { return [ { type: 'input', prop: 'nodeName', label: '节点名称', required: true, itemType: 'text', }, { type: 'select', prop: 'handleObjectType', label: '办理人', placeholder: '请选择办理人类型', required: true, itemWidth: 30, fn: (e) => { console.log(e) gethandleObjectList(e) }, //1用户 2部门负责人 3部门总监 4岗位 5角色 data: [ { label: '用户', value: 1, }, { label: '部门负责人', value: 2, }, { label: '部门总监', value: 3, }, { label: '岗位', value: 4, }, { label: '角色', value: 5, }, ], }, // { // type: "treeSelect", // prop: "handleObjectId", // label: "请选择办理人", // itemWidth: 30, // data: [], // }, { type: 'select', label: ' ', itemWidth: 30, prop: 'handleObjectId', placeholder: '请选择办理人', data: [], }, { type: 'input', prop: 'handlingMethod', label: '节点后置执行方法', required: true, itemType: 'text', }, { type: 'input', prop: 'jumpCondition', label: '条件表达式', required: true, itemType: 'text', }, { type: 'checkbox', prop: 'nodeButtonSet', label: '节点按钮', //1通过 2驳回 3返回上一步 4退回到发起人 data: [ { label: '通过', value: 1, }, { label: '驳回', value: 2, }, { label: '返回上一步', value: 3, }, { label: '退回到发起人', value: 4, }, ], }, { type: 'radio', prop: 'jobNumber11', label: '审批意见必填', data: [ { label: '是', value: 1, }, { label: '否', value: 0, }, ], }, ] }) const formOption = reactive({ inline: true, labelWidth: 100, itemWidth: 100, }) let graph const submitForm = () => { byform.value.handleSubmit((valid) => { flowDefinitionNodeObj.value[formData.data.id] = formData.data console.log(flowDefinitionNodeObj.value) }) } const submitFormData = { flowInfoId:null, titleTemplate:null, tenantId:Cookies.get('tenantId'), nodeObject:'', lineObject:'', flowDefinitionNodeList:[], } const submitAll = () => { if(proxy.title == '') { ElMessage({ message: '请输入流程标题', type: 'warning', }) return } submitFormData.titleTemplate = proxy.title const nodeList = graph.toJSON().cells console.log(nodeList) const isStart = false for (let i = 0; i < nodeList.length; i++) { const element = nodeList[i]; //是办理节点 if(element.id != 1 && element.shape != "edge") { console.log(element) if(!flowDefinitionNodeObj.value[element.id]) { ElMessage({ message: '有节点未配置,请检查节点123123123', type: 'warning', }) return } submitFormData.flowDefinitionNodeList.push({...flowDefinitionNodeObj.value[element.id],nodeType:2}) } if(element.id == "1") { submitFormData.flowDefinitionNodeList.push({ nodeName:'开始', nodeType:1, id:1, nodeButtonSet:'', parentId:0, }) } //说明是线 if(element.shape == "edge") { console.log(flowDefinitionNodeObj) if(!flowDefinitionNodeObj.value[element.target.cell]) { ElMessage({ message: '有节点未配置,请检查节点', type: 'warning', }) return } flowDefinitionNodeObj.value[element.target.cell].id = element.target.cell flowDefinitionNodeObj.value[element.target.cell].parentId = element.source.cell submitFormData.flowDefinitionNodeList = [] } } addVersion() console.log(flowDefinitionNodeObj.value) } //选取一个随机不重复的正整数id const randomId = () => { const id = Math.floor(Math.random() * 100000000000000000) if(flowDefinitionNodeObj.value[id]) { randomId() } else { return id } } const addVersion = () => { const idObg = {} for (let i = 0; i < submitFormData.flowDefinitionNodeList.length; i++) { const element = submitFormData.flowDefinitionNodeList[i]; if(isNaN(element.id)) { if(idObg[element.id]) { element.id = idObg[element.id] } else { const id = randomId() idObg[element.id] = id element.id = id } } if(isNaN(element.parentId) && element.nodeName != '开始') { if(idObg[element.parentId]) { element.parentId = idObg[element.parentId] } else { const id = randomId() idObg[element.parentId] = id element.parentId = id } } //nodeButtonSet转成字符串类型,用逗号隔开 if(element.nodeButtonSet) { element.nodeButtonSet = element.nodeButtonSet.join(',') } } console.log(submitFormData) proxy.post('/flowDefinition/addVersion',submitFormData) .then((res) => { console.log(res) ElMessage({ message: '保存成功', type: 'success', }) }) } //将组数里的id和parentId转换成整正整数类型 const changeId = (arr) => { for (let i = 0; i < arr.length; i++) { const element = arr[i]; element.id = parseInt(element.id) element.parentId = parseInt(element.parentId) } } const deleteFlowDefinitionNodeObj = (id) => { graph.removeNode(formData.data.id) delete flowDefinitionNodeObj.value[id] dialogVisible.value = false } const gethandleObjectList = (e) => { formData.data.handleObjectId = '' if(e === 1) { proxy.get('/tenantUser/list?pageNum=1&pageSize=1000&tenantId=' + Cookies.get('tenantId'),{}) .then((res) => { formConfig.value[2].data = res.rows.map((item) => { return { label: item.nickName, value: item.userId, } }) }) } if(e === 3 || e === 2) { proxy.get('/tenantDept/list?pageNum=1&pageSize=1000&tenantId=' + Cookies.get('tenantId'),{}) .then((res) => { formConfig.value[2].data =res.data.map(item=> { return { label: item.deptName, value: item.deptId, } }) }) } if(e === 4) { } if(e === 5) { proxy.get('/tenantRole/list?pageNum=1&pageSize=1000&tenantId=' + Cookies.get('tenantId'),{}) .then((res) => { formConfig.value[2].data = res.rows.map((item) => { return { label: item.roleName, value: item.roleId, } }) }) } } const getTenantDept = () => { } getTenantDept() const recursive = (data) => { data.map((item) => { item.label = item.deptName; item.id = item.deptId; if (item.children) { recursive(item.children); } else { item.children = []; } }); }; const pushRoom = (port: any) => { console.log(port) if(port.node.label == '结束') { flowDefinitionNodeObj.value[port.node.id] = { nodeName:'结束', nodeType:99, id:port.id, nodeButtonSet:'', } } console.log(flowDefinitionNodeObj.value) } //用于存储流程定义节点数据 const antvInit = () => { graph = new Graph({ height: 600, container: document.getElementById('graph-container')!, grid: true, onPortRendered: pushRoom, mousewheel: { enabled: true, zoomAtMousePosition: true, modifiers: 'ctrl', minScale: 0.5, maxScale: 3, }, connecting: { router: 'manhattan', connector: { name: 'rounded', args: { radius: 8, }, }, anchor: 'center', connectionPoint: 'anchor', allowBlank: false, snap: { radius: 20, }, createEdge() { return new Shape.Edge({ attrs: { line: { stroke: '#A2B1C3', strokeWidth: 2, targetMarker: { name: 'block', width: 12, height: 8, }, }, }, zIndex: 0, }) }, validateConnection({ targetMagnet }) { return !!targetMagnet }, }, highlighting: { magnetAdsorbed: { name: 'stroke', args: { attrs: { fill: '#5F95FF', stroke: '#5F95FF', }, }, }, }, }) const stencil = new Stencil({ title: '流程图', target: graph, stencilGraphWidth: 200, stencilGraphHeight: 180, collapsable: true, groups: [ { title: '基础流程图', name: 'group1', }, ], layoutOptions: { columns: 2, columnWidth: 80, rowHeight: 55, }, }) document.getElementById('stencil')!.appendChild(stencil.container) // #region 使用插件 graph .use( new Transform({ resizing: true, rotating: true, }) ) .use( new Selection({ enabled: true, rubberband: true, showNodeSelectionBox: true, }) ) .use( new Snapline({ enabled: true, }) ) .use( new Keyboard({ enabled: true, }) ) .use( new Clipboard({ enabled: true, }) ) .use( new History({ enabled: true, }) ) // 控制连接桩显示/隐藏 const showPorts = (ports: NodeListOf<SVGElement>, show: boolean) => { for (let i = 0, len = ports.length; i < len; i += 1) { ports[i].style.visibility = show ? 'visible' : 'hidden' } } graph.on('node:mouseenter', () => { const container = document.getElementById('graph-container')! const ports = container.querySelectorAll( '.x6-port-body' ) as NodeListOf<SVGElement> showPorts(ports, true) }) graph.on('node:mouseleave', () => { const container = document.getElementById('graph-container')! const ports = container.querySelectorAll( '.x6-port-body' ) as NodeListOf<SVGElement> showPorts(ports, false) }) // #endregion graph.on('cell:click', ({ e, x, y, cell, view }) => { console.log(cell) if (cell.label === '开始' || cell.label === '结束' || cell.shape === 'edge') { return } if (flowDefinitionNodeObj.value[cell.id]) { formData.data = flowDefinitionNodeObj.value[cell.id] } else { formData.data = { id: cell.id, cell: cell, } } dialogVisible.value = true }) // #region 初始化图形 const ports = { groups: { top: { position: 'top', attrs: { circle: { r: 4, magnet: true, stroke: '#5F95FF', strokeWidth: 1, fill: '#fff', style: { visibility: 'hidden', }, }, }, }, right: { position: 'right', attrs: { circle: { r: 4, magnet: true, stroke: '#5F95FF', strokeWidth: 1, fill: '#fff', style: { visibility: 'hidden', }, }, }, }, bottom: { position: 'bottom', attrs: { circle: { r: 4, magnet: true, stroke: '#5F95FF', strokeWidth: 1, fill: '#fff', style: { visibility: 'hidden', }, }, }, }, left: { position: 'left', attrs: { circle: { r: 4, magnet: true, stroke: '#5F95FF', strokeWidth: 1, fill: '#fff', style: { visibility: 'hidden', }, }, }, }, }, items: [ { group: 'top', }, { group: 'right', }, { group: 'bottom', }, { group: 'left', }, ], } Graph.registerNode( 'custom-rect', { inherit: 'rect', width: 66, height: 36, attrs: { body: { strokeWidth: 1, stroke: '#5F95FF', fill: '#EFF4FF', }, text: { fontSize: 12, fill: '#262626', }, }, ports: { ...ports }, }, true ) Graph.registerNode( 'custom-polygon', { inherit: 'polygon', width: 66, height: 36, attrs: { body: { strokeWidth: 1, stroke: '#5F95FF', fill: '#EFF4FF', }, text: { fontSize: 12, fill: '#262626', }, }, ports: { ...ports, items: [ { group: 'top', }, { group: 'bottom', }, ], }, }, true ) Graph.registerNode( 'custom-circle', { inherit: 'circle', width: 45, height: 45, attrs: { body: { strokeWidth: 1, stroke: '#5F95FF', fill: '#EFF4FF', }, text: { fontSize: 12, fill: '#262626', }, }, ports: { ...ports }, }, true ) Graph.registerNode( 'custom-image', { inherit: 'rect', width: 52, height: 52, markup: [ { tagName: 'rect', selector: 'body', }, { tagName: 'image', }, { tagName: 'text', selector: 'label', }, ], attrs: { body: { stroke: '#5F95FF', fill: '#5F95FF', }, image: { width: 26, height: 26, refX: 13, refY: 16, }, label: { refX: 3, refY: 2, textAnchor: 'left', textVerticalAnchor: 'top', fontSize: 12, fill: '#fff', }, }, ports: { ...ports }, }, true ) let firstLi = document.createElement('li') firstLi.innerText = '指标详情' console.log(firstLi) const r1 = graph.createNode({ shape: 'custom-rect', label: '开始', zIndex: 100, attrs: { body: { rx: 20, ry: 26, }, }, tools: [ { name: 'button', args: { firstLi, }, }, ], }) const r2 = graph.createNode({ shape: 'custom-rect', label: '办理', }) const r3 = graph.createNode({ shape: 'custom-rect', attrs: { body: { rx: 6, ry: 6, }, }, label: '分支', }) const r4 = graph.createNode({ shape: 'custom-polygon', attrs: { body: { refPoints: '0,10 10,0 20,10 10,20', }, }, label: '结束', }) stencil.load([ r2, r4], 'group1') graph.addNode({ shape: 'custom-rect', label: '开始', id: 1, x: 500, y: 100, }) } onMounted(() => { antvInit() //获取url router参数 const router = useRouter(); submitFormData.flowInfoId = router.currentRoute.value.query.id }) </script> <style lang="scss"> .x6-widget-stencil-title { display: none; } .x6-widget-stencil-content { top: 0 !important; } .vueFlow { position: relative; display: flex; justify-content: space-between; overflow: hidden; height: 600px; #stencil { position: absolute; top: 0; left: 0; z-index: 100; width: 200px; height: 500px; background: #fff; border-right: 1px solid #e8e8e8; } #container { } #graph-container { width: 100%; position: absolute; right: 0; top: 0; } } </style>