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

效果图如下:

目前只做了绘制部分,绘制方式也比较简单,点击工具栏中需要绘制的图形,在画布上左键点击将会绘制一个图形出来,工具栏选中第一个,再点击其他图像,长按鼠标左键可以移动,删除使用键盘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:
               
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值