自定义日历组件day-calendar实现

有个需求要实现一个日历,如下图所示:

固定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包。

在代码中做个详细的注释,如有不明白的地方可留言。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值