参考: 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}}; {{cellSize}}px; height: {{cellSize}}px;" wx:if="{{day.day <= 0}}"></view> <view class="calendar-cell" style="background: {{day.background}}; {{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}}; {{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; 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; 100%; background-color: #fff; } /*中间内容*/ .main-content{ flex: 1; height: 100%; overflow-y: hidden; max-height: 80vh; /* 内容高度最高80vh 以免内容太多溢出*/ }