基于vue3和element plus实现甘特图
作者:Emma?M
甘特图是一种重要的项目管理工具,它可以通过图形化的方式展示项目的进度和时间表,甘特图通常由一个横轴和一个纵轴组成,甘特图对于项目管理非常重要,所以本文给大家介绍了如何基于vue3和element plus实现甘特图,需要的朋友可以参考下
vue3 + element plus实现甘特图
效果展示
实现思路
甘特图,个人理解就是表格的一种展现形式。左侧填充数据,右侧用图例填充表示时间、工期、进度等信息。
技术选型
常用的ui框架有很多,以前用vue2的时候多搭配element ui,最近在看vue3的内容,所以选择了element plus。所以本文示例使用vue3+element plus实现甘特图。看过原理之后改用其他技术方案也很简单。
代码实现
新建项目 vue3 + element plus
自己搜索吧,有很多,这里不是重点。
封装组件
这里说下为什么要封装成组件,因为项目里可能多处用到类似的功能,总不能每次都拷贝,然后修改。一次封装,多次引用。
上代码:我存放的路径 /src/components/gantt/index.vue
<template> <div class="gantt"> <div class="legend"> <!-- 渲染图例 --> <i class="plan"></i> <label>计划</label> <i class="actuality"></i> <label>实际</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() } /** * 显示计划进度 */ 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() } /** * 显示实际进度 */ 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: '事项', prop: 'title', fixed: 'left', align: 'center', 'min-width': 120 }, { label: '开始', prop: 'planBegin', fixed: 'left', align: 'center', 'min-width': 120 }, { label: '结束', 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>
到此这篇关于基于vue3和element plus实现甘特图的文章就介绍到这了,更多相关vue3 element plus甘特图内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!