手写一个uniapp日历组件

效果如下:
在这里插入图片描述
代码如下:

<template>
  <view class="calendar-wrapper">
    <view class="header" v-if="headerBar">
      <view class="preWidth" @click="changeMonth('pre')">
        <view class="pre">
          <image src="../../static/img/icons/icon-arrow-x.png" mode="aspectFit"></image>
        </view>
      </view>

      <view>{{year+'年'+formatNum(month)+'月'}}</view>

      <view class="nextWidth" @click="changeMonth('next')">
        <view class="next">
          <image src="../../static/img/icons/icon-arrow-x.png" mode="aspectFit"></image>
        </view>
      </view>

    </view>

    <!-- 星期 -->
    <view class="week">
      <view class="week-day" v-for="(item, index) in weekDay" :key="index">{{ item }}</view>
    </view>

    <view :class="{ hide: !monthOpen }" class="content" :style="{ height: height }">
      <view :style="{ top: positionTop + 'rpx' }" class="days">
        <view class="item" v-for="(item, index) in dates" :key="index">
          <view class="day" @click="selectOne(item, $event)" :class="{
              choose: choose == `${item.year}-${item.month}-${item.date}`&&item.isCurM,
              nolm: !item.isCurM,
              today: isToday(item.year, item.month, item.date),
              isWorkDay: isWorkDay(item.year, item.month, item.date)}">
            {{ Number(item.date) }}
          </view>
          <view class="markDay" v-if="isMarkDay(item.year, item.month, item.date)&&item.isCurM"></view>
          <!-- <view class="today-text" v-if="isToday(item.year, item.month, item.date)"></view> -->
          <view class="badge-num" v-if="item.count">{{item.count}}</view>
        </view>
      </view>
    </view>

    <view class="weektoggle" @click="toggle">
      <!-- <image src="../../static/indexImg/icon_rili_jiantou@2x.png" mode="scaleToFill" v-if="collapsible"></image> -->
      <image src="../../static/img/icons/icon-arrow.png" mode="aspectFit" :class="{arrowUp: monthOpen}"></image>
    </view>

  </view>
</template>

<script>
  export default {
    name: 'ren-calendar',
    props: {
      // 星期几为第一天(0为星期日)
      weekstart: {
        type: Number,
        default: 1
      },
      // 标记的日期
      markDays: {
        type: Array,
        default: () => ([])
      },
      // 是否展开
      open: {
        type: Boolean,
        default: false
      },
      //是否可收缩
      collapsible: {
        type: Boolean,
        default: true
      },
      //未来日期是否不可点击
      disabledAfter: {
        type: Boolean,
        default: false
      },
      // 日期角标数据
      badgeDays: {
        type: Array,
        default: () => ([])
      }
    },
    data() {
      return {
        weektext: ['日', '一', '二', '三', '四', '五', '六'],
        year: new Date().getFullYear(), // 年
        month: new Date().getMonth() + 1, // 月
        dates: [], // 当前月的日期数据
        positionTop: 0,
        monthOpen: false,
        choose: '',
        headerBar: true // 月份切换按钮
      };
    },
    created() {
      this.monthOpen = this.open
      this.dates = this.monthDay(this.year, this.month);
      this.$emit('one-month', this.dates)
      this.calcTop()
    },
    mounted() {
      this.choose = this.getToday().date;
    },
    computed: {
      // 顶部星期栏
      weekDay() {
        return this.weektext.slice(this.weekstart).concat(this.weektext.slice(0, this.weekstart));
      },
      height() {
        return (this.dates.length / 7) * 80 + 'rpx';
      }
    },
    watch: {
      badgeDays: {
        handler(newVal) {
          this.initBadgeDate()
          this.$forceUpdate()
        }
      }
    },
    methods: {
      // 初始化渲染数据
      initBadgeDate() {
        this.dates.map(item => {
          const fullDate = `${item.year}-${item.month}-${item.date}`
          const obj = this.badgeDays.find(bItem => bItem.date === fullDate)
          item.count = obj ? obj.count : 0
          item.fullDate = fullDate
          return item
        })
      },
      formatNum(num) {
        let res = Number(num);
        return res < 10 ? '0' + res : res;
      },
      getToday() {
        let date = new Date();
        let y = date.getFullYear();
        let m = date.getMonth();
        let d = date.getDate();
        let week = new Date().getDay();
        let weekText = ['日', '一', '二', '三', '四', '五', '六'];
        let formatWeek = '星期' + weekText[week];
        let today = {
          date: y + '-' + this.formatNum(m + 1) + '-' + this.formatNum(d),
          week: formatWeek
        };
        return today;
      },
      // 获取当前月份数据
      monthDay(y, month) {
        let dates = [];
        let m = Number(month);
        let firstDayOfMonth = new Date(y, m - 1, 1).getDay(); // 当月第一天星期几
        let lastDateOfMonth = new Date(y, m, 0).getDate(); // 当月最后一天
        let lastDayOfLastMonth = new Date(y, m - 1, 0).getDate(); // 上一月的最后一天
        let weekstart = this.weekstart == 7 ? 0 : this.weekstart;
        let startDay = (() => {
          // 周初有几天是上个月的
          if (firstDayOfMonth == weekstart) {
            return 0;
          } else if (firstDayOfMonth > weekstart) {
            return firstDayOfMonth - weekstart;
          } else {
            return 7 - weekstart + firstDayOfMonth;
          }
        })();
        // let endDay = 7 - ((startDay + lastDateOfMonth) % 7); // 结束还有几天是下个月的
        let endDay = 42 - (startDay + lastDateOfMonth) 
        for (let i = 1; i <= startDay; i++) {
          dates.push({
            date: this.formatNum(lastDayOfLastMonth - startDay + i),
            day: weekstart + i - 1 || 7,
            month: m - 1 > 0 ? this.formatNum(m - 1) : 12,
            year: m - 1 > 0 ? y : y - 1
          });
        }
        for (let j = 1; j <= lastDateOfMonth; j++) {
          dates.push({
            date: this.formatNum(j),
            day: (j % 7) + firstDayOfMonth - 1 || 7,
            month: this.formatNum(m),
            year: y,
            isCurM: true //是否当前月份
          });
        }
        for (let k = 1; k <= endDay; k++) {
          dates.push({
            date: this.formatNum(k),
            day: (lastDateOfMonth + startDay + weekstart + k - 1) % 7 || 7,
            month: m + 1 <= 12 ? this.formatNum(m + 1) : '01',
            year: m + 1 <= 12 ? y : y + 1
          });
        }
        return dates;
      },
      isWorkDay(y, m, d) {
        //是否工作日
        let ymd = `${y}/${m}/${d}`;
        let formatDY = new Date(ymd.replace(/-/g, '/'));
        let week = formatDY.getDay();
        if (week == 0 || week == 6) {
          return false;
        } else {
          return true;
        }
      },
      // isFutureDay(y, m, d) {
      //   //是否未来日期
      //   let ymd = `${y}/${m}/${d}`;
      //   let formatDY = new Date(ymd.replace(/-/g, '/'));
      //   let showTime = formatDY.getTime();
      //   let curTime = new Date().getTime();
      //   if (showTime > curTime) {
      //     return true;
      //   } else {
      //     return false;
      //   }
      // },
      // 标记日期
      isMarkDay(y, m, d) {
        let flag = false;
        for (let i = 0; i < this.markDays.length; i++) {
          let dy = `${y}-${m}-${d}`;
          if (this.markDays[i] == dy) {
            flag = true;
            break;
          }
        }
        return flag;
      },
      isToday(y, m, d) {
        let checkD = y + '-' + m + '-' + d;
        let today = this.getToday().date;
        if (checkD == today) {
          return true;
        } else {
          return false;
        }
      },
      // 展开收起
      toggle() {
        this.monthOpen = !this.monthOpen;
        this.calcTop()
      },
      // 计算日期高度
      calcTop() {
        // this.headerBar = !this.headerBar;
        if (this.monthOpen) {
          this.positionTop = 0;
        } else {
          let index = -1;
          this.dates.forEach((i, x) => {
            this.isToday(i.year, i.month, i.date) && (index = x);
          });
          this.positionTop = -((Math.ceil((index + 1) / 7) || 1) - 1) * 80;
        }
      },
      // 点击回调
      selectOne(i, event) {
        let date = `${i.year}-${i.month}-${i.date}`;
        let selectD = new Date(date).getTime();
        let curTime = new Date().getTime();
        let week = new Date(date).getDay();
        let weekText = ['日', '一', '二', '三', '四', '五', '六'];
        let formatWeek = '星期' + weekText[week];
        let response = {
          date: date,
          week: formatWeek
        };
        if (!i.isCurM) {
          // console.log('不在当前月范围内');
          return false;
        }
        if (selectD > curTime) {
          if (this.disabledAfter) {
            console.log('未来日期不可选');
            return false;
          } else {
            this.choose = date;
            this.$emit('onDayClick', response);
          }
        } else {
          this.choose = date;
          this.$emit('onDayClick', response);
        }
      },
      //改变年月
      // changYearMonth(y, m) {
      //   this.dates = this.monthDay(y, m);
      //   this.year = y;
      //   this.month = m;
      // },
      changeMonth(type) {
        if (type == 'pre') {
          if (this.month + 1 == 2) {
            this.month = 12;
            this.year = this.year - 1;
          } else {
            this.month = this.month - 1;
          }
        } else {
          if (this.month + 1 == 13) {
            this.month = 1;
            this.year = this.year + 1;
          } else {
            this.month = this.month + 1;
          }
        }
        this.dates = this.monthDay(this.year, this.month);
        this.$emit('one-month', this.dates)
        this.calcTop()
      }
    }
  };
</script>

<style lang="scss" scoped>
  .calendar-wrapper {
    color: #333;
    font-size: 28rpx;
    text-align: center;
    background-color: #F2F8FF;
    padding-bottom: 50rpx;
    // box-shadow: 0 45rpx rgba(#cf3a18, .32);

    .header {
      display: flex;
      align-items: center;
      justify-content: space-around;
      height: 88rpx;
      color: #333333;
      font-size: 32rpx;
      font-weight: bold;

      .preWidth,
      .nextWidth {
        // background: rgba(red, 0.3);
        // width: 40rpx;
        // height: 40rpx;
        // padding: 10rpx;
        margin: 0 20rpx;
      }

      .pre,
      .next {
        // width: 30rpx;
        // height: 30rpx;
        // border-top: 20rpx solid transparent;
        // border-bottom: 20rpx solid transparent;
        image {
          width: 30rpx;
          height: 30rpx;
        }
      }

      .pre {
        // margin-right: 30rpx;
        // border-right: 20rpx solid #fff;
        // margin-right: 10rpx;
      }

      .next {
        transform: rotate(180deg);
        // margin-left: 30rpx;
        // border-left: 20rpx solid #fff;
        // margin-left: 10rpx;
      }
    }

    .week {
      display: flex;
      align-items: center;
      height: 80rpx;
      line-height: 80rpx;
      color: #666666;
      view {
        flex: 1;
      }
    }

    .content {
      position: relative;
      overflow: hidden;
      transition: height 0.4s ease;

      .days {
        transition: top 0.3s;
        display: flex;
        align-items: center;
        flex-wrap: wrap;
        position: relative;

        .item {
          position: relative;
          display: block;
          height: 80rpx;
          line-height: 80rpx;
          width: calc(100% / 7);
          .badge-num {
            position: absolute;
            z-index: 99;
            right: 1px;
            top: 2px;
            width: 20px;
            height: 20px;
            line-height: 20px;
            font-size: 12px;
            background: #32C5FF;
            border-radius: 50%;
            color: #fff;
          }
          .day {
            font-style: normal;
            display: inline-block;
            vertical-align: middle;
            width: 60rpx;
            height: 60rpx;
            line-height: 60rpx;
            overflow: hidden;
            border-radius: 10rpx;
            &.choose {
              background-color: #176AFF;
              color: #fff;
            }
            &.nolm {
              color: #333333;
              opacity: 0.3;
            }
          }
          .isWorkDay {
            color: #333333;
          }

          .today {
            color: #fff;
            background-color: #a8c0ff;
          }

          .workDay {
            font-style: normal;
            width: 8rpx;
            height: 8rpx;
            background: #4d7df9;
            border-radius: 10rpx;
            position: absolute;
            left: 50%;
            bottom: 0;
            pointer-events: none;
          }

          .markDay {
            font-style: normal;
            width: 8rpx;
            height: 8rpx;
            background: #fc7a64;
            border-radius: 10rpx;
            position: absolute;
            left: 50%;
            bottom: 0;
            pointer-events: none;
          }
        }
      }
    }

    .hide {
      height: 80rpx !important;
    }

    .dropDown {
      width: 50rpx;
      height: 50rpx;
      background-color: #fc7a64;
    }

    .weektoggle {
      width: 100rpx;
      height: 40rpx;
      position: relative;
      bottom: -30rpx;
      left: 50%;
      transform: translateX(-50%);
      
      // background-color: #fe6766;
      // border-radius: 0 0 20rpx 20rpx;
      // padding: 10rpx;
      .arrowUp {
        transform: rotate(180deg);
      }
      image {
        width: 50rpx;
        height: 40rpx;
      }
    }
  }
</style>

需要的图片素材如下:
请添加图片描述
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值