微信小程序日历的实现

最后更新于2022年6月22日,变更如下:

2022.06.22

  • 修复在苹果手机上日历无法展示的问题(感谢Lysa_snow)

2022.05.31

  • 引入dayjs优化日期计算逻辑
  • 删除iconfont字体,三角形使用css替代
  • 新增月份变化事件(bind:monthChange)和年份变化事件(bind:yearChange)
  • 日期变变化事件名由bind:change修改为bind:dateChange

一、先上一个demo的演示二维码

也可以直接拉到底部,有个码云地址
在这里插入图片描述

二、属性

参数说明类型默认值
defaultDate日历默认选中的时间String | Date | Number(可以被dayjs解析的格式即可)今天
spot底部需要展示小圆点的日期数组Array<String | Date | Number[]

三、事件

事件说明回调参数
bind:dateChange选中的日期变化时触发event.detail:{ date, month, year, dateString }
bind:monthChange选中的月份变化时触发event.detail:{ date, month, year, dateString }
bind:yearChange选中的年份变化时触发event.detail:{ date, month, year, dateString }

四、实现代码

WXML

<!--components/calendar/calendar.wxml-->
<view class="calendar">
  <view class="title flex">
    <view class="flex">
      <picker value="{{selectDay.year+'-'+(selectDay.month+1)}}" bindchange="editMonth" mode="date" fields="month" class="year-month">{{selectDay.year}}.{{selectDay.month>8?selectDay.month+1:"0"+(selectDay.month+1)}}</picker>
      <view class="last-icon" bindtap="lastMonth"> </view>
      <view class="next-icon" bindtap="nextMonth"> </view>
    </view>
    <view catchtap="openChange" class="flex open">
      <view>{{open?"收起":"展开"}}</view>
    </view>
  </view>

  <!-- 日历头部 -->
  <view class="flex-around calendar-week">
    <view class="view"></view>
    <view class="view"></view>
    <view class="view"></view>
    <view class="view"></view>
    <view class="view"></view>
    <view class="view"></view>
    <view class="view"></view>
  </view>

  <!-- 日历主体 -->
  <view class="calendar-main">
    <view class="date-group" style="height:{{open? dateList.length/7*72 : 72}}rpx;">
      <view class="flex-start flex-wrap date-transform" style="transform: translateY({{open?0:-72*transform}}rpx);">
        <view wx:for="{{dateList}}" wx:key="dateList" class="day">
          <view class="bg {{(item.year === selectDay.year && item.month === selectDay.month) ?'': 'other-month'}} {{item.dateString === selectDay.dateString?'select':''}}" catchtap="selectChange" data-date="{{item.date}}" data-year="{{item.year}}" data-month="{{item.month}}" data-date-string="{{item.dateString}}">
            {{item.date}}
          </view>
          <view class="spot" wx:if="{{item.spot}}"></view>
        </view>
      </view>
    </view>
  </view>
</view>

WXSS

/* components/calendar/calendar.wxss */
.flex {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.flex-start {
  display: flex;
  justify-content: flex-start;
  align-items: center;
}

.flex-center {
  display: flex;
  justify-content: center;
  align-items: center;
}

.flex-end {
  display: flex;
  justify-content: flex-end;
  align-items: center;
}

.flex-around {
  display: flex;
  justify-content: space-around;
  align-items: center;
}

.flex1 {
  flex: 1;
}

.flex-wrap {
  flex-wrap: wrap;
}

.align-start {
  align-items: flex-start;
}

.align-end {
  align-items: flex-end;
}

.align-stretch {
  align-items: stretch;
}

.calendar {
  background-color: #fff;
}

.calendar .title {
  font-size: 40rpx;
  color: #333;
  padding: 30rpx;
  line-height: 60rpx;
}

.calendar .title .year-month {
  margin-right: 20rpx;
}

.calendar .title .last-icon {
  width: 0;
  height: 0;
  border-right: 24rpx solid #999;
  border-top: 12rpx solid transparent;
  border-bottom: 12rpx solid transparent;
  margin-left: 10rpx;

}

.calendar .title .next-icon {
  width: 0;
  height: 0;
  border-left: 24rpx solid #999;
  border-top: 12rpx solid transparent;
  border-bottom: 12rpx solid transparent;
  margin-left: 40rpx;
}

.calendar .title .open {
  background-color: #f6f6f6;
  color: #999;
  font-size: 22rpx;
  line-height: 36rpx;
  border-radius: 18rpx;
  padding: 0 14rpx;
}

.calendar .calendar-week {
  line-height: 40rpx;
  padding: 0 25rpx;
  font-size: 28rpx;
  color: #999;
}

.calendar .calendar-week .view {
  width: 100rpx;
  text-align: center;
}

.calendar .calendar-main {
  padding: 30rpx 25rpx;
}

.calendar .calendar-main .date-group {
  transition: all 0.3s;
  overflow: hidden;

}

.calendar .calendar-main .date-transform {
  transition: all 0.3s;

}

.calendar .calendar-main .day {
  position: relative;
  width: 100rpx;
  color: #666;
  text-align: center;
  height: 72rpx;
}

.calendar .calendar-main .day .bg {
  height: 56rpx;
  line-height: 56rpx;
  font-size: 28rpx;
  color: #333;
  font-weight: bold;
}

.calendar .calendar-main .day .select {
  width: 56rpx;
  border-radius: 50%;
  text-align: center;
  color: #fff;
  background: #409eff;
  margin: 0 auto;
}

.calendar .calendar-main .day .other-month {
  color: #ccc;
}

.calendar .calendar-main .day .spot {
  width: 8rpx;
  height: 8rpx;
  background-color: #409eff;
  border-radius: 50%;
  margin: 6rpx auto 0;
}

JS部分

// components/calendar/calendar.js
const dayjs = require("./dayjs")
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    //底下需要展示小圆点的日期数组
    spot: {
      type: Array,
      value: []
    },
    //组件渲染时默认选中的时间
    defaultDate: {
      type: String,
      optionalTypes: [Date, Number],
      value: ''
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    dateList: [], //日历主体渲染数组
    selectDay: {}, //选中时间
    open: true, //日历是否展开
    transform: 0 //收起时日历高度偏移倍数
  },

  /**
   * 组件的方法列表
   */
  methods: {
    //picker设置月份
    editMonth(e) {
      const arr = e.detail.value.split("-")
      this.setDate(parseInt(arr[0]), parseInt(arr[1]) - 1)
    },
    //上月切换按钮点击
    lastMonth() {
      const lastMonth = dayjs(new Date(this.data.selectDay.year, this.data.selectDay.month - 1))
      this.setDate(lastMonth.year(), lastMonth.month())
    },
    //下月切换按钮点击
    nextMonth() {
      const nextMonth = dayjs(new Date(this.data.selectDay.year, this.data.selectDay.month + 1))
      this.setDate(nextMonth.year(), nextMonth.month())
    },
    //设置选中日期
    setDate(paramYear, paramMonth, paramDate) {
      const date = Math.min(dayjs(`${paramYear}-${paramMonth + 1}`).daysInMonth(), this.data.selectDay.date)
      const time = dayjs(`${paramYear}-${paramMonth + 1}-${paramDate || date}`)
      const selectDay = {
        year: paramYear,
        month: paramMonth,
        date: paramDate || date,
        dateString: time.format("YYYY-MM-DD"),
      }
      //设置收起时的日历主体偏移量
      let dateListStart = dayjs(`${paramYear}-${paramMonth + 1}`).day(0)
      this.setData({
        transform: dayjs(`${paramYear}-${paramMonth + 1}-${paramDate || date}`).day(0).diff(dateListStart, 'week')
      })
      if (paramYear !== this.data.selectDay.year) {
        this.setData({
          selectDay,
          open: true
        })
        this.dateListInit(paramYear, paramMonth)
        this.triggerEvent("dateChange", this.data.selectDay)
        this.triggerEvent("monthChange", this.data.selectDay)
        this.triggerEvent("yearChange", this.data.selectDay)
        return
      }
      if (paramMonth !== this.data.selectDay.month) {
        this.setData({
          selectDay,
          open: true
        })
        this.dateListInit(paramYear, paramMonth)
        this.triggerEvent("dateChange", this.data.selectDay)
        this.triggerEvent("monthChange", this.data.selectDay)
        return
      }
      if (paramDate && paramDate !== this.data.selectDay.date) {
        this.setData({
          selectDay
        })
        this.triggerEvent("dateChange", this.data.selectDay)
      }

    },

    //展开收起
    openChange() {
      this.setData({
        open: !this.data.open
      })
    },

    //日历主体的渲染方法
    dateListInit(paramYear = this.data.selectDay.year, paramMonth = this.data.selectDay.month) {
      let dateList = []; //需要遍历的日历数组数据
      let startDate = dayjs(`${paramYear}-${paramMonth + 1}`).day(0) //日历渲染开始日期
      let endDate = dayjs(`${paramYear}-${paramMonth + 1}`).endOf('month').day(6) //日历主体渲染结束日期
      const timeArr = this.data.spot.map(item => {
        return dayjs(item).format('YYYY-MM-DD')
      }) //底部小圆点需要展示的数组
      while (startDate < endDate) {
        const dateString = startDate.format("YYYY-MM-DD")
        dateList.push({
          date: startDate.date(),
          month: startDate.month(),
          year: startDate.year(),
          dateString,
          spot: timeArr.indexOf(dateString) !== -1
        })
        startDate = startDate.add(1, 'day')
      }
      this.setData({
        dateList: dateList
      })
    },

    //某一天被点击时
    selectChange(e) {
      const year = e.currentTarget.dataset.year
      const month = e.currentTarget.dataset.month
      const date = e.currentTarget.dataset.date
      this.setDate(year, month, date)
    }
  },
  //组件生命周期
  lifetimes: {
    attached() {
      let now = this.data.defaultDate ? dayjs(this.data.defaultDate) : dayjs()
      this.setDate(now.year(), now.month(), now.date())
    }
  },
  //监听参数变化
  observers: {
    spot: function () {
      this.dateListInit()
    }
  }
})

五、码云地址

https://gitee.com/GaoWeiQiang1996/wx-calendar

如果有帮助到你,给个免费的赞吧!

评论 73
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值