当前位置: 首页 > news >正文

前端使用canvas绘制简单工作流-react

效果图如下:

目前只做了绘制部分,绘制方式也比较简单,点击工具栏中需要绘制的图形,在画布上左键点击将会绘制一个图形出来,工具栏选中第一个,再点击其他图像,长按鼠标左键可以移动,删除使用键盘delete键,目前没做批量框选(懒得写了,按照点击选中的思路可以自己实现),工具栏最后一个画线,需要鼠标长按,起点与终点在图形上,路径为自动生成,不能自定义调整,但可以通过拖动节点改变路径。

节点结构如下:
 

type Node = {key: number;//标识type: 1 | 2 | 3 | 4 | 5 | 6;//1开始,2结束,3任务节点,4决策节点,5子流程节点,6连接线name?: string;//名称x: number;//坐标xy: number;//坐标yradius?: number;//半径width?: number;//宽heigth?: number;//高isCheck: boolean;//是否选中startNode?: number;//开始节点--作连接线时使用endNode?: number;//结束节点--作连接线时使用//连接线线路ponits?: { start: { x: number, y: number }, center?: { x: number, y: number }[], end: { x: number, y: number }, arrow?: { x: number, y: number }[] };endPoint?: { x: number, y: number };//结束点--作连接线未完成画虚线时使用
}

如需要传给后端需要调整格式(目前传不了,因为没做节点绑定人),后端的部分后面再做了,有空了再去优化,比如工具栏图标,UI什么的,完整代码如下
index.tsx
 


import { BranchesOutlined, DragOutlined, } from '@ant-design/icons';
import { Button,  Col,  Form,  Row,  Tooltip } from 'antd';import styles from './index.less'
import React, { MouseEvent, useEffect, useRef, useState } from 'react';
type Node = {key: number;//标识type: 1 | 2 | 3 | 4 | 5 | 6;//1开始,2结束,3任务节点,4决策节点,5子流程节点,6连接线name?: string;//名称x: number;//坐标xy: number;//坐标yradius?: number;//半径width?: number;//宽heigth?: number;//高isCheck: boolean;//是否选中startNode?: number;//开始节点--作连接线时使用endNode?: number;//结束节点--作连接线时使用//连接线线路ponits?: { start: { x: number, y: number }, center?: { x: number, y: number }[], end: { x: number, y: number }, arrow?: { x: number, y: number }[] };endPoint?: { x: number, y: number };//结束点--作连接线未完成画虚线时使用
}
const Test: React.FC = () => {const canvasRef = useRef<HTMLCanvasElement>(null);const [nodes, setNodes] = useState<Record<number, Node | undefined>>({});const [checkButton, setCheckButton] = useState<number>();const [checkNode, setCheckNode] = useState<number>(0);const [currentConnetLine, setCurrentConnetLine] = useState<number>(0);const [mouseDown, setMouseDown] = useState<boolean>(false);/*** 检查鼠标是否点击到线段* @param click 鼠标坐标* @param line 线段起始结束点* @param width 线段宽度*/const checkClickLine = (click: { x: number, y: number }, line: { start: { x: number, y: number }, end: { x: number, y: number } }, width: number): boolean => {const { x: startX, y: startY } = line.start;const { x: endX, y: endY } = line.end;// 计算线段的方向向量  const dx = endX - startX;const dy = endY - startY;// 计算线段长度  const length = Math.sqrt(dx * dx + dy * dy);// 如果线段长度为0,则直接比较点是否相同  if (length === 0) {const distance = Math.sqrt(Math.pow(click.x - startX, 2) + Math.pow(click.y - startY, 2));return distance <= width / 2;}// 计算点击点到线段所在直线的垂直距离  const u = ((click.x - startX) * dx + (click.y - startY) * dy) / (length * length);// 检查点击点是否在线段上  if (u < 0 || u > 1) {return false;}// 计算最近点  const xClosest = startX + u * dx;const yClosest = startY + u * dy;// 计算点击点到最近点的距离  const distance = Math.sqrt(Math.pow(click.x - xClosest, 2) + Math.pow(click.y - yClosest, 2));// 检查距离是否小于线段宽度的一半  return distance <= width / 2;}const checkNodeSelect = (node: Node, e: MouseEvent): boolean => {const canvas = canvasRef.current;if (canvas) {const rect = canvas.getBoundingClientRect();const x = e.clientX - rect.left;const y = e.clientY - rect.top;switch (node.type) {case 1:case 2:if (node.radius) {const distance = Math.sqrt(Math.pow(x - node.x, 2) + Math.pow(y - node.y, 2));return distance <= node.radius}break;case 3:case 4:if (node.heigth && node.width) {const leftTop = { x: node.x - node.width / 2, y: node.y - node.heigth / 2 };const rightBottom = { x: node.x + node.width / 2, y: node.y + node.heigth / 2 };return x >= leftTop.x && x <= rightBottom.x && y >= leftTop.y && y <= rightBottom.y;}break;case 5:if (node.radius) {const halfSize = node.radius / 2 - 3;const dx = Math.abs(x - node.x);const dy = Math.abs(y - node.y);return dx * dx + dy * dy <= halfSize * halfSize;;}breakcase 6:if (node.ponits) {let check = false;const ponits = node.ponits;if (ponits.center) {for (let i = 0, l = ponits.center.length; i < l; i++) {if (!check) {if (i == 0) {check = checkClickLine({ x, y }, { start: ponits.start, end: ponits.center[i] }, 5);if (!check) {check = checkClickLine({ x, y }, { start: ponits.center[i], end: ponits.center[i + 1] }, 5);}}else if (i == l - 1) {check = checkClickLine({ x, y }, { start: ponits.center[i], end: ponits.end }, 5);} else {check = checkClickLine({ x, y }, { start: ponits.center[i], end: ponits.center[i + 1] }, 5);}}}} else {check = checkClickLine({ x, y }, { start: ponits.start, end: ponits.end }, 5);}return check;}break;}}return false}const [form] = Form.useForm();/*** 获取节点的上下左右几个端点* @param node * @param direction 上下左右|1234*/const getNodePonit = (node: Node, direction: 1 | 2 | 3 | 4): { x: number, y: number } => {switch (node.type) {case 1: case 2:switch (direction) {case 1:return { x: node.x, y: node.y - (node.radius ? node.radius : 0) };case 2:return { x: node.x, y: node.y + (node.radius ? node.radius : 0) };case 3:return { x: node.x - (node.radius ? node.radius : 0), y: node.y };case 4:return { x: node.x + (node.radius ? node.radius : 0), y: node.y };}case 5:switch (direction) {case 1:return { x: node.x, y: node.y - (node.radius ? node.radius : 0) / 2 };case 2:return { x: node.x, y: node.y + (node.radius ? node.radius : 0) / 2 };case 3:return { x: node.x - (node.radius ? node.radius : 0) / 2, y: node.y };case 4:return { x: node.x + (node.radius ? node.radius : 0) / 2, y: node.y };}case 3: case 4:switch (direction) {case 1:return { x: node.x, y: node.y - (node.heigth ? node.heigth / 2 : 0) };case 2:return { x: node.x, y: node.y + (node.heigth ? node.heigth / 2 : 0) };case 3:return { x: node.x - (node.width ? node.width / 2 : 0), y: node.y };case 4:return { x: node.x + (node.width ? node.width / 2 : 0), y: node.y };}}return { x: 0, y: 0 };}/*** 获取箭头的三个点坐标* @param node 顶点* @param type 1,箭头向上,2箭头向下,3,箭头向左,4箭头向右*/const getArrow = (node: { x: number, y: number }, type: 1 | 2 | 3 | 4): { x: number, y: number }[] => {const width = 8;const height = 14;switch (type) {case 1:return [{ x: node.x, y: node.y }, { x: node.x - width / 2, y: node.y + height }, { x: node.x + width / 2, y: node.y + height }]case 2:return [{ x: node.x, y: node.y }, { x: node.x - width / 2, y: node.y - height }, { x: node.x + width / 2, y: node.y - height }]case 3:return [{ x: node.x, y: node.y }, { x: node.x + height, y: node.y - width / 2 }, { x: node.x + height, y: node.y + width / 2 }]case 4:return [{ x: node.x, y: node.y }, { x: node.x - height, y: node.y - width / 2 }, { x: node.x - height, y: node.y + width / 2 }]}}/*** 根据连接的两点计算线路点* @param startNode * @param endNode * @returns */const getConnetPoints = (startNode: Node, endNode: Node): { start: { x: number, y: number }, center?: { x: number, y: number }[], end: { x: number, y: number }, arrow?: { x: number, y: number }[] } => {if (startNode.x === endNode.x) {if (startNode.y > endNode.y) {//终点在正上方const end = getNodePonit(endNode, 2);return { start: getNodePonit(startNode, 1), end, arrow: getArrow(end, 1) }} else {const end = getNodePonit(endNode, 1);return { start: getNodePonit(startNode, 2), end, arrow: getArrow(end, 2) }}} else if (startNode.y === endNode.y) {if (startNode.x > endNode.x) {//终点在正左方const end = getNodePonit(endNode, 4);return { start: getNodePonit(startNode, 3), end, arrow: getArrow(end, 3) }} else {const end = getNodePonit(endNode, 3);return { start: getNodePonit(startNode, 4), end, arrow: getArrow(end, 4) }}} else if (startNode.x > endNode.x) {if (startNode.y > endNode.y) {if ((startNode.y - endNode.y) > (startNode.x - endNode.x)) {const start = getNodePonit(startNode, 1);const end = getNodePonit(endNode, 2);const centerY = start.y - (start.y - end.y) / 2;const center = [{ x: start.x, y: centerY }, { x: end.x, y: centerY }]const arrow = getArrow(end, 1)return { start, center, end, arrow }} else {const start = getNodePonit(startNode, 3);const end = getNodePonit(endNode, 4);const centerX = start.x - (start.x - end.x) / 2;const center = [{ x: centerX, y: start.y }, { x: centerX, y: end.y }]const arrow = getArrow(end, 3)return { start, center, end, arrow }}} else {if ((endNode.y - startNode.y) > (startNode.x - endNode.x)) {const start = getNodePonit(startNode, 2);const end = getNodePonit(endNode, 1);const centerY = end.y - (end.y - start.y) / 2;const center = [{ x: start.x, y: centerY }, { x: end.x, y: centerY }]const arrow = getArrow(end, 2)return { start, center, end, arrow }} else {const start = getNodePonit(startNode, 3);const end = getNodePonit(endNode, 4);const centerX = start.x - (start.x - end.x) / 2;const center = [{ x: centerX, y: start.y }, { x: centerX, y: end.y }]const arrow = getArrow(end, 3)return { start, center, end, arrow }}}} else {if (startNode.y > endNode.y) {if ((startNode.y - endNode.y) > (endNode.x - startNode.x)) {const start = getNodePonit(startNode, 1);const end = getNodePonit(endNode, 2);const centerY = start.y - (start.y - end.y) / 2;const center = [{ x: start.x, y: centerY }, { x: end.x, y: centerY }]const arrow = getArrow(end, 1)return { start, center, end, arrow }} else {const start = getNodePonit(startNode, 4);const end = getNodePonit(endNode, 3);const centerX = end.x - (end.x - start.x) / 2;const center = [{ x: centerX, y: start.y }, { x: centerX, y: end.y }]const arrow = getArrow(end, 4)return { start, center, end, arrow }}} else {if ((endNode.y - startNode.y) > (endNode.x - startNode.x)) {const start = getNodePonit(startNode, 2);const end = getNodePonit(endNode, 1);const centerY = end.y - (end.y - start.y) / 2;const center = [{ x: start.x, y: centerY }, { x: end.x, y: centerY }]const arrow = getArrow(end, 2)return { start, center, end, arrow }} else {const start = getNodePonit(startNode, 4);const end = getNodePonit(endNode, 3);const centerX = end.x - (end.x - start.x) / 2;const center = [{ x: centerX, y: start.y }, { x: centerX, y: end.y }]const arrow = getArrow(end, 4)return { start, center, end, arrow }}}}}useEffect(() => {if (canvasRef.current && nodes && Object.values(nodes).length > 0) {const ctx = canvasRef.current.getContext('2d');if (ctx) {ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);Object.values(nodes).map(node => {if (node) {switch (node.type) {case 1:case 2:if (node.radius) {ctx.beginPath();ctx.arc(node.x, node.y, node.radius, 0, 2 * Math.PI);ctx.fillStyle = node.type === 1 ? '#90EE90' : '#FFA07A';ctx.fill();ctx.lineWidth = 0.5;ctx.strokeStyle = 'black';ctx.stroke();// 添加文字  ctx.fillStyle = '#000000'; // 文字颜色  ctx.font = '12px Arial'; // 文字样式  ctx.textAlign = 'center'; // 文字对齐方式  ctx.fillText(node.type === 1 ? '开始' : '结束', node.x, node.y + 2);ctx.stroke();if (node.isCheck) {// 设定正方形的位置和尺寸const halfSize = node.radius + 2;// 绘制正方形的虚线边框  ctx.setLineDash([5, 5]); // 设置虚线样式,5个单位实线,5个单位间隔  ctx.beginPath();ctx.rect(node.x - halfSize, node.y - halfSize, halfSize * 2, halfSize * 2);ctx.strokeStyle = 'blue'; // 边框颜色  ctx.stroke();//绘制完虚线后重置线型为实线  ctx.setLineDash([]);}}break;case 3:case 4:if (node.heigth && node.width) {ctx.beginPath();const radius = 20; // 圆角的大小 const x = node.x - node.width / 2;const y = node.y - node.heigth / 2;ctx.strokeStyle = node.type == 3 ? '#FF0000' : '#ADD8E6';// 设置边框宽度  ctx.lineWidth = 0.5;ctx.moveTo(x + radius, y);ctx.arcTo(x + node.width, y, x + node.width, y + node.heigth, radius);ctx.arcTo(x + node.width, y + node.heigth, x, y + node.heigth, radius);ctx.arcTo(x, y + node.heigth, x, y, radius);ctx.arcTo(x, y, x + radius, y, radius);ctx.stroke();ctx.fillStyle = 'white'; // 矩形颜色  ctx.fill();// 添加文字  ctx.fillStyle = '#000000'; // 文字颜色  ctx.font = '12px Arial'; // 文字样式  ctx.textAlign = 'left'; // 文字对齐方式  const text = node.name ? node.name : '节点' + node.key;if (text.length < 8) {ctx.fillText(text, x + (5 * (8 - text.length)), y + 27);} else if (text.length < 15) {ctx.fillText(text.slice(0, 7), x + 8, y + 20);const twoLine = text.slice(7, text.length);ctx.fillText(twoLine, x + 8 + (5.7 * (7 - twoLine.length)), y + 38);} else {ctx.fillText(text.slice(0, 7), x + 8, y + 20);const twoLine = text.slice(7, 13) + '...';ctx.fillText(twoLine, x + 8, y + 38);}ctx.stroke();if (node.isCheck) {ctx.setLineDash([5, 5]);ctx.beginPath();ctx.rect(node.x - node.width / 2 - 2, node.y - node.heigth / 2 - 2, node.width + 4, node.width / 2 + 4);ctx.strokeStyle = 'blue';ctx.stroke();ctx.setLineDash([]);}}break;case 5:if (node.radius) {ctx.lineWidth = 0.5;ctx.strokeStyle = 'black';// 开始绘制菱形路径  ctx.beginPath();ctx.moveTo(node.x, node.y - node.radius / 2);ctx.lineTo(node.x + node.radius / 2, node.y);ctx.lineTo(node.x, node.y + node.radius / 2);ctx.lineTo(node.x - node.radius / 2, node.y);ctx.closePath();// 绘制边框  ctx.stroke();// 设置填充样式并填充颜色  ctx.fillStyle = 'white';ctx.fill();// 设置填充样式并填充颜色  ctx.beginPath();ctx.fillStyle = 'black';ctx.fill();ctx.font = '18px Arial';ctx.textAlign = 'center';ctx.textBaseline = 'middle';ctx.fillText('if', node.x, node.y + 2);ctx.stroke();if (node.isCheck) {const halfSize = node.radius / 2 + 2;ctx.setLineDash([5, 5]);ctx.beginPath();ctx.rect(node.x - halfSize, node.y - halfSize, halfSize * 2, halfSize * 2);ctx.strokeStyle = 'blue';ctx.stroke();ctx.setLineDash([]);}}breakcase 6:if (node.endPoint) {// 设置虚线样式  ctx.setLineDash([5, 5]); // 第一个数字是实线部分长度,第二个数字是间隔长度  ctx.lineDashOffset = 0; // 虚线偏移量  ctx.lineWidth = 2; // 线条宽度  const x2 = node.x;const y2 = node.y;const x1 = node.endPoint.x;const y1 = node.endPoint.y;const dx = x2 - x1;const dy = y2 - y1;const distanceP1P2 = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));const ratio = 20 / distanceP1P2;const p3 = { x: x1 + (x2 - x1) * ratio, y: y1 + (y2 - y1) * ratio };const unitX = dx / distanceP1P2;const unitY = dy / distanceP1P2;const nx = -dy / distanceP1P2;const ny = dx / distanceP1P2;const p4 = { x: p3.x + nx * 5, y: p3.y + ny * 5 };const p5 = { x: p3.x - nx * 5, y: p3.y - ny * 5 };const p6 = { x: x1 + unitX * 5, y: y1 + unitY * 5 };// 绘制箭头主体(直线)  ctx.beginPath();ctx.moveTo(node.x, node.y);ctx.lineTo(p6.x, p6.y);ctx.stroke();// 计算顶点p1到中垂线起点p2的向量  ctx.setLineDash([]);ctx.beginPath();ctx.moveTo(node.endPoint.x, node.endPoint.y);ctx.lineTo(p4.x, p4.y);ctx.lineTo(p5.x, p5.y);ctx.closePath();ctx.fillStyle = 'black'; // 箭头颜色  ctx.fill();// 重置虚线样式,如果之后还需要绘制实线或其他虚线样式  } else {if (node.ponits) {console.log(node.isCheck)const ponits = node.ponits;ctx.lineWidth = 2; // 线条宽度  ctx.strokeStyle = node.isCheck ? 'red' : 'black',ctx.beginPath();ctx.moveTo(ponits.start.x, ponits.start.y);if (ponits.center) {ponits.center.map(n => {ctx.lineTo(n.x, n.y);})}ctx.lineTo(ponits.end.x, ponits.end.y);ctx.stroke();if (ponits.arrow) {ctx.beginPath();ctx.lineWidth = 2;ctx.moveTo(ponits.arrow[0].x, ponits.arrow[0].y);ctx.lineTo(ponits.arrow[1].x, ponits.arrow[1].y);ctx.lineTo(ponits.arrow[2].x, ponits.arrow[2].y);ctx.closePath();ctx.fillStyle = node.isCheck ? 'red' : 'black', // 箭头颜色  ctx.fill();}}}break;}}})}}}, [nodes])const onMouseDown = (e: MouseEvent) => {const canvas = canvasRef.current;if (e.button === 0) {setMouseDown(true)} else {setMouseDown(false)}if (canvas) {const rect = canvas.getBoundingClientRect();const key = Object.values(nodes).length + 1;const x = e.clientX - rect.left;const y = e.clientY - rect.top;switch (checkButton) {case 0:if (e.button === 0) {let check = false;Object.values(nodes).map(node => {if (node) {if (!check && checkNodeSelect(node, e)) {setCheckNode(node.key);node.isCheck = true;check = true;} else {node.isCheck = false;}}})if (!check) {setCheckNode(0)}setNodes({ ...nodes })} else {setCheckNode(0);}breakcase 1:case 2:if (e.button === 0) {nodes[key] = { key, name: checkButton === 1 ? '开始' : '结束', type: checkButton, x, y, radius: 20, isCheck: false };setNodes({ ...nodes })}breakcase 3:case 4:if (e.button === 0) {nodes[key] = { key, name: checkButton === 3 ? '任务节点' : '子流程节点', type: checkButton, x, y, isCheck: false, width: 100, heigth: 50 };setNodes({ ...nodes })}breakcase 5:nodes[key] = { key, type: checkButton, name: '决策节点', x, y, isCheck: false, radius: 40 };setNodes({ ...nodes })breakcase 6:const currentConnetLine: Node = { key, type: checkButton, name: '连接线', x, y, isCheck: false }setCurrentConnetLine(key)let check = false;Object.values(nodes).map(node => {if (node) {if (!check && checkNodeSelect(node, e)) {currentConnetLine.startNode = node.keycheck = true;}}})nodes[key] = currentConnetLine;setNodes({ ...nodes })break;}}};const onMouseMove = (e: MouseEvent) => {const canvas = canvasRef.current;if (canvas && mouseDown) {const rect = canvas.getBoundingClientRect();switch (checkButton) {case 0:const node = nodes[checkNode];if (node) {node.x = e.clientX - rect.left;node.y = e.clientY - rect.top;//重新计算连接线Object.values(nodes).map(node => {if (node?.type === 6 && node.startNode && node.endNode) {const startNode = nodes[node.startNode];const endNode = nodes[node.endNode];if (startNode && endNode) {node.ponits = getConnetPoints(startNode, endNode)}}});setNodes({ ...nodes })}break;case 6:const lineNode = nodes[currentConnetLine];if (lineNode) {lineNode.endPoint = { x: e.clientX - rect.left, y: e.clientY - rect.top }setNodes({ ...nodes })}break;}}};const handleKeyDown = (event: any) => {if (event.key === 'Delete' && checkNode) {nodes[checkNode] = undefinedsetNodes({ ...nodes })}};useEffect(() => {document.addEventListener('keydown', handleKeyDown);return () => {document.removeEventListener('keydown', handleKeyDown);};}, [checkNode]);const onMouseUp = (e: MouseEvent) => {if (e.button === 0) {switch (checkButton) {case 6:const canvas = canvasRef.current;const connetLine = nodes[currentConnetLine];if (canvas && connetLine) {if (!connetLine.startNode) {nodes[currentConnetLine] = undefined;setCurrentConnetLine(0)setNodes({ ...nodes })return;}const rect = canvas.getBoundingClientRect();const x = e.clientX - rect.left;const y = e.clientY - rect.top;if (x === connetLine.x && y === connetLine.y) {nodes[currentConnetLine] = undefined;setCurrentConnetLine(0)} else {let check = false;Object.values(nodes).map(node => {if (node) {if (!check && checkNodeSelect(node, e) && connetLine.startNode !== node.key) {connetLine.endNode = node.key;check = true;}}})if (!check) {nodes[currentConnetLine] = undefined;} else {connetLine.endPoint = undefined;if (connetLine.endNode) {const startNode = nodes[connetLine.startNode];const endNode = nodes[connetLine.endNode];if (startNode && endNode) {connetLine.ponits = getConnetPoints(startNode, endNode)nodes[currentConnetLine] = connetLine;}}setCurrentConnetLine(0)}setNodes({ ...nodes })}break;}break;}setMouseDown(false)}};const changeButton = (btn: number) => {setCheckButton(btn);switch (btn) {case 1: case 2: case 3: case 4: case 5:setCheckNode(0);Object.values(nodes).map(node => {if (node)node.isCheck = false;})setNodes({ ...nodes })break;}}const typeStr = (type: number) => {switch (type) {case 1: return '开始节点';case 2: return '结束节点';case 3: return '任务节点';case 4: return '子流程节点';case 5: return '决策节点';case 6: return '连接线';}}return (<Row gutter={24}><Col span={6}><table className={styles.canvasTable}><thead><tr><th colSpan={2} ><p style={{ paddingTop: 10 }}>绘制工具</p></th></tr></thead><tbody ><tr><td style={{ backgroundColor: checkButton === 0 ? '#948f8f' : '' }} onClick={() => { changeButton(0) }} ><Tooltip title='选择'><Button onClick={() => { changeButton(0) }} icon={<DragOutlined />} /></Tooltip></td><td style={{ backgroundColor: checkButton === 1 ? '#948f8f' : '' }} onClick={() => { changeButton(1) }} ><Tooltip title='开始节点'><Button onClick={() => { changeButton(1) }} shape="circle" style={{ backgroundColor: '#90EE90' }}><p style={{ fontSize: '11px', paddingTop: '10px' }}>开始</p></Button></Tooltip></td></tr><tr><td style={{ backgroundColor: checkButton === 2 ? '#948f8f' : '' }} onClick={() => { changeButton(2) }}><Tooltip title='结束节点'><Button onClick={() => { changeButton(2) }} shape="circle" style={{ backgroundColor: '#FFA07A' }} ><p style={{ fontSize: '11px', paddingTop: '10px' }}>结束</p></Button></Tooltip></td><td style={{ backgroundColor: checkButton === 5 ? '#948f8f' : '' }} onClick={() => { changeButton(5) }}><Tooltip title='决策节点'><div className={styles.rotatedSquare} onClick={() => { changeButton(5) }}>if</div></Tooltip></td></tr><tr><td style={{ backgroundColor: checkButton === 3 ? '#948f8f' : '' }} onClick={() => { changeButton(3) }}><Tooltip title='任务节点'><Button onClick={() => { changeButton(3) }} shape="round" style={{ border: '1px solid #FF0000', width: '50px' }}><p style={{ fontSize: '11px', paddingTop: '10px' }}>任务</p></Button></Tooltip></td><td style={{ backgroundColor: checkButton === 4 ? '#948f8f' : '' }} onClick={() => { changeButton(4) }}><Tooltip title='子流程节点' ><Button onClick={() => { changeButton(4) }} shape="round" style={{ border: '1px solid #ADD8E6', width: '50px' }}><p style={{ fontSize: '11px', paddingTop: '10px' }}>子流程</p></Button></Tooltip></td></tr><tr><td style={{ backgroundColor: checkButton === 6 ? '#948f8f' : '' }} onClick={() => { changeButton(6) }} ><Tooltip title='连接节点'><Button onClick={() => { changeButton(6) }} icon={<BranchesOutlined />} /></Tooltip></td><td></td></tr></tbody></table><table className={styles.canvasTable}><thead><tr><th colSpan={2} ><p style={{ paddingTop: 10 }}>节点属性</p></th></tr></thead><tbody ><tr><td style={{ width: '80px' }}>节点名称</td><td >{(checkNode !== 0 && nodes[checkNode]) && (<div>{nodes[checkNode].name}</div>)}</td></tr><tr><td >节点类型</td><td >{(checkNode !== 0 && nodes[checkNode]) && (<div>{typeStr(nodes[checkNode].type)}</div>)}</td></tr></tbody></table></Col><Col span={18}><canvas onMouseDown={onMouseDown}onMouseMove={onMouseMove}onMouseUp={onMouseUp}ref={canvasRef}width='700px'height='400px'style={{ border: '1px solid #000' }}/></Col></Row>)
}
export default Test;

index.less:
 

.rotatedSquare {  background-color: rgb(216, 218, 223);  padding-top: 2px;margin-top: 2px;margin-left: 30px;cursor: pointer;height: 26px;width: 30px;clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); 
}  .canvasTable{border: 1px solid #000;width: 100%;
}
.canvasTable th{text-align: center;align-items: center;
}
.canvasTable td{padding: 5px;text-align: center;align-items: center;border: 1px solid #000
}


http://www.mrgr.cn/news/10715.html

相关文章:

  • redis 开启事务 pipeline.multi(); 需要关闭嘛?
  • java直接内存
  • MySQL 之 GROUP BY 讲解:常见用法与案例剖析
  • RabbitMQ中支持多种类型的交换机
  • 深度学习模板方法设计模式
  • Spring Cloud Stream与Kafka(二)
  • 计算机基础知识总结(八股文--计算机网络、操作系统、数据库、c++、数据结构与算法)
  • 实际项目中,运用Retrofit和OkHttp调用其他项目接口
  • 某音作品列表,视频列表
  • oracle ONS configuration failed NoserversAvailable:Subscription time out导致应用起不来
  • 鸿蒙(API 12 Beta3版)【使用Image完成图片接收器】图片开发指导依赖JS对象
  • 中断处理过程
  • 华为HCIP-datacom 真题 (2024年下半年最新题库)
  • Oracle taf高级特性使用
  • 【Linux】全面解析进程:优先级、环境变量与地址空间
  • 视频美颜SDK与直播美颜插件的集成策略与性能优化方案详解
  • 【持续更新】nPlayer Premium v1.7.7.7-191219安卓知名播放器最新免费高级修改版
  • OpenGL3.3_C++_Windows(36)
  • Mysql三个日志的作用及区别
  • UNIAPP 锚点跳转