有个需求要实现一个日历,如下图所示:
固定6行7列,不是当月日期要用颜色区别显示。点击上月日期要翻页到上一个月,例如上图中,点击28号,日历翻到了上一月。点击下月日期要翻到下一月,例如点击2号,日历翻到下一月。
另外还要显示农历,以及当年的公休日信息。
想着用vant组件库中的calendar组件实现此需求,但calendar组件不是固定6行7列。所以就自己来实现日历需求了。
实现此日历需求的关键是构建6行7列的数据。我们可以先构建一个一维数组,此一维数组中放着42个日期,然后再把这个一维数组分割为二维数组,二维数组有6个元素,每个元素又是一个一维数组,放着7个日期元素。具体代码如下:
<template>
<div class="day-bg">
<div class="calendar">
<div class="header">
<div
v-for="(week, index) in weeks"
:key="index"
:class="
currWeek === index
? ['day-week', 'active-week']
: ['day-week', 'inactive-week']
"
>
{{ week }}
</div>
</div>
<div class="thin"></div>
<div class="content">
<table>
<tbody>
<tr v-for="(row, index) in rows" :key="index">
<td
v-for="(item, dayIndex) in row.days"
:key="dayIndex"
width="80"
height="80"
:class="
selectedDay.rowIndex === index &&
selectedDay.colIndex === dayIndex
? ['detail', 'detail-select']
: ['detail']
"
@click="onDetail(index, dayIndex)"
>
<div
:class="
item.isInMonth
? item.currDay &&
(selectedDay.rowIndex !== index ||
selectedDay.colIndex !== dayIndex)
? ['day', 'day-today']
: ['day']
: ['day', 'day-not-month']
"
>
{{ item.day }}
</div>
<div
:class="
item.currDay &&
(selectedDay.rowIndex !== index ||
selectedDay.colIndex !== dayIndex)
? ['lunar', 'lunar-active']
: selectedDay.rowIndex === index &&
selectedDay.colIndex === dayIndex
? ['lunar', 'lunar-inactive']
: ['lunar']
"
>
{{ item.lunar }}
</div>
<div
v-if="item.isRest.length"
:class="item.isRest === '休' ? ['rest'] : ['rest', 'work']"
>
{{ item.isRest }}
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
import { calendar } from "js-calendar-converter";
export default {
data() {
return {
selectedDay: { //选中某一天的坐标
rowIndex: 0,
colIndex: 0,
},
currWeek: 0, //周信息
weeks: ["日", "一", "二", "三", "四", "五", "六"],
rows: [], //存放日期的二维数组
};
},
created() {
this.init();
this.currWeek = this.getCurrWeek();
},
methods: {
//取得具体年,月 中的天数,例如2023年1月 有多少天
getMonthDays(year, month) {
return new Date(year, month + 1, 0).getDate();
},
//取得哪年哪月第一天为周几
getMonthFirstDay() {
return new Date(this.year, this.month).getDay();
},
//当天是周几
getCurrWeek() {
return new Date().getDay();
},
getLunar(year, month, day) {
const info = calendar.solar2lunar(year, month + 1, day);
let res = "";
// 优先级:阳历节日>农历节日>节气>农历
if (info.festival != null && info.festival != "") {
res = info.festival;
} else if (info.lunarFestival != null && info.lunarFestival != "") {
res = info.lunarFestival;
} else if (info.Term != null && info.Term != "") {
res = info.Term;
} else if (info.IDayCn != null && info.IDayCn != "") {
res = info.IDayCn;
}
return res;
},
//判断2023年公休日
isRestDay(year, month, day) {
month += 1;
if (month === 13) {
year += 1;
month = 1;
}
if (month === 14) {
year += 1;
month = 2;
}
if (year !== 2023) {
return "";
}
if (month === 1) {
//1月
if ([1, 2, 21, 22, 23, 24, 25, 26, 27].includes(day)) {
return "休"; //休
}
if ([28, 29].includes(day)) {
return "班"; //班
}
}
if (month === 4) {
//4月
if ([5, 29, 30].includes(day)) {
return "休";
}
if (day === 23) {
return "班";
}
}
if (month === 5) {
//5月
if ([1, 2, 3].includes(day)) {
return "休";
}
if (day === 6) {
return "班";
}
}
if (month === 6) {
//6月
if ([22, 23, 24].includes(day)) {
return "休";
}
if (day === 25) {
return "班";
}
}
if (month === 9) {
//9月
if ([29, 30].includes(day)) {
return "休";
}
}
if (month === 10) {
//10月
if ([1, 2, 3, 4, 5, 6].includes(day)) {
return "休";
}
if ([7, 8].includes(day)) {
return "班";
}
}
return "";
},
//取得当天年,月,日
getCurrDate() {
const _date = new Date();
const currDay = _date.getDate();
const currMonth = _date.getMonth();
const currYear = _date.getFullYear();
return {
currDay,
currMonth,
currYear,
};
},
//获取哪年哪月中所有天数(以及一些信息,例如农历,是否为公休日等),并存入数组中。
initDate() {
const days = this.getMonthDays(this.year, this.month);
const { currDay, currMonth, currYear } = this.getCurrDate();
const isMonthYear = currMonth === this.month && currYear === this.year;
this.oneArr = [];
for (let i = 1; i <= days; i++) {
this.oneArr.push({
year: this.month === 12 ? this.year + 1 : this.year,
month: this.month === 12 ? 1 : this.month + 1,
day: i,
isInMonth: true, //表示是这个月的日期
currDay: i === currDay && isMonthYear, //表示是否为当天日期,当天日期要用颜色区别显示
lunar: this.getLunar(this.year, this.month, i), //农历
isRest: this.isRestDay(this.year, this.month, i),//是否为公休日
});
}
},
//因为日历是采用固定6行7列,所以要补全第1行。
paddingLeft() {
let prevMonthDays = this.getMonthDays(this.year, this.month - 1);//上个月有多少天
const monthFirstDay = this.getMonthFirstDay(); //当前月第1天 是周几。
for (let i = 0; i < monthFirstDay; i++) {
this.oneArr.unshift({
year: this.year,
month: this.month,
lunar: this.getLunar(this.year, this.month - 1, prevMonthDays),
isRest: this.isRestDay(this.year, this.month - 1, prevMonthDays),
day: prevMonthDays--,
isInMonth: false,
});
}
},
//因为日历是采用固定6行7列,所以要补全第6行。
paddingRight() {
const num = 42 - this.oneArr.length; //剩多少,补多少
for (let i = 1; i <= num; i++) {
this.oneArr.push({
year: this.month >= 11 ? this.year + 1 : this.year,
month:
this.month >= 11 ? (this.month === 11 ? 1 : 2) : this.month + 2,
lunar: this.getLunar(this.year, this.month + 1, i),
isRest: this.isRestDay(this.year, this.month + 1, i),
day: i,
isInMonth: false,
});
}
},
//把一维日期数组分割成二维数组,
buildDate() {
this.rows = [];
let _arr = [];
let _oneArr = this.oneArr;
for (let i = 0; i < _oneArr.length; i += 7) {
_arr.push(_oneArr.slice(i, i + 7));
}
for (let i = 0; i < 6; i++) {
this.rows.push({
days: _arr[i],
});
}
},
//点击上一月,下一月时,进行坐标定位。例如当前月选中的16号,那么点击到下月时,也是16号
setSelectIndex() {
const _rows = JSON.parse(JSON.stringify(this.rows));
let hadDone = false;
const nextMonthDays = this.getMonthDays(this.year, this.month);
if (this.currSelectDay > nextMonthDays) {
//如果选中的日期超过上一月或下一月最大天数,则取上一月或下一月的最大天数
this.currSelectDay = nextMonthDays;
}
_rows.forEach((row, rowIndex) => {
row.days.forEach((col, colIndex) => {
if (col.day === this.currSelectDay && !hadDone && col.isInMonth) {
this.selectedDay.rowIndex = rowIndex;
this.selectedDay.colIndex = colIndex;
this.currWeek = colIndex; //取得周信息
hadDone = true;
}
});
});
},
//初次打开日历页面时,定位出当天日期坐标
initSelectIndex() {
const _rows = JSON.parse(JSON.stringify(this.rows));
_rows.forEach((row, rowIndex) => {
row.days.forEach((col, colIndex) => {
if (col.currDay) {
this.selectedDay.rowIndex = rowIndex;
this.selectedDay.colIndex = colIndex;
this.currSelectDay = col.day;
this.num = col.num || 0;
}
});
});
},
processDate() {
this.initDate();
this.paddingLeft();
this.paddingRight();
this.buildDate();
},
getDateStr() {
let obj = {
dateStr: `${this.year}年${(this.month + 1)
.toString()
.padStart(2, "0")}月${this.currSelectDay
.toString()
.padStart(2, "0")}日 周${this.weeks[this.selectedDay.colIndex]}`,
};
obj.num = this.num;
this.$emit("date", obj);
return obj;
},
init() {
const { currDay, currMonth, currYear } = this.getCurrDate();
this.year = currYear;
this.month = currMonth;
this.nowDay = currDay;
this.processDate();
this.initSelectIndex();
this.currWeek = this.getCurrWeek();
this.getDateStr();
},
//下一个月
next() {
this.month = this.month + 1;
this.processDate();
if (this.month === 12) {
this.year = this.year + 1;
this.month = 0;
}
this.setSelectIndex();
this.getDateStr();
},
//上一个月
prev() {
if (this.month === 0) {
this.year = this.year - 1;
this.month = 12;
}
this.month = this.month - 1;
this.processDate();
this.setSelectIndex();
this.getDateStr();
},
//日期点击事件,如果选择的是上一月或下一月的日期,则进行翻页
onDetail(rowIndex, colIndex) {
const _rows = JSON.parse(JSON.stringify(this.rows));
this.selectedDay.rowIndex = rowIndex;
this.selectedDay.colIndex = colIndex;
this.currWeek = colIndex;
this.currSelectDay = _rows[rowIndex].days[colIndex].day;
this.num = _rows[rowIndex].days[colIndex].num || 0;
this.getDateStr();
if (!_rows[rowIndex].days[colIndex].isInMonth) {
rowIndex > 2 ? this.next() : this.prev();
}
},
},
};
</script>
<style lang="scss" scoped>
.day-bg {
display: flex;
justify-content: space-between;
.calendar {
width: 73.6rem;
height: 58rem;
background: rgba(48, 48, 57, 0.5);
border-radius: 0.4rem;
backdrop-filter: blur(20px);
.header {
display: flex;
justify-content: space-between;
padding: 0 4rem;
margin-top: 1.2rem;
.day-week {
width: 7.99rem;
height: 5.2rem;
line-height: 5.2rem;
text-align: center;
font-size: 2rem;
font-family: FZLTXHK--GBK1-0, FZLTXHK--GBK1;
font-weight: normal;
}
.inactive-week {
color: #aaaaaa;
}
.active-week {
background: linear-gradient(
180deg,
rgba(200, 164, 108, 0) 0%,
rgba(200, 164, 108, 0.5) 100%
);
opacity: 0.5;
color: #ffd18a;
}
}
.thin {
width: 65.6rem;
height: 0.1rem;
border: 0.1rem solid #4d4d4d;
margin: 0 auto;
}
.content {
display: flex;
justify-content: center;
margin-top: 1.2rem;
table,
td {
border-collapse: separate;
border-spacing: 1.6rem 0rem;
}
.detail {
font-family: Roboto-Regular, Roboto;
font-weight: 400;
color: #fafafa;
position: relative;
text-align: center;
.day {
font-size: 2.8rem;
line-height: 3.3rem;
}
.day-not-month {
opacity: 0.3;
}
.day-today {
color: #ffd18a;
}
.icon {
color: #aaaaaa;
font-size: 0.08rem;
margin-top: 0.2rem;
}
}
.detail-select {
background: #ffd18a;
color: #000000;
}
.lunar {
font-size: 1.6rem;
font-family: FZLTHK--GBK1-0, FZLTHK--GBK1;
font-weight: normal;
color: #888888;
}
.lunar-active {
color: #ffd18a;
}
.lunar-inactive {
color: #000000;
}
.rest {
position: absolute;
top: 0;
right: 0;
width: 2.4rem;
height: 2.8rem;
line-height: 2rem;
background: #442c32;
border-radius: 0rem 0rem 0rem 10rem;
font-size: 1.4rem;
font-family: FZLTHK--GBK1-0, FZLTHK--GBK1;
font-weight: normal;
color: #e76767;
}
.work {
background: #283f38;
color: #4bb976;
}
.detail-border {
border-right: 1px solid #4d4d4d;
}
}
}
.event {
width: 110rem;
}
.event-empty {
width: 110rem;
.content {
width: 108rem;
height: 58rem;
background: linear-gradient(
180deg,
rgba(51, 55, 64, 0.5) 0%,
#2e313a 100%
);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
font-size: 2.8rem;
font-family: FZLTXHK--GBK1-0, FZLTXHK--GBK1;
font-weight: normal;
color: #fafafa;
line-height: 3.2rem;
.img {
width: 30rem;
height: 30rem;
}
}
}
.loading-wrapper {
position: fixed;
top: 20%;
left: 20%;
}
}
</style>
注意,要提前安装js-calendar-converter 这个获取农历的npm包。
在代码中做个详细的注释,如有不明白的地方可留言。