vue 实现课程表甘特图

1 封装 components

<template>
	<view style="padding-bottom: 100rpx;">
		<view class="header flex_sb">
			<div class="header_list flex"  v-for="(item,index) in timeList" :key="item.value">
				<div>{{item.text}}</div>
				<div>{{item.value}}</div>
			</div>
		</view>
		<div class="left">
			<div class="box" v-for="(item,index) in dayTime" :key="index">
				<div class="left_list flex">
					<div style="color: #000;font-size: 30rpx;">{{index + 1}}</div>
					<div>{{item.startTime}}</div>
					<div>{{item.endTime}}</div>
				</div>
			</div>
		</div>
		<div class="content">
			<div class="week" v-for="(item,index) in courseData" :key="index">
				<div class="course_list" v-for="(course,courseIndex) in item" :key="courseIndex">
					<div class="course" :style="{height:(course.length * 150) + 'rpx',background:course.backgroundColor}" v-if="course.length > 0">
						{{course.name}}
					</div>
				</div>
			</div>
		</div>
	</view>
</template>

<script>
	export default {
		props:{
			data:{
				type:Array,
				default:() => []
			},
			palette:{
				type:Array,
				default:() => []
			},
			timeList:{
				type:Array,
				default:() => {
					return [{
						text:'星期',
						value:'日期'
					},{
						text:'周一',
						value:'02-02'
					},{
						text:'周二',
						value:'02-03'
					},{
						text:'周三',
						value:'02-04'
					},{
						text:'周四',
						value:'02-05'
					},{
						text:'周五',
						value:'02-06'
					},{
						text:'周六',
						value:'02-07'
					},{
						text:'周日',
						value:'02-08'
					}]
				}
			},
			dayTime:{
				type:Array,
				default:() => {
					return [{
							startTime:'08:10',
							endTime:'09:55'
						},{
							startTime:'09:05',
							endTime:'09:50'
						},{
							startTime:'10:10',
							endTime:'10:55'
						},{
							startTime:'11:05',
							endTime:'11:50'
						},{
							startTime:'14:00',
							endTime:'14:45'
						},{
							startTime:'14:55',
							endTime:'15:40'
						},{
							startTime:'16:00',
							endTime:'16:45'
						},{
							startTime:'16:55',
							endTime:'17:40'
						},{
							startTime:'18:10',
							endTime:'18:55'
						},{
							startTime:'17:05',
							endTime:'17:50'
						},{
							startTime:'20:10',
							endTime:'20:55'
						},{
							startTime:'21:05',
							endTime:'21:50'
						}]
				}
			}
		},
		data(){
			return {
				allPalette: [...this.palette,'#f05261', '#48a8e4', '#ffd061', '#52db9a',
					'#70d3e6', '#52db9a', '#3f51b5', '#f3d147', '#4adbc3', '#673ab7', 
					'#f3db49', '#76bfcd', '#b495e1', '#ff9800', '#8bc34a'],
			}
		},
		computed:{
			courseData(){
				/** 为数据标记背景颜色的函数 */
				let paletteIndex  = 0
				const getBackgroundColor = () => {
					const backgroundColor = this.allPalette[paletteIndex]
					paletteIndex++
					if(paletteIndex >= this.allPalette.length){
						paletteIndex = 0
					}
					return backgroundColor
				}
				
				/** 合并数据 */
				const listMerge = []
				this.data.forEach((list,i) => {
					if(!listMerge[i]) listMerge[i] = []
					list.forEach((item,index) => {
						if (!index) {
							return listMerge[i].push({ name: item, length: 1, backgroundColor: item === '' ? 'none' : getBackgroundColor() })
						}
						if (item === (listMerge[i][index - 1] || {}).name && item) {
						  const sameIndex = (listMerge[i][index - 1] || {}).sameIndex
						  if (sameIndex || sameIndex === 0) {
						    listMerge[i][sameIndex].length++
						    return listMerge[i].push({ name: item, length: 0, sameIndex: sameIndex })
						  }
						  listMerge[i][index - 1].length++
						  return listMerge[i].push({ name: item, length: 0, sameIndex: index - 1 })
						} else {
						  return listMerge[i].push({ name: item, length: 1, backgroundColor: item === '' ? 'none' : getBackgroundColor() })
						}
					})
				})
				return listMerge
			}
		}
	}
</script>

<style lang="scss">
	.header{
		position: relative;
		padding: 0 20rpx;
		font-size: 26rpx;
		&_list{
			flex-direction: column;
			height: 100rpx;
			font-weight: 600;
		}
	}
	.left{
		width: 100%;
		.box{
			width: 100%;
			&::after{
				content: "";
				position: absolute;
				left: 0;
				width: 100%;
				height: 1rpx;
				background: #999;
			}
		}
		&_list{
			flex-direction: column;
			height: 150rpx;
			color: #6a855c;
			font-size: 12rpx;
			width: 11%;
			background: #fae3f9;
		}
	}
	
	.content{
		position: relative;
		position: absolute;
		top: 100rpx;
		left: 88rpx;
		width: calc(100% - 88rpx);
		height: 100%;
		display: flex;
		.week{
			flex: 1;
			width: 0;
			flex-grow: 1;
			display: flex;
			flex-direction: column;
		}
		.course_list{
			word-break: break-all;
			color: white;
		}
		.course{
			border-radius: 10rpx;
			text-align: center;
			display: flex;
			justify-content: center;
			align-items: center;
			font-size: 16rpx;
			padding: 0 4rpx;
			width: 90%;
		}
	}
	
</style>

2 页面中使用

<template>
    <timetable :data="timetables"></timetable>
</template>

<script>
    import Timetable from '@/components/timetable.vue'
    export default {
		components:{Timetable},
        data() {
            return {
                timetables: [
                  ['大学英语', '大学英语', '大学英语', '', '', '', '毛概', '毛概', '', '', '', '选修'],
                  ['', '', '信号与系统', '信号与系统', '模拟电子技术基础', '模拟电子技术基础', '', '', '', '', '', ''],
                  ['大学体育', '大学体育', '形势与政策', '形势与政策', '', '', '', '', '', '', '', ''],
                  ['', '', '', '', '电装实习', '电装实习', '', '', '', '大学体育', '大学体育', ''],
                  ['', '', '数据结构与算法分析', '数据结构与算法分析', '', '', '', '', '信号与系统', '信号与系统', '', ''],
				  ['', '', '数据结构与算法分析', '数据结构与算法分析', '', '', '', '', '信号与系统', '信号与系统', '', ''],
				  ['', '', '数据结构与算法分析', '数据结构与算法分析', '', '', '', '', '信号与系统', '信号与系统', '', '']
                ]
            }
        }
    }
</script>

3 效果图

在这里插入图片描述