基于vue3和element plus實(shí)現(xiàn)甘特圖
vue3 + element plus實(shí)現(xiàn)甘特圖
效果展示
實(shí)現(xiàn)思路
甘特圖,個(gè)人理解就是表格的一種展現(xiàn)形式。左側(cè)填充數(shù)據(jù),右側(cè)用圖例填充表示時(shí)間、工期、進(jìn)度等信息。
技術(shù)選型
常用的ui框架有很多,以前用vue2的時(shí)候多搭配element ui,最近在看vue3的內(nèi)容,所以選擇了element plus。所以本文示例使用vue3+element plus實(shí)現(xiàn)甘特圖??催^(guò)原理之后改用其他技術(shù)方案也很簡(jiǎn)單。
代碼實(shí)現(xiàn)
新建項(xiàng)目 vue3 + element plus
自己搜索吧,有很多,這里不是重點(diǎn)。
封裝組件
這里說(shuō)下為什么要封裝成組件,因?yàn)轫?xiàng)目里可能多處用到類(lèi)似的功能,總不能每次都拷貝,然后修改。一次封裝,多次引用。
上代碼:我存放的路徑 /src/components/gantt/index.vue
<template> <div class="gantt"> <div class="legend"> <!-- 渲染圖例 --> <i class="plan"></i> <label>計(jì)劃</label> <i class="actuality"></i> <label>實(shí)際</label> </div> <el-table :data="data"> <!-- 渲染表格 --> <el-table-column v-for="(column, index) in columnsConfig" :key="index" v-bind="column" ></el-table-column> <el-table-column v-for="monthItem in monthData" :key="monthItem.month" align="center" min-width="80" :prop="monthItem.month" :label="monthItem.month" > <template #header> <span>{{ monthItem.month.substring(5) + '月' }}</span> </template> <el-table-column v-for="day in monthItem.dayArray" :key="day" align="center" :width="50" :prop="day" > <template #header> <span>{{ day.substring(8) }}</span> </template> <template #default="scope"> <i class="plan" v-if="showPlan(scope)"></i> <i class="empty" v-else></i> <i class="actuality" v-if="showActuality(scope)"></i> <i class="empty" v-else></i> </template> </el-table-column> </el-table-column> </el-table> </div> </template> <script setup> import { ref } from 'vue'; const props = defineProps({ data: { type: Array, default: [] }, columnsConfig: { type: Array, default: [] }, ganttConfig: { type: Object, default: { planBeginColumn: 'planBegin', planEndColumn: 'planEnd', actualityBeginColumn: 'actualityBegin', actualityEndColumn: 'actualityEnd' } } }) const monthData = ref({}) const init = () => { let minDate = undefined let maxDate = undefined props.data.forEach((row, index) => { let current = new Date(row[props.ganttConfig.planBeginColumn]) if (minDate) { minDate = minDate.getTime() < current.getTime() ? minDate : current } else { minDate = current } current = new Date(row[props.ganttConfig.planEndColumn]) if (maxDate) { maxDate = maxDate.getTime() > current.getTime() ? maxDate : current } else { maxDate = current } current = props.ganttConfig.actualityBeginColumn || row[props.ganttConfig.actualityBeginColumn] ? new Date(row[props.ganttConfig.actualityBeginColumn]) : undefined if (current) { minDate = minDate.getTime() < current.getTime() ? minDate : current } current = props.ganttConfig.actualityEndColumn || row[props.ganttConfig.actualityEndColumn] ? new Date(row[props.ganttConfig.actualityEndColumn]) : undefined if (current) { maxDate = maxDate.getTime() > current.getTime() ? maxDate : current } }) // 甘特圖前后各放寬2天 minDate = new Date(minDate.getTime() - 2 * 24 * 60 * 60 * 1000) maxDate = new Date(maxDate.getTime() + 2 * 24 * 60 * 60 * 1000) let current = new Date(format(minDate)) while(!isAfter(current, maxDate)) { const month = formatYearMonth(current) const day = format(current) if (monthData.value[month]) { monthData.value[month].dayArray.push(day) } else { monthData.value[month] = { month: month, dayArray: [day] } } // 加一天 current = after(current) } } /** * 格式化 YYYY-MM-DD */ const format = (date) => { const day = String(date.getDate()).padStart(2, '0') return formatYearMonth(date) + '-' + day } /** * 格式化 YYYY-MM */ const formatYearMonth = (date) => { const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') return year + '-' + month } /** * 加一天 */ const after = (date) => { return new Date(date.getTime() + 24 * 60 * 60 * 1000) } /** * date1是否大于等于date2 */ const isAfter = (date1, date2) => { return date1.getTime() >= date2.getTime() } /** * 顯示計(jì)劃進(jìn)度 */ const showPlan = ({row, column}) => { const currentDay = new Date(column.property) const begin = new Date(row[props.ganttConfig.planBeginColumn]) const end = new Date(row[props.ganttConfig.planEndColumn]) return currentDay.getTime() >= begin.getTime() && currentDay.getTime() <= end.getTime() } /** * 顯示實(shí)際進(jìn)度 */ const showActuality = ({row, column}) => { const currentDay = new Date(column.property) const begin = props.ganttConfig.actualityBeginColumn || row[props.ganttConfig.actualityBeginColumn] ? new Date(row[props.ganttConfig.actualityBeginColumn]) : undefined const end = props.ganttConfig.actualityEndColumn || row[props.ganttConfig.actualityEndColumn] ? new Date(row[props.ganttConfig.actualityEndColumn]) : undefined return begin && end && currentDay.getTime() >= begin.getTime() && currentDay.getTime() <= end.getTime() } init() </script> <style scoped> .plan { display: flex; width: calc(100% + 24px); height: 16px; background-color: limegreen; margin: 0 -12px; } .actuality { display: flex; width: calc(100% + 24px); height: 16px; background-color: yellow; margin: 0 -12px; } .empty { display: flex; width: calc(100% + 24px); height: 16px; margin: 0 -12px; } .legend { display: flex; line-height: 40px; flex-direction: row; justify-content: right; align-items: center; padding: 0 20px; * { margin: 0 5px; } i { width: 32px; height: 16px; } } </style>
引用組件
app.vue中引用上面的組件
<script setup> import Gantt from '@/components/gantt/index.vue' const data = ref([ { title: '第一階段', planBegin: '2022-01-01', planEnd: '2022-01-09', actualityBegin: '2022-01-02', actualityEnd: '2022-01-10' }, { title: '第二階段', planBegin: '2022-01-09', planEnd: '2022-01-15', actualityBegin: '2022-01-09', actualityEnd: '2022-01-18' } ]) const columnsConfig = ref([ { label: '事項(xiàng)', prop: 'title', fixed: 'left', align: 'center', 'min-width': 120 }, { label: '開(kāi)始', prop: 'planBegin', fixed: 'left', align: 'center', 'min-width': 120 }, { label: '結(jié)束', prop: 'planEnd', fixed: 'left', align: 'center', 'min-width': 120 } ]) </script> <template> <div> <Gantt :data="data" :columnsConfig="columnsConfig" :ganttConfig ="{ planBeginColumn: 'planBegin', planEndColumn: 'planEnd', actualityBeginColumn: 'actualityBegin', actualityEndColumn: 'actualityEnd' }" /> </div> </template> <style scoped> </style>
到此這篇關(guān)于基于vue3和element plus實(shí)現(xiàn)甘特圖的文章就介紹到這了,更多相關(guān)vue3 element plus甘特圖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Bootrap和Vue實(shí)現(xiàn)仿百度搜索功能
這篇文章主要介紹了使用Bootrap和Vue實(shí)現(xiàn)仿百度搜索功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-10-10vue-cli中vue本地實(shí)現(xiàn)跨域調(diào)試接口
這篇文章主要介紹了vue-cli中vue本地實(shí)現(xiàn)跨域調(diào)試接口,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01Vue3中ref和reactive的使用場(chǎng)景詳解
這篇文章主要介紹了Vue3中ref和reactive的使用場(chǎng)景,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04vue引用BootStrap以及引用bootStrap-vue.js問(wèn)題
這篇文章主要介紹了vue引用BootStrap以及引用bootStrap-vue.js問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10Vue Autocomplete 自動(dòng)完成功能簡(jiǎn)單示例
這篇文章主要介紹了Vue Autocomplete 自動(dòng)完成功能,結(jié)合簡(jiǎn)單示例形式分析了Vue使用el-autocomplete組件實(shí)現(xiàn)自動(dòng)完成功能相關(guān)操作技巧,需要的朋友可以參考下2019-05-05vue實(shí)現(xiàn)折疊展開(kāi)收縮動(dòng)畫(huà)效果
這篇文章主要介紹了vue實(shí)現(xiàn)折疊展開(kāi)收縮動(dòng)畫(huà),通過(guò)scrollHeight實(shí)現(xiàn),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2023-11-11