-->

微信小程序日历的实现

2020-08-13 09:11发布

一、先上一张最终的效果图

伸手党直接拉到底部,有个码云地址,如果有帮到你,记得点赞哦

二、思路

1.首先组件需要有一个当前时间defaultTime(用户传入,如不传,取当前时间),然后使用new Date()获取到这个时间所在的年和月。
2.写一个方法,接收2个参数,年份和月份,返回一个数组,数组里面是包含这个月份所有的日期和前后月份与其相同周的几天,如,假设传入2020,8,红框部分就是前后月份与其相同周的几天。

3.在点击前后箭头,或者点击数字设置指定月份的时候,继续调用第二步的方法,触发日历主体更新。
4.在展开状态时,展示一整个月份的日期,收起状态时,只展示当前周的7天,如图。

5.组件接收一个属性spot,是个数组,每一项是一个能被new Date()解析的日期格式,用于指定哪些日期下方需要展示小圆点。
6.组件绑定一个事件,bind:change,在每次日期改变时和首次日历首次渲染完成的时候把当前日期作为参数传入,触发这个事件。






三、实现代码

WXML部分

<!--components/calendar/calendar.wxml-->
<view class="calendar">
	<view class="title flex">
		<view class="flex">
			<picker value="{{selectDay.year+'-'+selectDay.month}}" bindchange="editMonth" mode="date" fields="month" class="year-month">{{selectDay.year}}.{{selectDay.month>9?selectDay.month:"0"+selectDay.month}}</picker>
			<view class="icon" bindtap="lastMonth" style="transform:rotate(180deg);">
				<view class="iconfont icon-playfill"></view>
			</view>
			<view class="icon" bindtap="nextMonth">
				<view class="iconfont icon-playfill"></view>
			</view>
		</view>
		<view catchtap="openChange" class="flex open">
			<view>{{open?"收起":"展开"}}</view>
			<view style="margin-left:6rpx;font-size:20rpx" class="iconfont icon-{{open?'fold':'unfold'}}"></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="flex-start flex-wrap calendar-main" style="height:{{dateList.length/7*72}}rpx">
		<view wx:for="{{dateList}}" wx:key="dateList" class="day">
			<view class="bg {{(item.year === selectDay.year && item.month === selectDay.month) ? (item.day === selectDay.day?'select':''): 'other-month'}}" catchtap="selectChange" data-day="{{item.day}}" data-year="{{item.year}}" data-month="{{item.month}}" data-date-string="{{item.dateString}}">
				{{item.day}}
			</view>
			<view class="spot" wx:if="{{item.spot}}"></view>
		</view>
	</view>
</view>

WXSS部分

/* components/calendar/calendar.wxss */
@font-face {
  font-family: "iconfont";
  src: url('iconfont.eot?t=1596614903470');
  /* IE9 */
  src: url('iconfont.eot?t=1596614903470#iefix') format('embedded-opentype'),
    /* IE6-IE8 */
    url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAL8AAsAAAAABxQAAAKvAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDHAqBVIFEATYCJAMQCwoABCAFhG0HQhswBsgekiRFQgQU8AMAmmCIh3/HXu9L8hFVWwXoyALpqqoqwAEfD6g66abEPBux/13TPwBUeZ0lAldI75LS0ctlzLY8NwVk3NQm5Co3lqhaWPE+z+X0pi+Q1bez3MZatCYtPuoFGAcU4F7UiyIroBPkltpFHBi88q89JtA0p1jEYe/wNMQryKpAPHbYMMT3CooStetCdeZgEd9U1NPX9ADgQf8+/kFlxJNUMrLx5LpHhPaf+GUsavP/JpxJQpHgHq8gYx0oxO1s41yUiMQoTWOibQN1dYmf+Dn0MhYO2zgfttC/PEKSiSrS3QabXjPzE3ON4GdII/FrrEsGhPjr/dYGcOyzM56gg6PM1BXvcbZIxY7zzRyS4znJs/m2VmjuRNq9edF1kcx9FbW85/nRgbXX6s5q/AX+vsGh/kBhUUHAYszwFx4acaAA6l7k9bgjo8Vbue4acDcbCqFqcBru+htf4l/I4EnzzQAa8lT+kNoAyG/TJuTdyDcsG+NB4r05rfm73irgx/jRrw4XMB+gyga24dQGHCqlg8RKyZ3cWuWLEWhFQlMTmOwwFLrCPwH3E+omB/I1sy2yunmysOuoaNlEVd0Omtb0HW8ZYaJEaWDVnoPQd4ek6zuyvkeysE+omPpCVT+i0HQaWRe2LIYjsYsRzImIrEvIIagKZQmeaHoniW1Owqzr0NIsYbqjjCpKy4ftPqIQdsUWfd5WyTlFlKky6nWeI5KkIo2pTiLwUjvnWmNZGZ37UKmgypDoAENgHCFCrJYgDgKVgsrxXOLo+5MImzkSjK3oFuazCEbnmB6pUKocQPZ5FZDuV97RzbOpxHEUQjEqGdLrzCMkEhWizc9zIgRcKfuBmqZRGR1Fod7S5/3yH56AJnJDiRQ5SlRU3yhUlcRoMStek/ASdUgSAAA=') format('woff2'),
    url('iconfont.woff?t=1596614903470') format('woff'),
    url('iconfont.ttf?t=1596614903470') format('truetype'),
    /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
    url('iconfont.svg?t=1596614903470#iconfont') format('svg');
  /* iOS 4.1- */
}

.iconfont {
  font-family: "iconfont" !important;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  line-height: 1;
  font-weight: normal;
}

.icon-unfold:before {
  content: "\e661";
}

.icon-fold:before {
  content: "\e6de";
}

.icon-playfill:before {
  content: "\e74f";
}

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

.direction-column {
  flex-direction: column;
}

.flex1 {
  flex: 1;
}

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

.flex-start {
  display: flex;
  justify-content: flex-start;
  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;
}

.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 .icon {
  padding: 0 16rpx;
  font-size: 32rpx;
  color: #999;
}

.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;
  transition: height 0.3s;
  align-content: flex-start;
  overflow: hidden;
}

.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: linear-gradient(-60deg, #0FDAC5, #1BC7B0);
  box-shadow: 0px 5px 16px 0px #C6F3ED;
  margin: 0 auto;
}

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

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

JS部分

// components/calendar/calendar.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    spot: {
      type: Array,
      value: []
    },
    defaultTime: {
      type: String,
      value: ''
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    dateList: [], //日历主体渲染数组
    selectDay: {}, //选中时间
  },

  /**
   * 组件的方法列表
   */
  methods: {
    /**
     * 时间戳转化为年 月 日 时 分 秒
     * time: 需要被格式化的时间,可以被new Date()解析即可
     * format:格式化之后返回的格式,年月日时分秒分别为Y, M, D, h, m, s,这个参数不填的话则显示多久前
     */
    formatTime(time, format) {
      function formatNumber(n) {
        n = n.toString()
        return n[1] ? n : '0' + n
      }

      function getDate(time, format) {
        const formateArr = ['Y', 'M', 'D', 'h', 'm', 's']
        const returnArr = []
        const date = new Date(time)
        returnArr.push(date.getFullYear())
        returnArr.push(formatNumber(date.getMonth() + 1))
        returnArr.push(formatNumber(date.getDate()))
        returnArr.push(formatNumber(date.getHours()))
        returnArr.push(formatNumber(date.getMinutes()))
        returnArr.push(formatNumber(date.getSeconds()))
        for (const i in returnArr) {
          format = format.replace(formateArr[i], returnArr[i])
        }
        return format
      }

      function getDateDiff(time) {
        let r = ''
        const ft = new Date(time)
        const nt = new Date()
        const nd = new Date(nt)
        nd.setHours(23)
        nd.setMinutes(59)
        nd.setSeconds(59)
        nd.setMilliseconds(999)
        const d = parseInt((nd - ft) / 86400000)
        switch (true) {
          case d === 0:
            const t = parseInt(nt / 1000) - parseInt(ft / 1000)
            switch (true) {
              case t < 60:
                r = '刚刚'
                break
              case t < 3600:
                r = parseInt(t / 60) + '分钟前'
                break
              default:
                r = parseInt(t / 3600) + '小时前'
            }
            break
          case d === 1:
            r = '昨天'
            break
          case d === 2:
            r = '前天'
            break
          case d > 2 && d < 30:
            r = d + '天前'
            break
          default:
            r = getDate(time, 'Y-M-D')
        }
        return r
      }
      if (!format) {
        return getDateDiff(time)
      } else {
        return getDate(time, format)
      }
    },
    //picker设置月份
    editMonth(e) {
      const arr = e.detail.value.split("-")
      const year = parseInt(arr[0])
      const month = parseInt(arr[1])
      this.setMonth(year, month)
    },
    //上月切换按钮点击
    lastMonth() {
      const lastMonth = new Date(this.data.selectDay.year, this.data.selectDay.month - 2)
      const year = lastMonth.getFullYear()
      const month = lastMonth.getMonth() + 1
      this.setMonth(year, month)
    },
    //下月切换按钮点击
    nextMonth() {
      const nextMonth = new Date(this.data.selectDay.year, this.data.selectDay.month)
      const year = nextMonth.getFullYear()
      const month = nextMonth.getMonth() + 1
      this.setMonth(year, month)
    },
    //设置月份
    setMonth(setYear, setMonth, setDay) {
      if (this.data.selectDay.year !== setYear || this.data.selectDay.month !== setMonth) {
        const day = Math.min(new Date(setYear, setMonth, 0).getDate(), this.data.selectDay.day)
        const time = new Date(setYear, setMonth - 1, setDay ? setDay : day)
        const data = {
          selectDay: {
            year: setYear,
            month: setMonth,
            day: setDay ? setDay : day,
            dateString: this.formatTime(time, "Y-M-D")
          }
        }
        if (!setDay) {
          data.open = true
        }
        this.setData(data)
        this.dateInit(setYear, setMonth)
        this.setSpot()
        this.triggerEvent("change", this.data.selectDay)
      }
    },
    //展开收起
    openChange() {
      this.setData({
        open: !this.data.open
      })
      this.triggerEvent("aaa", { a: 0 })
      this.dateInit()
      this.setSpot()
    },
    //设置日历底下是否展示小圆点
    setSpot() {
      const timeArr = this.data.spot.map(item => {
        return this.formatTime(item, "Y-M-D")
      })
      this.data.dateList.forEach(item => {
        if (timeArr.indexOf(item.dateString) !== -1) {
          item.spot = true
        } else {
          item.spot = false
        }
      })
      this.setData({
        dateList: this.data.dateList
      })
    },
    //日历主体的渲染方法
    dateInit(setYear = this.data.selectDay.year, setMonth = this.data.selectDay.month) {
      let dateList = []; //需要遍历的日历数组数据
      let now = new Date(setYear, setMonth - 1)//当前月份的1号
      let startWeek = now.getDay(); //目标月1号对应的星期
      let dayNum = new Date(setYear, setMonth, 0).getDate() //当前月有多少天
      let forNum = Math.ceil((startWeek + dayNum) / 7) * 7 //当前月跨越的周数
      if (this.data.open) {
        //展开状态,需要渲染完整的月份
        for (let i = 0; i < forNum; i++) {
          const now2 = new Date(now)
          now2.setDate(i - startWeek + 1)
          let obj = {};
          obj = {
            day: now2.getDate(),
            month: now2.getMonth() + 1,
            year: now2.getFullYear(),
            dateString: this.formatTime(now2, "Y-M-D")
          };
          dateList[i] = obj;
        }
      } else {
        //非展开状态,只需要渲染当前周
        for (let i = 0; i < 7; i++) {
          const now2 = new Date(now)
          //当前周的7天
          now2.setDate(Math.ceil((this.data.selectDay.day + startWeek) / 7) * 7 - 6 - startWeek + i)
          let obj = {};
          obj = {
            day: now2.getDate(),
            month: now2.getMonth() + 1,
            year: now2.getFullYear(),
            dateString: this.formatTime(now2, "Y-M-D")
          };
          dateList[i] = obj;
        }
      }
      this.setData({
        dateList: dateList
      })
    },
    //一天被点击时
    selectChange(e) {
      const year = e.currentTarget.dataset.year
      const month = e.currentTarget.dataset.month
      const day = e.currentTarget.dataset.day
      const dateString = e.currentTarget.dataset.dateString
      const selectDay = {
        year: year,
        month: month,
        day: day,
        dateString: dateString
      }
      if (this.data.selectDay.year !== year || this.data.selectDay.month !== month) {
        this.setMonth(year, month, day)
      } else if (this.data.selectDay.day !== day) {
        this.setData({
          selectDay: selectDay
        })
        this.triggerEvent("change", this.data.selectDay)
      }
    }
  },
  lifetimes: {
    attached() {
      let now = this.data.defaultTime ? new Date(this.data.defaultTime) : new Date()
      let selectDay = {
        year: now.getFullYear(),
        month: now.getMonth() + 1,
        day: now.getDate(),
        dateString: this.formatTime(now, "Y-M-D")
      }
      this.setMonth(selectDay.year, selectDay.month, selectDay.day)
    }
  },
  observers: {
    spot: function (spot) {
      this.setSpot()
    }
  }
})

四、码云地址

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

如果有帮助到你,记得点赞哦!

标签: