Skip to content

H5自定义日历组件

在h5开发中, UI框架样式往往很难满足设计需求, 下面是一个自定义的日历组件

效果图: avatar

代码

    html,
      body {
        margin: 0;
        padding: 1rem;
      }
      * {
        box-sizing: border-box;
      }
      [v-cloak] {
        display: none !important;
      }
      .square {
        position: relative;
        height: 0;
        padding-top: 100%;
      }
      .square .square__item {
        height: 100%;
        width: 100%;
        position: absolute;
        left: 0;
        top: 0;
      }
      .square .square__item::after {
        content: "";
        padding-top: inherit;
        display: block;
      }

      .calendar-card {
        position: relative;
        background: #05e2ba;
        border-radius: 2rem;
        overflow: hidden;
        margin: 0 2rem;
        width: calc(100% - 4rem);
        box-shadow: 0px 1.2rem 3rem 0px rgba(149, 192, 216, 0.5);
        font-weight: 500;
        z-index: 10;
      }

      .calendar-card .calendar-title {
        /* width: 146px; */
        width: 100%;
        height: 4rem;
        margin: auto;
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 0 1rem;
        font-weight: bold;
        font-family: Baseus Sans SC, Baseus Sans SC;
      }
      .calendar-card .calendar-title .date-b {
        padding: 1rem;
        color: #272c2e;
        text-align: center;
      }
      .calendar-card .calendar-title .date-b.is-prev {
        transform: rotateZ(180deg);
        transform-origin: center;
      }

      .calendar-card .calendar-title .now-date {
        min-width: 8rem;
        height: 2.3rem;
        font-family: PingFangSC-Medium;
        font-size: 1.6rem;
        color: #333333;
        text-align: center;
        font-weight: 500;
      }

      .calendar-card .calendar-week {
        background: #fff;
        border-radius: 2rem;
        overflow: hidden;
        padding: 1.6rem 0.2rem 1.6rem;
      }

      .calendar-card .calendar-week .calendar__weekdays {
        display: flex;
        flex-wrap: wrap;
      }

      .calendar-card .calendar-week .calendar__weekdays .calendar__weekday {
        flex: 1;
        line-height: 3rem;
        text-align: center;
        font-family: PingFangSC-Regular;
        font-size: 1.4rem;
        color: #333333;
        text-align: center;
        font-weight: 500;
        margin: 0 0.3rem;
      }
      .calendar-card
        .calendar-week
        .calendar__weekdays
        .calendar__weekday
        .calendar__weekday__item {
        height: 100%;
        width: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      .calendar-card .calendar-week .calendar__weekdays .calendar__week {
        display: inline-block;
        width: 14.2857142857%;
        gap: 0.4rem;
        padding: 0.4rem;
      }
      .calendar-card .calendar-week .calendar__weekdays .calendar__week .solar {
        position: relative;
        height: 100%;
        width: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
        color: #797d8044;
      }

      .calendar-card .calendar-week .calendar__week .solar.this__month {
        color: #797d80;
      }

      /* 已签到 */
      .calendar-card
        .calendar-week
        .calendar__weekdays
        .calendar__week
        .solar.is__signed {
        border-radius: 50%;
        background: #fff000;
        color: #181a20;
      }
      /* 已签到且连续7天的日期 */
      .calendar-card
        .calendar-week
        .calendar__weekdays
        .calendar__week
        .solar.is__signed__week {
        color: #181a20;
      }
      .calendar-card
        .calendar-week
        .calendar__weekdays
        .calendar__week
        .solar.is__signed__week
        .sign__week__bg {
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%) rotateZ(45deg);
        background: #fff000;
        border-radius: 0.4rem;
        width: 3.2rem;
        height: 3.2rem;
      }

      .calendar-card
        .calendar-week
        .calendar__weekdays
        .calendar__week
        .solar.is__signed__week
        .sign__week__bg::after {
        position: absolute;
        left: 0;
        top: 0;
        content: "";
        transform: rotateZ(45deg);
        background: #fff000;
        border-radius: 0.4rem;
        width: 3.2rem;
        height: 3.2rem;
      }

      .calendar-card
        .calendar-week
        .calendar__weekdays
        .calendar__week
        .solar__text {
        position: relative;
        z-index: 1;
        /* color: #181a20; */
      }
      .calendar-card .calendar-week .calendar__weekdays .calendar__week i {
        display: none;
      }
      .calendar-card
        .calendar-week
        .calendar__weekdays
        .calendar__week
        .is__today
        i {
        display: inline-block;
        position: absolute;
        left: 50%;
        bottom: -0.6rem;
        width: 0.6rem;
        height: 0.6rem;
        transform: translateX(-50%);
        border-radius: 0.3rem;
        background-color: #181a20;
      }

<div id="app" v-cloak>
      <!-- 日历组件 -->
      <section class="calendar-card">
        <div class="calendar-title">
          <span class="date-b is-prev" @click="handlePrevious()">
            <van-icon
              name="play"
              :style="{color: showMonth == 0 ? '#0FAB90': '#272C2E'}"
            />
          </span>
          <span class="now-date">{{ showDate | showDateFilter }}</span>
          <span class="date-b" @click="handleNext()">
            <van-icon
              name="play"
              :style="{color: showMonth < 11 ? '#272C2E' : '#0FAB90'}"
            />
          </span>
        </div>
        <div class="calendar-week">
          <div class="calendar__weekdays">
            <div class="calendar__weekday" v-for="i in weekdays" :key="i">
              <div class="square">
                <div class="square__item">
                  <div class="calendar__weekday__item">{{ i }}</div>
                </div>
              </div>
            </div>
          </div>
          <div class="calendar__weekdays calendar__months">
            <div
              class="calendar__week"
              v-for="date in thisMonth"
              :class="{'active-day': selectDay == date.fullDate}"
              :key="date.fullDate"
            >
              <div class="square">
                <div class="square__item">
                  <div
                    class="solar"
                    :class="{
              'this__month': date.month == showMonth, 
            }"
                  >
                    <span class="solar__text">
                      {{date.date}}
                      <i></i>
                    </span>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </section>
      <!-- 日历组件 end -->
    </div>

js需要引入自适应尺寸代码, 保证组件自适应: H5响应式尺寸适配

 new Vue({
      el: "#app",
      data() {
        return {
          // 日历中展示的日期
          showDate: new Date(),
          // 日历中展示的月份
          showMonth: new Date().getMonth(),
          // 日历中展示的年份
          showYear: new Date().getFullYear(),
          // 现在的日期
          nowDate: new Date(),
          selectDay: "",
          weekdays: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
          thisMonth: [],
          isCurMonth: true,
        };
      },
      filters: {
        showDateFilter(date) {
          var year = date.getFullYear();
          var month = date.getMonth() + 1; // 月份从0开始,因此需要加1
          return `${year} ${month.toString().padStart(2, "0")}`;
        },
      },
      watch: {
        showDate: {
          handler(newd) {
            if (newd) {
              this.getMonthList();

              this.showMonth = newd.getMonth();
              this.showYear = newd.getFullYear();

              this.isCurMonth = this.showMonth == this.nowDate.getMonth();
            }
          },
        },
      },
      mounted() {
        this.getMonthList();
      },
      methods: {
        isToday(str) {
          var now = new Date().setHours(0, 0, 0, 0);
          var showDate = new Date(str).setHours(0, 0, 0, 0);
          return showDate - now == 0;
        },

        changeMonthIndex(date) {
          return date.getFullYear() + date.getMonth();
        },

        getMonthList() {
          // 获取当前日期
          var currentDate = new Date(this.showDate);
          // 将日期设置为当月的第一天
          currentDate.setDate(1);
          // 获取当前月份
          var currentMonth = currentDate.getMonth();
          // 创建一个数组来存储结果
          var monthDates = [];
          // 补充前面的周日和周一数据
          var firstDayOfWeek = currentDate.getDay(); // 当月第一天是星期几
          var daysBefore = (firstDayOfWeek + 7) % 7; // 前面需要补充的天数
          let startDate = "";
          if (currentDate.getMonth() == 0 && daysBefore > 0) {
            startDate = new Date(
              currentDate.getFullYear() - 1,
              12,
              1 - daysBefore
            );
          } else {
            startDate = new Date(
              currentDate.getFullYear(),
              currentDate.getMonth(),
              1 - daysBefore
            );
          }
          for (let i = 0; i < daysBefore; i++) {
            var date = new Date(
              startDate.getFullYear(),
              startDate.getMonth(),
              startDate.getDate() + i
            );
            monthDates.push({
              fullDate: this.dateFormat(date, "yyyyMMdd"),
              unDate: date, // 未格式化的日期
              date: date.getDate(), // 格式化公历日期
              month: date.getMonth(),
            });
          }
          // 循环获取当月的日期和农历日期
          while (currentDate.getMonth() === currentMonth) {
            var date = new Date(currentDate);
            // 组合日期和农历日期,并添加到结果数组中
            monthDates.push({
              fullDate: this.dateFormat(date, "yyyyMMdd"),
              unDate: date, // 未格式化的日期
              date: date.getDate(), // 格式化公历日期
              month: date.getMonth(),
            });
            currentDate.setDate(currentDate.getDate() + 1);
          }
          // 下个月补的天数
          var nextMonthDays =
            monthDates.length % 7 === 0 ? 0 : 7 - (monthDates.length % 7);

          if (nextMonthDays !== 0) {
            for (let i = 0; i < nextMonthDays; i++) {
              var date = new Date(currentDate);
              // 组合日期和农历日期,并添加到结果数组中
              monthDates.push({
                fullDate: this.dateFormat(date, "yyyyMMdd"),
                unDate: date, // 未格式化的日期
                date: date.getDate(), // 格式化公历日期
                month: date.getMonth(),
              });
              currentDate.setDate(currentDate.getDate() + 1);
            }
          }
          this.thisMonth = monthDates;
        },
        // 日期格式化
        dateFormat(oldDate, format = "yyyyMMdd") {
          const date = new Date(oldDate);
          const y = date.getFullYear();
          const M = (date.getMonth() + 1).toString().padStart(2, "0");
          const d = date.getDate().toString().padStart(2, "0");
          const h = date.getHours().toString().padStart(2, "0");
          const m = date.getMinutes().toString().padStart(2, "0");
          const s = date.getSeconds().toString().padStart(2, "0");
          const nFormat = format
            .replace("yyyy", y)
            .replace("MM", M)
            .replace("dd", d)
            .replace("hh", h)
            .replace("mm", m)
            .replace("ss", s);
          return nFormat;
        },
      },
    });

根据设计稿,如果为375,则设计稿上10px = 1rem,可修改recalc方法中的375为750,以方便在750设计稿中使用10=1rem的比例