vue2結合element-ui的gantt圖實現(xiàn)可拖拽甘特圖
一、前言
接到公司需求,要做一個可拖拽的甘特圖來實現(xiàn)排期需求,官方的插件要付費還沒有中文的官方文檔可以看,就去找了各種開源的demo來看,功能上都不是很齊全,于是總結了很多demo,合在一起組成了一版較為完整的滿足需求的甘特圖。
二、主要功能
1.拖拽 拖拽功能是甘特圖的主要功能,該demo實現(xiàn)了甘特圖時間塊上、下、左、右拖拽功能。
2.排序 拖拽后時間塊進行排序,計算重疊區(qū)域大小確定插入位置。
3.時間選擇 結合element-ui的日期時間選擇器來確定時間軸。
4.搜索 搜索已存在的時間塊,并定位到相應位置。
5.新建排期任務 使用element-ui的彈框以及表單 新建成功的排期列表添加到排期任務中。
6.右鍵菜單 右擊時間塊,可以進行查看、刪除、修改等操作。
7.撤回 每刪除或移動時間塊后,增加一條操作記錄,點擊撤回可撤回當前操作。
8.批量操作 在批量操作后點擊保存,才向后端存儲數(shù)據。
三、功能實現(xiàn)
1.默認時間軸線今天的前三后四
// 設置默認時間 當前日期前三后四
defaultDate() {
const beg = new Date(new Date().getTime() - 3600 * 1000 * 24 * 3)
.toISOString()
.replace('T', ' ')
.split('.')[0] //默認開始時間3天前
const end = new Date(new Date().getTime() + 3600 * 1000 * 24 * 4)
.toISOString()
.replace('T', ' ')
.split('.')[0]//默認結束時間4天后
this.choiceTime = [beg, end] //將值設置給插件綁定的數(shù)據
// console.log(this.value1);
},2.拖拽事件實現(xiàn)
onMouseDown(e, blockId, rowIndex) {
// 刪除模式下不處理拖動事件
if (this.isDeleteMode) {
return;
}
this.moveX = 0;
this.moveY = 0;
// 用box 移動,不采用 Doucment
const box = this.$refs.box;
const dom = e.target;
// 算出鼠標相對元素的位置
const disX = e.clientX - dom.offsetLeft;
const disY = e.clientY - dom.offsetTop;
console.log('鼠標正在拖動',e.clientX,dom.offsetLeft);
// 當點擊下來的時候 nowSuck 其實等于的就是當前index
this.nowSuck = rowIndex;
// 讓本來擁有手掌樣式的class取消
dom.classList.remove('gantt-grab');
// 讓整個box 鼠標都是抓住
box.classList.add('gantt-grabbing');
// 如果事件綁定在 dom上,那么一旦鼠標移動過快就會脫離掌控
box.onmousemove = ee => {
// 獲得當前受到拖拽的div 用于計算suck 所謂拖引的行數(shù)
const top = parseInt(dom.style.top);
// 四舍五入 獲得磁性吸附激活的值 (索引值) 60是block的height 10是時間塊距離block的top suck 可以當作row的索引
let suck = Math.round((top - 10) / 60) + rowIndex;
// suck的邊界值設置
if (suck < 0) {
suck = 0;
} else if (suck > this.ganttData.length - 1) {
suck = this.ganttData.length - 1;
}
// suck 行樣式變化
this.nowSuck = suck;
// 移動事件
this.onMouseMove(ee, disX, disY, dom);
// dom.style.left=this.moveX;
};
// 不管在哪里,鼠標松開的時候,清空事件 所以對于這個 “不管在哪里,使用了window”
window.onmouseup = () => {
// 鼠標松開了,讓時間塊恢復手掌樣式
dom.classList.add('gantt-grab');
// 整個box 不在抓住了,變成箭頭鼠標
box.classList.remove('gantt-grabbing');
// 當移動距離小于5時,不做移動處理
//console.log('移動距離:', this.moveX, this.moveY);
if (this.moveX < 1 && this.moveY < 1 && this.moveX > -1 && this.moveY > -1) {
console.log('無效移動');
box.onmousemove = null;
window.onmouseup = null;
this.nowSuck = -1;
return;
}
// 保存操作日志
this._addHisList(this.ganttData);
const index = this.ganttData[rowIndex][this.listKey].findIndex(item => {
return item.id === blockId;
});
const oldTimeBlock = this.ganttData[rowIndex][this.listKey][index];
// let timeId = oldTimeBlock.id;
// startTime 與 endTime 用于數(shù)據重新替換 上面css已經經過計算 15px為1小時
const startTime = new Date(Date.parse(this.choiceTime[0]) + (parseInt(dom.style.left) * 3600000) / 15);
const endTime = new Date(this._getTime(startTime) + (parseInt(dom.style.width) * 3600000) / 15);
// console.log(startTime, endTime, dom.style.width, parseInt(dom.style.left) * 60 * 1000);
const suck = this.nowSuck;
// 加入新數(shù)據
const data = oldTimeBlock;
// 更新開始時間和結束時間
this.$set(data, 'startTime', startTime);
this.$set(data, 'endTime', endTime);
// 修改時間塊的equipmentId
this.$set(data, 'equipmentId', this.ganttData[suck].id);
/**
* 本來dom元素磁性吸附是打算用document.appendChild() 方式來做的,但是對于vue來說 for出來的子元素就算變了位置,其index也不屬于新的row
*/
// 老數(shù)據 刪除
this.ganttData[rowIndex][this.listKey].splice(index, 1);
// 新數(shù)據加入
this.ganttData[suck][this.listKey].push(data);
// 坐標定位 磁性吸附 永遠的10px 不知道為啥動態(tài)綁定的元素也會受到以前元素的影響,可能是 for 中 index帶來的影響
dom.style.top = this.defaultTop + 'px';
// 松開鼠標的時候 清空
box.onmousemove = null;
window.onmouseup = null;
// 需要重新計算吸附位置,以及整行重新排列
this.$nextTick(() => {
this._recalcRowTimes(data, this.ganttData[suck][this.listKey]);
});
// 將當前row 清空
this.nowSuck = -1;
// 改變位置后回調事件
this.afterChangePosition(data, this.ganttData[rowIndex].id, this.ganttData[suck].id);
};
},
/**
* 鼠標移動的時候,前置條件鼠標按下
* @param e 時間塊的 event 事件
* @param disX x軸
* @param disY y軸
* @param targetDom 時間塊的dom 其實可以直接 e.target 獲取
*/
onMouseMove(e, disX, disY, targetDom) {
// 用鼠標的位置減去鼠標相對元素的位置,得到元素的位置
let left = e.clientX - disX;
const top = e.clientY - disY;
console.log('x軸移動距離',left);
this.moveX = left;
this.moveY = top;
// 移動位置不能小于零(開始時間)
if (left < 0) {
left = 0;
}
//拖動時間塊關閉右鍵菜單
this.menuVisible = false;
targetDom.style.left = left + 'px';
targetDom.style.top = top + 'px';
},
/**
* 時間塊位置變化后回調事件
* @param data 時間塊的值 包括時間塊id startTime endTime
* @param rowOId 時間塊原來所在的那個飛機id (一條橫線)
* @param rowNId 時間塊新的所在的那個飛機id
*/
afterChangePosition(data, rowOId, rowNId) {
console.log('時間塊位置變化后回調事件', rowOId, rowNId);
},
save() {
console.log(JSON.stringify(this.ganttData));
},3. 右擊設置自定義右鍵菜單
onRightClick(MouseEvent, row, block) {
//編輯需要把時間長度先計算好
MouseEvent.preventDefault(); //關閉瀏覽器右鍵默認事件
block.timeDiff = (block.endTime - block.startTime) / 3600000;
this.editRow = row;
this.editBlock = block;
// this.menuVisible = false; // 先把模態(tài)框關死,目的是 第二次或者第n次右鍵鼠標的時候 它默認的是true
this.menuVisible = true; // 顯示模態(tài)窗口,跳出自定義菜單欄
console.log('喚醒點擊事件', this.menuVisible, this.editBlock, MouseEvent.clientX);
this.CurrentRow = row;
var menu = document.querySelector('.menu');
if (MouseEvent.clientX > 1800) {
menu.style.left = MouseEvent.clientX - 100 + 'px';
} else {
menu.style.left = MouseEvent.clientX + 1 + 'px';
}
document.addEventListener('click', this.cancelMouse); // 給整個document新增監(jiān)聽鼠標事件,點擊任何位置執(zhí)行foo方法
if (MouseEvent.clientY > 700) {
menu.style.top = MouseEvent.clientY - 30 + 'px';
} else {
menu.style.top = MouseEvent.clientY - 10 + 'px';
}
console.log('位置問題', MouseEvent.clientY - 30 + 'px', MouseEvent.clientY - 10 + 'px');
// this.styleMenu(menu);
},
cancelMouse() {
// document.oncontextmenu=false;
// 取消鼠標監(jiān)聽事件 菜單欄
this.menuVisible = false;
document.removeEventListener('click', this.foo); // 關掉監(jiān)聽,
},4.計算時間選擇器相差天數(shù)以渲染時間軸
choiceTimeArr() {
const timeArr = [];
// 時間戳毫秒為單位
// 尾時間-首時間 算出頭尾的時間戳差 再換算成天單位 毫秒->分->時->天
// const diffDays = (this.choiceTime[1].getTime() - this.choiceTime[0].getTime()) / 1000 / 60 / 60 / 24;
const diffDays = Math.abs(Date.parse(this.choiceTime[1])- Date.parse(this.choiceTime[0])) / 1000 / 60 / 60 / 24
console.log('我是時間差啊', diffDays);
// 一天的時間戳)
const oneDayMs = 24 * 60 * 60 * 1000;
// 差了多少天就便利多少天 首時間+當前便利的天數(shù)的毫秒數(shù)
for (let i = 0; i < diffDays + 1; i++) {
// 時間戳來一個相加,得到的就是時間數(shù)組
timeArr.push(new Date(Date.parse(this.choiceTime[0]) + i * oneDayMs));
}
// console.log(diffDays, oneDayMs, timeArr);
return timeArr;
},5.搜索功能(使用element-ui的示例)
// 搜索數(shù)據數(shù)組
loadAll() {
return [
{ "value": "三全鮮食(北新涇店)", "address": "長寧區(qū)新漁路144號" },
{ "value": "Hot honey 首爾炸雞(仙霞路)", "address": "上海市長寧區(qū)淞虹路661號" },
{ "value": "新旺角茶餐廳", "address": "上海市普陀區(qū)真北路988號創(chuàng)邑金沙谷6號樓113" },
{ "value": "瀧千家(天山西路店)", "address": "天山西路438號" },
{ "value": "胖仙女紙杯蛋糕(上海凌空店)", "address": "上海市長寧區(qū)金鐘路968號1幢18號樓一層商鋪18-101" },
{ "value": "貢茶", "address": "上海市長寧區(qū)金鐘路633號" },
{ "value": "豪大大香雞排超級奶爸", "address": "上海市嘉定區(qū)曹安公路曹安路1685號" },
{ "value": "茶芝蘭(奶茶,手抓餅)", "address": "上海市普陀區(qū)同普路1435號" },
{ "value": "十二瀧町", "address": "上海市北翟路1444弄81號B幢-107" },
{ "value": "星移濃縮咖啡", "address": "上海市嘉定區(qū)新郁路817號" },
{ "value": "阿姨奶茶/豪大大", "address": "嘉定區(qū)曹安路1611號" },
{ "value": "新麥甜四季甜品炸雞", "address": "嘉定區(qū)曹安公路2383弄55號" }
];
},
querySearchAsync(queryString, cb) {
var restaurants = this.restaurants;
var results = queryString ? restaurants.filter(this.createStateFilter(queryString)) : restaurants;
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
cb(results);
}, 3000 * Math.random());
},
createStateFilter(queryString) {
return (state) => {
return (state.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
};
},
handleSelect(item) {
console.log(item);
},四、實現(xiàn)效果
由于需求是以彈框形式實現(xiàn),沒有做全屏顯示,具體效果如下:

甘特圖實現(xiàn)
總結
到此這篇關于vue2結合element-ui的gantt圖實現(xiàn)可拖拽甘特圖的文章就介紹到這了,更多相關vue2實現(xiàn)可拖拽甘特圖內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vue?動態(tài)路由component?傳遞變量報錯問題解決
這篇文章主要為大家介紹了vue?動態(tài)路由component?傳遞變量報錯問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-05-05
vue3.0語法糖內的defineProps及defineEmits解析
這篇文章主要介紹了vue3.0語法糖內的defineProps及defineEmits解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04
vscode搭建vue環(huán)境完整圖文教程(適合新手小白)
Vue框架的優(yōu)秀設計和強大的生態(tài)系統(tǒng)成為了越來越多開發(fā)者選擇Vue的原因,在實際項目過程中一個高效的開發(fā)環(huán)境能夠大大提高開發(fā)效率,這篇文章主要給大家介紹了關于vscode搭建vue環(huán)境的相關資料,需要的朋友可以參考下2023-10-10
解決Element中el-date-picker組件不回填的情況
這篇文章主要介紹了解決Element中el-date-picker組件不回填的情況,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11
詳解vue中父子組件傳遞參數(shù)props的實現(xiàn)方式
這篇文章主要給大家介紹了在vue中,父子組件傳遞參數(shù)?props?實現(xiàn)方式,文章通過代碼示例介紹的非常詳細,對我們的學習或工作有一定的參考價值,需要的朋友可以參考下2023-07-07
vue基于vant實現(xiàn)上拉加載下拉刷新的示例代碼
普遍存在于各種app中的上拉加載下拉刷新功能,本文主要介紹了vue基于vant實現(xiàn)上拉加載下拉刷新,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01

