小程式手寫 的選擇日期 區間
阿新 • • 發佈:2020-08-27
參考:https://www.cnblogs.com/bin521/p/10244502.html
專案中實踐
js
// plugin/components/calendar/calendar.js /** * 屬性: * 01、year:年份:整型 * 02、month:月份:整型 * 03、day:日期:整型 * 04、startDate:日曆起點:字串[YYYY-MM] * 05、endDate:日曆終點:字串[YYYY-MM] * 06、header:是否顯示標題:布林型 * 07、next:是否顯示下個月按鈕:布林型 * 08、prev:是否顯示上個月按鈕:布林型 * 09、weeks:是否顯示周標題:布林型 * 10、showMoreDays:是否顯示上下月份的數字:布林型 * 11、lunar:是否顯示農曆 布林型 * 11、weeksType:周標題型別:字串[en、full-en、cn] * 12、cellSize: 單元格大小 整型 * 13、daysColor:設定日期字型、背景顏色 * 14、activeType: 日期背景效果(正方形、圓形)[rounded, square] * * 事件方法: * 1、nextMonth:點選下個月 * 2、prevMonth:點選上個月 * 3、dateChange: 日期選擇器變化 * * 樣式: * calendar-style 日曆整體樣式 * header-style 標題樣式 * board-style 面板樣式 */ const lunar = require('./lunar.js'); const minYear = 1900; const maxYear = 2099; let dateStart, dateEnd; Component({ /** * 元件的屬性列表 */ properties: { /** * 年份 */ year: { type: Number, value: new Date().getFullYear(), observer: '_yearChange' }, /** * 月份1~12 */ month: { type: Number, value: new Date().getMonth() + 1, observer: '_monthChange' }, /** * 日期 */ day: { type: Number, value: new Date().getDate(), observer: '_dayChange' }, /** * 日曆範圍起點 */ startDate: { type: String, value: '1900-01-01', observer: '_setStartDate' }, /** * 日曆範圍終點 */ endDate: { type: String, value: '2099-12', observer: '_setEndDate' }, /** * 是否顯示標題 */ header: { type: Boolean, value: true, observer: '_headerChange' }, /** * 是否顯示下個月按鈕 */ next: { type: Boolean, value: true }, /** * 是否顯示上個月按鈕 */ prev: { type: Boolean, value: true }, /** * 顯示額外上下月份日期 */ showMoreDays: { type: Boolean, value: false, observer: '_moreChange' }, /** * 是否顯示周標題 */ weeks: { type: Boolean, value: true, observer: '_showWeeksChange' }, /** * 周標題型別 */ weeksType: { type: String, value: 'en', observer: '_weeksTypeChange' }, /** * 設定日期字型、背景顏色 */ daysColor: { type: Array, value: [], observer: '_setDaysColor' }, /** * 單元格大小 */ cellSize: { type: Number, value: 30, observer: '_setCellSize' }, /** * 設定日期背景效果 */ activeType: { type: String, value: 'rounded', observer: '_setActiveType' }, /** * 是否顯示農曆 */ lunar: { type: Boolean, value: false, observer: '_showLunar' }, /** * 額外選項 */ addon: { type: String, value: 'none', observer: '_setAddon' }, /** * 日期附加選項 */ daysAddon: { type: Array, value: [], observer: '_setDaysAddon' }, moreDays: { type: Boolean, value: false, observer: '_setMoreDays' } }, /** * 元件的初始資料 */ data: { days_array: [], // 日期陣列 days_color: [], // 日期字型、背景顏色陣列 days_addon: [], // 日期附件 weekTitle: ['S', 'M', 'T', 'W', 'T', 'F', 'S'], max_year: 2099, // 最大年份 max_month: 12, // 最大月份 max_day: 31, min_year: 1900, // 最小年份 min_month: 1, // 最小月份 min_day: 1, // 最小日期 moreDays: false }, /** * 元件的方法列表 */ methods: { /** * 檢查年份 */ _checkYear: function(year) { if (year < minYear) { throw new RangeError('年份不能小於' + minYear + '年'); } else if (year > maxYear) { throw new RangeError('年份不能大於' + maxYear + '年'); } return true; }, /** * 檢查月份 */ _checkMonth: function(month) { if (month < 1) { throw new RangeError('月份不能小於1'); } else if (month > 12) { throw new RangeError('月份不能大於12'); } return true; }, /** * 檢查日期是否輸入正確 * @param day int 日期 */ _checkDay: function(day) { if (day < 1) { throw new RangeError('日期不能小於1'); } else if (day > 31) { throw new RangeError('日期不能大於31'); } return true; }, /** * 年份屬性改變 */ _yearChange: function(newYear, oldYear) { if (this._checkYear(newYear)) { this.setData({ year: newYear, days_array: this._setCalendarData(newYear, this.data.month) }); } }, /** * 月份屬性改變 */ _monthChange: function(newMonth, oldMonth) { if (this._checkMonth(newMonth)) { this.setData({ month: newMonth, days_array: this._setCalendarData(this.data.year, newMonth) }); } }, /** * 日期屬性改變 */ _dayChange: function(newDay, oldDay) { if (this._checkDay(newDay)) { this.setData({ day: newDay }); } }, /** * 設定起始日期 */ _setStartDate: function(newDate, oldDate) { if (newDate.length <= 10 && newDate.indexOf('-') == 4) { const year = parseInt(newDate.split('-')[0]); const month = parseInt(newDate.split('-')[1]); const day = parseInt(newDate.split('-')[2]); if (!isNaN(year) && year >= minYear && !isNaN(month) && month >= 1 && month <= 12) { this.setData({ startDate: newDate, min_year: year, min_month: month, min_day: day }); this._setCalendarData(); } else { throw new Error('起始日期必須是YYYY-MM-DD格式,且大於等於1900-01-01'); } } else { throw new Error('起始日期必須是YYYY-MM-DD格式'); } }, /** * 設定結束日期 */ _setEndDate: function(newDate, oldDate) { if (newDate.length <= 10 && newDate.indexOf('-') == 4) { const year = parseInt(newDate.split('-')[0]); const month = parseInt(newDate.split('-')[1]); const day = parseInt(newDate.split('-')[2]); if (!isNaN(year) && year <= 2099 && !isNaN(month) && month >= 1 && month <= 12) { this.setData({ endDate: newDate, max_year: year, max_month: month, max_day: day }); } else { throw new Error('結束日期必須是YYYY-MM-DD格式,且小於等於2099-12'); } } else { throw new Error('結束日期必須是YYYY-MM-DD格式'); } }, /** * 是否顯示標題 */ _headerChange: function(newHeader, oldHeader) { this.setData({ header: !!newHeader }); }, /** * 是否顯示額外的月份日期 */ _moreChange: function(newMore, oldMore) { this.setData({ showMoreDays: !!newMore, days_array: this._setCalendarData(this.data.year, this.data.month) }); }, /** * 周標題型別 */ _weeksTypeChange: function(newVal, oldVal) { switch (newVal) { case 'en': this.setData({ weeksType: 'en', weekTitle: ['S', 'M', 'T', 'W', 'T', 'F', 'S'] }); break; case 'cn': this.setData({ weeksType: 'cn', weekTitle: ['日', '一', '二', '三', '四', '五', '六'] }); break; case 'full-en': this.setData({ weeksType: 'full-en', weekTitle: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] }); break; default: this.setData({ weeksType: 'en', weekTitle: ['S', 'M', 'T', 'W', 'T', 'F', 'S'] }); break; } }, /** * 是否顯示周標題 */ _showWeeksChange: function(newVal, oldVal) { this.setData({ weeks: !!newVal, }); }, /** * 設定單元格寬度 */ _setCellSize: function(newSize, oldSize) { this.setData({ cellSize: newSize }); }, /** * 是否顯示農曆 */ _showLunar: function(newConfig, oldConfig) { this.setData({ lunar: !!newConfig }); }, /** * 設定日期單元格字型顏色、背景 */ _setDaysColor: function(newDaysColor, oldDaysColor) { this.setData({ days_color: newDaysColor }, function() { this.setData({ days_array: this._setCalendarData(this.data.year, this.data.month) }); }); }, /** * 設定日期背景效果 */ _setActiveType: function(newType, oldType) { switch (newType) { case 'rounded': case 'square': this.setData({ activeType: newType }); break; default: this.setData({ activeType: 'rounded' }); } }, /** * 設定日曆 * @param int year 年份 * @param int month 月份,取值1~12 */ _setCalendarData: function(year, month) { const empty_days_count = new Date(year, month - 1, 1).getDay(); // 本月第一天是周幾,0是星期日,6是星期六 let empty_days = new Array; const prev_month = month - 1 == 0 ? 12 : month - 1; // 上個月的月份數 const prev_year = month - 1 == 0 ? this.data.year - 1 : this.data.year; /** * 上個月的日期 */ for (let i = 0; i < empty_days_count; i++) { empty_days.push({ state: 'inactive', day: -1, month: prev_month, year: prev_year, info: 'prev', color: '#c3c6d1', background: 'transparent' }); } /** * 下個月的日期 */ const last_day = new Date(year, month, 0); // 本月最後一天 const days_count = last_day.getDate(); // 本月最後一天是幾號 const last_date = last_day.getDay(); // 本月最後一天是星期幾 const next_month = month + 1 == 13 ? 1 : month + 1; // 下個月的月份數 const next_year = month + 1 == 13 ? this.data.year + 1 : this.data.year; let empty_days_last = new Array; for (let i = 0; i < 6 - last_date; i++) { empty_days_last.push({ state: 'inactive', day: -2, month: next_month, year: next_year, info: 'next', color: '#c3c6d1', background: 'transparent' }); } /** * 本月的日期 */ let temp = new Array; for (let i = 1; i <= days_count; i++) { temp.push({ state: 'inactive', day: i, month: month, year: year, info: 'current', color: '#4a4f74', background: 'transparent' }); } const days_range = temp; // 本月 let days = empty_days.concat(days_range, empty_days_last); // 上個月 + 本月 + 下個月 // 如果要顯示前後月份的日期 if (this.data.showMoreDays) { // 顯示下月的日期 let index = days.findIndex(element => { return element.day == -2; }); if (index != -1) { const length = days.length; const count = length - index; for (let i = 1; i <= count; i++) { days[index + i - 1].day = i; } } // 顯示上月的日期 index = days.findIndex(element => { return element.day == 1; }) - 1; if (index != -1) { const last_month_day = new Date(year, month - 1, 0).getDate(); for (let i = 0; i <= index; i++) { days[i].day = last_month_day - index + i; } } } /** * 設定日期顏色、背景 */ for (let i = 0; i < this.data.days_color.length; i++) { const item = this.data.days_color[i]; const background = item.background ? item.background : 'transparent'; for (let j = 0; j < days.length; j++) { if (days[j].month == item.month && days[j].day == item.day) { if (item.color) { days[j].color = item.color + '!important'; } if (item.background) { days[j].background = item.background + '!important'; } } } } /** * 設定農曆 */ for (let i = 0; i < days.length; i++) { const item = days[i]; const solarDay = item.day; const solarMonth = item.month; const solarYear = year; if (solarDay > 0) { const lunarDate = lunar.solarToLunar(year, solarMonth, solarDay); days[i]['lunarMonth'] = lunarDate.monthStr; days[i]['lunarDay'] = lunarDate.dayStr; if (lunarDate.dayStr == '初一') { days[i]['lunarDay'] = lunarDate.monthStr; } } } // 設定起止日期 for (let i = 0; i < days.length; i++) { if (days[i].year < this.data.min_year || (days[i].year == this.data.min_year && days[i].month < this.data.min_month) || (days[i].year == this.data.min_year && days[i].month == this.data.min_month && days[i].day < this.data.min_day)) { days[i].color = '#c3c6d1'; } if (days[i].year > this.data.max_year || (days[i].year == this.data.max_year && days[i].month > this.data.max_month) || (days[i].year == this.data.max_year && days[i].month == this.data.max_month && days[i].day > this.data.max_day)) { days[i].color = '#c3c6d1'; } } let days_array = new Array; let week = new Array; for (let i = 0; i < days.length; i++) { week.push(days[i]); if (i % 7 == 6) { days_array.push(week); week = new Array; } } if (week.length > 0) { days_array.push(week); } return days_array; }, _setAddon: function(newAddon, oldAddon) { if (newAddon == 'none') { this.setData({ lunar: false, daysAddon: [], addon: 'none' }); } else if (newAddon == 'lunar') { this.setData({ lunar: true, daysAddon: [], addon: 'lunar' }); } else if (newAddon == 'custom') { this.setData({ addon: 'custom', lunar: false, }); } else if (newAddon == 'mixed') { this.setData({ addon: 'mixed', lunar: true }); } }, /** * 自定義日期陣列 */ _setDaysAddon: function(newAddon, oldAddon) { if (typeof(newAddon) == 'object' && newAddon instanceof Array) { this.setData({ days_addon: newAddon }); } }, /** * 是否允許選自日期區間 */ _setMoreDays: function(newMore, oldMore) { this.setData({ moreDays: !!newMore, }); }, /** * 點選下個月 */ nextMonth: function() { const eventDetail = { prevYear: this.data.year, prevMonth: this.data.month }; if (this.data.month == 12) { this.setData({ year: this.data.year + 1, month: 1 }); } else { this.setData({ month: this.data.month + 1 }); } this.setData({ days_array: this._setCalendarData(this.data.year, this.data.month) }); eventDetail['currentYear'] = this.data.year; eventDetail['currentMonth'] = this.data.month; this.triggerEvent('nextMonth', eventDetail); }, /** * 點選上個月 */ prevMonth: function() { const eventDetail = { prevYear: this.data.year, prevMonth: this.data.month }; if (this.data.month == 1) { this.setData({ year: this.data.year - 1, month: 12 }); } else { this.setData({ month: this.data.month - 1 }) } this.setData({ days_array: this._setCalendarData(this.data.year, this.data.month) }); eventDetail['currentYear'] = this.data.year; eventDetail['currentMonth'] = this.data.month; this.triggerEvent('prevMonth', eventDetail); }, /** * 日期選擇器變化 */ dateChange: function(event) { const eventDetail = { prevYear: this.data.year, prevMonth: this.data.month }; const value = event.detail.value; const date = new Date(value); const year = date.getFullYear(); const month = date.getMonth() + 1; this.setData({ year: year, month: month, days_array: this._setCalendarData(year, month) }); eventDetail['currentYear'] = year; eventDetail['currentMonth'] = month; this.triggerEvent('dateChange', eventDetail); }, /** * 點選具體日期 */ dayClick: function(event) { const click_day = event.currentTarget.dataset.day; // 判斷選中日期是否在範圍內 if (click_day.year < this.data.min_year || (click_day.year == this.data.min_year && click_day.month < this.data.min_month) || (click_day.year == this.data.min_year && click_day.month == this.data.min_month && click_day.day < this.data.min_day)) { return; } if (click_day.year > this.data.max_year || (click_day.year == this.data.max_year && click_day.month > this.data.max_month) || (click_day.year == this.data.max_year && click_day.month == this.data.max_month && click_day.day > this.data.max_day)) { return; } // 選擇單日期 if (!this.data.moreDays) { this.triggerEvent('dayClick', click_day); } else { const click_day = event.currentTarget.dataset.day; if (dateStart == undefined) { dateStart = click_day; // 設定選中日期背景色 let temp = temp = new Array(1);; temp[0] = { background: '#ea9518', day: dateStart.day, month: dateStart.month } this.setData({ days_color: temp }); this.setData({ days_array: this._setCalendarData(dateStart.year, dateStart.month) }); } else { dateEnd = click_day; } if (dateEnd != undefined) { // 選擇同一天返回 if (dateStart.year == dateEnd.year && dateStart.month == dateEnd.month && dateStart.day == dateEnd.day) { return; } // 當開始日期超過結束日期時,開始日期和結束日期對換 if (dateStart.year > dateEnd.year || (dateStart.year == dateEnd.year && dateStart.month > dateEnd.month) || (dateStart.year == dateEnd.year && dateStart.month == dateStart.month && dateStart.day > dateEnd.day)) { let temp = dateEnd; dateEnd = dateStart; dateStart = temp; } const eventDetail = { dateStart: dateStart, dateEnd: dateEnd } // 設定選中日期背景色 // 設定選中日期背景色 let temp = new Array(); var start = this.parse(dateStart.year + '-' + dateStart.month + '-' + dateStart.day, 'y-m-d'); var end = this.parse(dateEnd.year + '-' + dateEnd.month + '-' + dateEnd.day, 'y-m-d'); for (let i = 0; start <= end && i < 20; i++) { temp.push({ day: start.getDate(), month: start.getMonth() + 1, background: "#ea9518" }) start = this.nextDay(start); } this.setData({ days_color: temp }); console.log(this.data.days_color) this.setData({ days_array: this._setCalendarData(click_day.year, click_day.month) }); dateStart = undefined; dateEnd = undefined; // 睡眠3秒 let start = new Date().getTime(); while (true) if (new Date().getTime() - start > 1000) break; this.triggerEvent('dayClick', eventDetail); } } }, /** * @description 將字串轉換為日期,支援格式y-m-d ymd (y m r)以及標準的 * @return {Date} 返回日期物件 */ parse: function(dateStr, formatStr) { if (typeof dateStr === 'undefined') return null; if (typeof formatStr === 'string') { var _d = new Date(formatStr); //首先取得順序相關字串 var arrStr = formatStr.replace(/[^ymd]/g, '').split(''); if (!arrStr && arrStr.length != 3) return null; var formatStr = formatStr.replace(/y|m|d/g, function(k) { switch (k) { case 'y': return '(\\d{4})'; case 'm': ; case 'd': return '(\\d{1,2})'; } }); var reg = new RegExp(formatStr, 'g'); var arr = reg.exec(dateStr) var dateObj = {}; for (var i = 0, len = arrStr.length; i < len; i++) { dateObj[arrStr[i]] = arr[i + 1]; } return new Date(dateObj['y'], dateObj['m'] - 1, dateObj['d']); } return null; }, nextDay: function(d) { let end_date = new Date(d); end_date = +end_date + 1000*60*60*24; end_date = new Date(end_date); return end_date; }, }, created: function() {}, attached: function() { const year = this.data.year; const month = this.data.month; this.setData({ days_array: this._setCalendarData(year, month) }); }, ready: function() {}, externalClasses: [ 'calendar-style', // 日曆整體樣式 'header-style', // 標題樣式 'board-style', // 面板樣式 ] })
wxml
<view class="calendar calendar-style"> <!--主標題--> <view class="calendar-header header-style" wx:if="{{header}}"> <text wx:if="{{year == min_year && month == min_month}}"></text> <text class="cwj-icon cwj-calendar-icon-left" bindtap="prevMonth" wx:elif="{{prev}}"></text> <text wx:else></text> <picker mode="date" value="{{year}}-{{month}}" start="{{startDate}}" end="{{endDate}}" bindchange="dateChange" fields="month"> <text>{{year}}年{{month}}月</text> </picker> <text wx:if="{{year == max_year && month == max_month}}"></text> <text class="cwj-icon cwj-calendar-icon-right" bindtap="nextMonth" wx:elif="{{next}}"></text> <text wx:else></text> </view> <!--日曆面板--> <view class="calendar-board board-style"> <!--周標題--> <view class="calendar-weeks" wx:if="{{weeks && weekTitle.length == 7}}"> <text class="calendar-weekday" wx:for="{{weekTitle}}" wx:key="unique">{{item}}</text> </view> <!--日期--> <view class="calendar-days"> <block wx:for="{{days_array}}" wx:for-item="item" wx:key="unique" wx:for-index="i"> <!--日期行--> <view class="calendar-row"> <block wx:for="{{days_array[i]}}" wx:for-item="day" wx:key="unique"> <view class="calendar-cell" style="background: {{day.background}}; width: {{cellSize}}px; height: {{cellSize}}px;" wx:if="{{day.day <= 0}}"></view> <view class="calendar-cell" style="background: {{day.background}}; width: {{cellSize}}px; height: {{cellSize}}px;" wx:elif="{{activeType == 'square'}}" bindtap="dayClick" data-day="{{day}}"> <block wx:if="{{day.info == 'prev'}}"> <text class="calendar-day" style="color: {{day.color}};">{{day.day}}</text> <block wx:if="{{addon == 'lunar'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:if="{{lunar}}">{{day.lunarDay}}</text> </block> <block wx:elif="{{addon == 'custom'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};">{{days_addon[c]}}</text> </block> <block wx:elif="{{addon == 'mixed'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:if="{{days_addon[i] != ''}}">{{days_addon[i]}}</text> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:else>{{day.lunarDay}}</text> </block> </block> <block wx:elif="{{day.info == 'next'}}"> <text class="calendar-day" style="color: {{day.color}};">{{day.day}}</text> <block wx:if="{{addon == 'lunar'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:if="{{lunar}}">{{day.lunarDay}}</text> </block> <block wx:elif="{{addon == 'custom'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};">{{days_addon[i]}}</text> </block> <block wx:elif="{{addon == 'mixed'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:if="{{days_addon[i] != ''}}">{{days_addon[i]}}</text> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:else>{{day.lunarDay}}</text> </block> </block> <block wx:else> <text class="calendar-day" style="color: {{day.color}};">{{day.day}}</text> <block wx:if="{{addon == 'lunar'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:if="{{lunar}}">{{day.lunarDay}}</text> </block> <block wx:elif="{{addon == 'custom'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};">{{days_addon[i]}}</text> </block> <block wx:elif="{{addon == 'mixed'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:if="{{days_addon[i] != ''}}">{{days_addon[i]}}</text> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:else>{{day.lunarDay}}</text> </block> </block> </view> <view class="calendar-cell cell-rounded" style="background: {{day.background}}; width: {{cellSize}}px; height: {{cellSize}}px;" wx:else bindtap="dayClick" data-day="{{day}}"> <block wx:if="{{day.info == 'prev'}}"> <text class="calendar-day" style="color: {{day.color}};">{{day.day}}</text> <block wx:if="{{addon == 'lunar'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:if="{{lunar}}">{{day.lunarDay}}</text> </block> <block wx:elif="{{addon == 'custom'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};">{{days_addon[i]}}</text> </block> <block wx:elif="{{addon == 'mixed'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:if="{{days_addon[i] != ''}}">{{days_addon[i]}}</text> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:else>{{day.lunarDay}}</text> </block> </block> <block wx:elif="{{day.info == 'next'}}"> <text class="calendar-day" style="color: {{day.color}};">{{day.day}}</text> <block wx:if="{{addon == 'lunar'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:if="{{lunar}}">{{day.lunarDay}}</text> </block> <block wx:elif="{{addon == 'custom'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};">{{days_addon[i]}}</text> </block> <block wx:elif="{{addon == 'mixed'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:if="{{days_addon[i] != ''}}">{{days_addon[i]}}</text> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:else>{{day.lunarDay}}</text> </block> </block> <block wx:else> <text class="calendar-day" style="color: {{day.color}};">{{day.day}}</text> <block wx:if="{{addon == 'lunar'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:if="{{lunar}}">{{day.lunarDay}}</text> </block> <block wx:elif="{{addon == 'custom'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};">{{days_addon[i]}}</text> </block> <block wx:elif="{{addon == 'mixed'}}"> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:if="{{days_addon[i] != ''}}">{{days_addon[i]}}</text> <text class="calendar-lunar-day" style="color: {{day.color}};" wx:else>{{day.lunarDay}}</text> </block> </block> </view> </block> </view> </block> </view> </view> </view>
wxss
/* plugin/components/calendar/calendar.wxss */ /* * 字型 */ @font-face {font-family: "cwj-icon"; src: url('//at.alicdn.com/t/font_601455_q8aev4obbmon7b9.eot?t=1522928336476'); /* IE9*/ src: url('//at.alicdn.com/t/font_601455_q8aev4obbmon7b9.eot?t=1522928336476#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff'), url('//at.alicdn.com/t/font_601455_q8aev4obbmon7b9.ttf?t=1522928336476') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ url('//at.alicdn.com/t/font_601455_q8aev4obbmon7b9.svg?t=1522928336476#cwj-icon') format('svg'); /* iOS 4.1- */ } .cwj-icon { font-family:"cwj-icon" !important; font-size:16px; font-style:normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .cwj-calendar-icon-left:before { content: "\e697"; } .cwj-calendar-icon-up:before { content: "\e6a5"; } .cwj-calendar-icon-down:before { content: "\e6a6"; } .cwj-calendar-icon-right:before { content: "\e6a7"; } .cwj-calendar-icon-dot:before { content: "\e608"; } /** * 日曆元件 */ .calendar { display: block; margin: 0rpx; font-size: 28rpx; } /** * 日曆主標題 */ .calendar-header { display: flex; align-items: center; justify-content: space-around; margin: 0rpx 20rpx 20rpx 20rpx; text-align: center; font-weight: bold; } /** * 日曆周標題 */ .calendar-weeks { display: flex; justify-content: space-around; font-weight: bold; } /** * 日曆周標題單個專案 */ .calendar-weekday { display: flex; justify-content: center; align-items: center; width: 40rpx; height: 40rpx; margin-top: 10rpx; margin-bottom: 10rpx; text-align: center; } /** * 日曆日期行 */ .calendar-row { display: flex; justify-content: space-around; } /** * 日曆單個日期項 */ .calendar-cell { display: flex; flex-direction: column; justify-content: center; align-items: center; margin-top: 10rpx; margin-bottom: 10rpx; text-align: center; } .cell-rounded { border-radius: 50%; } .calendar-lunar-day { font-size: 20rpx; }
lunar.js
/* * 農曆資料表 * * 農曆分大小月,大月30天,小月29天,但一年中哪個月為大月,哪個月為小月,是無規律的。 * 農曆每十年有4個閏年,但哪一年為閏年也是不確定的。 * 而閏月中,哪個閏月為大月,哪個為小月也是不確定的。 * * 下面共20行,每行10個數據。每個資料代表一年,從陽曆1900.1.31日起,為第一個資料年的開始,即陽曆1900.1.31=農曆0.1.1。 * 200個數據可推200年的農曆,因此目前最大隻能推算到2100年 * * 對於每一個數據項,5個十六進位制數 = 20個二進位制位 * 前4位,即0在這一年是閏年時才有意義,它代表這年閏月的大小月,為1則閏大月,為0則閏小月。 * 中間12位,即4bd,每位代表一個月,為1則為大月,為0則為小月。 * 最後4位,即8,代表這一年的閏月月份,為0則不閏。首4位要與末4位搭配使用。 */ const lunarInfo = new Array( 0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900年~1909年 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910年~1919年 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920年~1929年 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930年~1939年 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940年~1949年 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950年~1959年 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960年~1969年 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6, // 1970年~1979年 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980年~1989年 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0, // 1990年~1999年 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000年~2009年 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010年~2019年 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020年~2029年 0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030年~2039年 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040年~2049年 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050年~2059年 0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060年~2069年 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070年~2079年 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080年~2089年 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090年~2099年 0x0d520 // 2100年 ); const minYear = 1900; // 能計算的最小年份 const maxYear = 2100; // 能計算的最大年份 // 陽曆每月天數,遇到閏年2月需加1天 const solarMonth = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); // 農曆月份別稱 const monthName = new Array('正月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '冬月', '臘月'); // 二十四節氣 const solarTerm = new Array( '小寒', '大寒', '立春', '雨水', '驚蟄', '春分', '清明', '穀雨', '立夏', '小滿', '芒種', '夏至', '小暑', '大暑', '立秋', '處暑', '白露', '秋分', '寒露', '霜降', '立冬', '小雪', '大雪', '冬至' ); // 二十節氣相關係數 const termInfo = new Array( 0, 21208, 42467, 63836, 85337, 107014, 128867, 150921, 173149, 195551, 218072, 240693, 263343, 285989, 308563, 331033, 353350, 375494, 397447, 419210, 440795, 462224, 483532, 504758); /** * 檢查年份是否輸入正確 * @param year int 年份 */ function _checkYear(year) { if (year < minYear) { throw new RangeError('年份不能小於' + minYear + '年'); } else if (year > maxYear) { throw new RangeError('年份不能大於' + maxYear + '年'); } return true; } /** * 檢查月份是否輸入正確 * @param month int 月份 */ function _checkMonth(month) { if (month < 1) { throw new RangeError('月份不能小於1'); } else if (month > 12) { throw new RangeError('月份不能大於12'); } return true; } /** * 檢查日期是否輸入正確 * @param day int 日期 */ function _checkDay(day) { if (day < 1) { throw new RangeError('日期不能小於1'); } else if (day > 31) { throw new RangeError('日期不能大於31'); } return true; } /** * 返回農曆year年中哪個月是閏月,沒有閏月返回0 * @param year int 年份 */ function getLunarLeapMonth(year) { if (_checkYear(year)) { return lunarInfo[year - minYear] & 0xf; // 最後4位,代表這一年的閏月月份,為0則今年沒有閏月 } } /** * 返回農曆year年閏月的天數(如果沒有閏月則返回0) * @param year int 年份 */ function getLeapMonthDaysCount(year) { if (getLunarLeapMonth(year)) { return lunarInfo[year - minYear] & 0x10000 ? 30 : 29; // 前4位,即0在這一年是閏年時才有意義,它代表這年閏月的大小月 } return 0; } /** * 返回農曆year年的總天數 * @param year int 年份 */ function getLunarYearDaysCount(year) { if (_checkYear(year)) { let sum = 348; // 29天 * 12個月 = 348日 for (let i = 0x8000; i > 0x8; i >>= 1) { sum += (lunarInfo[year - minYear] & i ? 1 : 0); } return sum + getLeapMonthDaysCount(year); } } /** * 返回農曆year年month月的天數 * @param year int 年份 * @param month int 月份 1~12 */ function getLunarYearMonthDaysCount(year, month) { if (_checkYear(year) && _checkMonth(month)) { return lunarInfo[year - minYear] & (0x10000 >> month) ? 30 : 29; } } /** * 農曆日期的中文字串 * @param day int 日期 */ function getLunarDayString(day) { if (_checkDay(day)) { const nStr1 = new Array('日', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'); const nStr2 = new Array('初', '十', '廿', '卅'); let str = ''; switch (day) { case 10: str = '初十'; break; case 20: str = '二十'; break; case 30: str = '三十'; break; default: str = nStr2[Math.floor(day / 10)]; str += nStr1[day % 10]; break; } return str; } } /** * 返回某年的第n個節氣為幾日(從0小寒起算) * @param year int 年份 * @param n 節氣編號 0~23 */ function getLunarTermDay(year, n) { if (_checkYear(year) && n <= 23 && n >= 0) { const sTermInfo = new Array(0, 21208, 42467, 63836, 85337, 107014, 128867, 150921, 173149, 195551, 218072, 240693, 263343, 285989, 308563, 331033, 353350, 375494, 397447, 419210, 440795, 462224, 483532, 504758); const offDate = new Date((31556925974.7 * (year - minYear) + sTermInfo[n] * 60000) + Date.UTC(minYear, 0, 6, 2, 5)); return offDate.getUTCDate(); } } /** * 陽曆日期轉農曆日期 * @param year int 年份 * @param month int 月份 1~12 * @param day int 日期 1~31 */ function solarToLunar(year, month, day) { if (_checkYear(year) && _checkMonth(month) && _checkDay(day)) { const baseDate = new Date(minYear, 0, 31); // 基礎日期1900年1月31日 const objDate = new Date(year, month - 1, day); // 目標日期 let offset = (objDate - baseDate) / 86400000; // 偏移天數 60 * 60 * 24 * 1000 = 86400000,1天的毫秒數 let monCycle = 14; let temp = 0; let i = 0; for (i = minYear; i < maxYear && offset > 0; i++) { temp = getLunarYearDaysCount(i); // 農曆year年的總天數 if (offset - temp < 0) { break; } else { offset -= temp; } monCycle += 12; } const lunarYear = i; // 農曆年份 const leap = getLunarLeapMonth(lunarYear); // 當年閏月是哪個月 const isLeapYear = leap > 0 ? true : false; // 當年是否有閏月 let isLeapMonth = false; // 當前農曆月份是否是閏月 for (i = 1; i <= 12 && offset > 0; i++) { if (leap > 0 && i == (leap + 1) && !isLeapMonth) { --i; isLeapMonth = true; temp = getLeapMonthDaysCount(year); } else { temp = getLunarYearMonthDaysCount(year, i); } if (isLeapMonth && i == (leap + 1)) { isLeapMonth = false; } offset -= temp; if (!isLeapMonth) { monCycle++; } } if (offset == 0 && leap > 0 && i == leap + 1) { if (isLeapMonth) { isLeapMonth = false; } else { isLeapMonth = true; --i; --monCycle; } } if (offset < 0) { offset += temp; --i; --monCycle; } const lunarMonth = i; // 農曆月份 const lunarDay = offset + 1; // 農曆日期 let monthStr = ''; if (isLeapYear) { if (lunarMonth < leap) { monthStr = monthName[lunarMonth - 1]; } else if (lunarMonth == leap) { monthStr = '閏' + monthName[lunarMonth - 1]; } else { monthStr = monthName[lunarMonth - 2]; } } else { monthStr = monthName[lunarMonth - 1]; } return { year: lunarYear, // 農曆年份 month: lunarMonth, // 農曆月份 day: lunarDay, // 農曆日期 isLeap: isLeapMonth, // 是否閏月 monthStr: monthStr, // 月份字串 dayStr: getLunarDayString(lunarDay) // 日期字串 }; } } /** * 陽曆某個月份天數 * @param year int 年份 * @param month int 月份 1~12 */ function getSolarMonthDaysCount(year, month) { if (_checkYear(year) && _checkMonth(month)) { if (month == 2) { return (((year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0)) ? 29 : 28); } else { return solarMonth[month - 1]; } } } /** * 獲取指定日期是陽曆年中的第幾天 * @param year int 年份 * @param month int 月份 1-12 * @param day int 日期 */ function getSolarDayNumber(year, month, day) { if (_checkYear(year) && _checkMonth(month) && _checkDay(day)) { const date = new Date(year, month - 1, day); const d = date.getDate(); // 本月第幾天 const m = month - 1; let sum = d; for (let i = 0; i < m; i++) { sum += solarMonth[i]; } if (m > 1 && (year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { sum += 1; } return sum; } } /** * 計算指定日期是否屬於24節氣 * @param year int 年份 * @param month int 月份 1~12 * @param day int 日期 1~31 */ function getLunar24Days(year, month, day) { if (_checkYear(year) && _checkMonth(month) && _checkDay(day)) { const baseDate = new Date(1900, 0, 6, 2, 5, 0); let str = false; for (let i = 1; i <= 24; i++) { const num = 525948.76 * (year - 1900) + termInfo[i]; const timestamp = baseDate.getTime() + num * 60 * 1000; const newDate = new Date(timestamp); if (getSolarDayNumber(newDate.getFullYear(), newDate.getMonth() + 1, newDate.getDate()) == getSolarDayNumber(year, month, day)) { str = solarTerm[i]; break; } } return str; } } module.exports = { getLunarLeapMonth, // 返回農曆year年中哪個月是閏月,沒有閏月返回0 getLeapMonthDaysCount, // 返回農曆year年閏月的天數(如果沒有閏月則返回0) getLunarYearDaysCount, // 返回農曆year年的總天數 getLunarYearMonthDaysCount, // 返回農曆year年month月的天數 getLunarDayString, // 農曆日期的中文字串 getLunarTermDay, // 返回某年的第n個節氣為第幾日 getSolarMonthDaysCount, // 獲取陽曆某個月份有多少天 getSolarDayNumber, // 獲取指定日期是陽曆年中的第幾天 getLunar24Days, // 計算指定日期是否屬於24節氣 solarToLunar, // 陽曆日期轉農曆日期 }
在 其他頁面中使用
<!-- 日曆元件 -->
// 包裹了一層模態框 元件可以自己實現 <modalView show="{{showModal}}" clickMaskClose="{{clickMaskClose}}"> <view class="calendar"> <view class="calendar-cancel" bindtap="cancelSelect">取消</view> <calendar cell-size="45" weeks-type="cn" active-type="square" binddayClick="handleSelectDate" moreDays="true" show-more-days="true" startDate="{{startDate}}" endDate="{{endDate}}" /> </view> </modalView>
// 包裹了一層模態框
js
Component({ /** * 元件的屬性列表 */ properties: { //是否顯示modal彈窗 show: { type: Boolean, value: false, observer: '_setShow' }, //控制底部是一個按鈕還是兩個按鈕,預設兩個 single: { type: Boolean, value: false }, // 控制clickMask是否關閉 clickMaskClose: { type: Boolean, value: true, observer: '_setClickMaskClose' }, top: { type: Number, value: 0 }, bottom: { type: Number, value: 0 } }, /** * 元件的初始資料 */ data: { clickMaskClose: true, show: false, top: 0, bottom: 0 }, /** * 元件的方法列表 */ methods: { _setClickMaskClose: function(newData, oldData) { this.setData({ clickMaskClose: newData }) }, _setShow: function (newData, oldData) { this.setData({ show: newData }) }, // 點選modal的回撥函式 clickMask: function() { // 點選modal背景關閉遮罩層,如果不需要註釋掉即可 if (this.data.clickMaskClose) { this.setData({ show: false }) } }, // 點選取消按鈕的回撥函式 cancel: function() { this.setData({ show: false }) this.triggerEvent('cancel') //triggerEvent觸發事件 }, // 點選確定按鈕的回撥函式 confirm: function() { this.setData({ show: false }) this.triggerEvent('confirm') } } })
wxml
<!--components/modal/modal.wxml--> <view class='modal-mask' wx:if='{{show}}' bindtap='clickMask'> <view class='modal-content' style="bottom: {{bottom}}px;"> <scroll-view scroll-y class='main-content'> <slot></slot> </scroll-view> </view> </view>
wxss
/* components/modal/modal.wxss */ /* components/modal/modal.wxss */ /*遮罩層*/ .modal-mask{ display: flex; position: fixed; left: 0; right: 0; top: 0; bottom: 0; background-color: rgba(0,0,0,0.5); z-index: 9999999; } /*遮罩內容*/ .modal-content{ display: flex; position: absolute; flex-direction: column; width: 100%; background-color: #fff; } /*中間內容*/ .main-content{ flex: 1; height: 100%; overflow-y: hidden; max-height: 80vh; /* 內容高度最高80vh 以免內容太多溢位*/ }