import React, { useState, useEffect } from 'react'; import { Button, Modal, notification, Spin } from 'antd'; import { SaveNodeChange, GetFlowNode } from '@/services/workflow/workflow'; import { ExclamationCircleOutlined } from '@ant-design/icons'; import { Prompt } from 'react-router-dom'; import * as go from 'gojs'; import styles from '../workflow.less'; import NodeModal from './flowChartComponents/NodeModal'; import imgUrl from '@/assets/images/icons/closeBlue.png'; const { confirm } = Modal; let diagram = null; const FlowChart = props => { const { flowData, flowID, chartLoading, leaveCallBack } = props; const [visible, setVisible] = useState(false); const [editMsg, setEditMsg] = useState({}); // 编辑节点的信息 const [modalType, setModalType] = useState(''); // 存入弹窗是编辑还是新增 const [nodeKey, setNodeKey] = useState(''); // 存入编辑节点的key const [DeleteNodes, setDeleteNodes] = useState([]); // 删除节点数组 const [DeleteLines, setDeleteLines] = useState([]); // 删除线数组 const [deleteLine, setDeleteLine] = useState(); // 删除的线id const [deleteNode, setDeleteNode] = useState(); // 删除的节点id const [AddNodes, setAddNodes] = useState([]); // 新增数组 const [initFlowData, setInitFlowData] = useState({}); // 初始数据,用来比对是否有修改流程图 const [currentFlowData, setCurrentFlowData] = useState({ Nodes: [], Lines: [], }); // 组件内得流程图数据 const [showLeaveTip, setShowLeaveTip] = useState(false); // 离开路由是否又提醒 const [newSerialNo, setNewSerialNo] = useState(0); const [nodeLength, setNodeLength] = useState(); const objGo = go.GraphObject.make; // 监听删除,给删除数组里添加删除id useEffect(() => { if (deleteLine) { setDeleteLines([...DeleteLines, deleteLine]); } }, [deleteLine]); useEffect(() => { if (deleteNode) { setDeleteNodes([...DeleteNodes, deleteNode]); } }, [deleteNode]); // 初始化 useEffect(() => { // 初始化流程图 init(); // 监听节点或线的删除事件 diagram.addDiagramListener('SelectionDeleted', e => { e.subject.each(n => { // 如果删除得节点不是新增得就给id放入到删除节点数组中 if (n.data.NodeId && !AddNodes.some(item => item === n.data.NodeId)) { setTimeout(() => { setDeleteNode(n.data.NodeId); }, 0); } if (n.data.LineId) { setTimeout(() => { setDeleteLine(n.data.LineId); }, 0); } }); }); // 监听节点或线的删除前事件 diagram.commandHandler.canDeleteSelection = () => // 用例获取选中的节点或线 diagram.selection.all(() => { // 判断是否存在不允许删除的节点或线 showDeleteConfirm(); return false; }); }, []); useEffect(() => { if (flowData) { // 每次切换时清空删除得id数组跟新增得id数组 setDeleteNodes([]); setDeleteLines([]); setAddNodes([]); setDeleteNode(''); setDeleteLine(''); setCurrentFlowData(JSON.parse(JSON.stringify(flowData))); setShowLeaveTip(false); } }, [flowData]); // 存入在树形流程中选择得流程数据 useEffect(() => { let nodeDataArray; if (currentFlowData.Nodes.length === 0) { nodeDataArray = []; } else { // 处理老数据,让老数据可以正常展示 nodeDataArray = currentFlowData.Nodes.map((item, index) => { let obj; obj = item; obj.key = item.NodeId; if (obj.points === '') { if (obj.NodeType === '1') { obj.points = `${(index * 200).toString()}" 100"`; } else { obj.points = `${(index * 200).toString()}" -22"`; } } return obj; }); } // 保存初始数据 setInitFlowData( JSON.parse( JSON.stringify({ Nodes: nodeDataArray, Lines: currentFlowData.Lines, }), ), ); diagram.model = go.Model.fromJson({ linkFromPortIdProperty: 'fromPort', // 所需信息: linkToPortIdProperty: 'toPort', // 标识数据属性名称 nodeDataArray, linkDataArray: currentFlowData.Lines, }); }, [currentFlowData]); // 删除提醒 const showDeleteConfirm = () => { confirm({ title: '确定要删除所选中的节点吗?', icon: <ExclamationCircleOutlined />, content: '', okText: '是', okType: 'danger', cancelText: '否', onOk() { delNode(); }, onCancel() {}, }); }; // 删除节点 const delNode = () => { setShowLeaveTip(true); leaveCallBack(true); diagram.commandHandler.deleteSelection(); }; // 流程图初始化 const init = () => { diagram = objGo(go.Diagram, 'myDiagramDiv', { 'undoManager.isEnabled': true, allowDragOut: false, 'dragSelectingTool.isEnabled': false, // 禁止多选 allowCopy: false, // 禁止复制 nodeSelectionAdornmentTemplate: objGo( go.Adornment, 'Auto', objGo(go.Shape, 'Rectangle', { fill: 'white', stroke: null }), ), // 去掉节点点击时的边框颜色 }); // 节点配置 diagram.nodeTemplate = objGo( go.Node, 'Auto', new go.Binding('location', 'points', go.Point.parse).makeTwoWay(go.Point.stringify), // 节点样式配置 objGo( go.Panel, objGo( go.Shape, new go.Binding('width', 'NodeType', v => (v === '0' ? 135 : 105)), new go.Binding('height', 'NodeType', v => (v === '0' ? 75 : 105)), new go.Binding('figure', 'NodeType', v => (v === '0' ? 'RoundedRectangle' : 'Ellipse')), new go.Binding('strokeWidth', 'NodeType', v => (v === '0' ? 1 : 15)), new go.Binding('stroke', 'NodeType', v => { // 普通节点 if (v === '0') { return '#0587E0'; } // 开始节点 if (v === '1') { return '#d7efff'; } // 结束节点 if (v === '2') { return '#d7efff'; } return ''; }), new go.Binding('fill', 'NodeType', v => { // 普通节点 if (v === '0') { return '#DCF2FE'; } // 开始节点 if (v === '1') { return '#0AC03D'; } // 结束节点 if (v === '2') { return '#8585FF'; } return ''; }), ), ), // 节点文案 objGo( go.TextBlock, { maxSize: new go.Size(130, NaN), wrap: go.TextBlock.WrapFit }, new go.Binding('text', 'NodeName', v => v.slice(0, 6)), new go.Binding('stroke', 'NodeType', v => (v === '0' ? '#077BD6' : '#fff')), ), objGo( go.Picture, { source: imgUrl, // 图片路径 desiredSize: new go.Size(12, 12), alignment: go.Spot.TopRight, // 对齐主要形状上的端口 alignmentFocus: go.Spot.TopRight, // 就在形状里面 click() { // 删除节点 showDeleteConfirm(); }, }, new go.Binding('margin', 'NodeType', v => (v === '0' ? 5 : 17)), ), // 我们的小命名端口,每侧一个: makePort('T', go.Spot.Top), makePort('L', go.Spot.Left), makePort('R', go.Spot.Right), makePort('B', go.Spot.Bottom), { // 处理鼠标进入/离开事件以显示/隐藏端口 mouseEnter(e, node) { showSmallPorts(node, true); }, mouseLeave(e, node) { showSmallPorts(node, false); }, // 处理双击 doubleClick(e, node) { // 双击事件 handlerDC(e, node); // 双击执行的方法 }, }, ); // 链接设置 diagram.linkTemplate = objGo( go.Link, { routing: go.Link.Orthogonal, curve: go.Link.JumpOver, corner: 5, toShortLength: 4, }, new go.Binding('points').makeTwoWay(), objGo( go.Shape, // 链接路径形状 { isPanelMain: true, strokeWidth: 2, stroke: '#1685FF' }, ), objGo( go.Shape, // 箭头 { toArrow: 'Standard', stroke: '#1685FF', fill: '#1685FF' }, ), ); // 初始化流程的节点数组 diagram.model = objGo(go.GraphLinksModel, { linkFromPortIdProperty: 'fromPort', // 所需信息: linkToPortIdProperty: 'toPort', // 标识数据属性名称 nodeDataArray: currentFlowData.Nodes, linkDataArray: currentFlowData.Lines, }); }; // 是否显示端口 const showSmallPorts = (node, show) => { node.ports.each(port => { if (port.portId !== '') { // 不要更改默认端口,这是大形状 port.fill = show ? 'rgba(5,135,224,.3)' : null; } }); }; // 创建节点端口 const makePort = (name, spot) => // 端口基本上只是一个小的透明方块 objGo( go.Shape, 'Circle', { fill: null, // 默认情况下不可见; 由 showSmallPorts 设置为半透明灰色,定义如下 stroke: null, desiredSize: new go.Size(8, 8), alignment: spot, // 对齐主要形状上的端口 alignmentFocus: spot, // 就在形状里面 portId: name, // 将此对象声明为“端口” fromSpot: spot, toSpot: spot, // 声明链接可以在此端口连接的位置 cursor: 'pointer', // 显示不同的光标以指示潜在的链接点 }, new go.Binding('fromLinkable', 'NodeType', v => v !== '2'), // 是否允许用户绘制的链接到这里 new go.Binding('toLinkable', 'NodeType', v => v !== '1'), // 声明用户是否可以从这里绘制链接 ); // 双击节点 const handlerDC = (e, node) => { setNodeKey(node.part.data.key); setEditMsg(node.part.data); setModalType('edit'); setVisible(true); }; // 新增节点 const addNode = () => { const list = JSON.parse(diagram.model.toJson()).nodeDataArray; console.log(list, 'list'); let newNum; if (list.length > 0) { // eslint-disable-next-line prefer-spread newNum = Math.max.apply(Math, list.map(item => item.SerialNo)) + 1; } else { newNum = 1; } setNodeLength(list.length); console.log(newNum); setNewSerialNo(newNum); setModalType('add'); setVisible(true); }; // 节点配置回调 const nodeCallBack = obj => { let nameIsRepeat; let { nodes } = diagram; let keyArr = []; // 遍历输出节点对象 nodes.each(node => { keyArr = [...keyArr, Number(node.data.key)]; if (obj.NodeName === node.data.NodeName) { nameIsRepeat = true; if (modalType === 'edit' && obj.NodeName === editMsg.NodeName) { nameIsRepeat = false; } } }); if (nameIsRepeat) { notification.error({ message: '提示', duration: 3, description: '节点名称不能重复', }); return; } if (modalType === 'add') { // 新增节点 // 新增得key比最大得key值+1 let newKey; if (keyArr.length === 0) { newKey = 1; } else { newKey = keyArr.reduce((num1, num2) => (num1 > num2 ? num1 : num2)) + 1; } diagram.model.addNodeData({ key: newKey, NodeId: newKey, ...obj, }); setAddNodes([...AddNodes, newKey]); } if (modalType === 'edit') { // 编辑节点 let nodeData = diagram.model.findNodeDataForKey(nodeKey); const { NodeName, NodeType, roleList, SerialNo, aheadHandle, NodeHandling } = obj; nodeData.NodeName = NodeName; nodeData.NodeType = NodeType; nodeData.NodeId = nodeKey; nodeData.roleList = roleList; nodeData.SerialNo = SerialNo; nodeData.aheadHandle = aheadHandle; nodeData.NodeHandling = NodeHandling; diagram.model.updateTargetBindings(nodeData); } // 关闭时进行数据比对看数据是否改变 let diagramObj = JSON.parse(diagram.model.toJson()); let stageJson = { Nodes: diagramObj.nodeDataArray, Lines: diagramObj.linkDataArray, }; if (JSON.stringify(stageJson.Nodes) === JSON.stringify(initFlowData.Nodes)) { setShowLeaveTip(false); leaveCallBack(false); } else { leaveCallBack(true); setShowLeaveTip(true); } setVisible(false); }; // 获取保存后的流程数据 const getFlowData = () => { GetFlowNode({ flowID }).then(res => { if (res.code === 0) { // 保存后离开不用提醒要修改数据了 setShowLeaveTip(false); leaveCallBack(false); setCurrentFlowData(res.data); } else { notification.error({ title: '提示', duration: 3, description: res.msg, }); } }); }; const isRepeat = (arr, key) => { let obj = {}; for (let i = 0; i < arr.length; i++) { if (obj[arr[i][key]]) { return false; } obj[arr[i][key]] = arr[i]; } return obj; }; // 保存流程 const saveFlow = () => { let diagramObj = JSON.parse(diagram.model.toJson()); // let list = isRepeat(diagramObj.nodeDataArray, 'SerialNo'); // if (!list) { // notification.error({ // message: '提示', // duration: 3, // description: '请检查序号是否重复', // }); // return; // } SaveNodeChange({ FlowId: flowID, DeleteNodes, DeleteLines, Lines: diagramObj.linkDataArray, Nodes: diagramObj.nodeDataArray, }) .then(res => { if (res.code === 0) { setDeleteNodes([]); setDeleteLines([]); setAddNodes([]); setDeleteNode(''); setDeleteLine(''); getFlowData(); notification.success({ message: '提示', duration: 3, description: '保存成功', }); } else { notification.error({ message: '提示', duration: 8, description: res.msg, }); } }) .catch(() => { notification.error({ message: '提示', duration: 3, description: '网络异常请稍后重试', }); }); }; return ( <> <Prompt message="编辑的内容还未保存,确定要离开该页面吗?" when={showLeaveTip} /> <div className={styles.buttonList}> <Button onClick={() => addNode()}>添加节点</Button> <Button type="primary" onClick={() => saveFlow()}> 保存 </Button> </div> <Spin spinning={chartLoading}> <div id="myDiagramDiv" className={styles.myDiagramDiv} style={{ backgroundColor: '#EFF8FA' }} /> </Spin> <NodeModal visible={visible} editMsg={editMsg} newSerialNo={newSerialNo} nodeNum={nodeLength} modalType={modalType} handleCancel={() => setVisible(false)} onSubumit={obj => nodeCallBack(obj)} /> </> ); }; export default FlowChart;