AI手写日历可多选控件分享

引言

        在项目中有个需要日历多选的控件,一开始用了uniapp的插件,但效果没这么满意,所以结合chat手撸了一个日历插件。支持选中的背景图上传以及开始时间和结束时间的传递。

以下是效果图。

源代码

<template>
	<view class="calendar-container" ref="calendarContainer">
		<view class="canlendar-header">
			<view class="header">
				<text>{{ CurrentData.year }}年{{ CurrentData.month }}月</text>
			</view>
			<view class="weekdays">
				<view v-for="(day, index) in weekdays" :key="index">{{ day }}</view>
			</view>
		</view>
		<scroll-view scroll-y="true" style="height: 80%" @scroll="handleScroll">
			<view v-for="(monthData, monthIndex) in months" :key="monthIndex" class="month-container">
				<view v-if="monthIndex != 0" class="header">
					<text>{{ monthData.year }}年{{ monthData.month }}月</text>
				</view>
				<view class="calendar">
					<view class="day" v-for="(day, index) in monthData.days" :key="index" :class="{ empty: day.date === null }" @click="selectDate(day)">
						<image v-if="isSelected(day)" :src="imagePath" class="selected-image"></image>
						<view v-if="day.data !== null" :style="{ color: day.flag ? '#000' : '#ccc' }">{{ day.date }}</view>
					</view>
				</view>
			</view>
		</scroll-view>
		<view class="tui-btn-box">
			<view class="tui-btn" @tap="confirmSelection">确定</view>
		</view>
	</view>
</template>

<script>
export default {
	props: {
		initialSelectedDates: {
			type: Array,
			default: () => []
		},
		BGimage: {
			type: String,
			default: () => ''
		},
		startDate: {
			type: String,
			default: () => new Date() // 当前日期
		},
		endDate: {
			type: String,
			default: () => ''
		}
	},
	data() {
		return {
			months: [],
			selectedDates: [...this.initialSelectedDates],
			CurrentData: {
				year: null,
				month: null
			},
			imagePath: this.BGimage || '/static/images/home/dogHand.png',
			weekdays: ['日', '一', '二', '三', '四', '五', '六']
		};
	},
	mounted() {
		this.NewloadCalendar();
	},
	methods: {
		NewloadCalendar() {
			const result = [];

			// 将字符串日期转换为 Date 对象
			let start = new Date(this.startDate);
			let end;

			// 处理 endDate
			if (this.endDate) {
				end = new Date(this.endDate);
			} else {
				// endDate为空, 将 end 设置为 start 的两个自然月之后
				end = new Date(start);
				end.setMonth(end.getMonth() + 2);
			}

			// 确保 endDate 在 startDate 之后
			if (end < start) {
				uni.showToast({
					title: '结束日期必须晚于开始日期',
					icon: 'none',
					mask: true
				});
				return;
			}

			// 定义最大处理范围(在这里设置为一年)
			const maxDateRange = 365; // 365天

			// 计算日期差
			const dateDiff = Math.floor((end - start) / (1000 * 60 * 60 * 24));
			if (dateDiff > maxDateRange) {
				uni.showToast({
					title: '日期范围过长,最大可处理范围为一年',
					icon: 'none',
					mask: true
				});
				return;
			}

			let current = new Date(start);

			// 修复逻辑,确保包括最后一个月的日期
			while (current <= end) {
				const year = current.getFullYear();
				const month = current.getMonth(); // 月份是0开始的
				const days = [];

				// 找到一个月的最后一天
				const lastDay = new Date(year, month + 1, 0).getDate(); // 当月最后一天

				// 获取当前月份第一天是星期几(0 = 星期天,6 = 星期六)
				let firstDayOfWeek = new Date(year, month, 1).getDay();

				for (var i = 0; i < firstDayOfWeek; i++) {
					days.push({
						date: null // 空白日期
					});
				}
				if (!result.length) {
					for (var i = 0; i < start.getDate(); i++) {
						days.push({
							date: i + 1,
							month: month + 1, // 转换为1-12
							year: year,
							flag: false
						});
					}
				}
				// 收集当前月的所有日期
				for (let day = 1; day <= lastDay; day++) {
					const dateObj = {
						date: day,
						month: month + 1, // 转换为1-12
						year: year,
						flag: true
					};

					// 创建当前日期(注意这里 month 不减1)
					const currentDate = new Date(year, month, day);

					// 判断当前日期是否在范围内(包括今天的日期)
					if (currentDate >= start && currentDate <= end) {
						days.push(dateObj);
					}
				}

				// 如果找到任何天数,将其添加到结果中
				if (days.length > 0) {
					result.push({
						year: year,
						month: month + 1, // 转换为1-12
						days: days,
						firstDayOfWeek: firstDayOfWeek // 增加第一天是星期几的判断
					});
				}

				// 移动到下一个月
				current.setMonth(month + 1); // 确保每次设定为下一个月
				current.setDate(1); // 设置为该月的第一天
			}

			this.months = result;
			// start.getDay()
			// for(var i = 0;i<start.getDay();i++){
			// 	this.months[0].push()
			// }
			// this.months.
			this.CurrentData = {
				year: result[0]?.year ?? null,
				month: result[0]?.month ?? null
			};
			this.checkfirstDay()
			console.log(this.months, 'months');
		},
		checkfirstDay() {
			var startDate = new Date(this.startDate)
			var start = startDate.getDate()
			var Xingqi = new Date(startDate.getFullYear(),startDate.getMonth(),1).getDay()
			this.months[0].days[start+Xingqi-1].flag = true;
		},
		selectDate(day) {
			if (day.date === null) return;
			if (!day.flag) return false;
			const dateStr = `${day.year}-${day.month}-${day.date}`;
			if (this.selectedDates.includes(dateStr)) {
				this.selectedDates = this.selectedDates.filter((date) => date !== dateStr);
			} else {
				this.selectedDates.push(dateStr);
				this.selectedDates.sort((a, b) => new Date(a) - new Date(b));
			}
		},
		isSelected(day) {
			if (day.date === null) return false;
			if (!day.flag) return false;
			const dateStr = `${day.year}-${day.month}-${day.date}`;
			return this.selectedDates.includes(dateStr);
		},
		confirmSelection() {
			this.selectedDates.sort((a, b) => new Date(a) - new Date(b));
			this.$emit('confirm', this.selectedDates);
			console.log('已确认选中的日期:', this.selectedDates);
			this.$emit('close');
		},
		handleScroll(event) {
			const scrollTop = event.detail.scrollTop;
			const monthHeight = 400;
			const scrollBottom = scrollTop + 400;
			let currentMonthIndex = Math.floor(scrollTop / monthHeight);

			if (currentMonthIndex < 0) {
				currentMonthIndex = 0;
			} else if (currentMonthIndex >= this.months.length) {
				currentMonthIndex = this.months.length - 1;
			}

			if (scrollBottom > (currentMonthIndex + 1) * monthHeight) {
				currentMonthIndex++;
			}

			if (currentMonthIndex < this.months.length) {
				this.CurrentData = {
					year: this.months[currentMonthIndex].year,
					month: this.months[currentMonthIndex].month
				};
			}
		}
	}
};
</script>

<style scoped>
.calendar-container {
	width: 100%;
	margin: 0 auto;
	height: 60vh;
}
.canlendar-header {
	width: 100%;
	box-shadow: 5px 5px 10px rgba(51, 51, 51, 0.1);
}
.header {
	text-align: left;
	font-size: 20px;
	margin: 20rpx;
}
.weekdays {
	display: grid;
	grid-template-columns: repeat(7, 1fr);
	margin-bottom: 5px;
	text-align: center;
	color: #999;
}
.calendar {
	display: grid;
	grid-template-columns: repeat(7, 1fr);
	gap: 5px;
	margin-left: 20rpx;
}
.day {
	width: 40px;
	height: 40px;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	cursor: pointer;
	position: relative;
}

.day.selected {
	background-color: transparent;
	color: white;
	font-weight: bold;
}

.selected-image {
	width: 40px;
	height: 40px;
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	z-index: -1;
}

.day.empty {
	background-color: transparent;
	cursor: default;
}
.month-container {
}
.tui-btn-box {
	position: fixed;
	bottom: 0;
	left: 0;
	margin: auto;
	height: 100rpx;
	background-color: #fff;
	width: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
}
.tui-btn {
	margin: auto;
	margin-top: 10px;
	width: 80%;
	border-radius: 30rpx;
	background-color: rgba(255, 115, 115, 1);
	height: 60rpx;
	margin-bottom: 20rpx;
	line-height: 60rpx;
	color: #fff;
	text-align: center;
}
</style>

 自行领取使用哦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值