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

news2024/11/17 1:31:47

效果图如下:

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

节点结构如下:
 

type Node = {
    key: number;//标识
    type: 1 | 2 | 3 | 4 | 5 | 6;//1开始,2结束,3任务节点,4决策节点,5子流程节点,6连接线
    name?: string;//名称
    x: number;//坐标x
    y: number;//坐标y
    radius?: 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;//坐标x
    y: number;//坐标y
    radius?: 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;;
                    }
                    break
                case 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([]);
                                    }
                                }
                                break
                            case 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);
                    }
                    break
                case 1:
                case 2:
                    if (e.button === 0) {
                        nodes[key] = { key, name: checkButton === 1 ? '开始' : '结束', type: checkButton, x, y, radius: 20, isCheck: false };
                        setNodes({ ...nodes })
                    }
                    break
                case 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 })
                    }
                    break
                case 5:
                    nodes[key] = { key, type: checkButton, name: '决策节点', x, y, isCheck: false, radius: 40 };
                    setNodes({ ...nodes })
                    break
                case 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.key
                                check = 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] = undefined
            setNodes({ ...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.coloradmin.cn/o/2076176.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

丢掉Beyond Compare吧!新款文件差异对比工具WinMerge更具性价比!

今天想和大家分享一款非常实用的免费开源文件比较工具&#xff1a;WinMerge。 作为一名长期从事互联网行业的人&#xff0c;我经常需要处理大量的文档和代码文件&#xff0c;文件对比工具在我的日常工作中可谓是必不可少的“左膀右臂”。 也相信很多朋友在处理多个文档内容或者…

96页PPT集团战略解码会工具与操作流程

德勤集团在战略解码过程中通常会用到以下一些具体工具&#xff1a; 一、平衡计分卡&#xff08;Balanced Scorecard&#xff09; 财务维度&#xff1a; 明确关键财务指标&#xff0c;如营业收入、利润、投资回报率等。你可以通过分析历史财务数据和行业趋势&#xff0c;确定…

HUSB381A:带线PD适配器的绝佳选择

HUSB381A是慧能泰半导体全新推出的一款采用SOP8封装&#xff0c;集成MOS的USB PD Source芯片&#xff0c;带CC1和CC2引脚&#xff0c;支持不可分离线缆&#xff08;Captive Cable&#xff09;PD适配器和纯PD快充充电器应用。HUSB381A支持最大功率20V5A 100W应用&#xff0c;支持…

单片机驱动彩屏最简方案:单片机_RA8889最小开发板驱动控制TFT彩屏介绍(一)方案架构

本文介绍使用单片机RA8889来驱动和控制彩屏的最小方案。文章从RA8889的架构功能、硬件电路设计及软件设计三个方面来说明。 小编已发布多篇文章介绍了单片机RA8889来驱动控制彩屏&#xff0c;但是仍有不少单片机玩家可能对驱动彩屏还不算熟悉&#xff0c;在此加推一个短篇介绍…

审计发现 FBI 的数据存储管理存在重大漏洞

据The Hacker News消息&#xff0c;美国司法部监察长办公室 &#xff08;OIG&#xff09; 的一项审计发现&#xff0c; FBI 在库存管理和处置涉及机密数据的电子存储媒体方面存在“重大漏洞”。 OIG 的审计显示&#xff0c;FBI 对包含敏感但未分类 &#xff08;SBU&#xff09…

橙子投屏,轻松连接大屏幕

对于某腾、某爱、某酷投屏大家在熟悉不过了吧&#xff0c;一款非常好用的投屏软件&#xff0c;但是使用起来还是限制颇多&#xff0c;比如有犷郜&#xff0c;还必须同步使用手机App才能实现投屏功能&#xff0c;关键还得开会员&#xff0c;劝退不少小伙伴。但是现在手机往往占据…

API代理指南:跨境业务的数据桥梁

在当今全球化的经济环境中&#xff0c;跨境业务已成为企业拓展国际市场、实现业务增长的重要途径。但面临着法律法规差异、网络复杂性和数据安全等诸多挑战。为了有效应对这些挑战&#xff0c;API&#xff08;应用程序编程接口&#xff09;代理成为了跨境业务中不可或缺的一部分…

接口自动化框架设计必备利器之参数传递

在我们设计自动化测试框架的时候&#xff0c;我们会经常将测试数据保存在外部的文件&#xff08;如Excel、YAML&#xff09;中&#xff0c;实现测试脚本与测试数据解耦&#xff0c;方便后期维护。 当涉及到业务场景接口用例时&#xff0c;由于接口与接口存在关联关系&#xff…

5步掌握Python Django开发办公管理系统核心技巧

&#x1f393; 作者&#xff1a;计算机毕设小月哥 | 软件开发专家 &#x1f5a5;️ 简介&#xff1a;8年计算机软件程序开发经验。精通Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等技术栈。 &#x1f6e0;️ 专业服务 &#x1f6e0;️ 需求定制化开发源码提…

Unity实现棋盘方格

本文参考&#xff1a;p1_哔哩哔哩_bilibili 一、精要提炼 1、Button自带的白色底图是圆角的&#xff0c;Image组件自带的白色底图是方角的。 2、2D中Instantiate指定的位置为屏幕坐标系的位置&#xff0c;左下角为(0,0) 3、求某个组件的位置&#xff1a;xx.transform.posi…

(javaweb)事务管理+AOP

目录 1.spring事务管理 2.rollbackFor&#xff08;异常回滚属性&#xff09; 3.propagation&#xff08;事物传播行为&#xff09; AOP基础 1.AOP概述 AOP快速入门 AOP核心概念 APO进阶 1.通知类型 2.通知顺序 3.切入点表达式 4.连接点 5.AOP案例 1.spring事务管理…

谷歌浏览器翻译不了网页怎么解决

谷歌浏览器的网页翻译功能因其便捷性和高效性&#xff0c;成为了许多用户跨语言浏览的重要工具。然而&#xff0c;有时候用户可能会遇到无法使用谷歌浏览器翻译网页的情况。接下来将为大家分析为何谷歌浏览器翻译不了网页以及解决方法&#xff0c;希望对你有所帮助。&#xff0…

从dxf文件中提取spline曲线

1.使用的工具 libDxf-负责解析dxf文件&#xff0c;提取图形元素 open_Nurbs-负责spline曲线的计算 2.效果如下 左图是CAD绘制的spline曲线&#xff0c;右图是程序中显示的曲线&#xff0c;红色点是在CAD中作图时鼠标点击的拟合点。 3.main.cpp代码 #include"zmDXF.h&…

【香橙派系列教程】(十五) VSCode SSH远程连接开发板,以香橙派为例

【十五】VSCode远程连接香橙派 文章目录 【十五】VSCode远程连接香橙派第一步&#xff1a;安装vscode第二步&#xff1a;安装Remote Development第三步&#xff1a;字体设置第四步&#xff1a;配置远程连接第五步&#xff1a;配置远程目录 第一步&#xff1a;安装vscode 之前发…

SuperMap WebGPA外部大数据集群部署实践

SuperMap WebGPA外部大数据集群部署实践 特别说明&#xff1a;部署前准备&#xff1a; 1.安装包说明2. 许可配置3. 机器情况部署过程说明&#xff1a; 1.设置ip主机名映射&#xff08;主机ip需要固定ip&#xff09;2. 查看防火强状态&#xff0c;关闭防火墙3. 新建用户&#…

dll修复工具下载:dll文件丢失问题?一键解决系统难题!

dll修复工具是Windows电脑必不可少的一款系统修复软件 电脑的使用频率很高&#xff0c;不可避免的会遇到丢失dll文件的情况。因此&#xff0c;dll修复工具也是能够解决的dll文件缺失的方法之一。那么如何使用dll修复工具解决缺失dll文件问题呢&#xff1f;本文将为您详细介绍电…

基于单片机的一氧化碳报警系统的设计与实现

摘 要&#xff1a; 一氧化碳对人体有害&#xff0c;尤其超标时会影响人们的健康 。 因此文章设计了一款基于单片机的一氧化氮报警器设计。 论文通过传感器检测一氧化碳浓度&#xff0c;经过 AD 转换&#xff0c;再把检测信号传递给单片机&#xff0c;经过分析处理&#xff0c…

graphRAG原理解析——基于微软graphRAG+Neo4j llm-graph-builder

知识图谱生成 llm-graph-builder&#xff08;以下简称 LGB&#xff09;也使用了最新的 graph RAG 的思路&#xff0c;使用知识图谱来加持RAG&#xff0c;提供更加准确和丰富的知识问答。知识图谱的生成上&#xff0c;利用大模型的泛化能力来自动生成和构建知识图谱&#xff0…

企事业单位数据资料防外泄如何实现?这5个小技巧等你来掌握!

企事业单位的数据资料防外泄是一项重要的任务&#xff0c;它关乎企业的核心竞争力和信息安全。 以下是五个实用的小技巧&#xff0c;可以帮助企事业单位有效地防止数据外泄&#xff1a; 1. 数据加密 技巧说明&#xff1a;通过对敏感数据进行加密处理&#xff0c;即使数据被非…

Python的10个构建桌面应用的技巧

Python以其简洁的语法和强大的库支持&#xff0c;成为开发桌面应用的热门选择。无论是创建简单的工具还是复杂的图形界面程序&#xff0c;Python都能大显身手。下面&#xff0c;我们将通过10个实用技巧&#xff0c;逐步引导你进入Python桌面应用开发的世界。 如果你对Python感兴…