konva vue3 实现工程进度图

1,123 阅读8分钟

konva vue3 实现工程进度图

项目背景

项目全称是《施工管理计划图》,用来记录当前施工的进度,到达某一个阶段,在某一个阶段所用的人力以及物力的相关数据,对过去现在未来的工程做好时间规划,能够全局预览整体的项目进度。目前第一版本已经上线使用,开发仓促,有很多不足,涉及到公司项目,并不能发源码,还望理解。

已完成的功能

  1. 虚拟渲染(因为时间跨度长,不能将所有的线都展示出来,否则卡);
  2. 拖动画线(一线两点的连接方式);
  3. 改变线的结构(在已经绘制好的线段下拖动线,改变线的形状);
  4. 放大缩小(目前不支持绘制时候放大缩小);
  5. 拖动画板;
  6. 导出图片;
  7. 事件点击、悬浮等交互;
  8. 自定义线,点的样式,背景;
  9. 支持搜索,定位;

不支持的功能: 删除点后以及更新点后的状态逻辑使后台进行运算的,页面只负责渲染;

项目成品图

  • 组件目录 image.png

  • 整体样式 微信截图_20250115164810.png

  • 悬浮和右键 微信截图_20250115164853.png

  • 绘制时候1 微信截图_20250115164940.png

  • 绘制时候2 微信截图_20250115165112.png

  • 特殊线 image.png

项目所用插件及参考文档

  • vue 3*
  • konva 9.3.6
  • pinia 2.1.7
  • dayjs 1.11.10
  • merge-images 2.0.0

中文网konvajs:konvajs-doc.bluehymn.com/docs/index.…

官网:konvajs.org

git: github.com/konvajs/kon…

在这个项目开发中为了让konva实例数据共享,使用 pinia 做组件级别的共享数据,结合vue3里面api hooks等。

在初始阶段,我将整个项目划分成一个组件,数据由组件外部透入,并将这个组件定义为 预览编辑模式,因为编辑模式时候使用的距离(x,y)是要实际大小的,所以就没有做缩放下能支持编辑,所有的线都是有两个端点的,一线两端点。

konva 是 支持层的概念 ,所以将组件划分为三层:

  1. 第一层是底层那些格格数据,第一次绘制时候没有做虚拟加载只是将其静态化,发现数据量大时候也是有卡顿的,(静态化:konva cache 一种缓存api,将其转化成图片,结合场景使用 ),后来改成部分数据静态化,部分按照拖动方式进行虚拟加载;
  2. 第二层是线和点的数据,就是界面上那些流程;
  3. 第三层是动态数据,就是拖动,绘制时候常用数据;因为拖完后要删掉,使用频率高,用完清空,所以放在最上层;

这里的虚拟加载思路其实很简单,在进入到页面时候,获取画板容器的宽高,存起来,然后在绘制过程中,比较第一层的数据是否在这个宽高范围内。当然为了让系统看起来流畅,实际比较范围 我是将宽高乘以了3 。这样在拖动时候就增加了用户体验,最后监听容器的大小,发生变化,loading... 重新加载。

image.png

第二层同理,区别是第二层要将时间转化成x数据

第三层要实时同步位置信息等

重点功能介绍

  1. 项目数据结构(组件外部透入)
{
  "dataLeftHead": [  
    {
      "endDate": "2025-12-05",    // 整体项目的时间范围
      "startDate": "2024-12-05",  // 整体项目的时间范围
      "blockId": "1",             // 子体id
      "blockPId": "1",            // 主体id
      "name": "未命名重大节点1",   // 子体name
      "nameP": "未命名主体",       // 主体name
      "height": 500,              // 子体绘制高度
      "blockSort": 0              // 子体所在位置
    }
  ],
  "dataNode": [
    {
      "blockId": "1",
      "blockPId": "1",
      "circleId": "r1",
      "dayData": "2024-12-09",         // 端点的时间 (X轴位置,后面是时间转化成x 轴)
      "blockSort": 0,
      "pointSourceIds": [              // 出 点线的id  
        "line1"
      ],
      "pointTargetIds": [              // 入 点线的id  
        
      ],
      "y": 132,                       // 端点的y轴 (y轴位置),小于 子体绘制高度
      "text": "01"                    // 端点名称
    },
    {
      "blockId": "1",
      "blockPId": "1",
      "circleId": "r2",
      "dayData": "2025-11-24",
      "blockSort": 0,
      "pointSourceIds": [
        
      ],
      "pointTargetIds": [
        "line1"
      ],
      "y": 242,
      "text": "02"
    }
  ],
  "dataLine": [
    {
      "blockId": "1",
      "blockPId": "1",
      "endDate": "2024-12-12",              // 线的起始时间 (转化成 x )
      "lineId": "line1",                    // 线的id
      "lineName": "工序1",                  // 线的名称
      "lineType": 1,                        // 线的类型 默认1
      "startDate": "2024-12-09",            // 线的起始时间 (转化成 x )
      "points": [                           // 相对点的偏移量
        0,
        0
      ]
    }
  ]
}
  1. init 初始化阶段 数据改变并没有用watch 监听,而是像父级透了个init方法,在此方法中 对数据格式的判断以及处理,将基础数据储存到pinia中,在pinia中,我创建了三个持久化,一个是用来处理储存数据的 drawData.ts,一个是用来放置konva实例的 drawBoard.ts ,一个是常用的实例相关方法 drawContent.ts

在drawData.ts中,定义了如下数据


export const drawData = defineStore({
  id: `${baseParam.systemDefaultPrefix}_${baseParam.projectDefaultPrefix}_DRAW_DATA`,
  state: (): IDrawDataStore => {
    return {
      // 页面所需
      pageMode: EnumPageType.VIEW, // 页面模式
      pageViewMode: EnumViewPageType.DEFAULT, // 预览时候模式
      isExportImages: false, // 是否导出图片

      nodeViewValue: [], // 过滤条件
      mainViewValue: [], // 过滤条件

      // 基础数据
      baseData: {
        startDate: '',
        endDate: '',
        id: '',
        name: '',
        projectCode: '',
        status: '',
        projectMajorList: [],
      },

      dataLeftHead: [], // 左侧头部数据 list结构
      dataLeftTreeHead: [], // 左侧头部数据 tree结构
      dataLeftHeightHead: [], // 左侧头部数据 list结构

      dataTopHeadTree: [], // 顶部头部数据
      dataTopHeadList: [], // 顶部头部数据

      dataNodeMap: {},  // 点的数据 map 结构
      dataLineMap: {},  // 线的数据 map 结构
      dataNodeList: [],  // 点的数据 list结构
      dataLineList: [],  // 线的数据 list结构

      // 页面数据
      maxHeightPage: 0,
      maxWidthPage: 0, // 画板的大小
      widthPage: 0,
      heightPage: 0, // 页面的宽高

      //其它数据
      isMouseDownLine: false, // 是否鼠标按下了
      isCtrlKey: false, // 是否鼠标按下了
    }
  },
  actions: {
    // 数据处理
    dataProcessing(data: IApiData) {
      // 这里将接口返回的数据处理成想要的格式
    }
  }
})

在drawBoard.ts中,定义了如下数据,这里放的是全局公用的konva实例

export const drawBoard = defineStore({
    id: `${baseParam.systemDefaultPrefix}_${baseParam.projectDefaultPrefix}_DRAW_BOARD`,
    state: ():IDrawBoard => {
        return {
            // 页面所需
            stage: null,    // 舞台

            layerBg:null,   // 背景层
            gpBgContent:null, // 背景层的背景组
            gpBgContentIn:null, // 背景层的背景

            layerHead:null, // 头部层
            gpHeadTitleContent:null, // 头部层 标题
            gpHeadTitleText:null, // 头部层 标题
            gpHeadTopContent:null, // 头部层 上面head
            gpHeadTopContentIn:null, // 头部层 上面head
            gpHeadLeftContent:null, // 头部层 左面head
            gpHeadLeftContentIn:null, // 头部层 左面head
            gpHeadAngularContent:null, // 头部层 角

            layerFlow:null, // 流程层
            gpFlowContent:null, // 流程层 组
            gpFlowContentIn:null, // 流程层 组
            gpFlowBlockMap:{}, // 流程层 组 map
            gpFlowBaseBlockMap:{}, // 流程层 组 map
            gpFlowInBlockMap:{}, // 流程层 组 操作 map

            layerDraw:null, // 绘制层

            isLoadIng:false,   // 刷新
            loadIngText:'加载中···',

            scale:1,// 大小缩放
        }
   }
})

3.基础服务好了以后开始搭建舞台,创建舞台hooks ,在hooks里面写,主要是为了后期好管理代码,方便查看 这里的dom就是容器,hooksBackMethod是我自定义hooks的回调方法, keepAdd和keepSubtract是加减法。

// 创建舞台
const createStage = (domBox: any) => {
  let dom = domBox?.dom
  let w = dom?.offsetWidth
  let h = dom?.offsetHeight
  drawDataStore.widthPage = w
  drawDataStore.heightPage = h
  drawBoardStore.stage = new Konva.Stage({
    x: default_size.stage_padding,
    y: default_size.stage_padding,
    container: dom,
    width: w,
    height: h,
    listening: true,
    draggable: false,
  })
  // 关闭默认右键 阻止右键菜单
  drawBoardStore.stage.on('contextmenu', (e: any) => {
    e.evt.preventDefault()
    return false
  })
}
// 设置拖动
const setStageDraggable = (flage: boolean, domBox: any) => {
  let dom = domBox?.dom
  drawBoardStore.stage && drawBoardStore.stage.draggable(flage)
  if (flage) {
    drawBoardStore.stage?.on('dragend', () => {
      console.log('dragend:stage')
      dom.style.cursor = 'grab'

      hooksBackMethod('dragend')
    })
  }
}
// 设置放大缩小
const setMouseScale = (domBox: any) => {
  drawBoardStore.stage?.on('wheel', (e: any) => {
    console.log('wheel:stage')
    e.evt.preventDefault()
    let oldScale = <number>drawBoardStore.stage?.scaleX()
    let pointer: any = drawBoardStore.stage?.getPointerPosition()
    // 初始数据
    let mousePointTo = {
      // @ts-ignore
      x: (pointer.x - drawBoardStore.stage?.x()) / oldScale,
      // @ts-ignore
      y: (pointer.y - drawBoardStore.stage?.y()) / oldScale,
    }
    // 滚动
    if (e.evt.wheelDelta) {
      if (e.evt.wheelDelta > 0) {
        if (drawBoardStore.scale >= scaleInfo.max) {
          return
        }
        drawBoardStore.scale = keepAdd(drawBoardStore.scale, scaleInfo.step)
        let newScale = oldScale + scaleInfo.step
        drawBoardStore.stage?.scale({ x: newScale, y: newScale })
        let newPos = {
          x: pointer.x - mousePointTo.x * newScale,
          y: pointer.y - mousePointTo.y * newScale,
        }
        drawBoardStore.stage?.position(newPos)
      } else {
        if (drawBoardStore.scale <= scaleInfo.min) {
          return
        }
        drawBoardStore.scale = keepSubtract(drawBoardStore.scale, scaleInfo.step)
        let newScale = oldScale - scaleInfo.step
        drawBoardStore.stage?.scale({ x: newScale, y: newScale })
        let newPos = {
          x: pointer.x - mousePointTo.x * newScale,
          y: pointer.y - mousePointTo.y * newScale,
        }
        drawBoardStore.stage?.position(newPos)
      }
    }
    // 结束
    wheelTimeout.value && clearTimeout(wheelTimeout.value)
    wheelTimeout.value = setTimeout(() => {
      // 缩小后重新绘制 x
      hooksBackMethod('dragend')
      wheelTimeout.value = null
    }, 300)
  })
}
// 设置点击事件效果
const setEventStageDraggable = (domBox: any) => {
  let dom = domBox?.dom
  drawBoardStore.stage?.on('mousedown', (e: any) => {
    console.log('mousedown:stage')
    if (drawDataStore.pageMode !== EnumPageType.READONLY) {
      drawContentStore.delAllSelect()
    }
    // 左键
    if (e.evt.button === 0) {
      dom.style.cursor = 'grabbing'
    } else if (e.evt.button === 2) {
      dom.style.cursor = 'pointer'
    }
  })
  drawBoardStore.stage?.on('mouseup', (e: any) => {
    console.log('mouseup:stage')
    if (drawDataStore.pageMode === EnumPageType.EDIT) {
      let info = e.target?.getAttrs()
      if (info?.name == nameVariable.gpFlowBlockRect) {
        // 自定义光标
        dom.style.cursor = `url(${zhizhen}) 8 8,cell`
      } else {
        dom.style.cursor = 'grab'
      }
    } else {
      dom.style.cursor = 'grab'
    }
  })
}
// 清除舞台
const clearStage = () => {
  drawBoardStore.stage && drawBoardStore.stage.destroy()
  drawBoardStore.stage = null
}

4.创建背景,这里就用了个最简单的方式做了个虚拟化处理,通过页面宽度,和窗口的大小比较来渲染,所以数据一定要做好全局,对数据操作时候处理也要妥当。 lineNotListen1()和lineNotListen2()和rectNotListen1()都是绘制图形的方法,

// 设置背景
const setLayerBgContent = (minData: any, maxData: any) => {
  let isExportImages = drawDataStore.isExportImages
  // 清理
  drawBoardStore.gpBgContentIn?.removeChildren()
  // 计算
  let boxHeight = drawDataStore.maxHeightPage - calc_size.title_angular_h
  // 计算
  let boxWidth = drawDataStore.maxWidthPage - calc_size.angular_ww
  let lW = 0
  let num = 0
  for (let i = 0; i < drawDataStore.dataTopHeadTree.length; i++) {
    let item = drawDataStore.dataTopHeadTree[i]
    let isFatherLast = drawDataStore.dataTopHeadTree.length - 1 === i
    for (let j = 0; j < item.children.length; j++) {
      let isLast = item.children.length - 1 === j
      lW = lW + default_size.top_head_w
      let obj = item.children[j]
      // 不是最后一根线
      if (!(isLast && isFatherLast)) {
        // 只渲染显示部分
        if ((lW >= minData.x && maxData.x > lW) || isExportImages) {
          // 显示背景
          if (obj.isWeekend) {
            let lineFill = rectNotListen1({
              height: boxHeight,
              x: lW,
              y: 1,
            })
            drawBoardStore.gpBgContentIn?.add(lineFill)
          }
          let lineXian = lineNotListen1({
            stroke: obj.isToday ? default_color.layerBgGpStrokeToday : default_color.layerBgGpStroke,
            dashEnabled: obj.isToday ? true : !isLast,
            strokeWidth: obj.isToday ? 2 : 1,
            points: [lW, 0, lW, boxHeight],
          })
          drawBoardStore.gpBgContentIn?.add(lineXian)
        }
        // 不循环多余的
        if (maxData.x < lW && !isExportImages) {
          break
        }
      }
      num = num + 1
    }
  }
  // 线
  let lineBox = lineNotListen2([boxWidth, 0, boxWidth, boxHeight, 0, boxHeight])
  drawBoardStore.gpBgContentIn?.add(lineBox)
  // 优化到缓存
  // drawBoardStore.gpBgContentIn?.cache()
}
// 创建矩形1 传高度
export const rectNotListen1 = (data:any) =>{
    return new Konva.Rect({
        width: default_size.top_head_w,
        fill:default_color.layerBgGpFill,
        listening: false,
        ...LowConfig,
        ...data
    })
}

// 创建线 body
export const lineNotListen1 = (data: any) => {
  return new Konva.Line({
    stroke: default_color.layerBgGpStroke,
    strokeWidth: 1,
    closed: false,
    dash: [5, 2],
    listening: false,
    ...LowConfig,
    ...data,
  })
}

  1. 自定义线 ,线比较特殊时候要用到,没办法只能手写样式了,hitFunc可以理解为一条线隐藏的能操作的范围,有个直观的方法能直接看到这个范围 drawBoardStore.layerFlow?.toggleHitCanvas() ,sceneFunc是实际展示的, 比如下面的方法以及使用例子:
// 生成波浪线
export const getWaveLineBase = (context: any, shape: any, data: any) => {
  let sXP = data[0][0]
  let sYP = data[0][1]
  let eXP = data[1][0]
  let eYP = data[1][1]

  let zf = 3
  let bd = 5
  context.beginPath()
  context.moveTo(sXP, sYP)
  let waveWidth = Math.abs(eXP - sXP)
  let waveOne = Math.trunc(waveWidth / bd)
  let j = waveWidth / waveOne
  let swave = sXP
  for (let i = 0; i < waveOne; i++) {
    swave = swave + j
    let sy = i % 2 === 1 ? eYP + zf : eYP - zf
    if (i == waveOne - 1) {
      context.lineTo(eXP, eYP)
    } else {
      context.lineTo(swave, sy)
    }
  }
  context.lineWidth = shape.getAttr('strokeWidth')
  context.strokeStyle = shape.getAttr('strokeWave')
  context.stroke()
}
// 文字 底部时间
export const getTextBaseBomTime = (context: any, data: any) => {
  if (data.text) {
    context.font = '400 10px'
    context.fillStyle = default_color.textInfo
    context.fillText(data.text, data.x, data.y + 11)
  }
}
// 文字 上面的工序
export const getTextBaseTopInfo = (context: any, data: any) => {
  let { text, x, y, maxWidth, height } = data
  context.font = '400 10px custom-RobotoRegular'
  let lineHeight = 11
  context.textAlign = 'center'
  let metrics = context.measureText(text, context.font)
  // 计算文本宽度
  let textWidth = metrics.actualBoundingBoxRight + metrics.actualBoundingBoxLeft
  // 宽度大于
  if (textWidth > maxWidth) {
    // 字符分隔为数组
    let arrText = text.split('')
    let line = ''
    let newY = y - height + 7

    for (let n = 0; n < arrText.length; n++) {
      let testLine = line
      // 换行符
      if (arrText[n] === '\n') {
        context.fillText(line, x, newY)
        line = ''
        newY += lineHeight
      } else {
        testLine = line + arrText[n]
        let metricsIn = context.measureText(testLine, context.font)
        // 计算文本宽度
        let textWidthIn = metricsIn.actualBoundingBoxRight + metricsIn.actualBoundingBoxLeft

        if (textWidthIn > maxWidth && n > 0) {
          context.fillText(line, x, newY)
          line = arrText[n]
          newY += lineHeight
        } else {
          line = testLine
        }
      }
    }
    context.fillText(line, x, newY)
  } else {
    let defaultY = y - 4
    context.fillText(text, x, defaultY)
  }
}
// 生成三角箭头
export const getTriangleArrowBase = (context: any, shape: any, data: any) => {
  let { sXP, sYP, eXP, eYP, lineColor } = data
  let arrowSize = 6
  context.beginPath()
  context.moveTo(eXP, eYP)
  let angle = Math.atan2(eYP - sYP, eXP - sXP)
  context.lineWidth = shape.getAttr('strokeWidth')
  if (lineColor) {
    context.strokeStyle = shape.getAttr('stroke')
    context.fillStyle = shape.getAttr('fill')
  } else {
    context.strokeStyle = shape.getAttr('strokeWave')
    context.fillStyle = shape.getAttr('fillWave')
  }
  context.lineTo(eXP - arrowSize * Math.cos(angle - Math.PI / 6), eYP - arrowSize * Math.sin(angle - Math.PI / 6))
  context.lineTo(eXP - arrowSize * Math.cos(angle + Math.PI / 6), eYP - arrowSize * Math.sin(angle + Math.PI / 6))
  context.fill()
  context.stroke()
}


// 使用

// 自定义线
return new Konva.Shape({
  name: nameVariable.gpLine,
  stroke: default_color.lineStroke,
  fill: default_color.lineStroke,
  strokeProgress: default_color.lineProgressStroke,
  strokeWave: default_color.lineWaveStroke,
  fillWave: default_color.lineWaveStroke,
  strokeWidth: 1,
  hitStrokeWidth: calc_size.circle_d,
  dash: [5, 2],
  dashEnabled: false,
  points,
  ...dataLine,
  sceneFunc(context: any, shape: any) {
    // 时间
    getTextBaseBomTime(context, {
      x: xText,
      y: newPoints[1],
      text: timeText,
    })
    // 详情
    getTextBaseTopInfo(context, {
      text: dataLine.lineName,
      x: x,
      y: newPoints[1],
      maxWidth: maxTextWidth,
      width,
      height,
    })
    // 线
    getWaveLineBase(context, shape, [
      [newPoints[0], newPoints[1]],
      [newPoints[2], newPoints[3]],
    ])
    getLineBase(context, shape, [
      [newPoints[2], newPoints[3]],
      [newPoints[4], newPoints[5]],
    ])
    getTriangleArrowBase(context, shape, {
      sXP: newPoints[4],
      sYP: newPoints[5],
      eXP: newPoints[4],
      eYP: newPoints[5],
      lineColor: true,
    })

    // 进度线
    if (dataLine.progress) {
      getLineBaseProgress(context, shape, progressPoints)
    }
  },
  hitFunc(context: any, shape: any) {
     ...
  },
})


6.绘制线时候的操作,代码很多,有一些常量,多关注事件处理的逻辑吧,就是线注册鼠标按下事件mousedown后,然后在监听鼠标拖动事件和抬起事件,实时获取当前鼠标位置,更新、销毁


// 绘制线
const drawLinesHandle = (data: any, viewFn?: any, dialogFn?: any) => {
  // 为每一个组注册事件
  for (const gpFlowBlockMapKey in drawBoardStore.gpFlowBlockMap) {
    let gp = drawBoardStore.gpFlowBlockMap[gpFlowBlockMapKey]
    let gpIn = drawBoardStore.gpFlowInBlockMap[gpFlowBlockMapKey]
    if (gp && gpIn) {
      eventLineArrow(<Group>gp, <Group>gpIn)
    }
  }

  // 监听绘制
  function eventLineArrow(gp: Group, gpIn: Group) {
    gp.on('mousedown', function (e: any) {
      console.log('mousedown:gpFlowBlockMap')
      let info = gp.getAttrs()
      // 回显 点的哪里
      viewFn &&
        viewFn({
          blockName: info?.blockName,
          blockPName: info?.blockPName,
          blockId: info?.blockId,
        })
      // 先清理
      // engineerDrawBoardStore.layerDraw.destroyChildren()
      // engineerDrawBoardStore.layerDraw?.hide()

      // 右键绘制
      if (e.evt.button === 2) {
        e.cancelBubble = true
        if (e.target.getName() === nameVariable.gpFlowBlockDisableRect) {
          return
        }
        drawLinesRightEvent(
          {
            gp,
            gpIn,
            dom: data?.dom,
            msgDom: data?.msgDom,
          },
          dialogFn
        )
      }
    })
  }
}
// 绘制线事件
const drawLinesRightEvent = (data: any, dialogFn?: Function) => {
  let gp = data.gp
  let dom = data.dom
  let gpIn = data.gpIn
  let msgDom = data.msgDom
  const { x: xnode, y: ynode } = gp.getRelativePointerPosition()
  const { x: xAbsolute, y: yAbsolute } = gp.absolutePosition()
  const { height } = gp.size()
  const { blockId, blockPId, blockName, blockPName, blockSort, startDate: blockStartDate, endDate: blockEndDate } = gp.getAttrs()
  // 跟据时间取得宽度
  let minBlockX = getWidthByTimeList(drawDataStore.dataTopHeadList, blockStartDate, true)
  let maxBlockX = getWidthByTimeList(drawDataStore.dataTopHeadList, blockEndDate)
  // 是否翻转
  let reversal = false
  // 是否绘制
  let isDraw = false
  // 起始点做标
  const startPoint = { x: xnode, y: ynode }
  // 对xy轴数据处理,找近似值
  startPoint.x = getNumberCenWidth(startPoint.x)
  startPoint.y = getNumberCenHeight(startPoint.y)
  // 高度的容差
  if (startPoint.y < calc_size.circle_h) {
    startPoint.y = calc_size.circle_h
  }
  let remainder = height % calc_size.circle_h
  let maxH = height - remainder - calc_size.circle_h
  if (startPoint.y > maxH) {
    startPoint.y = maxH
  }
  // 宽度的容差
  if (startPoint.x < minBlockX) {
    startPoint.x = minBlockX
  }
  if (startPoint.x > maxBlockX) {
    startPoint.x = maxBlockX
  }

  // 开始显示
  drawBoardStore.layerDraw?.show()
  // 设置绘制中心组位置
  drawBoardStore.layerDraw?.absolutePosition({ x: xAbsolute, y: yAbsolute })
  // 绘制边界显示
  // let boundaryLine = gp.findOne(`.${nameVariable.gpFlowBlockRect}`)
  // boundaryLine&&boundaryLine.opacity(1)

  // 是不是起始点圆,终点圆
  let isCircleEnd = false
  let circleEnd: any = null
  // 获取 存在点的同一个坐标
  let circleStart = gpIn.findOne(function (node: any) {
    return node.x() == startPoint.x && node.y() == startPoint.y
  })
  let isCircleStart = !!circleStart

  // 绘制时间线
  let timeLineStart = lineListen6({ visible: true, points: [startPoint.x, -yAbsolute, startPoint.x, height] })
  drawBoardStore.layerDraw?.add(timeLineStart)
  let timeLineEnd = lineListen6()
  drawBoardStore.layerDraw?.add(timeLineEnd)
  // 绘制线
  let line = lineArrowListen5(startPoint)
  drawBoardStore.layerDraw?.add(line)
  // 绘制 左侧圆点
  let circleLeft = circleListen1(startPoint)
  drawBoardStore.layerDraw?.add(circleLeft)
  // 绘制 右侧圆点
  let circleRight = circleListen1(startPoint, { visible: true })
  drawBoardStore.layerDraw?.add(circleRight)
  // 绘制 中间时间段
  let textCen = textNotListen4(startPoint)
  drawBoardStore.layerDraw?.add(textCen)

  // 获取时间 绘制时间
  let endDate: any = null
  let startDate = getTimeByWidthList(drawDataStore.dataTopHeadList, startPoint.x)
  // 鼠标移动事件
  gp.on('mousemove', (e: any) => {
    let name = e.target.getName()
    if (name === nameVariable.gpFlowBlockDisableRect) {
      dom.style.cursor = 'not-allowed'
    } else {
      dom.style.cursor = 'pointer'
    }
    // 坐标
    const { x: xnodeIn, y: ynodeIn } = gp.getRelativePointerPosition()
    const endPoint = { x: xnodeIn, y: ynodeIn }
    // 对xy轴数据处理,找近似值
    endPoint.x = getNumberCenWidth(endPoint.x)
    endPoint.y = getNumberCenHeight(endPoint.y)
    // 高度容差
    if (endPoint.y < calc_size.circle_h) {
      endPoint.y = calc_size.circle_h
    }
    if (endPoint.y > maxH) {
      endPoint.y = maxH
    }
    // 宽度的容差
    if (endPoint.x < minBlockX) {
      endPoint.x = minBlockX
    }
    if (endPoint.x > maxBlockX) {
      endPoint.x = maxBlockX
    }

    // 获取 存在点的同一个坐标
    circleEnd = gpIn.findOne(function (node: any) {
      return node.x() == endPoint.x && node.y() == endPoint.y
    })
    isCircleEnd = !!circleEnd

    // 是否移动
    let blockDraw = Math.abs(startPoint.x - endPoint.x) >= calc_size.circle_f_d || Math.abs(startPoint.y - endPoint.y) >= calc_size.circle_f_d
    isDraw = startPoint.x != endPoint.x && blockDraw
    // 线显示时机
    if (blockDraw) {
      line.show()
    } else {
      line.hide()
    }
    circleLeft.show()

    // 计算 开始--------------------
    // 动态圆
    circleRight.position({
      x: endPoint.x,
      y: endPoint.y,
    })
    // 设置线
    timeLineEnd.show()
    timeLineEnd.points([endPoint.x, -yAbsolute, endPoint.x, height])
    // 获取更新后的xy坐标
    let { points: newLinePoint, reversal: reversalPoint } = getDrawPoints(startPoint, endPoint, gpIn, {
      circleEnd,
      isCircleEnd,
      isCircleStart,
      circleStart,
    })
    reversal = reversalPoint
    line.points(newLinePoint)
    // 绘制时间
    endDate = getTimeByWidthList(drawDataStore.dataTopHeadList, endPoint.x)
    let textDay = getDayByTime(startDate, endDate)
    let { x: tx, y: ty } = getTimePositionByPoints(newLinePoint)
    textCen.position({ x: tx, y: ty })
    textCen.text(textDay == 0 ? '' : textDay.toString())
    textCen.show()

    let textS = dayjs(reversal ? endDate : startDate)
      .add(1, 'day')
      .format('YYYY-MM-DD')
    let textE = reversal ? startDate : endDate
    let bjSE = dayjs(textS).diff(dayjs(textE), 'day') > 0 ? textS : textE
    // 显示弹框
    msgDom?.switchHandle(true, {
      s: textS,
      e: bjSE,
      d: textDay,
      event: e.evt,
    })
  })
  // 鼠标松开事件
  document.addEventListener('mouseup', mouseupHandle)

  // 鼠标抬起时候清除并发送数据
  function mouseupHandle() {
    // 有效数据
    if (isDraw) {
      let isDialog = true
      // 获取坐标
      let points = line.points()
      let lineList = gpIn.find((item: any) => {
        return item.getAttr('name') === nameVariable.gpLine
      })
      if (isCircleStart && isCircleEnd) {
        isDialog = true
      } else {
        // 判断有没有重叠
        for (let i = 0; i < lineList.length; i++) {
          let item = lineList[i]
          if (item) {
            if (doTwoLinesOverlap(item.getAttr('points'), points)) {
              isDialog = false
              break
            }
          }
        }
      }
      // 无重叠弹框
      if (isDialog) {
        let circleStartPoints = reversal ? circleRight.position() : circleLeft.position()
        let circleEndPoints = reversal ? circleLeft.position() : circleRight.position()
        let pointsNums = (drawDataStore.dataLineList?.length || 0) + 1
        let circleStartOld = circleStart
        let circleEndOld = circleEnd
        let isCircleStartOld = isCircleStart
        let isCircleEndOld = isCircleEnd
        let startDateResult = startDate
        let endDateResult = endDate
        if (reversal) {
          circleStartOld = circleEnd
          circleEndOld = circleStart
          isCircleStartOld = isCircleEnd
          isCircleEndOld = isCircleStart
          startDateResult = endDate
          endDateResult = startDate
        }
        let previousIds: any = []
        let parallelIds: any = []
        let nextIds: any = []
        let startId = ''
        let endId = ''
        // 获取id
        if (isCircleStartOld && circleStartOld) {
          previousIds = circleStartOld.getAttr('pointTargetIds')
          startId = circleStartOld.getAttr('circleId')
        }
        if (isCircleEndOld && circleEndOld) {
          nextIds = circleEndOld.getAttr('pointSourceIds')
          parallelIds = circleEndOld.getAttr('pointTargetIds')
          endId = circleEndOld.getAttr('circleId')
        }
        dialogFn &&
          dialogFn({
            blockId,
            blockPId,
            blockName,
            blockPName,
            blockSort,
            previousIds,
            parallelIds,
            nextIds,
            circleStartPoints,
            circleEndPoints,
            startId,
            endId,
            points,
            pointsNums,

            isCircleStart: isCircleStartOld,
            isCircleEnd: isCircleEndOld,
            startDate: dayjs(startDateResult).add(1, 'day').format('YYYY-MM-DD'),
            endDate: endDateResult,
          })
      }
    }
    msgDom?.switchHandle(false)
    dom.style.cursor = `url(${zhizhen}) 8 8,cell`
    // boundaryLine&&boundaryLine.opacity(0)
    circleLeft && circleLeft.remove()
    circleRight && circleRight.remove()
    textCen && textCen.remove()
    timeLineStart && timeLineStart.remove()
    timeLineEnd && timeLineEnd.remove()
    line && line.remove()
    clearLayerDraw()
    gp.off('mousemove')
    document.removeEventListener('mouseup', mouseupHandle)
  }
}

常量方便后面改动

image.png

7.导出图片

// 导出到图片
export function useImageExportHooks() {
    const drawDataStore = drawData()
    const drawBoardStore = drawBoard()
    const pixelRatio = 1

    // 导出
    const imageExport = async (time:any) => {
        try {
            if(drawBoardStore.stage){
                let w = 0
                let xHead = 0
                let x = 0
                let name =`img_${drawDataStore.baseData?.startDate}_${drawDataStore.baseData?.endDate}.png`
                // 查看导出
                if(drawDataStore.pageMode===EnumPageType.VIEW||drawDataStore.pageMode===EnumPageType.OVERVIEW){
                    if(!time||time?.length!=2){
                        msgError('请选择时间!')
                        return false
                    }
                    let day = dayjs(time[1]).add(1,'day').diff(time[0],'day')
                    x = getWidthByTimeList(drawDataStore.dataTopHeadList,time[0],true) - default_size.top_head_w
                    w = day * default_size.top_head_w
                    xHead = calc_size.angular_ww + default_size.top_head_w
                    // 头截图
                    let img1: any = await toImg1({
                        w:xHead,
                        x:0
                    })
                    // 时间截图
                    let img2: any = await toImg1({
                        w:w,
                        x:x+xHead
                    })
                    return new Promise(resolve => {
                        // 合并
                        mergeImages([
                            {
                                src:img1,
                                x: 0,
                                y: 0,
                            },
                            {
                                src: img2,
                                x: xHead*pixelRatio,
                                y: 0,
                            }
                        ],{
                            width:(w+xHead)*pixelRatio
                        }).then((data:any)=>{
                            // 下载
                            downloadURI(data, name);
                            resolve(true)
                        })
                    }).catch(()=>false)
                }
                // 编辑导出
                else{
                    return new Promise(resolve => {
                        setTimeout(async () => {
                            let img: any = await toImg1()
                            // 下载
                            downloadURI(img, name);
                            resolve(true)
                        }, 500)
                    }).catch(()=>false)
                }
            }else {
                msgError('导出失败!')
                return false
            }
        }catch (e) {
            console.log(e)
            msgError('导出失败!')
            return false
        }
    }

    // 导出方式1
    const toImg1 = (data?:any) => {
        return new Promise(resolve => {
            if(drawDataStore.pageMode===EnumPageType.EDIT){
                drawBoardStore.stage?.toImage({
                    pixelRatio: pixelRatio,
                    width:drawDataStore.widthPage,
                    height:drawDataStore.heightPage,
                    callback:(dataURL:any)=>{
                        resolve(dataURL.src)
                    }
                });
            }else if(drawDataStore.pageMode===EnumPageType.VIEW){
                drawBoardStore.stage?.toImage({
                    pixelRatio: pixelRatio,
                    x:data?.x?data?.x:0,
                    width:data?.w?data.w:drawDataStore.maxWidthPage,
                    height:drawDataStore.maxHeightPage,
                    callback:(dataURL:any)=>{
                        resolve(dataURL.src)
                    }
                });
            }else if(drawDataStore.pageMode===EnumPageType.OVERVIEW){
                drawBoardStore.stage?.toImage({
                    pixelRatio: pixelRatio,
                    x:data?.x?data?.x:0,
                    width:data?.w?data.w:drawDataStore.maxWidthPage,
                    height:drawDataStore.maxHeightPage,
                    callback:(dataURL:any)=>{
                        resolve(dataURL.src)
                    }
                });
            }
        })
    }

    return{
        imageExport
    }
}

7.常用方法

/**
 @文件名: contants
 @来源:YongPing.Wang|2024/05/09 14:36
 @描述:
 **/
import { calc_size, default_size, nameVariable } from '@/components/engineerDraw/variables.ts'
import type {
    IApiDataProjectMajor,
    IDataLeftHeightHead,
    IDateDayList,
    IDateDayTree,
    IDateDayTreeItem,
    IDrawDataLeftHead,
    IDrawDataLine,
    IDrawDataLineMap,
    IDrawDataNode,
    IDrawDataNodeMap,
} from '@/components/engineerDraw/interfaceData.ts'
import { closestMultiple, getRangeDays, isWeekendDay } from '@/components/engineerDraw/tools.ts'
import dayjs from 'dayjs'
import isToday from 'dayjs/plugin/isToday'
import { numberSelfIncreasing } from '@/units/jsUnits.ts'

dayjs.extend(isToday)

// 获取组最小高度
export const getGroupHeight = (h:number):number => {
    return h?(h<100?default_size.left_head_h:h):default_size.left_head_h
}
// 处理组的数据高度
export const getGroupAllHeightByData = (data:IApiDataProjectMajor[],drawDataLeftHead:IDrawDataLeftHead[]): {listHeightData:IDataLeftHeightHead[],listData:IDrawDataLeftHead[],treeData:IApiDataProjectMajor[] } => {
    let blockSort = 0
    let listArr:IDrawDataLeftHead[] = []
    let listHeightArr:IDataLeftHeightHead[] = []
    let y = 0
    for (let i = 0; i < data.length; i++) {
      let item = data[i]
      if (item.children) {
        item.children.forEach((obj: any) => {
          let hData = (drawDataLeftHead || []).find((info: any) => {
            return info.blockPId === obj.parentId && info.blockId === obj.id
          })
          // 有高度
          obj.height = getGroupHeight(hData?.height || 0)
          // 新数据
          listHeightArr.push({
            blockId: obj.id,
            y: y,
            height: obj.height,
          })
          listArr.push({
            endDate: obj.endDate,
            startDate: obj.startDate,
            blockId: obj.id,
            blockPId: obj.parentId,
            name: obj.name,
            nameP: item.name,
            height: obj.height,
            blockSort: blockSort,
          })
          y = y + obj.height
          // 排序
          blockSort++
        })
      }
    }

    return {
        treeData:data,
        listData:listArr,
        listHeightData:listHeightArr
    }
}
// 处理数据时间计算
export const getRangeTimeDays = (startDate: string, endDate: string): { dateDayTree: IDateDayTree[]; dateDayList: IDateDayList[] } => {
  let list: any[] = getRangeDays(startDate, endDate)
  let kongSday = dayjs(startDate).add(-1, 'day').format('YYYY-MM-DD')
  let kongEday = dayjs(endDate).add(1, 'day').format('YYYY-MM-DD')
  list.unshift(kongSday)
  list.push(kongEday)
  let dateDayList: IDateDayList[] = []
  let dateDayTree: IDateDayTree[] = []
  let x = 0
  let firstData: IDateDayTreeItem = {
    name: '',
    isToday: false,
    dateTime: '',
    isWeekend: false,
  }
  let lastData: IDateDayTreeItem = {
    name: '',
    isToday: false,
    dateTime: '',
    isWeekend: false,
  }
  for (let i = 0; i < list.length; i++) {
    let isFirst = i === 0
    let isLast = list.length - 1 === i
    let item = dayjs(list[i])
    let yearMonth = item.format('YYYY-MM')
    let day = item.format('DD')
    let dateTime = item.format('YYYY-MM-DD')
    let isWeek = isWeekendDay(item)
    let isToday = item.isToday()

    x = x + default_size.top_head_w
    dateDayList.push({
      time: dateTime,
      x: x,
    })

    let indexArr = dateDayTree.findIndex((item: any) => {
      return item.name == yearMonth
    })
    let itemDay = {
      name: day,
      isToday,
      dateTime,
      isWeekend: isWeek,
    }
    if (!(isFirst || isLast)) {
      if (indexArr > -1) {
        dateDayTree[indexArr].children.push(itemDay)
      } else {
        dateDayTree.push({
          name: yearMonth,
          children: [itemDay],
        })
      }
    }
    if (isFirst) {
      firstData = itemDay
    }
    if (isLast) {
      lastData = itemDay
    }
  }
  dateDayTree[0]?.children.unshift(firstData)
  dateDayTree[dateDayTree.length - 1]?.children.push(lastData)
  return {
    dateDayTree,
    dateDayList,
  }
}
// 根剧数据计算最终宽高
export const getWHPageByData = (timeData:IDateDayList[],leftData:IDrawDataLeftHead[]):{height:number,width:number} => {
    let w = timeData[timeData.length-1]?.x
    let h = 0
    leftData.forEach((item:any)=>{
        h = h+ item.height
    })
    return {
        height:h+calc_size.title_angular_h,
        width:w+calc_size.angular_ww
    }
}
// 排序节点 并返回字
export const sortNode = (array:IDrawDataNode[]) => {
    let data = (array||[]).sort((a:IDrawDataNode,b:IDrawDataNode)=>{
        if(a.dayData===b.dayData){
            if(a.blockSort === b.blockSort){
                return  a.y-b.y
            }else {
                return a.blockSort -b.blockSort
            }
        }
        return Date.parse(a.dayData) - Date.parse(b.dayData)
    })
    data.forEach((item:any,index:number,arr:any)=>{
        item.text = numberSelfIncreasing(index,String(arr.length).length - 1)
    })
    return data;
}
// 获取格式化后的 node 分组管理
export const getNodeGroup = (data:IDrawDataNode[]):IDrawDataNodeMap=>{
    let map:IDrawDataNodeMap = {}
    for (let i = 0; i < data.length; i++) {
        let item  = data[i]
        if(map[item.blockId]){
            map[item.blockId].push(item)
        }else {
            map[item.blockId] = [item]
        }
    }
    return map
}
// 获取格式化后的 line 分组管理
export const getLineGroup = (data:IDrawDataLine[]):IDrawDataLineMap=>{
    let map:IDrawDataLineMap = {}
    for (let i = 0; i < (data||[]).length; i++) {
        let item  = data[i]
        if(map[item.blockId]){
            map[item.blockId].push(item)
        }else {
            map[item.blockId] = [item]
        }
    }
    return map
}
// 返回自定义属性
export const getCustomProperties  = (item:any):any=>{
    return {
        blockPId:item.blockPId,
        blockId:item.blockId,
        blockSort:item.blockSort,
        startDate:item.startDate,
        endDate:item.endDate,
        blockName:item.name,
        blockPName:item.nameP,
    }
}
// 获取宽度中间的值
export const getNumberCenWidth = (num:number) => {
    return closestMultiple(num,default_size.top_head_w)
}
// 获取高度中间的值
export const getNumberCenHeight = (num:number) => {
    return closestMultiple(num,calc_size.circle_h)
}
// 跟据时间取得宽度
export const getWidthByTimeList = (timeList:IDateDayList[],time:string,flage?:boolean)=>{
    let info = timeList.find((item:IDateDayList)=>{
        return item.time == time
    })
    if(info){
        if(flage){
            return info.x - default_size.top_head_w
        }else {
            return info.x
        }
    }
    return 0
}
// 跟据宽度取得时间
export const getTimeByWidthList = (timeList:IDateDayList[],x:number,flage?:boolean)=>{
    let xOld = x
    if(flage){
        xOld = x - default_size.top_head_w
    }
    let info = timeList.find((item:IDateDayList)=>{
        return item.x == xOld
    })
    if(info){
        return info.time
    }
    return ''
}
// 跟据时间过去天数
export const getDayByTime = (s:string,e:string)=>{
    return Math.abs(dayjs(s).diff(dayjs(e),'day'))
}
// 拖动时候获取点位
export const getDrawPoints = (startPoint:any,endPoint:any,gpIn:any,data:any)=>{
    let {isCircleEnd,isCircleStart,circleEnd,circleStart} = data
    let reversal = false
    let newSX,newSY,newEX,newEY
    let newLinePoint = []
    let circleToleranceDraw =  default_size.circle_r
    // 判断是 x 轴 是不是拐弯(2点 3点)
    if(Math.abs(startPoint.x - endPoint.x) > circleToleranceDraw){
        // 判断运动方向
        if(startPoint.x > endPoint.x){
            if(Math.abs(startPoint.y - endPoint.y) > circleToleranceDraw){
                // 终点是圆
                if(isCircleEnd){
                    newSX = startPoint.x - default_size.circle_r
                    newEX = endPoint.x + calc_size.circle_f_d
                }else {
                    newSX = startPoint.x - calc_size.circle_f_d
                    newEX = endPoint.x + default_size.circle_r
                }
            }else{
                if(isCircleEnd&&isCircleStart){
                    newSX = startPoint.x - calc_size.circle_f_d
                    newEX = endPoint.x + calc_size.circle_f_d
                }else {
                    newSX = startPoint.x - default_size.circle_r
                    newEX = endPoint.x  + default_size.circle_r
                }
            }
        }else{
            if(Math.abs(startPoint.y - endPoint.y) > circleToleranceDraw){
                // 终点是圆
                if(isCircleEnd){
                    if(startPoint.y < endPoint.y){
                        newSX = startPoint.x + default_size.circle_r
                        newEX = endPoint.x - calc_size.circle_f_d
                    }else{
                        newSX = startPoint.x + default_size.circle_r
                        newEX = endPoint.x - calc_size.circle_f_d
                    }
                }else{
                    newSX = startPoint.x + calc_size.circle_f_d
                    newEX = endPoint.x - default_size.circle_r
                }
            }else{
                if(isCircleEnd&&isCircleStart){
                    newSX = startPoint.x + calc_size.circle_f_d
                    newEX = endPoint.x - calc_size.circle_f_d
                }else {
                    newSX = startPoint.x + default_size.circle_r
                    newEX = endPoint.x - default_size.circle_r
                }
            }
        }
    }else{
        // 竖向拖动
        newSX = startPoint.x
        newEX = endPoint.x
    }
    // 判断是 y 轴 是不是拐弯(2点 3点)
    if(Math.abs(startPoint.y - endPoint.y) > circleToleranceDraw){
        if(startPoint.y > endPoint.y){
            if(Math.abs(startPoint.x - endPoint.x) > circleToleranceDraw){
                // 终点是圆
                if(isCircleEnd){
                    if(startPoint.y < endPoint.y){
                        newSY = startPoint.y-default_size.circle_r
                        newEY = endPoint.y
                        newLinePoint = [newSX, newSY,newSX, newEY,newEX, newEY]
                    }else {
                        newSY = startPoint.y
                        newEY = endPoint.y + default_size.circle_r
                        newLinePoint = [newSX, newSY,newEX, newSY,newEX, newEY]
                    }
                }else{
                    newSY = startPoint.y- default_size.circle_r
                    newEY = endPoint.y
                    newLinePoint = [newSX, newSY,newSX, newEY,newEX, newEY]
                }
            }else{
                newSY = startPoint.y - default_size.circle_r
                newEY = endPoint.y + default_size.circle_r
                newLinePoint = [newSX, newSY, newEX, newEY]
            }
        }else{
            if(Math.abs(startPoint.x - endPoint.x) > circleToleranceDraw){
                // 终点是圆
                if(isCircleEnd){
                    newSY = startPoint.y
                    newEY = endPoint.y - default_size.circle_r
                    newLinePoint = [newSX, newSY,newEX, newSY,newEX, newEY]
                }else{
                    newSY = startPoint.y + default_size.circle_r
                    newEY = endPoint.y
                    newLinePoint = [newSX, newSY,newSX, newEY,newEX, newEY]
                }
            }else{
                newSY = startPoint.y + default_size.circle_r
                newEY = endPoint.y - default_size.circle_r
                newLinePoint = [newSX, newSY, newEX, newEY]
            }
        }
    }else{
        // 横向拖动
        if(isCircleEnd&&isCircleStart){
            let sS = []
            if(startPoint.x > endPoint.x){
                sS = circleEnd.getAttr('pointSourceIds')||[]
            }else {
                sS = circleStart.getAttr('pointSourceIds')||[]
            }
            let aP =0
            let bP=0
            let cP =0
            for (let i = 0; i < sS.length; i++) {
                let lineId = sS[i]
                let lineS = gpIn.findOne((item:any)=>{
                    return item.getAttr('lineId') == lineId && item.getAttr('name')===nameVariable.gpLine
                })
                if(lineS){
                    let p = lineS.getAttr('points')
                    if(p[1]>p[3]){
                        bP++
                    }else if(p[1]<p[3]){
                        cP++
                    }else {
                        aP++
                    }
                }
            }
            if(aP===0){
                newSY = startPoint.y
                newEY = endPoint.y
                newLinePoint = [newSX, newSY,newEX, newEY]
            }else {
                let tph
                if(bP<cP||bP==cP){
                    newSY = startPoint.y - default_size.circle_r
                    newEY = endPoint.y - default_size.circle_r
                    tph = (newSY||0) - calc_size.circle_h - default_size.circle_r - default_size.tolerance_draw
                }else {
                    newSY = startPoint.y + default_size.circle_r
                    newEY = endPoint.y + default_size.circle_r
                    tph = (newSY||0) + calc_size.circle_h + default_size.circle_r + default_size.tolerance_draw
                }
                newLinePoint = [newSX, newSY, newSX,tph, newEX,tph,newEX, newEY]
            }
        }else{
            newSY = startPoint.y
            newEY = endPoint.y
            newLinePoint = [newSX, newSY, newEX, newEY]
        }
    }
    // 换箭头方向
    if(startPoint.x > endPoint.x){
        if(newLinePoint.length===4){
            newLinePoint = [newLinePoint[2],newLinePoint[3],newLinePoint[0],newLinePoint[1]]
        }
        if(newLinePoint.length===6){
            newLinePoint = [newLinePoint[4],newLinePoint[5],newLinePoint[2],newLinePoint[3],newLinePoint[0],newLinePoint[1]]
        }
        if(newLinePoint.length===8){
            newLinePoint = [newLinePoint[6],newLinePoint[7],newLinePoint[4],newLinePoint[5],newLinePoint[2],newLinePoint[3],newLinePoint[0],newLinePoint[1]]
        }
        reversal = true
    }else {
        reversal = false
    }
    return {
        points:newLinePoint,
        reversal
    }
}
// 判断两条线是否有重叠
export const doTwoLinesOverlap = (points1:any, points2:any) =>{
    let sX1,sX2,sY
    if(points1.length===4){
        sY = points1[1]
        sX1=points1[0]
        sX2=points1[2]
    }else if(points1.length===6){
        sY = points1[3]
        if(points1[0]===points1[2]){
            sX1=points1[2]
            sX2=points1[4]
        }else {
            sX1=points1[0]
            sX2=points1[2]
        }
    }else if(points1.length===8){
        sY = points1[3]
        sX1=points1[2]
        sX2=points1[4]
    }
    let eX1,eX2,eY
    if(points2.length===4){
        eY = points2[1]
        eX1=points2[0]
        eX2=points2[2]
    }else if(points2.length===6){
        eY = points2[3]
        if(points2[0]===points2[2]){
            eX1=points2[2]
            eX2=points2[4]
        }else {
            eX1=points2[0]
            eX2=points2[2]
        }
    }else if(points2.length===8){
        eY = points2[3]
        eX1=points2[2]
        eX2=points2[4]
    }
    if(eY===sY){
        if(eX1<=sX1&&sX1<=eX2||eX1<=sX2&&sX2<=eX2||sX1<=eX1&&eX1<=sX2||sX1<=eX2&&eX2<=sX2){
            return true
        }
    }
    return false
}
// 获取时间在中见的位置 跟据坐标
export const getTimePositionByPoints = (points:any)=>{
    let y = points[1]
    let x = points[0]
    let n = 3
    let jian = 0
    if(points.length===4){
        y = points[1]
        jian = (points[2] - points[0])/2
        x = points[0]+jian
    }else if(points.length===6){
        y = points[3]
        if( points[3] == points[5]){
            jian = (points[4] - points[2])/2
            x = points[2]+jian
        }else {
            jian = (points[2] - points[0])/2
            x = points[0]+jian
        }
    }else if(points.length===8){
        y = points[3]
        jian = (points[4] - points[2])/2
        x = points[2]+jian
    }
    if(jian>=default_size.top_head_w*5){
        n = 6
    }
    return {
        y:y + 4,
        x:x - n
    }
}
// 根据两个点生成一条线的数据
export const getPointsByTwoCricles = (sR:any,eR:any,lD:any)=>{
    let lp = lD.points
    let x1 = sR.x
    let x2 = eR.x
    let y1 = sR.y
    let y2 = eR.y
    // let sSIds = sR.pointSourceIds
    // let sTIds = sR.pointTargetIds
    // let eSIds = eR.pointSourceIds
    // let eTIds = eR.pointTargetIds
    if(lp.length===4){
        let p0 = lp[0]
        let p1 = lp[1]
        let p2 = lp[2]
        let p3 = lp[3]

        if(p1>p0){
            return [
                x1 + calc_size.circle_f_d,y1+ default_size.circle_r,
                x1 + calc_size.circle_f_d,y1+p1,
                x2 - calc_size.circle_f_d,y1+p2,
                x2 - calc_size.circle_f_d,y1+p3+ default_size.circle_r
            ]
        }else {
            return [
                x1 + calc_size.circle_f_d,y1- default_size.circle_r,
                x1 + calc_size.circle_f_d,y1+p1,
                x2 - calc_size.circle_f_d,y1+p2,
                x2 - calc_size.circle_f_d,y1+p3- default_size.circle_r
            ]
        }
    }
    else if(lp.length===3){
        let p1 = lp[1]
        let p2 = lp[2]
        if(p1==p2){
            if(y2>y1){
                return [
                    x1 + calc_size.circle_f_d,
                    y1 + default_size.circle_r,
                    x1 + calc_size.circle_f_d,
                    y1-p1 + default_size.circle_r,
                    x2 - default_size.circle_r,
                    y1-p2 + default_size.circle_r
                ]
            }else {
                return [
                    x1 + calc_size.circle_f_d,
                    y1 - default_size.circle_r,
                    x1 + calc_size.circle_f_d,
                    y1-p1 - default_size.circle_r,
                    x2 - default_size.circle_r,
                    y1-p2 - default_size.circle_r
                ]
            }
        }else {
            return [
                x1 + default_size.circle_r,
                y1 ,
                x2 - calc_size.circle_f_d,
                y1,
                x2 - calc_size.circle_f_d,
                y1-p2
            ]
        }
    }
    else {
        return [
            x1 + default_size.circle_r,
            y1,
            x2 - default_size.circle_r,
            y2
        ]
    }
}
// 跟据点生成新的相对坐标
export const getPointByOldPoints = (points:number[])=>{
    let newP:number[] = []
    if(points.length===8){
        let p0 = points[3]<points[1]?points[1] + default_size.circle_r:points[1] - default_size.circle_r
        let p1 =  points[3] - p0
        let p2 =  points[5] - p0
        let p3 =  points[7] - points[1]
        newP = [0,p1,p2,p3]
    }else if(points.length===6){
        let p1 = points[1] - points[3]
        let p2 = points[1] - points[5]
        if(points[1] == points[3]){
            newP = [0,0,p2]
        }else {
            newP = [0,p1,p1]
        }
    }else {
        newP = [0,0]
    }
    return newP
}
// 获取点 最大最小值
export const getMaxMinPoints = (points:Array<number>,type?:boolean) => {
    let list = []
    for (let i = 0; i < points.length; i++) {
        if(type){
            if(i%2===1){
                list.push(points[i])
            }
        }else {
            if(i%2===0){
                list.push(points[i])
            }
        }
    }
    let max = Math.max(...list)
    let min = Math.min(...list)
    return {
        max:max,
        min:min
    }
}
// 跟据点位返回一个矩形 选中的 信息
export const getSelectRectByPoints = (points:Array<number>) => {
    let {min,max} = getMaxMinPoints(points)
    let rectW = 0
    let rectY = 0
    let rectX = 0

    if(points.length===8){
        rectW = max - min+default_size.circle_r
        rectY = points[3]
        rectX = points[0]-calc_size.circle_f_d
    }else if(points.length===6){
        rectW = max - min+calc_size.circle_f_d+default_size.circle_r
        rectY = points[3]
        if(points[1]===points[3]){
            rectX = points[0]-default_size.circle_r
        }else {
            rectX = points[0]-calc_size.circle_f_d
        }
    }else {
        rectW = max - min + calc_size.circle_d
        rectY = points[1]
        rectX = points[0]-default_size.circle_r
    }

    // 创建一个矩形
    return  {
        x:rectX,
        y:rectY,
        width:rectW
    }
}
// 获取并计算下面时间的位置
export const getLineBomTimePosition = (points:Array<number>,timeText:string|number)=>{
    let x1 = points[0]
    let x2 = points[1]
    let newx = 0
    if(x1&&x2&&timeText){
        newx = (x2-x1)/2 + x1
        if(timeText.toString().length===1){
            newx = newx - 3
        }else if(timeText.toString().length===2){
            newx = newx - 6
        }else if(timeText.toString().length===3){
            newx = newx - 9
        }
    }
    return newx
}
// 获取文字的位置
export const getTextSizePosition = (points:Array<number>)=>{
    let x1 = points[0]
    let x2 = points[1]
    let cenText = 0
    let maxTextWidth = 0
    if(x1&&x2){
        maxTextWidth = x2 - x1
        cenText = maxTextWidth/2 + x1
    }
    return{
        maxTextWidth,
        cenText
    }
}
// 获取文字的宽高 用于线上的上面名称
export const getTextHeightWidth = (text:string,maxW:number) =>{
    if(!text){
        return {
            width: 0,
            height:0
        }
    }
    // 创建临时元素
    const _span = document.createElement('span')
    // 放入文本
    _span.innerText = text
    // 设置文字大小
    _span.style.fontFamily= 'custom-RobotoRegular'
    _span.style.fontSize = '10px'
    _span.style.fontWeight = '400'
    _span.style.lineHeight = '11px'
    _span.style.width = maxW - 1+'px'

    // span元素转块级
    _span.style.position = 'absolute'
    _span.style.zIndex = '-1'
    _span.style.wordBreak = 'break-all'
    // span放入body中
    document.body.appendChild(_span)
    // 获取span的宽度
    let width = _span.offsetWidth
    let height = _span.offsetHeight
    // 从body中删除该span
    document.body.removeChild(_span)
    // 返回span宽度
    return {
        width,
        height
    }
}
// 处理数据时间计算
export const getRangeDays = (startDate: string, endDate: string): number[] => {
  let begin = dayjs(startDate).format('YYYY-MM-DD')
  let end = dayjs(endDate).format('YYYY-MM-DD')
  let ab = begin.toString().split('-')
  let ae = end.toString().split('-')
  let db = new Date()
  db.setUTCFullYear(Number(ab[0]), Number(ab[1]) - 1, Number(ab[2]))
  let de = new Date()
  de.setUTCFullYear(Number(ae[0]), Number(ae[1]) - 1, Number(ae[2]))
  let unixDb = db.getTime() - 24 * 60 * 60 * 1000
  let unixDe = de.getTime() - 24 * 60 * 60 * 1000
  let arr: number[] = []
  for (let k = unixDb; k <= unixDe; ) {
    k = k + 24 * 60 * 60 * 1000
    arr.push(k)
  }
  return arr
}
// 是不是周末
export const isWeekendDay = (date: any) => {
  const dayOfWeek = date.day()
  return dayOfWeek === 5 || dayOfWeek === 6 // 6代表周六,0代表周日
}
// 每隔指定字符直接插入值
export const setStrInsert = (str: any, val: string, leng: number) => {
  try {
    if (str) {
      let reg = new RegExp(`\S{1,${leng}}`, 'g')
      let ma = str.match(reg)
      return ma.join(val || '\n')
    } else {
      return ''
    }
  } catch (e) {
    console.log(e)
    return str
  }
}
// 获取指定x整数倍
export const closestMultiple = (n: number, x: number) => {
  if (x > n) return x
  n = n + parseInt(String(x / 2), 10)
  n = n - (n % x)
  return n
}
// 获取最大最小
export const getMaxMinByList = (list: any[]) => {
  return {
    max: Math.max.apply(null, list),
    min: Math.min.apply(null, list),
  }
}

最后

第一篇文章,没想到写的有点乱,还望理解,有问题欢迎评论。