Vue-Gantt-chart甘特图实现拖拽功能
发布日期:2022-05-31文章字数:1.1K
去年做了一个展示任务计划的甘特图项目,我使用了Vue-Gantt-chart 这个插件来实现,要求实现拖拽功能还有一些别的需求,最近抽空把实现拖拽功能的代码重新整理了一下,发布出来。改动如下:
样式调整,添加顶部部的时间刻度格和左侧日期。滚动插件使用 iscroll 实现,使滚动条样式在各浏览器下保持一致,支持鼠标按住拖动,类似手机上的按住滚动效果。
数据分组:不同属性的甘特行可以分组,分组后数据渲染也是动态的,即只渲染浏览器视口内的数据,我本机测试万级数据(500行25列)轻微卡顿。
数据搜索:搜索后高亮显示结果,并滚动到相应任务位置,若搜索到多个结果,继续点搜索按钮跳转到下一个结果。
甘特块拖拽调整:基于浏览器原生拖拽事件实现,不同行之间的甘特块可以拖拽调整,调整时可以做一些校验,代码里暂时只做了时间校验,拖拽后默认会有一个黑色阴影块显示原来的任务,在配置项里可以设置为不显示,调整确认弹窗也可以选择显示或不显示(默认不显示)。
右键菜单:若想要调整的行竖向间距过大不方便拖拽时,可使用右键菜单调整任务,可以选择复制或交换。
因为原插件的代码只包含了一个甘特图,添加了分组功能后,原来的动态渲染的代码就失效了,分组后的动态渲染功能我是这样实现的:
分组时给每一行的甘特数据添加一个rawIndex(原始索引)字段,甘特行的位置采用绝对定位,使用rawIndex来计算绝对定位的top值,直接rawIndex * 行高就是top值,这样即使上面的元素不显示也不影响渲染的元素位置。
然后使用getBoundingClientRect这个API来获取每个甘特组元素相对于浏览器窗口的top和bottom值,计算出需要显示的起始索引和结束索引。这是垂直方向上的动态渲染实现。水平方向上的动态渲染没有改动,是根据时间判断的。
判断的逻辑还是比较绕的,需要判断负值的情况,详细代码如下:
/**
* 分割出dom中需要显示的数据
*/
sliceData() {
if (!this.wrapperElement) return false;
const {
unVisibleHeight,
heightOfBlocksWrapper,
cellHeight,
preload,
datas
} = this;
const ClientRect = this.wrapperElement.getBoundingClientRect();
const windowHeight = window.innerHeight;
let startPosition = 0;
let endPosition = 0;
const top = ClientRect.top;
const bottom = ClientRect.bottom;
this.top = top;
this.bottom = bottom;
if (top <= 0) {
startPosition = Math.abs(top) + unVisibleHeight;
endPosition = startPosition + heightOfBlocksWrapper;
if (bottom > unVisibleHeight && bottom <= windowHeight) {
endPosition = startPosition + bottom;
} else if (bottom <= unVisibleHeight) {
this.startRenderNum = 0;
this.endRenderNum = 0;
return;
}
} else if (top > 0 && top <= unVisibleHeight) {
startPosition = unVisibleHeight - top;
endPosition = startPosition + heightOfBlocksWrapper;
} else if (top > unVisibleHeight && top <= windowHeight) {
startPosition = 0;
endPosition = windowHeight - top;
} else if (top > windowHeight) {
this.startRenderNum = 0;
this.endRenderNum = 0;
return;
}
//没有高度,不需要渲染元素
if (heightOfBlocksWrapper === 0 || cellHeight === 0) {
this.startRenderNum = 0;
this.endRenderNum = 0;
return;
}
// 为 0 全部渲染
if (preload === 0) {
this.startRenderNum = 0;
this.endRenderNum = datas.length;
return;
}
const startRenderNum = Math.ceil(startPosition / cellHeight) - preload;
this.startRenderNum = startRenderNum < 0 ? 0 : startRenderNum;
const endRenderNum = Math.ceil(endPosition / cellHeight) + preload;
this.endRenderNum =
endRenderNum > datas.length ? datas.length : endRenderNum;
}
以下图为例,假设红色框是浏览器的窗口,红框外面是一个甘特组,包含14行数据,每行高50像素,浏览器窗口高400,那么获取到的top和bottom就是-150和550,可以计算出渲染起始位置为:150-550索引值为中间8个:3-10。
多个分组同时出现在窗口内时,如下图,上面的渲染起始位置为:100-300 索引值:2-5,下面的渲染起始位置为0-200,索引值为0-3。
当然上面只是理想状态下的情况,如果一行的位置只滚动到一半也是不显示的,实际代码中需要加一个预渲染值,这里设置为1,起始和结尾都多渲染一列。
动图演示
拖拽移动
数据分组
搜索
拖拽调整任务
右键菜单调整任务