基于vue3和element plus实现甘特图

 更新时间:2024年06月02日 10:27:05   作者: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甘特图内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用Bootrap和Vue实现仿百度搜索功能

    使用Bootrap和Vue实现仿百度搜索功能

    这篇文章主要介绍了使用Bootrap和Vue实现仿百度搜索功能,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-10-10
  • 前端必知必会之Vue $emit()方法详解

    前端必知必会之Vue $emit()方法详解

    这篇文章主要介绍了前端必知必会之Vue $emit()方法的相关资料,Vue.js中的$emit()方法用于在子组件中创建自定义事件,并在父组件中捕获这些事件,这在需要从子组件向父组件传递信息的大型项目中非常有用,需要的朋友可以参考下
    2025-02-02
  • vue-cli中vue本地实现跨域调试接口

    vue-cli中vue本地实现跨域调试接口

    这篇文章主要介绍了vue-cli中vue本地实现跨域调试接口,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-01-01
  • vue2.0父子组件间通信的实现方法

    vue2.0父子组件间通信的实现方法

    本篇文章主要介绍了vue2.0父子组件间通信的实现方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-04-04
  • Vue3中ref和reactive的使用场景详解

    Vue3中ref和reactive的使用场景详解

    这篇文章主要介绍了Vue3中ref和reactive的使用场景,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-04-04
  • vue引用BootStrap以及引用bootStrap-vue.js问题

    vue引用BootStrap以及引用bootStrap-vue.js问题

    这篇文章主要介绍了vue引用BootStrap以及引用bootStrap-vue.js问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10
  • vue中axios的二次封装实例讲解

    vue中axios的二次封装实例讲解

    在本篇文章里小编给大家整理了关于vue中axios的二次封装实例以及相关知识点总结,需要的朋友们可以学习下。
    2019-10-10
  • 详解Nuxt.js Vue服务端渲染摸索

    详解Nuxt.js Vue服务端渲染摸索

    本篇文章主要介绍了详解Nuxt.js Vue服务端渲染摸索,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-02-02
  • Vue Autocomplete 自动完成功能简单示例

    Vue Autocomplete 自动完成功能简单示例

    这篇文章主要介绍了Vue Autocomplete 自动完成功能,结合简单示例形式分析了Vue使用el-autocomplete组件实现自动完成功能相关操作技巧,需要的朋友可以参考下
    2019-05-05
  • vue实现折叠展开收缩动画效果

    vue实现折叠展开收缩动画效果

    这篇文章主要介绍了vue实现折叠展开收缩动画,通过scrollHeight实现,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧
    2023-11-11

最新评论