引言
在项目中有个需要日历多选的控件,一开始用了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>
自行领取使用哦~