<div class="modal">
<div class="calendar">
<div class="serivce_title">
<span>预约体验</span>
<span class="cancel_bottom" @click="cancelDate">取消</span>
</div>
<div class="date_day">
<div class="date_week_day_list">
<div class="date_mouth">
<span :class="{active: choseMouthIndex == index}" v-for="item, index in getMonthsIncludingNextFour()" :key="index" @click="choseMouth(item, index)">{{ item.name }}</span>
</div>
<div class="date_week_list">
<span v-for="(item, key) in Array.from(week_list)" :key="key">{{ item[1] }}</span>
</div>
<div class="date_day_list">
<p v-for="(item, key) in date_day_list" :key="key">
<span
:class="[
item.is_chose
? choseDay == item.day
? 'choseDay'
: item.is_today
? 'today'
: 'chose'
: 'grey'
]"
@click="choseDateDay(item)"
>{{ item.day }}</span
>
</p>
</div>
</div>
<div class="date_time_list">
<slot name="timeList"></slot>
</div>
</div>
</div>
</div>
</template>
export default {
name: 'calendar',
props: {
choseDate: {
type: String,
default: ''
}
},
data() {
return {
year: '',
mouth: '',
choseDay: '',
week: '',
nowMouth: '',
nowDay: '',
selectedDate: '',
week_list: '',
date_day: '',
date_day_list: '',
firstDayWeek: '',
lastMouthDay: '',
choseMouthIndex: '0'
}
},
created() {
this.year = this.choseDate.split('-')[0]
this.mouth = this.choseDate.split('-')[1]
this.choseDay = this.choseDate.split('-')[2]
this.week = new Date(this.choseDate).getDay()
this.nowMouth = new Date().getMonth() + 1 > 9 ? new Date().getMonth() + 1 : '0' + (new Date().getMonth() + 1)
this.nowDay = new Date().getDate()
this.week_list = new Map([
[0, '日'],
[1, '一'],
[2, '二'],
[3, '三'],
[4, '四'],
[5, '五'],
[6, '六']
])
},
methods: {
// 获取输入月份的天数
getMonthDay (mouth) {
this.date_day = new Date(this.year, mouth, 0).getDate()
this.firstDayWeek = new Date(this.year, mouth - 1, 1).getDay()
this.date_day_list = Array.from({ length: this.date_day }, (v, i) => {
return {
day: i + 1,
week: this.week,
is_today: mouth < this.nowMouth ? false : i + 1 === new Date().getDate(),
is_chose: new Date(this.year, mouth - 1, i + 1, 23, 59, 59) >= new Date(),
date: `${this.year}-${mouth}-${i + 1 > 9 ? i + 1 : '0' + (i + 1)}`,
zhDate: this.getLunarDate(`${this.year}-${mouth}-${i + 1 > 9 ? i + 1 : '0' + (i + 1)}`)
}
})
for (let i = 0; i < this.firstDayWeek; i++) {
this.date_day_list.unshift({
day: '',
week: '',
is_today: false
})
}
},
// 下个月
nextMonth () {
if (this.mouth === 12) {
this.mouth = 1
this.year = this.year + 1
} else {
this.mouth = Number(this.mouth) + 1 < 10 ? '0' + (Number(this.mouth) + 1) : Number(this.mouth) + 1
}
},
choseDateDay (item) {
if (item.is_chose) {
this.choseDay = item.day
this.selectedDate = item.date
this.$emit('choseDateDay', this.selectedDate || this.choseDate)
}
},
// 切换月份
choseMouth (item, index) {
this.mouth = item.mouth.slice(0, -1)
this.year = item.year
this.choseMouthIndex = index
},
cancelDate () {
this.$emit('cancelDate')
},
confirmDate () {
this.$emit('confirmDate', this.selectedDate || this.choseDate)
},
getMonthsIncludingNextFour() {
const now = new Date();
const currentYear = now.getFullYear();
const currentMonth = now.getMonth() + 1; // Months are zero-indexed in JavaScript
const monthNames = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
const months = []
for (let i = 0; i < 4; i++) {
let nextMonth = currentMonth + i;
let nextYear = currentYear;
if (nextMonth > 12) {
nextMonth -= 12;
nextYear += 1;
}
const monthString = (nextYear === currentYear || i === 0) ? {mouth:monthNames[nextMonth - 1], year: currentYear, name: monthNames[nextMonth - 1]} : monthNames[nextMonth - 1] == '1月' ? {name:`${nextYear}年${monthNames[nextMonth - 1]}`, year: nextYear, mouth: monthNames[nextMonth - 1]} : {mouth:monthNames[nextMonth - 1], year: nextYear, name: monthNames[nextMonth - 1]};
months.push(monthString);
}
return months;
},
// 判断时间段是否在区间
isCurrentDateAfterFixedDate(fixedDateStr) {
// 获取当前日期和时间
const now = new Date();
// 将日期字符串转换为 Date 对象
// 注意:这里假设日期字符串是有效的,并且符合 ISO 8601 格式或 JavaScript 能够解析的其他格式
const fixedDate = new Date(fixedDateStr);
// 检查转换是否有效(即日期字符串是否被正确解析)
if (isNaN(fixedDate.getTime())) {
throw new Error('Invalid date string');
}
// 比较两个日期
return now < fixedDate;
},
// 获取农历
getLunarDate(zhdate) {
const date = new Date(zhdate);
const formatter = new Intl.DateTimeFormat('zh-Hans-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
calendar: 'chinese' // 注意:并非所有环境都支持 'chinese' 日历
});
const parts = formatter.formatToParts(date);
let day = null;
parts.forEach(({ type, value }) => {
if (type === 'day') {
day = parseInt(value, 10);
}
});
return day;
},
},
watch: {
choseDate: {
handler(newValue) {
this.week = new Date(`${this.year}-${this.mouth}-${newValue}`).getDay()
}
},
mouth: {
handler(newValue) {
this.getMonthDay(newValue)
}
}
}
}
</script>
.modal {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.3);
z-index: 999;
}
.calendar {
width: 100%;
min-height: 170px;
background: #F5F6F6;
border-radius: 20px;
overflow: hidden;
position: absolute;
bottom: 0;
left: 0;
display: flex;
flex-direction: column;
}
.date_week_day_list {
width: calc(100% - 24px);
background-color: #fff;
border-radius: 6px;
margin: 0 auto;
padding-bottom: 6px;
}
.date_week_list {
display: grid;
grid-template-columns: repeat(7, 1fr);
}
.date_week_list > span {
display: flex;
justify-content: center;
align-items: center;
font-weight: 500;
font-size: 12px;
color: #AEB0B7;
line-height: 17px;
}
.date_day {
max-height: 70vh;
overflow-y: auto;
}
.date_day_list {
display: grid;
grid-template-columns: repeat(7, 1fr);
}
.date_day_list > p {
display: flex;
justify-content: center;
align-items: center;
padding: 6px 0;
font-weight: 500;
font-size: 14px;
color: #1F2023;
}
.date_day_list p span {
display: flex;
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
}
.date_day_list span.grey {
color: #c5c5c5;
}
.date_day_list span.chose {
color: #212121;
}
.date_day_list span.choseDay {
background: #7E98F2;
border-radius: 6px;
color: #fff;
}
.date_day_list span.today {
border: 2px solid #7E98F2;
border-radius: 50%;
color: #212121;
box-sizing: border-box;
}
.serivce_title {
width: 100%;
height: 56px;
background: linear-gradient( 114deg, #EEF7FF 0%, #F6F1F5 80%, #FFE9E7 100%);
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.serivce_title .cancel_bottom {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
font-size: 14px;
color: #5E7ADE;
line-height: 20px;
}
.date_mouth {
height: 74px;
padding: 0 12px;
font-size: 14px;
color: #54575D;
line-height: 20px;
display: flex;
align-items: center;
}
.date_mouth > span {
margin-right: 24px;
}
.date_mouth .active {
font-weight: 600;
font-size: 16px;
color: #5E7ADE;
position: relative;
}
.date_mouth .active::after {
content: '';
display: inline-block;
width: 24px;
height: 4px;
background-color: #7E98F2;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: -8px;
border-bottom-left-radius: 3px;
border-top-right-radius: 3px;
}
.date_time_list {
width: calc(100% - 24px);
background-color: #fff;
border-radius: 6px;
margin: 0 auto;
padding-bottom: 6px;
margin-top: 12px;
}
</style>