<template>
<div class="calendar-panel">
<!-- 日历头部 -->
<div class="calendar-header">
<button @click="changeMonth(-1)"><</button>
<span v-if="currentdataType=='day'" @click="showYearMonthPanel" class="header-text">
{{ currentMoment.format('YYYY年 MM月') }}
</span>
<span v-if="currentdataType=='month'" @click="showYearMonthPanel" class="header-text">
{{ currentMoment.format('YYYY年') }}
</span>
<button @click="changeMonth(1)">></button>
</div>
<!-- 主日历面板 -->
<div class="calendar-content">
<!-- 星期表头 -->
<div class="calendar-weeks" v-show="currentdataType=='day'">
<div v-for="week in weeks" :key="week" class="week-item">{{ week }}</div>
</div>
<!-- 日期格子 -->
<div class="calendar-days" v-if="currentdataType=='day'">
<div v-for="day in days" :key="day.date" class="day-item" :class="{
'other-month': !day.currentMonth,
'selected': isSelected(day.date),
'today': isToday(day.date)
}" @click="selectDate(day.date)">
{{ day.day }}
</div>
</div>
<div class="calendar-months" v-if="currentdataType=='month'">
<div v-for="month in monthss" :key="month" class="month-item" :class="{
'selected': isSelected(month.month),'today': isToday(month.month),
}" @click="selectDate(month.month)">
{{ month.key }}
</div>
</div>
</div>
<!-- 年月选择面板 -->
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import moment from 'moment'
import 'moment/locale/zh-cn' // 设置中文
moment.locale('zh-cn') // 使用中文
const props = defineProps({
modelValue: {
type: String,
default: moment().format('YYYY-MM-DD')
},
type: {
type: String,
default: 'day'
}
})
const currentdataType = ref(props.type)
const emit = defineEmits(['update:modelValue'])
// 基础数据
const weeks = ['日', '一', '二', '三', '四', '五', '六']
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
// 当前显示的日期
const currentMoment = ref(moment(props.modelValue))
// 年月选择面板控制
const showYearMonth = ref(false)
const tempYear = ref(currentMoment.value.year())
const tempMonth = ref(currentMoment.value.month())
const yearRangeStart = ref(Math.floor(currentMoment.value.year() / 10) * 10)
// 计算当月所有日期
const days = computed(() => {
const days = []
const firstDay = moment(currentMoment.value).startOf('month')
const lastDay = moment(currentMoment.value).endOf('month')
// 上月补充的天数
const firstDayWeek = firstDay.day()
for (let i = firstDayWeek; i > 0; i--) {
const date = moment(firstDay).subtract(i, 'days')
days.push({
date: date,
day: date.date(),
currentMonth: false
})
}
// 当月的天数
for (let i = 1; i <= lastDay.date(); i++) {
const date = moment(currentMoment.value).date(i)
days.push({
date: date,
day: i,
currentMonth: true
})
}
// 下月补充的天数
const lastDayWeek = lastDay.day()
for (let i = 1; i < 7 - lastDayWeek; i++) {
const date = moment(lastDay).add(i, 'days')
days.push({
date: date,
day: date.date(),
currentMonth: false
})
}
return days
})
const monthss = computed(() => {
const months = []
const firstDay = moment(currentMoment.value).year()
for (let i = 1; i <=12; i++) {
months.push({
month:moment(`${firstDay}-${i<10?i.toString().padStart(2, '0'):i}`),
date:`${firstDay}-${i<10?i.toString().padStart(2, '0'):i}`,
key:i+'月'
})
}
return months
})
// 年份范围
const yearRange = computed(() => {
return Array.from({ length: 12 }, (_, i) => yearRangeStart.value + i - 1)
})
const yearRangeText = computed(() => {
return `${yearRangeStart.value} - ${yearRangeStart.value + 9}`
})
// 显示年月选择面板
const showYearMonthPanel = () => {
showYearMonth.value = true
tempYear.value = currentMoment.value.year()
tempMonth.value = currentMoment.value.month()
yearRangeStart.value = Math.floor(currentMoment.value.year() / 10) * 10
}
// 切换年份范围
const changeYearRange = (offset) => {
yearRangeStart.value += offset
}
// 切换月份
const changeMonth = (offset) => {
if (currentdataType.value == 'day') {
currentMoment.value = moment(currentMoment.value).add(offset, 'months')
} else if (currentdataType.value == 'month') {
currentMoment.value = moment(currentMoment.value).add(offset, 'year')
}
}
// 选择年份
const selectYear = (year) => {
tempYear.value = year
}
// 选择月份
const selectMonth = (month) => {
tempMonth.value = month
currentMoment.value = moment([tempYear.value, month, 1])
showYearMonth.value = false
}
// 选择日期
const selectDate = (date) => {
emit('update:modelValue', date.format('YYYY-MM-DD'))
}
// 判断是否是选中日期
const isSelected = (date) => {
if(currentdataType.value=='day'){
return date.format('YYYY-MM-DD') === moment(props.modelValue).format('YYYY-MM-DD')
}else if(currentdataType.value=='month'){
return date.format('YYYY-MM') === moment(props.modelValue).format('YYYY-MM')
}
}
// 判断是否是今天
const isToday = (date) => {
if(currentdataType.value=='day'){
return date.format('YYYY-MM-DD') === moment().format('YYYY-MM-DD')
}else if(currentdataType.value=='month'){
return date.format('YYYY-MM') === moment().format('YYYY-MM')
}
}
// 判断是否是当前月份
const isCurrentMonth = (month) => {
return month === moment().month() && tempYear.value === moment().year()
}
</script>
<style scoped>
.calendar-panel {
width: 100%;
border-radius: 4px;
background: #fff;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.calendar-content {
flex: 1;
display: flex;
flex-direction: column;
}
.calendar-header button {
border: none;
background: none;
cursor: pointer;
padding: 4px 8px;
}
.calendar-weeks {
display: grid;
grid-template-columns: repeat(7, 1fr);
text-align: center;
margin-bottom: 8px;
}
.week-item {
height: 32px;
line-height: 32px;
color: #606266;
}
.calendar-days {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 2px;
}
.calendar-months {
display: grid;
flex: 1;
padding: 10px;
grid-template-columns: repeat(4, 1fr);
gap: 2px;
}
.day-item {
height: 32px;
line-height: 32px;
text-align: center;
cursor: pointer;
border-radius: 4px;
}
.month-item {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
cursor: pointer;
border-radius: 4px;
}
.day-item:hover {
background-color: #f5f7fa;
}
.month-item:hover {
background-color: #f5f7fa;
}
.day-item.other-month {
color: #c0c4cc;
}
.day-item.selected {
background-color: #409eff;
color: #fff;
}
.month-item.selected {
background-color: #409eff;
color: #fff;
}
.day-item.today {
color: #409eff;
font-weight: bold;
}
.month-item.today {
color: #409eff;
font-weight: bold;
}
.day-item.selected.today {
color: #fff;
}
.month-item.selected.today {
color: #fff;
}
</style>
在页面中使用:
//日期格式的
<penlCaneldar v-model="selectedDate" type="day"/>
const selectedDate = ref(moment().format('YYYY-MM-DD'))
月份格式的
<penlCaneldar v-model="selectedDate" type="month" />
const selectedDate = ref(moment().format('YYYY-MM-DD'))