vue封装移动端日历,可折叠展开,以及考勤
效果图:
注意:日历中小圆点可代表考勤
可收起
代码:
<template><div class="calendar-container"><!-- 头部导航 --><div class="calendar-header"><!-- <button @click="prevMonth" class="calendar-nav-btn">上月</button> --><van-icon name="arrow-left" @click="prevMonth" class="calendar-nav-btn" /><h3>{{ currentYear }}年{{ currentMonth }}月</h3><van-icon name="arrow" @click="nextMonth" class="calendar-nav-btn" /><!-- <button @click="nextMonth" class="calendar-nav-btn">下月</button> --></div><!-- 星期标题 --><div class="calendar-weekdays"><span v-for="day in weekdays" :key="day" class="weekday">{{ day }}</span></div><!-- 日历收起部分 --><div class="calendar-days" v-if="!isExpand"><!-- 当月日期 --><span v-for="day in weekDates" :key="day.id" class="calendar-day" :class="{'today': day.isToday,'selected': isSelectedZD(day),'disabled': isDisabled(day.date)}" @click="!isDisabled(day.date) && selectDate(day.date)">{{ formatDatess(day) }}<span v-if="isTrueEheck(day.date)" :style="{ backgroundColor: bgcColor(day.date) }"class="cirel"></span></span></div><!-- 收齐按钮 --><div class="open-btn" v-if="!isExpand" @click="() => {isExpand = true}"><van-icon name="arrow-down" /></div><!-- 日期网格展开部分 --><div class="calendar-days" v-if="isExpand"><!-- 上个月的日期(灰色显示) --><span v-for="prevDay in prevMonthDays" :key="prevDay.id" class="calendar-day prev-month-day">{{ prevDay.day }}</span><!-- 当月日期 --><span v-for="day in currentMonthDays" :key="day.id" class="calendar-day" :class="{'today': day.isToday,'selected': isSelected(day.date),'disabled': isDisabled(day.date)}" @click="!isDisabled(day.date) && selectDate(day.date)">{{ day.day }}<span v-if="isTrueEheck(day.date)" :style="{ backgroundColor: bgcColor(day.date) }"class="cirel"></span></span><!-- 下个月的日期(灰色显示) --><span v-for="nextDay in nextMonthDays" :key="nextDay.id" class="calendar-day next-month-day">{{ nextDay.day }}</span></div><!-- 收齐按钮 --><div class="open-btn" v-if="isExpand" @click="() => {isExpand = false}"><van-icon name="arrow-up" /></div></div>
</template><script>
export default {name: 'Calendar',props: {// 默认选中日期,格式:YYYY-MM-DDdefaultValue: {type: String,default: ''},// 最小可选日期,格式:YYYY-MM-DDminDate: {type: String,default: ''},// 最大可选日期,格式:YYYY-MM-DDmaxDate: {type: String,default: ''},// 禁用的日期数组,格式:['YYYY-MM-DD', 'YYYY-MM-DD']disabledDates: {type: Array,default: () => []},// 禁用的日期数组,格式:['YYYY-MM-DD', 'YYYY-MM-DD']checkList: {type: Array,default: () => []}},data() {return {currentDate: new Date(),weekdays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],selectedDate: null,isExpand: true,todatL: new Date(),jintian: new Date()}},computed: {currentYear() {return this.currentDate.getFullYear()},currentMonth() {return this.currentDate.getMonth() + 1},// 当月第一天是星期几(0-6)firstDayOfMonth() {const firstDay = new Date(this.currentYear, this.currentMonth - 1, 1)return firstDay.getDay()},// 当月的总天数daysInMonth() {return new Date(this.currentYear, this.currentMonth, 0).getDate()},// 上个月的总天数daysInPrevMonth() {return new Date(this.currentYear, this.currentMonth - 1, 0).getDate()},// 当月日期数据currentMonthDays() {const days = []const today = new Date()const isCurrentMonth =today.getFullYear() === this.currentYear &&today.getMonth() === this.currentMonth - 1for (let i = 1; i <= this.daysInMonth; i++) {const date = new Date(this.currentYear, this.currentMonth - 1, i)const dateStr = this.formatDate(date)days.push({id: `current-${i}`,day: i,date: dateStr,isToday: isCurrentMonth && today.getDate() === i})}return days},// 上个月需要显示的日期数据prevMonthDays() {const days = []// 上个月需要显示的天数(根据当月第一天是星期几来确定)const displayCount = this.firstDayOfMonthfor (let i = displayCount; i > 0; i--) {const day = this.daysInPrevMonth - i + 1const date = new Date(this.currentYear, this.currentMonth - 2, day)const dateStr = this.formatDate(date)days.push({id: `prev-${day}`,day,date: dateStr})}return days},// 下个月需要显示的日期数据nextMonthDays() {const days = []// 当月最后一天是星期几const lastDayOfMonth = new Date(this.currentYear, this.currentMonth - 1, this.daysInMonth).getDay()// 下个月需要显示的天数(凑够42个格子,6行7列)const displayCount = 42 - (this.prevMonthDays.length + this.currentMonthDays.length)for (let i = 1; i <= displayCount; i++) {const date = new Date(this.currentYear, this.currentMonth, i)const dateStr = this.formatDate(date)days.push({id: `next-${i}`,day: i,date: dateStr})}return days},weekDates() {if (!this.todatL) return [];// 转换为 Date 对象const baseDate = new Date(this.todatL);// 获取选中日期的星期(周日=0,周一=1,...,周六=6)const dayOfWeek = baseDate.getDay();const dates = [];// 计算从周日到周六的偏移量:周日为 -dayOfWeek,依次递增到周六为 6 - dayOfWeekfor (let i = -dayOfWeek; i <= 6 - dayOfWeek; i++) {const newDate = new Date(baseDate);newDate.setDate(baseDate.getDate() + i); // 计算偏移后的日期dates.push(newDate);}return dates;}},mounted() {// 如果有默认值,设置选中日期if (this.defaultValue) {this.selectedDate = this.defaultValue}},methods: {// 格式化日期为 YYYY-MM-DDformatDate(date) {const year = date.getFullYear()const month = String(date.getMonth() + 1).padStart(2, '0')const day = String(date.getDate()).padStart(2, '0')return `${year}-${month}-${day}`},// 切换到上个月prevMonth() {this.currentDate = new Date(this.currentYear, this.currentMonth - 2, 1)},// 切换到下个月nextMonth() {this.currentDate = new Date(this.currentYear, this.currentMonth, 1)},// 判断日期是否被选中isSelected(date) {return this.selectedDate === date},// 判断是否存在考勤isTrueEheck(date) {let isTru = falsethis.checkList.forEach(item => {if (date == item.rq) {isTru = true}})return isTru},// 判断显示颜色bgcColor(date) {let color = ''this.checkList.forEach(item => {if (date == item.rq) {color = item.color}})return color},// 判断日期是否被禁用isDisabled(date) {// 检查是否在最小日期之前if (this.minDate && date < this.minDate) {return true}// 检查是否在最大日期之后if (this.maxDate && date > this.maxDate) {return true}// 检查是否在禁用日期列表中if (this.disabledDates.includes(date)) {return true}return false},// 选择日期selectDate(date) {this.todatL = datethis.jintian = datethis.selectedDate = datethis.$emit('select', date)},// 折叠数据处理formatDatess(date) {const day = String(date.getDate()).padStart(2, '0'); // 日期补零return day;},// 判断折叠是否选中isSelectedZD(date) {console.log(this.jintian, '111111111')const date2 = new Date(this.jintian)const date1 = new Date(date)return date2.getDate() == date1.getDate();}}
}
</script><style scoped lang="scss">
.calendar-container {font-family: Arial, sans-serif;max-width: 400px;margin: 0 auto;padding: 0 15px;border: 1px solid #e0e0e0;/* border-radius: 8px; */background-color: #fff;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}.calendar-header {display: flex;justify-content: center;align-items: center;/* margin-bottom: 15px; */
}.calendar-nav-btn {/* background-color: #f0f0f0; */border: none;padding: 8px 12px;border-radius: 4px;cursor: pointer;transition: background-color 0.2s;
}/* .calendar-nav-btn:hover {background-color: #e0e0e0;
} */.calendar-weekdays {display: grid;grid-template-columns: repeat(7, 1fr);margin-bottom: 10px;
}.weekday {text-align: center;font-weight: bold;color: #666;padding: 5px;
}.calendar-days {display: grid;grid-template-columns: repeat(7, 1fr);gap: 5px;
}.calendar-day {position: relative;display: flex;justify-content: center;align-items: center;height: 40px;border-radius: 4px;cursor: pointer;transition: background-color 0.2s;
}.calendar-day:hover:not(.disabled) {background-color: #f0f0f0;
}.prev-month-day,
.next-month-day {color: #ccc;cursor: default;
}.today {background-color: #e7f3ff;font-weight: bold;
}.selected {background-color: #2196f3 !important;color: white;
}.disabled {color: #ddd;cursor: not-allowed;background-color: #f9f9f9;
}.cirel {position: absolute;top: 6px;right: 6px;width: 5px;height: 5px;border-radius: 50%;background-color: red;
}.open-btn {width: 100%;text-align: center;
}
</style>
使用
<template><div class="work-home-main"><calendar :checkList="checkList" @select="handleDateSelect" /></div>
</template><script>
import calendar from './components/calendar.vue'
export default {components: { calendar },props: {},data() {return {// 考勤时间,以及小圆点颜色数组,入不需要考勤则可不传checkList: [{rq: '2025-05-09',color: '#13891f'},{rq: '2025-06-09',color: '#13891f'},{rq: '2025-06-08',color: '#f59a23'},{rq: '2025-06-07',color: '#f59a23'}]}},computed: {},created() { },mounted() {},watch: {},methods: {// 点击日历获取日期handleDateSelect(val) {this.listParam.rq = valconsole.log(val)}}
}
</script>
<style scoped lang="scss">
</style>