Vue3 + FullCalendar日历添加事件

737 阅读2分钟
  • 效果展示

image.png

  • 依赖包下载
cnpm install @fullcalendar/vue3 @fullcalendar/core @fullcalendar/daygrid
  • 完整组件代码
<template>
	<div class="right_container">
		<div class="header_container">
			<el-date-picker
				v-model="dateValue"
				type="month"
				value-format="YYYY-MM"
				placeholder="选择月"
				@change="dateChange"
				:clearable="false"
				size="default"
				style="width: 100px"
			>
			</el-date-picker>
		</div>
		<div class="full_calendar_container">
			<FullCalendar ref="calendarRef" :options="calendarOptions" />
		</div>
	</div>
</template>

<script setup lang="ts">
import { ref, watch, onMounted, reactive, toRefs } from 'vue';
import FullCalendar from '@fullcalendar/vue3';
import dayGridPlugin from '@fullcalendar/daygrid';
import zhLocale from '@fullcalendar/core/locales/zh-cn';
import { flightTaskManageApi } from '@/api/flightTaskManage';

// @ts-ignore
import { ElMessage } from 'element-plus';

const getCurrentYearMonth = (useUTC = false) => {
	const now = new Date();

	// 获取年份
	const year = useUTC ? now.getUTCFullYear() : now.getFullYear();

	// 获取月份(0-11),并格式化为两位数
	const month = (useUTC ? now.getUTCMonth() : now.getMonth()) + 1;
	const formattedMonth = month.toString().padStart(2, '0');

	return `${year}-${formattedMonth}`;
};
const state = reactive({
	loading: false,
	dateValue: getCurrentYearMonth(),
	dataList: [] as any[],
});
const { loading, dateValue, dataList } = toRefs(state);

// 日历ref
const calendarRef = ref();

// 日历配置项
const calendarOptions: any = ref({
	plugins: [dayGridPlugin],
	initialView: 'dayGridMonth',
	locales: [zhLocale],
	locale: 'zh-cn',
	// 日期显示配置
	fixedWeekCount: false,
	// contentHeight: 'auto',  // 高度设置为auto
	showNonCurrentDates: true, // 显示跨月日期
	// 隐藏头部工具栏
	// headerToolbar: false,
	headerToolbar: {
		// 头部toolba
		left: 'prev',
		// left: 'prev,next today',
		center: 'title',
		// right: 'timeGridDay,timeGridWeek,dayGridMonth',
		right: 'next',
	},

	// 星期标题定制
	dayHeaderContent: (args: any) => {
		const weekDays = ['日', '一', '二', '三', '四', '五', '六'];
		return weekDays[args.date.getDay()];
	},
	// 日期格子定制
	dayCellContent: (args: any) => {
		return {
			html: `<div class="day-cell">
                        ${args.dayNumberText.padStart(2, '0')}
                     </div>`,
		};
	},
	// 行高设置

	//   dayMaxEvents: 3, // 最大显示3个事件
	//  dayMaxEventRows: 1, // 强制单行显示

	eventTimeFormat: {
		// 时间格式
		hour: '2-digit',
		minute: '2-digit',
		second: '2-digit',
		meridiem: 'short',
	},
	// 初始日期设置
	initialDate: calendarRef.value,
	// 样式配置
	dayHeaderClassNames: 'custom-header',
	dayCellClassNames: 'custom-cell',
	height: '100%', // 高度设置为100%

	// events: [
	// 	//日程事件的json
	// 	{ title: 'event 1', date: '2025-04-23 12:00:00' },
	// 	{ title: 'event 2', date: '2025-04-24 05:59:23' },
	// 	{ title: 'event 3', date: '2025-04-25 08:23:00' },
	// 	{ title: 'event 4', date: '2025-04-25 09:30:00' },
	// 	{ title: 'event 5', date: '2025-04-26 12:00:00' },
	// 	{ title: 'event 2', date: '2025-04-26 15:00:00' },
	// 	{ title: 'event 2', date: '2025-04-26 15:00:00' },
	// 	{ title: 'event 2', date: '2025-04-26 15:00:00' },
	// ],
	// events: matchList.value, //绑定展示事件
});

// 模版
const template = {
	// 已执行
	temp1: {
		title: '已执行',
		start: '2025-05-23',
		color: '#fff', // 背景色
		textColor: '#00D188', // 文字颜色
		// 使用自定义 CSS 类
		classNames: ['temp1'],
	},
	// 执行中
	temp2: {
		title: '执行中',
		start: '2025-05-23',
		color: '#fff', // 背景色
		textColor: '#348CF2', // 文字颜色
		// 使用自定义 CSS 类
		classNames: ['temp2'],
	},
	// 任务失败
	temp3: {
		title: '任务失败',
		start: '2025-05-23',
		color: '#fff', // 背景色
		textColor: '#FF0000', // 文字颜色
		// 使用自定义 CSS 类
		classNames: ['temp3'],
	},
	// 已取消
	temp4: {
		title: '已取消',
		start: '2025-05-23',
		color: '#fff', // 背景色
		textColor: '#FF8C4A', // 文字颜色
		// 使用自定义 CSS 类
		classNames: ['temp4'],
	},
	// 待执行
	temp5: {
		title: '待执行',
		start: '2025-05-23',
		color: '#fff', // 背景色
		textColor: '#979797', // 文字颜色
		// 使用自定义 CSS 类
		classNames: ['temp5'],
	},
};

// 日程事件的json
const matchList = ref([
	{
		time: '2025-05-23',
		type: '1',
		num: 10,
	},

	{
		time: '2025-05-23',
		type: '2',
		num: 10,
	},
	{
		time: '2025-05-23',
		type: '3',
		num: 10,
	},
	{
		time: '2025-05-23',
		type: '4',
		num: 10121,
	},
	{
		time: '2025-05-23',
		type: '5',
		num: 5951,
	},
]);

// 日期改变
const dateChange = (val: string) => {
	console.log(val);
	// refreshEvents();
	gotoToday(val);
	// 获取新数据
	getDataList();
};

// 获取数据列表
const getDataList = async () => {
	loading.value = true;
	const params = {
		date: dateValue.value,
	};
	try {
		const { msg, data, code } = await flightTaskManageApi().getDataList(params);
		if (code == 1000) {
			dataList.value = data.list;
		} else {
			ElMessage({
				message: msg,
				type: 'error',
			});
		}
	} catch (err) {
		console.log(err);
	} finally {
		loading.value = false;
	}
};

onMounted(() => {
	getDataList();
});

// 获取日历 API
const getCalendarApi = () => {
	return calendarRef.value.getApi();
};

// 示例1:重新渲染日历
const refreshCalendar = () => {
	const calendarApi = getCalendarApi();
	calendarApi.render(); // 强制重新渲染
	calendarApi.updateSize(); // 更新尺寸(容器大小变化时使用)
};

// 示例2:跳转到指定日期
const gotoToday = (val: string) => {
	getCalendarApi().gotoDate(val); // 跳转今日
};

// 示例3:动态更新事件
const refreshEvents = () => {
	const calendarApi = getCalendarApi();

	// 先移除所有事件
	calendarApi.getEvents().forEach((event: any) => event.remove());

	// 模拟异步获取新数据
	//   const newEvents = await fetch('/api/events')

	// 添加新事件(模拟异步获取数据)

	// 添加新事件
	// newEvents.forEach((event) => {
	// 	calendarApi.addEvent(event);
	// });
	setTimeout(() => {
		calendarApi.addEvent({
			id: '1',
			title: '第一个任务',
			start: '2025-04-23',
			allDay: true,
			color: '#FECACA',
			textColor: '#6B7280',
		});
	}, 0);
};
// 监听dataList变化,重新渲染日历
watch(dataList, (newVal, oldVal) => {
	const calendarApi = getCalendarApi();
	calendarApi.getEvents().forEach((event: any) => event.remove());
	matchList.value.forEach((item: any) => {
		let temp: any;
		switch (item.type) {
			case '1':
				temp = template.temp1;
				break;
			case '2':
				temp = template.temp2;
				break;
			case '3':
				temp = template.temp3;
				break;
			case '4':
				temp = template.temp4;
				break;
			case '5':
				temp = template.temp5;
				break;
		}
		let obj = {
			...temp,
			title: `${temp.title}(${item.num})`,
			start: item.time,
		};
		calendarApi.addEvent(obj);
	});
});
</script>

<style scoped lang="scss">
.right_container {
	height: 100%;
	flex: 90;
	background-color: #fff;
	padding: 15px;
	display: flex;
	flex-direction: column;
	.header_container {
		margin-bottom: 15px;
		display: flex;
		align-items: center;
		justify-content: space-between;
	}
	.full_calendar_container {
		flex: 1;
	}
	::v-deep .fc {
		--fc-daygrid-day-frame-height: 115px; /* 固定单元格高度 */
	}
	::v-deep .fc-daygrid-day {
		height: var(--fc-daygrid-day-frame-height);
		min-height: var(--fc-daygrid-day-frame-height);
		overflow: hidden;
		position: relative;
	}
	/* 自定义事件样式 */
	::v-deep .temp1 {
		font-weight: bold;
		display: flex;
		align-items: center;
		&::before {
			content: '●';
			color: #00d188;
			font-size: 16px;
		}
	}

	::v-deep .temp2 {
		font-weight: bold;
		display: flex;
		align-items: center;
		&::before {
			content: '●';
			color: #348cf2;
			font-size: 16px;
		}
	}
	::v-deep .temp3 {
		font-weight: bold;
		display: flex;
		align-items: center;
		&::before {
			content: '●';
			color: #ff0000;
			font-size: 16px;
		}
	}
	::v-deep .temp4 {
		font-weight: bold;
		display: flex;
		align-items: center;
		&::before {
			content: '●';
			color: #ff8c4a;
			font-size: 16px;
		}
	}
	::v-deep .temp5 {
		font-weight: bold;
		display: flex;
		align-items: center;
		&::before {
			content: '●';
			color: #979797;
			font-size: 16px;
		}
	}
	// 表头星期的样式
	::v-deep .fc .fc-col-header-cell-cushion {
		padding: 8px 4px;
	}
	::v-deep .fc-col-header .fc-scrollgrid-sync-inner {
		background: #dfe7ff;
	}
}
</style>