vue2結(jié)合element-ui的gantt圖實(shí)現(xiàn)可拖拽甘特圖
一、前言
接到公司需求,要做一個(gè)可拖拽的甘特圖來(lái)實(shí)現(xiàn)排期需求,官方的插件要付費(fèi)還沒(méi)有中文的官方文檔可以看,就去找了各種開(kāi)源的demo來(lái)看,功能上都不是很齊全,于是總結(jié)了很多demo,合在一起組成了一版較為完整的滿足需求的甘特圖。
二、主要功能
1.拖拽 拖拽功能是甘特圖的主要功能,該demo實(shí)現(xiàn)了甘特圖時(shí)間塊上、下、左、右拖拽功能。
2.排序 拖拽后時(shí)間塊進(jìn)行排序,計(jì)算重疊區(qū)域大小確定插入位置。
3.時(shí)間選擇 結(jié)合element-ui的日期時(shí)間選擇器來(lái)確定時(shí)間軸。
4.搜索 搜索已存在的時(shí)間塊,并定位到相應(yīng)位置。
5.新建排期任務(wù) 使用element-ui的彈框以及表單 新建成功的排期列表添加到排期任務(wù)中。
6.右鍵菜單 右擊時(shí)間塊,可以進(jìn)行查看、刪除、修改等操作。
7.撤回 每刪除或移動(dòng)時(shí)間塊后,增加一條操作記錄,點(diǎn)擊撤回可撤回當(dāng)前操作。
8.批量操作 在批量操作后點(diǎn)擊保存,才向后端存儲(chǔ)數(shù)據(jù)。
三、功能實(shí)現(xiàn)
1.默認(rèn)時(shí)間軸線今天的前三后四
// 設(shè)置默認(rèn)時(shí)間 當(dāng)前日期前三后四 defaultDate() { const beg = new Date(new Date().getTime() - 3600 * 1000 * 24 * 3) .toISOString() .replace('T', ' ') .split('.')[0] //默認(rèn)開(kāi)始時(shí)間3天前 const end = new Date(new Date().getTime() + 3600 * 1000 * 24 * 4) .toISOString() .replace('T', ' ') .split('.')[0]//默認(rèn)結(jié)束時(shí)間4天后 this.choiceTime = [beg, end] //將值設(shè)置給插件綁定的數(shù)據(jù) // console.log(this.value1); },
2.拖拽事件實(shí)現(xiàn)
onMouseDown(e, blockId, rowIndex) { // 刪除模式下不處理拖動(dòng)事件 if (this.isDeleteMode) { return; } this.moveX = 0; this.moveY = 0; // 用box 移動(dòng),不采用 Doucment const box = this.$refs.box; const dom = e.target; // 算出鼠標(biāo)相對(duì)元素的位置 const disX = e.clientX - dom.offsetLeft; const disY = e.clientY - dom.offsetTop; console.log('鼠標(biāo)正在拖動(dòng)',e.clientX,dom.offsetLeft); // 當(dāng)點(diǎn)擊下來(lái)的時(shí)候 nowSuck 其實(shí)等于的就是當(dāng)前index this.nowSuck = rowIndex; // 讓本來(lái)?yè)碛惺终茦邮降腸lass取消 dom.classList.remove('gantt-grab'); // 讓整個(gè)box 鼠標(biāo)都是抓住 box.classList.add('gantt-grabbing'); // 如果事件綁定在 dom上,那么一旦鼠標(biāo)移動(dòng)過(guò)快就會(huì)脫離掌控 box.onmousemove = ee => { // 獲得當(dāng)前受到拖拽的div 用于計(jì)算suck 所謂拖引的行數(shù) const top = parseInt(dom.style.top); // 四舍五入 獲得磁性吸附激活的值 (索引值) 60是block的height 10是時(shí)間塊距離block的top suck 可以當(dāng)作row的索引 let suck = Math.round((top - 10) / 60) + rowIndex; // suck的邊界值設(shè)置 if (suck < 0) { suck = 0; } else if (suck > this.ganttData.length - 1) { suck = this.ganttData.length - 1; } // suck 行樣式變化 this.nowSuck = suck; // 移動(dòng)事件 this.onMouseMove(ee, disX, disY, dom); // dom.style.left=this.moveX; }; // 不管在哪里,鼠標(biāo)松開(kāi)的時(shí)候,清空事件 所以對(duì)于這個(gè) “不管在哪里,使用了window” window.onmouseup = () => { // 鼠標(biāo)松開(kāi)了,讓時(shí)間塊恢復(fù)手掌樣式 dom.classList.add('gantt-grab'); // 整個(gè)box 不在抓住了,變成箭頭鼠標(biāo) box.classList.remove('gantt-grabbing'); // 當(dāng)移動(dòng)距離小于5時(shí),不做移動(dòng)處理 //console.log('移動(dòng)距離:', this.moveX, this.moveY); if (this.moveX < 1 && this.moveY < 1 && this.moveX > -1 && this.moveY > -1) { console.log('無(wú)效移動(dòng)'); 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ù)據(jù)重新替換 上面css已經(jīng)經(jīng)過(guò)計(jì)算 15px為1小時(shí) 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ù)據(jù) const data = oldTimeBlock; // 更新開(kāi)始時(shí)間和結(jié)束時(shí)間 this.$set(data, 'startTime', startTime); this.$set(data, 'endTime', endTime); // 修改時(shí)間塊的equipmentId this.$set(data, 'equipmentId', this.ganttData[suck].id); /** * 本來(lái)dom元素磁性吸附是打算用document.appendChild() 方式來(lái)做的,但是對(duì)于vue來(lái)說(shuō) for出來(lái)的子元素就算變了位置,其index也不屬于新的row */ // 老數(shù)據(jù) 刪除 this.ganttData[rowIndex][this.listKey].splice(index, 1); // 新數(shù)據(jù)加入 this.ganttData[suck][this.listKey].push(data); // 坐標(biāo)定位 磁性吸附 永遠(yuǎn)的10px 不知道為啥動(dòng)態(tài)綁定的元素也會(huì)受到以前元素的影響,可能是 for 中 index帶來(lái)的影響 dom.style.top = this.defaultTop + 'px'; // 松開(kāi)鼠標(biāo)的時(shí)候 清空 box.onmousemove = null; window.onmouseup = null; // 需要重新計(jì)算吸附位置,以及整行重新排列 this.$nextTick(() => { this._recalcRowTimes(data, this.ganttData[suck][this.listKey]); }); // 將當(dāng)前row 清空 this.nowSuck = -1; // 改變位置后回調(diào)事件 this.afterChangePosition(data, this.ganttData[rowIndex].id, this.ganttData[suck].id); }; }, /** * 鼠標(biāo)移動(dòng)的時(shí)候,前置條件鼠標(biāo)按下 * @param e 時(shí)間塊的 event 事件 * @param disX x軸 * @param disY y軸 * @param targetDom 時(shí)間塊的dom 其實(shí)可以直接 e.target 獲取 */ onMouseMove(e, disX, disY, targetDom) { // 用鼠標(biāo)的位置減去鼠標(biāo)相對(duì)元素的位置,得到元素的位置 let left = e.clientX - disX; const top = e.clientY - disY; console.log('x軸移動(dòng)距離',left); this.moveX = left; this.moveY = top; // 移動(dòng)位置不能小于零(開(kāi)始時(shí)間) if (left < 0) { left = 0; } //拖動(dòng)時(shí)間塊關(guān)閉右鍵菜單 this.menuVisible = false; targetDom.style.left = left + 'px'; targetDom.style.top = top + 'px'; }, /** * 時(shí)間塊位置變化后回調(diào)事件 * @param data 時(shí)間塊的值 包括時(shí)間塊id startTime endTime * @param rowOId 時(shí)間塊原來(lái)所在的那個(gè)飛機(jī)id (一條橫線) * @param rowNId 時(shí)間塊新的所在的那個(gè)飛機(jī)id */ afterChangePosition(data, rowOId, rowNId) { console.log('時(shí)間塊位置變化后回調(diào)事件', rowOId, rowNId); }, save() { console.log(JSON.stringify(this.ganttData)); },
3. 右擊設(shè)置自定義右鍵菜單
onRightClick(MouseEvent, row, block) { //編輯需要把時(shí)間長(zhǎng)度先計(jì)算好 MouseEvent.preventDefault(); //關(guān)閉瀏覽器右鍵默認(rèn)事件 block.timeDiff = (block.endTime - block.startTime) / 3600000; this.editRow = row; this.editBlock = block; // this.menuVisible = false; // 先把模態(tài)框關(guān)死,目的是 第二次或者第n次右鍵鼠標(biāo)的時(shí)候 它默認(rèn)的是true this.menuVisible = true; // 顯示模態(tài)窗口,跳出自定義菜單欄 console.log('喚醒點(diǎn)擊事件', 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); // 給整個(gè)document新增監(jiān)聽(tīng)鼠標(biāo)事件,點(diǎn)擊任何位置執(zhí)行foo方法 if (MouseEvent.clientY > 700) { menu.style.top = MouseEvent.clientY - 30 + 'px'; } else { menu.style.top = MouseEvent.clientY - 10 + 'px'; } console.log('位置問(wèn)題', MouseEvent.clientY - 30 + 'px', MouseEvent.clientY - 10 + 'px'); // this.styleMenu(menu); }, cancelMouse() { // document.oncontextmenu=false; // 取消鼠標(biāo)監(jiān)聽(tīng)事件 菜單欄 this.menuVisible = false; document.removeEventListener('click', this.foo); // 關(guān)掉監(jiān)聽(tīng), },
4.計(jì)算時(shí)間選擇器相差天數(shù)以渲染時(shí)間軸
choiceTimeArr() { const timeArr = []; // 時(shí)間戳毫秒為單位 // 尾時(shí)間-首時(shí)間 算出頭尾的時(shí)間戳差 再換算成天單位 毫秒->分->時(shí)->天 // 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('我是時(shí)間差啊', diffDays); // 一天的時(shí)間戳) const oneDayMs = 24 * 60 * 60 * 1000; // 差了多少天就便利多少天 首時(shí)間+當(dāng)前便利的天數(shù)的毫秒數(shù) for (let i = 0; i < diffDays + 1; i++) { // 時(shí)間戳來(lái)一個(gè)相加,得到的就是時(shí)間數(shù)組 timeArr.push(new Date(Date.parse(this.choiceTime[0]) + i * oneDayMs)); } // console.log(diffDays, oneDayMs, timeArr); return timeArr; },
5.搜索功能(使用element-ui的示例)
// 搜索數(shù)據(jù)數(shù)組 loadAll() { return [ { "value": "三全鮮食(北新涇店)", "address": "長(zhǎng)寧區(qū)新漁路144號(hào)" }, { "value": "Hot honey 首爾炸雞(仙霞路)", "address": "上海市長(zhǎng)寧區(qū)淞虹路661號(hào)" }, { "value": "新旺角茶餐廳", "address": "上海市普陀區(qū)真北路988號(hào)創(chuàng)邑金沙谷6號(hào)樓113" }, { "value": "瀧千家(天山西路店)", "address": "天山西路438號(hào)" }, { "value": "胖仙女紙杯蛋糕(上海凌空店)", "address": "上海市長(zhǎng)寧區(qū)金鐘路968號(hào)1幢18號(hào)樓一層商鋪18-101" }, { "value": "貢茶", "address": "上海市長(zhǎng)寧區(qū)金鐘路633號(hào)" }, { "value": "豪大大香雞排超級(jí)奶爸", "address": "上海市嘉定區(qū)曹安公路曹安路1685號(hào)" }, { "value": "茶芝蘭(奶茶,手抓餅)", "address": "上海市普陀區(qū)同普路1435號(hào)" }, { "value": "十二瀧町", "address": "上海市北翟路1444弄81號(hào)B幢-107" }, { "value": "星移濃縮咖啡", "address": "上海市嘉定區(qū)新郁路817號(hào)" }, { "value": "阿姨奶茶/豪大大", "address": "嘉定區(qū)曹安路1611號(hào)" }, { "value": "新麥甜四季甜品炸雞", "address": "嘉定區(qū)曹安公路2383弄55號(hào)" } ]; }, 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); },
四、實(shí)現(xiàn)效果
由于需求是以彈框形式實(shí)現(xiàn),沒(méi)有做全屏顯示,具體效果如下:
甘特圖實(shí)現(xiàn)
總結(jié)
到此這篇關(guān)于vue2結(jié)合element-ui的gantt圖實(shí)現(xiàn)可拖拽甘特圖的文章就介紹到這了,更多相關(guān)vue2實(shí)現(xiàn)可拖拽甘特圖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue?動(dòng)態(tài)路由component?傳遞變量報(bào)錯(cuò)問(wèn)題解決
這篇文章主要為大家介紹了vue?動(dòng)態(tài)路由component?傳遞變量報(bào)錯(cuò)問(wèn)題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05vue3.0語(yǔ)法糖內(nèi)的defineProps及defineEmits解析
這篇文章主要介紹了vue3.0語(yǔ)法糖內(nèi)的defineProps及defineEmits解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04vscode搭建vue環(huán)境完整圖文教程(適合新手小白)
Vue框架的優(yōu)秀設(shè)計(jì)和強(qiáng)大的生態(tài)系統(tǒng)成為了越來(lái)越多開(kāi)發(fā)者選擇Vue的原因,在實(shí)際項(xiàng)目過(guò)程中一個(gè)高效的開(kāi)發(fā)環(huán)境能夠大大提高開(kāi)發(fā)效率,這篇文章主要給大家介紹了關(guān)于vscode搭建vue環(huán)境的相關(guān)資料,需要的朋友可以參考下2023-10-10解決Element中el-date-picker組件不回填的情況
這篇文章主要介紹了解決Element中el-date-picker組件不回填的情況,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11詳解vue中父子組件傳遞參數(shù)props的實(shí)現(xiàn)方式
這篇文章主要給大家介紹了在vue中,父子組件傳遞參數(shù)?props?實(shí)現(xiàn)方式,文章通過(guò)代碼示例介紹的非常詳細(xì),對(duì)我們的學(xué)習(xí)或工作有一定的參考價(jià)值,需要的朋友可以參考下2023-07-07vue基于vant實(shí)現(xiàn)上拉加載下拉刷新的示例代碼
普遍存在于各種app中的上拉加載下拉刷新功能,本文主要介紹了vue基于vant實(shí)現(xiàn)上拉加載下拉刷新,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01富文本編輯器quill.js開(kāi)發(fā)之自定義格式擴(kuò)展
這篇文章主要為大家介紹了富文本編輯器quill.js開(kāi)發(fā)之自定義格式擴(kuò)展,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08vue頁(yè)面切換到滾動(dòng)頁(yè)面顯示頂部的實(shí)例
下面小編就為大家分享一篇vue頁(yè)面切換到滾動(dòng)頁(yè)面顯示頂部的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03