konva vue3 实现工程进度图
项目背景
项目全称是《施工管理计划图》,用来记录当前施工的进度,到达某一个阶段,在某一个阶段所用的人力以及物力的相关数据,对过去现在未来的工程做好时间规划,能够全局预览整体的项目进度。目前第一版本已经上线使用,开发仓促,有很多不足,涉及到公司项目,并不能发源码,还望理解。
已完成的功能
- 虚拟渲染(因为时间跨度长,不能将所有的线都展示出来,否则卡);
- 拖动画线(一线两点的连接方式);
- 改变线的结构(在已经绘制好的线段下拖动线,改变线的形状);
- 放大缩小(目前不支持绘制时候放大缩小);
- 拖动画板;
- 导出图片;
- 事件点击、悬浮等交互;
- 自定义线,点的样式,背景;
- 支持搜索,定位;
不支持的功能: 删除点后以及更新点后的状态逻辑使后台进行运算的,页面只负责渲染;
项目成品图
-
组件目录
-
整体样式
-
悬浮和右键
-
绘制时候1
-
绘制时候2
-
特殊线
项目所用插件及参考文档
- 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
在这个项目开发中为了让konva实例数据共享,使用 pinia 做组件级别的共享数据,结合vue3里面api hooks等。
在初始阶段,我将整个项目划分成一个组件,数据由组件外部透入,并将这个组件定义为 预览 和 编辑模式,因为编辑模式时候使用的距离(x,y)是要实际大小的,所以就没有做缩放下能支持编辑,所有的线都是有两个端点的,一线两端点。
konva 是 支持层的概念 ,所以将组件划分为三层:
- 第一层是底层那些格格数据,第一次绘制时候没有做虚拟加载只是将其静态化,发现数据量大时候也是有卡顿的,(静态化:konva cache 一种缓存api,将其转化成图片,结合场景使用 ),后来改成部分数据静态化,部分按照拖动方式进行虚拟加载;
- 第二层是线和点的数据,就是界面上那些流程;
- 第三层是动态数据,就是拖动,绘制时候常用数据;因为拖完后要删掉,使用频率高,用完清空,所以放在最上层;
这里的虚拟加载思路其实很简单,在进入到页面时候,获取画板容器的宽高,存起来,然后在绘制过程中,比较第一层的数据是否在这个宽高范围内。当然为了让系统看起来流畅,实际比较范围 我是将宽高乘以了3 。这样在拖动时候就增加了用户体验,最后监听容器的大小,发生变化,loading... 重新加载。
第二层同理,区别是第二层要将时间转化成x数据
第三层要实时同步位置信息等
重点功能介绍
- 项目数据结构(组件外部透入)
{
"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
]
}
]
}
- 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,
})
}
- 自定义线 ,线比较特殊时候要用到,没办法只能手写样式了,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)
}
}
常量方便后面改动
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),
}
}
最后
第一篇文章,没想到写的有点乱,还望理解,有问题欢迎评论。