微信小程序-自定義日曆組件

前言

日曆是咱們開發過程當中常常會使用到的一個功能,po主在開發小程序的過程當中就遇到一個場景須要使用日曆組件。首先上網搜索一番,可是沒有找到合適本身的,因而便決定本身寫一款小程序日曆組件。html

先上效果圖:git

效果體驗:github

功能分析

這款日曆組件主要是提供日期選擇功能。首先是年月日,而後是時分秒。而且提供配置開始時間和結束時間設置,以及週末是否可選的設置項。web

整體來講分爲兩大部分:小程序

  • 年月日選擇功能
  • 時分秒選擇功能

實現分析

年月日選擇

構造數據結構

要實現年月日選擇功能,首先咱們使用下面代碼中的 setMonthData 函數構造一個 7 * 6 的數據結構, 這個數據結構包含當月,以及部分上月和下月的天數。在構造的時候經過 isValidDay 函數判斷當天是否能夠選擇,而後加上 valid 一個標識,以便在渲染的時候用於顯示和點擊判斷。數據結構

構造完成的數據結構以下:函數

{
    date: date, // 當天日期
    dateStr: date.toString(), // 當天日期的字符串,渲染時候比較使用,因爲小程序的wxml的語法限制,這裏把日期轉化成字符串進行比較
    day: date.getDate(), // 當天日期的天數
    valid: valid, // 當天日期是不是能夠選擇的
    currentMonth: false // 當天日期是不是本月的日期
}

setMonthData 和 isValidDay 函數代碼: this

// 根據條件判斷當天日期是否有效
isValidDay(date) {
    let {startDate, endDate, needWeek} = this.data;

    let valid = false;

    if (startDate && endDate) { // 開始日期和結束日期都存在
        if (date >= startDate && date < endDate) {
            valid = true;
        }
    } else if (startDate && !endDate) { // 開始日期存在結束日期不存在
        if (date >= startDate) {
            valid = true;
        }
    } else if (!startDate && endDate) { // 開始日期不存在結束日期存在
        if (date < endDate) {
            valid = true;
        }
    } else {
        valid = true;
    }

    if (!needWeek) {
        let weekDay = date.getDay();
        if (weekDay === 0 || weekDay === 6) {
            valid = false;
        }
    }
    return valid;
}
//  根據傳入的日期構造數據
setMonthData() {
    const currentDate = this.data.currentDate;

    let year = currentDate.getFullYear(), month = currentDate.getMonth();

    // 當月全部天數的數據結構
    let currentData = [];

    // 獲取當月1號的星期0~6
    let firstDayWeek = new Date(year, month, 1).getDay();

    // 天數, 用來標識當前是本月中的哪一天
    let dayIndex = 0;

    // 第1行
    let firstCol = [];

    for (let i = 0; i < 7; i++) {
        if (i < firstDayWeek) {
            let date = new Date(year, month, dayIndex - (firstDayWeek - i) + 1);
            let valid = this.isValidDay(date);
            firstCol.push({
                date: date,
                dateStr: date.toString(),
                day: date.getDate(),
                valid: valid,
                currentMonth: false
            })
        } else {
            dayIndex += 1;
            let date = new Date(year, month, dayIndex);
            let valid = this.isValidDay(date);
            firstCol.push({
                date: date,
                dateStr: date.toString(),
                day: dayIndex,
                valid: valid,
                currentMonth: true
            });
        }
    }

    currentData.push(firstCol);

    // 第2~4行
    for (let i = 0; i < 3; i++) {
        let col = [];
        for (let j = 0; j < 7; j++) {
            dayIndex += 1;
            let date = new Date(year, month, dayIndex);
            let valid = this.isValidDay(date);
            col.push({
                date: date,
                dateStr: date.toString(),
                day: dayIndex,
                valid: valid,
                currentMonth: true
            });
        }
        currentData.push(col);
    }

    // 第5行
    let lastCol = [];

    // 餘下一行中本月的天數
    let restDay = new Date(year, month + 1, 0).getDate() - dayIndex;

    for (let i = 0; i < 7; i++) {
        if (i < restDay) {
            dayIndex += 1;
            let date = new Date(year, month, dayIndex);
            let valid = this.isValidDay(date);
            lastCol.push({
                date: date,
                dateStr: date.toString(),
                day: dayIndex,
                valid: valid,
                currentMonth: true
            });
        } else {
            let date = new Date(year, month + 1, i - restDay + 1);
            let valid = this.isValidDay(date);
            lastCol.push({
                date: date,
                dateStr: date.toString(),
                day: date.getDate(),
                valid: valid,
                currentMonth: false
            });
        }
    }

    currentData.push(lastCol);

    let restDay2 = restDay - 7;

    // 第6行
    let lastCol2 = [];

    for (let i = 0; i < 7; i++) {
        if (i < restDay2) {
            dayIndex += 1;
            let date = new Date(year, month, dayIndex);
            let valid = this.isValidDay(date);
            lastCol2.push({
                date: date,
                dateStr: date.toString(),
                day: dayIndex,
                valid: valid,
                currentMonth: true
            });
        } else {
            let date = new Date(year, month + 1, i - restDay2 + 1);
            let valid = this.isValidDay(date);
            lastCol2.push({
                date: date,
                dateStr: date.toString(),
                day: date.getDate(),
                valid: valid,
                currentMonth: false
            });
        }
    }

    currentData.push(lastCol2);

    this.setData({
        currentData: currentData
    });
} 

渲染數據結構

構造完當月數據後咱們就能夠在頁面渲染這些數據,渲染的時候須要加一些判斷條件:spa

 class="cell {{td.currentMonth ? '' : 'otherMonth'}} {{td.valid ? '' : 'disabled'}} {{td.date && td.dateStr === selectedDateStr ? 'cur' : ''}}" 3d

currentMonth 控制當天日期顯示是否爲灰色, valid 控制當天日期是不是禁用狀態,dateStr 用於和當天日期 selectedDateStr 進行比較以顯示選中的日期狀態

另外在每一個td 上的點擊方法 chooseDate 中判斷 valid 爲 true後再進行日期的選擇 

上述部分相關代碼:

// 選擇日期方法
chooseDate(event) {
    let {i, j} = event.currentTarget.dataset;

    let td = this.data.currentData[i][j];

    if(td.valid) {
        this.setSelectedDate(td.date);
    }

}
// 設置當天選中的日期
setSelectedDate(date) {
    if(!date) {
        this.setData({
            selectedDate: null,
            selectedDateStr: null,
            selectedDateShow: null
        });
        return;
    } else {
        const year = date.getFullYear();
        const month = date.getMonth() + 1;
        const day = date.getDate();
        const selectedDateShow = [year, month, day].map(this.fixZero).join('-');
        this.setData({
            selectedDate: date,
            selectedDateStr: date.toString(),
            selectedDateShow: selectedDateShow
        });
    }
}
//渲染代碼
<view class='date-body' wx:if="{{mode==='date'}}">
    <view class='tr'>
        <view class='th'><view class="cell"></view></view>
        <view class='th'><view class="cell"></view></view>
        <view class='th'><view class="cell"></view></view>
        <view class='th'><view class="cell"></view></view>
        <view class='th'><view class="cell"></view></view>
        <view class='th'><view class="cell"></view></view>
        <view class='th'><view class="cell"></view></view>
    </view>
    <view class="tr"
          wx:for="{{currentData}}"
          wx:key="{{i}}"
          wx:for-item="tr"
          wx:for-index="i">
        <view class="td"
              wx:for="{{tr}}"
              wx:key="{{j}}"
              wx:for-item="td"
              wx:for-index="j"
              data-i="{{i}}"
              data-j="{{j}}"
              bindtap="chooseDate">
            <view hover-class="none"
                  class="cell {{td.currentMonth ? '' : 'otherMonth'}}
                  {{td.valid ? '' : 'disabled'}}
                  {{td.date && td.dateStr === selectedDateStr ? 'cur' : ''}}">
                {{td.day}}
            </view>
        </view>
    </view>
</view>
//樣式代碼
.picker .date-body .tr > .td > .cell {
  display: inline-block;
  width: 60rpx;
  height: 60rpx;
  line-height: 60rpx;
  margin: 6rpx;
  cursor: pointer;
  border-radius: 50%;
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  color: #333333;
}
.picker .date-body .tr > .td > .cell.otherMonth {
  color: #999999;
}
.picker .date-body .tr > .td > .cell.cur {
  background-color: #ff4158 !important;
  color: #ffffff;
}
.picker .date-body .tr > .td > .cell.disabled {
  color: #cccccc;
  background: #efefef;
  cursor: not-allowed;
}

構造數據結構

至此這個頁面的功能基本完成了,可是頂部還有一些功能箭頭,用於切換下月,下一年,以及上月,上一年。

這個功能咱們經過下面的changeDate方法來實現。首先這個函數中先根據當前事件 type 來判斷是 增長一年(月),或者是減小一年(月)。而後對當月的日期 currentDate 進行從新構造,

構造完成後調用 setCurrentDate 方法從新設置當月的日期,最後再調用 setMonthData 從新渲染日期列表數據。

上述部分相關代碼:

// 改變日期
changeDate(event) {
    let currentDate = this.data.currentDate;
    let year = currentDate.getFullYear(), month = currentDate.getMonth();
    let type = event.currentTarget.dataset.type;

    switch (type) {
        case 'year-':
            currentDate.setFullYear(year - 1)
            break;
        case 'year+':
            currentDate.setFullYear(year + 1)
            break;
        case 'month-':
            currentDate.setMonth(month - 1)
            break;
        case 'month+':
            currentDate.setMonth(month + 1)
            break;
    }

    this.setCurrentDate(currentDate);

    this.setMonthData();
}
// 設置日期
setCurrentDate(date) {
    if(!date) {
        this.setData({
            currentDate: null,
            currentDateStr: null
        });
    } else {
        const year = date.getFullYear();
        const month = date.getMonth() + 1;
        const dateStr = [year, month].map(this.fixZero).join('-');

        this.setData({
            currentDate: date,
            currentDateStr: dateStr
        });
    }
}

時分秒選擇

一開始po主使用的是 scroll-view 來實現時間選擇功能的。須要經過三個 scroll-view 進行聯動,監聽每一個 scroll-view  的 scroll 事件,而後根據 scrollTop 來判斷當前選中的項。不過體驗上仍是有所欠缺,一是 scroll-view 滑動會有滾動條出現,二是初次渲染時候若是給定一個初始值會明顯看到 scroll-view 的滾動過程。

後來在小程序文檔中找到  picker-view組件,這個組件能完美的解決上述的兩個體驗不佳的問題,並且使用很是簡單。

 picker-view  實現時分秒選擇相關代碼:

{
    hours: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
    minutes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 26, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
    seconds: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 26, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
    initTimeValue: [0, 0, 0], // 初始化選中時間的時,分,秒
    timeValue: [0, 0, 0], // 選中時間的時,分,秒
}
<view class='time-body' wx:if="{{mode==='time'}}">
    <picker-view indicator-class="selectItem" value="{{initTimeValue}}" bindchange="bindChange">
        <picker-view-column>
            <view wx:for="{{hours}}" wx:key="*this" class="item">{{item}}時</view>
        </picker-view-column>
        <picker-view-column>
            <view wx:for="{{minutes}}" wx:key="*this" class="item">{{item}}分</view>
        </picker-view-column>
        <picker-view-column>
            <view wx:for="{{seconds}}" wx:key="*this" class="item">{{item}}秒</view>
        </picker-view-column>
    </picker-view>
</view>

代碼實現 

具體實現代碼我放在github上了,感興趣的同窗能夠自取,若有不知足需求的地方能夠盡情修改,有問題能夠在評論區@我

相關文章
相關標籤/搜索