UNPKG

wx-mini-weview

Version:

weixin UI

1,359 lines (1,238 loc) 63.5 kB
Component({ options: { styleIsolation: 'apply-shared' }, properties: { value: { type: null, // 允许任意类型 value: null, observer(newVal) {console.log('observer', newVal) if (!this.data.isInitialized) { this.setData({ pendingValue: newVal }); return; } if (this.data._skipValueObserver) return; this.handleValueProperty(newVal); } }, // 主题色彩样式:primary, secondary, info, success, danger, warning, error, poppy, pink, jade, forest, cute, dark theme: { type: String, value: 'wv-theme-poppy wv-content-light' }, // 日期格式化:YYYY-MM-DD, YYYY/MM/DD, MM/DD/YYYY, etc. formatDatetime: { type: String, value: 'YYYY-MM-DD' }, // 初始化时加载的月份数量 initialMonthCount: { type: Number, value: 3 }, // 最小可选日期,格式:YYYY-MM-DD minDate: { type: String, value: '' }, // 最大可选日期,格式:YYYY-MM-DD maxDate: { type: String, value: '' }, // 日历组件大小:default, small, mini size: { type: String, value: 'default' }, // 选择模式:single(单选), multiple(多选), range(范围选择) selectMode: { type: String, value: 'single', observer: function(newValue, oldValue) { if (newValue !== oldValue) { this.resetSelection(); } } }, // 禁用的星期几:[0,1,2,3,4,5,6],其中0代表周日,1代表周一,以此类推 disabledDaysOfWeek: { type: Array, value: [], observer: function(newValue) { clearTimeout(this._updateDisabledDaysOfWeekTimer); this._updateDisabledDaysOfWeekTimer = setTimeout(() => { this.updateDisabledDaysFlags(newValue); this.updateMonthsDisabledStatus(); this.clearDisabledSelections(); }, this.throttleDelay); } }, // 禁用的具体日期:['2024-08-15', '2024-08-16'],格式为YYYY-MM-DD disabledDates: { type: Array, value: [], observer: function(newValue) { clearTimeout(this._updateDisabledDatesTimer); this._updateDisabledDatesTimer = setTimeout(() => { this.updateMonthsDisabledStatus(); this.clearDisabledSelections(); // 清除已被禁用的日期选择 }, this.throttleDelay); } } }, data: { isInitialized: false, pendingValue: null, _skipValueObserver: false, // 添加这个标志 date: '', dates: [], rangeStartDate: '', rangeEndDate: '', formatedDates: [], formatedDateWeeks: [], formatedDateWeeksMondayBased: [], selectFinished: false, monthsList: [], selectedDate: '', selectedDates: [], rangeHoverDate: '', scrollViewHeight: '0px', minDateObj: null, maxDateObj: null, visibleMonths: [], throttleDelay: 300, currentMonthIndex: -1, scrollTop: 0, dayCellHeight: 'auto', currentMonth: { year: '', month: '' }, daySundayDisabled: false, dayMondayDisabled: false, dayTuesdayDisabled: false, dayWednesdayDisabled: false, dayThursdayDisabled: false, dayFridayDisabled: false, daySaturdayDisabled: false, yearRange: [], monthRange: [], yearPickerIndex: 0, monthPickerIndex: 0, tempYear: 0, tempMonth: 0, isProgrammaticScrolling: false, scrollLockTimer: null }, lifetimes: { attached() { const that = this this.initializeDates(); this.initCalendarData(); this.updateDisabledDaysFlags(this.properties.disabledDaysOfWeek); this.initYearMonthPicker(); }, ready() { } }, methods: { // 触发 select 事件 triggerSelectEvent() { // 使用 buildEventData 创建事件数据 const dateToUse = this.properties.selectMode === 'single' ? this.data.date : (this.properties.selectMode === 'multiple' && this.data.dates.length > 0) ? this.data.dates[0] : (this.properties.selectMode === 'range' && this.data.rangeStartDate) ? this.data.rangeStartDate : this.formatDate(new Date(), 'YYYY-MM-DD'); const eventData = this.buildEventData(dateToUse); this.triggerEvent('select', eventData); }, initializeDates() { const today = new Date(); const formattedTodayDate = this.formatDate(today, 'YYYY-MM-DD'); const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); const formattedTomorrowDate = this.formatDate(tomorrow, 'YYYY-MM-DD'); const minDateObj = this.properties.minDate ? new Date(this.properties.minDate) : null; const maxDateObj = this.properties.maxDate ? new Date(this.properties.maxDate) : null; if (minDateObj) minDateObj.setHours(0, 0, 0, 0); if (maxDateObj) maxDateObj.setHours(0, 0, 0, 0); this.setData({ _skipValueObserver: true }); // 基础数据 let initialData = { minDateObj, maxDateObj }; // 检查是否有传入的value值 const hasValue = this.properties.value !== null && this.properties.value !== undefined && this.properties.value !== ''; // 根据选择模式设置合理的初始值 switch (this.properties.selectMode) { case 'single': // 如果有value就用value,否则用今天日期 const singleValue = hasValue && typeof this.properties.value === 'string' ? this.properties.value : formattedTodayDate; initialData = { ...initialData, date: singleValue, selectedDate: singleValue, // 只有在没有传入value时才设置默认value ...(hasValue ? {} : { value: singleValue }), selectFinished: true }; break; case 'multiple': // 如果有value就用value,否则用包含今天的数组 const multipleValue = hasValue && Array.isArray(this.properties.value) ? this.properties.value : [formattedTodayDate]; initialData = { ...initialData, dates: multipleValue, selectedDates: multipleValue, // 只有在没有传入value时才设置默认value ...(hasValue ? {} : { value: multipleValue }), selectFinished: true }; break; case 'range': // 如果有value就用value中的startDate和endDate,否则用今天和明天 let rangeStartDate = formattedTodayDate; let rangeEndDate = formattedTomorrowDate; if (hasValue && typeof this.properties.value === 'object') { if (this.properties.value.startDate) { rangeStartDate = this.properties.value.startDate; } if (this.properties.value.endDate) { rangeEndDate = this.properties.value.endDate; } } initialData = { ...initialData, rangeStartDate, rangeEndDate, // 只有在没有传入value时才设置默认value ...(hasValue ? {} : { value: { startDate: rangeStartDate, endDate: rangeEndDate } }), selectFinished: !!(rangeStartDate && rangeEndDate) }; break; } this.setData(initialData, () => { this.setData({ isInitialized: true, _skipValueObserver: false }, () => { if (this.data.pendingValue) { this.handleValueProperty(this.data.pendingValue); this.setData({ pendingValue: null }); } }); }); }, handleValueProperty(value) { if (!value) return; console.log('handleValueProperty', value) clearTimeout(this._handleValuePropertyTimer); this._handleValuePropertyTimer = setTimeout(() => {console.log('handleValueProperty ininin', value) let result = false; switch (this.properties.selectMode) { case 'single': if (typeof value === 'string') { result = this.selectSingleDate(value); } break; case 'multiple': if (Array.isArray(value)) { result = this.selectMultipleDates(value); } break; case 'range': if (value && typeof value === 'object' && (value.startDate || value.endDate)) { result = this.selectDateRange(value.startDate, value.endDate); } break; } // If selection was successful, manually trigger the select event // This ensures the calendarData is updated in the calendar-picker if (result) { this.triggerSelectEvent(); } }, this.throttleDelay); }, initCalendarData() { const today = new Date(); const year = today.getFullYear(); const month = today.getMonth() + 1; const currentDate = new Date(year, month - 1, 1); const monthsList = []; const buffer = 3; const currentMonthKey = this.getMonthKey(year, month); let currentMonthIndex = -1; for (let i = buffer; i >= 0; i--) { const date = this.addMonths(currentDate, -i); if (this.isMonthInRange(date)) { const monthData = this.generateMonthData(date.getFullYear(), date.getMonth() + 1); if (this.getMonthKey(monthData.year, monthData.month) === currentMonthKey) { monthData.isCurrentMonth = true; currentMonthIndex = monthsList.length; } monthsList.push(monthData); } } for (let i = 1; i <= buffer; i++) { const date = this.addMonths(currentDate, i); if (this.isMonthInRange(date)) { const monthData = this.generateMonthData(date.getFullYear(), date.getMonth() + 1); if (this.getMonthKey(monthData.year, monthData.month) === currentMonthKey) { monthData.isCurrentMonth = true; currentMonthIndex = monthsList.length; } monthsList.push(monthData); } } var dayCellHeight = 0; if(this.properties.size === 'mini') { dayCellHeight = 26; }else if(this.properties.size === 'small') { dayCellHeight = 32; }else { dayCellHeight = 40; } this.setData({ dayCellHeight, scrollViewHeight: (dayCellHeight * 6 + 43) + 'px', monthsList, currentMonthIndex, currentMonth: { year, month } }); }, generateMonthData(year, month) { const days = []; const daysInMonth = new Date(year, month, 0).getDate(); const firstDay = new Date(year, month - 1, 1).getDay(); for (let i = 0; i < firstDay; i++) { days.push({ day: '', date: '' }); } const today = new Date(); const todayYear = today.getFullYear(); const todayMonth = today.getMonth() + 1; const todayDay = today.getDate(); for (let i = 1; i <= daysInMonth; i++) { const currentDate = new Date(year, month - 1, i); const dayOfWeek = currentDate.getDay(); const dateStr = `${year}-${month.toString().padStart(2, '0')}-${i.toString().padStart(2, '0')}`; const isDisabled = (this.data.minDateObj && currentDate < this.data.minDateObj) || (this.data.maxDateObj && currentDate > this.data.maxDateObj) || this.properties.disabledDaysOfWeek.includes(dayOfWeek) || (this.properties.disabledDates && this.properties.disabledDates.includes(dateStr)); days.push({ day: i, date: dateStr, isToday: year === todayYear && month === todayMonth && i === todayDay, isWeekend: dayOfWeek % 6 === 0, dayOfWeek, isDisabled, isSelected: false, inRange: false }); } return { year, month, days, key: this.getMonthKey(year, month) }; }, updateSelectionState() { clearTimeout(this._updateSelectionStateTimer); this._updateSelectionStateTimer = setTimeout(() => { const { monthsList, date, dates, rangeStartDate, rangeEndDate, rangeHoverDate, selectMode } = this.data; // 性能优化关键点 1:使用高效数据结构 const selectedDatesSet = new Set(dates); let rangeStart = 0, rangeEnd = 0; // 性能优化关键点 2:预计算时间戳 // 只有在range模式下且有起止日期时才计算范围,否则强制设为0确保清除范围样式 if (selectMode === 'range' && rangeStartDate && (rangeEndDate || rangeHoverDate)) { const [start, end] = this.getDateRange(rangeStartDate, rangeEndDate, rangeHoverDate); rangeStart = start.getTime(); rangeEnd = end.getTime(); } else { // 重要:确保在非range模式下,范围值被重置为0 rangeStart = 0; rangeEnd = 0; } // 性能优化关键点 3:每天只检查一次"今天"状态 const now = Date.now(); const shouldCheckToday = !this.lastTodayCheck || (now - this.lastTodayCheck > 86400000); // 性能优化关键点 4:最小化对象创建 let hasChanges = false; const optimizedMonthsList = monthsList.map(monthItem => { if (monthItem.isBoundary || !monthItem.days?.length) return monthItem; const optimizedDays = monthItem.days.map(day => { if (!day.date) return day; // 性能优化关键点 5:缓存时间戳 const dateStamp = day._cachedStamp || (day._cachedStamp = new Date(day.date).getTime()); let newState = {}; // 状态计算逻辑 switch (selectMode) { case 'single': newState.isSelected = day.date === date; // 重要:确保清除range相关的所有状态 newState.inRange = false; newState.isRangeStart = false; newState.isRangeEnd = false; break; case 'multiple': newState.isSelected = selectedDatesSet.has(day.date); // 重要:确保清除range相关的所有状态 newState.inRange = false; newState.isRangeStart = false; newState.isRangeEnd = false; break; case 'range': newState.isRangeStart = day.date === rangeStartDate; newState.isRangeEnd = day.date === rangeEndDate; newState.isSelected = newState.isRangeStart || newState.isRangeEnd; newState.inRange = dateStamp > rangeStart && dateStamp < rangeEnd; break; } // 性能优化关键点 6:智能更新今天状态 if (shouldCheckToday) { newState.isToday = dateStamp === this.todayStamp; } // 性能优化关键点 7:变化检测 const hasChange = Object.keys(newState).some(key => day[key] !== newState[key] ); if (!hasChange) return day; hasChanges = true; return { ...day, ...newState }; }); // 性能优化关键点 8:数组引用保留 return optimizedDays !== monthItem.days ? { ...monthItem, days: optimizedDays } : monthItem; }); // 性能优化关键点 9:避免不必要的 setData if (!hasChanges && !shouldCheckToday) return; // 更新缓存时间戳 if (shouldCheckToday) { this.lastTodayCheck = now; this.todayStamp = new Date().setHours(0,0,0,0); } // 性能优化关键点 10:批量更新 this.setData({ monthsList: optimizedMonthsList }); }, this.throttleDelay) }, getDateRange(startStr, endStr, hoverStr) { // 安全转换日期对象 const parseDate = (str) => { if (!str) return null; const date = new Date(str); return isNaN(date.getTime()) ? null : date; }; const start = parseDate(startStr); const end = parseDate(endStr) || parseDate(hoverStr) || start; // 处理无效日期情况 const defaultDate = new Date(0); // 1970-01-01 作为默认 const validStart = start instanceof Date ? start : defaultDate; const validEnd = end instanceof Date ? end : defaultDate; // 返回时间戳对 return validStart > validEnd ? [validEnd, validStart] : [validStart, validEnd]; }, handleScroll(e) { if (this.data.isProgrammaticScrolling) { console.log('程序滚动中,忽略滚动事件处理'); return; } clearTimeout(this._handleScrollTimer); this._handleScrollTimer = setTimeout(() => { if (!this.data.isProgrammaticScrolling) { this.checkVisibleMonths(); } }, this.data.throttleDelay); }, checkVisibleMonths() { const query = this.createSelectorQuery(); query.selectAll('.month-section').boundingClientRect(); query.select('.month-scroll-view').boundingClientRect(); query.exec((res) => { if (!res || !res[0] || !res[1]) return; const monthSections = res[0]; const scrollView = res[1]; const visibleMonths = []; const scrollViewTop = scrollView.top; const scrollViewBottom = scrollView.bottom; const scrollViewHeight = scrollView.height; const scrollViewCenter = scrollViewTop + scrollViewHeight / 2; let centerMonth = null; let minDistance = Infinity; monthSections.forEach((section, index) => { if (section.bottom > scrollViewTop && section.top < scrollViewBottom) { const sectionData = { index, year: parseInt(section.dataset.year), month: parseInt(section.dataset.month) }; visibleMonths.push(sectionData); const sectionCenter = section.top + section.height / 2; const distance = Math.abs(sectionCenter - scrollViewCenter); if (distance < minDistance) { minDistance = distance; centerMonth = sectionData; } } }); if (visibleMonths.length > 0) { this.setData({ visibleMonths }); this.ensureBufferMonths(visibleMonths); if (centerMonth || visibleMonths.length > 0) { const newCurrentMonth = centerMonth || visibleMonths[0]; const { year, month } = newCurrentMonth; this.setData({ currentMonth: { year, month }, yearPickerIndex: this.data.yearRange.indexOf(year), monthPickerIndex: month - 1 }); this.updateSelectionState(); } } }); }, ensureBufferMonths(visibleMonths) { if (visibleMonths.length === 0) return; const firstVisible = visibleMonths[0]; const lastVisible = visibleMonths[visibleMonths.length - 1]; const earliestDate = new Date(firstVisible.year, firstVisible.month - 1, 1); const latestDate = new Date(lastVisible.year, lastVisible.month - 1, 1); const buffer = 3; let reachedMinBoundary = false; let reachedMaxBoundary = false; for (let i = 1; i <= buffer; i++) { const prevDate = this.addMonths(earliestDate, -i); if (this.isMonthInRange(prevDate)) { const prevYear = prevDate.getFullYear(); const prevMonth = prevDate.getMonth() + 1; if (this.findMonthIndex(prevYear, prevMonth) === -1) { this.addMonth(prevYear, prevMonth, 'prepend'); } } else { reachedMinBoundary = true; break; } } for (let i = 1; i <= buffer; i++) { const nextDate = this.addMonths(latestDate, i); if (this.isMonthInRange(nextDate)) { const nextYear = nextDate.getFullYear(); const nextMonth = nextDate.getMonth() + 1; if (this.findMonthIndex(nextYear, nextMonth) === -1) { this.addMonth(nextYear, nextMonth, 'append'); } } else { reachedMaxBoundary = true; break; } } this.updateBoundaries(reachedMinBoundary, reachedMaxBoundary); }, updateBoundaries(reachedMinBoundary, reachedMaxBoundary) { const monthsList = [...this.data.monthsList]; const existingMinBoundary = monthsList.findIndex(m => m.isBoundary && m.boundaryType === 'min') >= 0; const existingMaxBoundary = monthsList.findIndex(m => m.isBoundary && m.boundaryType === 'max') >= 0; let changed = false; if (reachedMinBoundary && !existingMinBoundary) { monthsList.unshift(this.generateBoundaryData('min')); changed = true; } if (reachedMaxBoundary && !existingMaxBoundary) { monthsList.push(this.generateBoundaryData('max')); changed = true; } if (changed) { this.setData({ monthsList }); } }, generateBoundaryData(boundaryType) { return { year: '', month: '', days: [], isBoundary: true, boundaryType, key: `boundary-${boundaryType}-${Date.now()}` }; }, addMonth(year, month, position = 'append') { if (this.findMonthIndex(year, month) !== -1) { return; } const monthData = this.generateMonthData(year, month); const today = new Date(); if (today.getFullYear() === year && today.getMonth() + 1 === month) { monthData.isCurrentMonth = true; } let newMonthsList = [...this.data.monthsList]; if (position === 'prepend') { newMonthsList.unshift(monthData); } else if (position === 'append') { newMonthsList.push(monthData); } else { const insertIndex = this.findInsertIndex(monthData); newMonthsList.splice(insertIndex, 0, monthData); } newMonthsList.sort((a, b) => { if (a.isBoundary && a.boundaryType === 'min') return -1; if (b.isBoundary && a.boundaryType === 'min') return 1; if (a.isBoundary && a.boundaryType === 'max') return 1; if (b.isBoundary && a.boundaryType === 'max') return -1; if (a.year !== b.year) return a.year - b.year; return a.month - b.month; }); this.setData({ monthsList: newMonthsList }); }, selectDate(e) { const date = e.currentTarget.dataset.date; if (!date || e.currentTarget.dataset.disabled === true) return; const [year, month, day] = date.split('-').map(num => parseInt(num, 10)); this.ensureMonthIsLoaded(year, month); const selectMode = this.properties.selectMode; if (selectMode === 'single') { this.setData({ date, selectedDate: date // 同步更新selectedDate }, () => { this.updateSelectionState(); }); } else if (selectMode === 'multiple') { const dates = [...this.data.dates]; const index = dates.indexOf(date); if (index > -1) { dates.splice(index, 1); } else { dates.push(date); } this.setData({ dates, selectedDates: [...dates] // 同步更新selectedDates }, () => { this.updateSelectionState(); }); } else if (selectMode === 'range') { this.handleRangeSelection(date); return; // 提前返回,避免下面的重复触发 } // 只在这里统一触发一次事件 this.triggerEvent('select', this.buildEventData(date)); }, handleRangeSelection(date) { const { rangeStartDate, rangeEndDate } = this.data; const [year, month, day] = date.split('-').map(num => parseInt(num, 10)); // Case 1: No date selected or starting new selection if (!rangeStartDate || (rangeStartDate && rangeEndDate)) { this.setData({ rangeStartDate: date, rangeEndDate: '', rangeHoverDate: '', selectFinished: false // Explicitly set to false when only start date is selected }, () => { this.updateSelectionState(); // Pass selectFinished: false to indicate incomplete selection const eventData = this.buildEventData(date); eventData.selectFinished = false; // Ensure it's false when only start date is selected this.triggerEvent('select', eventData); }); } // Case 2: Start date is selected, selecting end date else { const startDate = new Date(rangeStartDate); const currentDate = new Date(date); // If selecting a date before the start date, swap them if (currentDate < startDate) { this.setData({ rangeStartDate: date, rangeEndDate: rangeStartDate, rangeHoverDate: '', selectFinished: true // Explicitly set to true when range is complete }, () => { this.updateSelectionState(); // Pass selectFinished: true to indicate complete selection const eventData = this.buildEventData(date); eventData.selectFinished = true; // Ensure it's true when range is complete this.triggerEvent('select', eventData); }); } // Normal end date selection else { this.setData({ rangeEndDate: date, rangeHoverDate: '', selectFinished: true // Explicitly set to true when range is complete }, () => { this.updateSelectionState(); // Pass selectFinished: true to indicate complete selection const eventData = this.buildEventData(date); eventData.selectFinished = true; // Ensure it's true when range is complete this.triggerEvent('select', eventData); // Check for disabled dates within range if (this.hasDisabledDatesInRange(this.data.rangeStartDate, date)) { this.setData({ rangeStartDate: '', rangeEndDate: '', rangeHoverDate: '', selectFinished: false // Reset to false when selection is cleared }, () => this.updateSelectionState()); this.triggerEvent('select', { selectMode: 'range', cleared: true, reason: 'disabled', selectFinished: false // Ensure it's false when selection is cleared }); } }); } } }, buildEventData(date) { const [year, month, day] = date.split('-').map(num => parseInt(num, 10)); const dateObj = new Date(year, month - 1, day); const selectMode = this.properties.selectMode; const weekday = dateObj.getDay(); const dayNames = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']; const shortDayNames = ['日', '一', '二', '三', '四', '五', '六']; const weekdayMondayBased = (weekday === 0 ? 6 : weekday - 1); const isToday = this.isToday(dateObj); const isWeekend = weekday === 0 || weekday === 6; const formattedDate = this.formatDate(dateObj, this.properties.formatDatetime); // 基础事件数据 const eventData = { // 当前选择数据 date: date, // 添加基础date字段 formatedDate: formattedDate, // 添加基础formatedDate字段 selectYear: year, selectMonth: month, selectDay: day, selectDate: date, selectFormatedDate: formattedDate, selectDateWeek: dayNames[weekday], selectDateWeekMondayBased: weekdayMondayBased, selectIstoday: isToday, selectIsWeekend: isWeekend, // 选择模式 selectMode, // 选择是否完成标志: 单选和多选直接是true, range的时候选完了end才true selectFinished: selectMode !== 'range' || (this.data.rangeStartDate && this.data.rangeEndDate) }; // 根据不同选择模式添加特定数据 if (selectMode === 'single') { // 单选模式 eventData.dates = [this.data.date]; // 修改为数组,保持一致性 eventData.formatedDates = this.data.date ? [this.formatDate(new Date(this.data.date), this.properties.formatDatetime)] : []; eventData.formatedDateWeeks = this.data.date ? [dayNames[new Date(this.data.date).getDay()]] : []; eventData.formatedDateWeeksMondayBased = this.data.date ? [new Date(this.data.date).getDay() === 0 ? 6 : new Date(this.data.date).getDay() - 1] : []; // 添加标准化的value字段 - 单选模式直接返回日期字符串 eventData.value = this.data.date; } else if (selectMode === 'multiple') { // 多选模式 eventData.dates = this.data.dates; eventData.formatedDates = this.data.dates.map(d => this.formatDate(new Date(d), this.properties.formatDatetime) ); eventData.formatedDateWeeks = this.data.dates.map(d => dayNames[new Date(d).getDay()] ); eventData.formatedDateWeeksMondayBased = this.data.dates.map(d => { const day = new Date(d).getDay(); return day === 0 ? 6 : day - 1; }); // 添加标准化的value字段 - 多选模式返回日期数组 eventData.value = this.data.dates; } else if (selectMode === 'range') { // 范围选择模式 eventData.rangeStartDate = this.data.rangeStartDate; eventData.rangeEndDate = this.data.rangeEndDate; eventData.formatedRangeStartDate = this.data.rangeStartDate ? this.formatDate(new Date(this.data.rangeStartDate), this.properties.formatDatetime) : ''; eventData.formatedRangeEndDate = this.data.rangeEndDate ? this.formatDate(new Date(this.data.rangeEndDate), this.properties.formatDatetime) : ''; let allDates = []; let formatedDates = []; let formatedDateWeeks = []; let formatedDateWeeksMondayBased = []; if (this.data.rangeStartDate && this.data.rangeEndDate) { const [startDate, endDate] = this.getDateRange( this.data.rangeStartDate, this.data.rangeEndDate, null ); const current = new Date(startDate); while (current <= endDate) { const dateStr = this.formatDate(current, 'YYYY-MM-DD'); allDates.push(dateStr); const dayWeek = current.getDay(); formatedDates.push(this.formatDate(current, this.properties.formatDatetime)); formatedDateWeeks.push(dayNames[dayWeek]); formatedDateWeeksMondayBased.push(dayWeek === 0 ? 6 : dayWeek - 1); current.setDate(current.getDate() + 1); } } eventData.dates = allDates; eventData.formatedDates = formatedDates; eventData.formatedDateWeeks = formatedDateWeeks; eventData.formatedDateWeeksMondayBased = formatedDateWeeksMondayBased; // 添加标准化的value字段 - 范围模式返回对象 eventData.value = { startDate: this.data.rangeStartDate, endDate: this.data.rangeEndDate }; } return eventData; }, handleDayHover(e) { if (this.properties.selectMode !== 'range' || !this.data.rangeStartDate || this.data.rangeEndDate) return; const date = e.currentTarget.dataset.date; if (!date) return; this.setData({ rangeHoverDate: date }, () => this.updateSelectionState()); }, ensureMonthIsLoaded(year, month) { const existingIndex = this.findMonthIndex(year, month); if (existingIndex === -1) { this.addMonth(year, month); } }, selectSingleDate(dateStr) { const dateRegex = /^\d{4}-\d{2}-\d{2}$/; if (!dateRegex.test(dateStr)) return false; const [year, month, day] = dateStr.split('-').map(num => parseInt(num, 10)); const dateObj = new Date(year, month - 1, day); if (isNaN(dateObj.getTime())) return false; if (this.data.minDateObj && dateObj < this.data.minDateObj) return false; if (this.data.maxDateObj && dateObj > this.data.maxDateObj) return false; if (this.properties.disabledDaysOfWeek.includes(dateObj.getDay())) return false; if (this.properties.disabledDates.includes(dateStr)) return false; this.setData({ date: dateStr, selectedDate: dateStr // 同步更新 }, () => { this.ensureMonthIsLoaded(year, month); this.updateSelectionState(); this.scrollToMonth(year, month); }); return true; }, selectMultipleDates(datesArray) { const dateRegex = /^\d{4}-\d{2}-\d{2}$/; const validDates = []; for (const dateStr of datesArray) { if (!dateRegex.test(dateStr)) continue; const [year, month, day] = dateStr.split('-').map(num => parseInt(num, 10)); const dateObj = new Date(year, month - 1, day); if (isNaN(dateObj.getTime())) continue; if (this.data.minDateObj && dateObj < this.data.minDateObj) continue; if (this.data.maxDateObj && dateObj > this.data.maxDateObj) continue; if (this.properties.disabledDaysOfWeek.includes(dateObj.getDay())) continue; if (this.properties.disabledDates.includes(dateStr)) continue; validDates.push(dateStr); this.ensureMonthIsLoaded(year, month); } if (validDates.length === 0) return false; const mostRecentDate = validDates[validDates.length - 1]; const [recentYear, recentMonth] = mostRecentDate.split('-').map(num => parseInt(num, 10)); this.setData({ dates: validDates, selectedDates: validDates // 同步更新 }, () => { this.updateSelectionState(); this.scrollToMonth(recentYear, recentMonth); }); return true; }, selectDateRange(startDateStr, endDateStr) { // 检查是否至少有一个参数 if (!startDateStr && !endDateStr) return false; const dateRegex = /^\d{4}-\d{2}-\d{2}$/; let startDate = null, endDate = null; let validStartDate = false, validEndDate = false; // 验证开始日期(如果提供) if (startDateStr) { if (!dateRegex.test(startDateStr)) return false; const [startYear, startMonth, startDay] = startDateStr.split('-').map(num => parseInt(num, 10)); startDate = new Date(startYear, startMonth - 1, startDay); if (isNaN(startDate.getTime())) return false; if (this.data.minDateObj && startDate < this.data.minDateObj) return false; if (this.data.maxDateObj && startDate > this.data.maxDateObj) return false; if (this.properties.disabledDaysOfWeek.includes(startDate.getDay())) return false; if (this.properties.disabledDates.includes(startDateStr)) return false; validStartDate = true; // 确保加载开始日期所在月份 this.ensureMonthIsLoaded(startYear, startMonth); } // 验证结束日期(如果提供) if (endDateStr) { if (!dateRegex.test(endDateStr)) return false; const [endYear, endMonth, endDay] = endDateStr.split('-').map(num => parseInt(num, 10)); endDate = new Date(endYear, endMonth - 1, endDay); if (isNaN(endDate.getTime())) return false; if (this.data.minDateObj && endDate < this.data.minDateObj) return false; if (this.data.maxDateObj && endDate > this.data.maxDateObj) return false; if (this.properties.disabledDaysOfWeek.includes(endDate.getDay())) return false; if (this.properties.disabledDates.includes(endDateStr)) return false; validEndDate = true; // 确保加载结束日期所在月份 this.ensureMonthIsLoaded(endYear, endMonth); } // 如果两个日期都有效,并且开始日期在结束日期之后,交换它们 let finalStartDate = startDateStr || ''; let finalEndDate = endDateStr || ''; if (validStartDate && validEndDate && startDate > endDate) { finalStartDate = endDateStr; finalEndDate = startDateStr; // 交换引用,用于后续验证 [startDate, endDate] = [endDate, startDate]; } // 如果两个日期都有效,检查范围内是否有禁用日期 if (validStartDate && validEndDate && this.hasDisabledDatesInRange(finalStartDate, finalEndDate)) { return false; } // 设置日期并更新UI this.setData({ rangeStartDate: finalStartDate, rangeEndDate: finalEndDate, rangeHoverDate: '', // 只有同时有开始和结束日期时才算完成 selectFinished: !!(finalStartDate && finalEndDate) }, () => { this.updateSelectionState(); // 根据情况确定滚动到哪个月份 if (validEndDate) { const [scrollYear, scrollMonth] = finalEndDate.split('-').map(num => parseInt(num, 10)); this.scrollToMonth(scrollYear, scrollMonth); } else if (validStartDate) { const [scrollYear, scrollMonth] = finalStartDate.split('-').map(num => parseInt(num, 10)); this.scrollToMonth(scrollYear, scrollMonth); } }); return true; }, hasDisabledDatesInRange(startDateStr, endDateStr) { const { disabledDaysOfWeek, disabledDates } = this.properties; if (!disabledDaysOfWeek.length && !disabledDates.length) return false; const startDate = new Date(startDateStr); const endDate = new Date(endDateStr); const currentDate = new Date(startDate); while (currentDate <= endDate) { const dateStr = this.formatDate(currentDate, 'YYYY-MM-DD'); if (disabledDaysOfWeek.includes(currentDate.getDay()) || disabledDates.includes(dateStr)) { return true; } currentDate.setDate(currentDate.getDate() + 1); } return false; }, updateDisabledDaysFlags(disabledDays) { this.setData({ daySundayDisabled: disabledDays.includes(0), dayMondayDisabled: disabledDays.includes(1), dayTuesdayDisabled: disabledDays.includes(2), dayWednesdayDisabled: disabledDays.includes(3), dayThursdayDisabled: disabledDays.includes(4), dayFridayDisabled: disabledDays.includes(5), daySaturdayDisabled: disabledDays.includes(6) }); }, updateMonthsDisabledStatus() { clearTimeout(this._updateMonthsDisabledStatusTimer); this._updateMonthsDisabledStatusTimer = setTimeout(() => { const monthsList = this.data.monthsList.map(monthItem => { if (monthItem.isBoundary) return monthItem; const updatedDays = monthItem.days.map(day => { if (!day.date) return day; const dateObj = new Date(day.date); const isDisabled = (this.data.minDateObj && dateObj < this.data.minDateObj) || (this.data.maxDateObj && dateObj > this.data.maxDateObj) || this.properties.disabledDaysOfWeek.includes(dateObj.getDay()) || (this.properties.disabledDates && this.properties.disabledDates.includes(day.date)); return { ...day, isDisabled }; }); return { ...monthItem, days: updatedDays }; }); this.setData({ monthsList }, () => { this.updateSelectionState(); }); }, this.throttleDelay) }, resetSelection() { // 清空所有选择状态 this.setData({ date: '', selectedDate: '', dates: [], selectedDates: [], rangeStartDate: '', rangeEndDate: '', rangeHoverDate: '', selectFinished: false // Explicitly reset selectFinished }, () => { // 强制立即更新选择状态,不要延迟 clearTimeout(this._updateSelectionStateTimer); // 遍历所有月份的所有日期,清除选择状态 const updatedMonthsList = this.data.monthsList.map(monthItem => { if (monthItem.isBoundary || !monthItem.days?.length) return monthItem; const updatedDays = monthItem.days.map(day => { if (!day.date) return day; // 显式清除所有选择和范围状态 return { ...day, isSelected: false, inRange: false, isRangeStart: false, isRangeEnd: false }; }); return { ...monthItem, days: updatedDays }; }); this.setData({ monthsList: updatedMonthsList }, () => { // 确保UI状态已更新后,再次调用updateSelectionState this.updateSelectionState(); }); }); }, clearDisabledSelections() { const { selectMode } = this.properties; const { selectedDate, selectedDates, rangeStartDate, rangeEndDate } = this.data; let needUpdate = false; if (selectMode === 'single' && selectedDate) { if (this.isDateDisabled(selectedDate)) { this.setData({ selectedDate: '' }); needUpdate = true; } } else if (selectMode === 'multiple' && selectedDates.length > 0) { const validDates = selectedDates.filter(date => !this.isDateDisabled(date)); if (validDates.length !== selectedDates.length) { this.setData({ selectedDates: validDates }); needUpdate = true; } } else if (selectMode === 'range') { if (rangeStartDate && this.isDateDisabled(rangeStartDate)) { this.setData({ rangeStartDate: '', rangeEndDate: '', rangeHoverDate: '', selectFinished: false // Reset to false when selection is cleared }); needUpdate = true; } else if (rangeEndDate && this.isDateDisabled(rangeEndDate)) { this.setData({ rangeEndDate: '', rangeHoverDate: '', selectFinished: false // Reset to false when selection is cleared }); needUpdate = true; } else if (rangeStartDate && rangeEndDate && this.hasDisabledDatesInRange(rangeStartDate, rangeEndDate)) { this.setData({ rangeStartDate: '', rangeEndDate: '', rangeHoverDate: '', selectFinished: false // Reset to false when selection is cleared }); needUpdate = true; } } if (needUpdate) { this.updateSelectionState(); this.triggerEvent('select', { selectMode: this.properties.selectMode, cleared: true, reason: 'disabled', selectFinished: false // Ensure it's false when selection is cleared }); } }, isDateDisabled(dateStr) { if (!dateStr) return false; const dateRegex = /^\d{4}-\d{2}-\d{2}$/; if (!dateRegex.test(dateStr)) return false; const [year, month, day] = dateStr.split('-').map(num => parseInt(num, 10)); const dateObj = new Date(year, month - 1, day); if (isNaN(dateObj.getTime())) return false; if (this.data.minDateObj && dateObj < this.data.minDateObj) return true; if (this.data.maxDateObj && dateObj > this.data.maxDateObj) return true; if (this.properties.disabledDaysOfWeek.includes(dateObj.getDay())) return true; if (this.properties.disabledDates && this.properties.disabledDates.includes(dateStr)) return true; return false; }, initYearMonthPicker() { const currentYear = new Date().getFullYear(); const yearRange = []; for (let y = 1949; y <= 2100; y++) { yearRange.push(y); } const monthRange = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; const { year, month } = this.data.currentMonth; this.setData({ yearRange, monthRange, yearPickerIndex: yearRange.indexOf(year) !== -1 ? yearRange.indexOf(year) : yearRange.indexOf(currentYear), monthPickerIndex: month - 1 }); }, prevMonth() { const { year, month } = this.data.currentMonth; let newYear = year; let newMonth = month - 1; if (newMonth < 1) { newMonth = 12; newYear -= 1; } const newDate = new Date(newYear, newMonth - 1, 1); if (this.isMonthInRange(newDate)) { this.navigateToMonth(newYear, newMonth); } else { const minYear = this.data.minDateObj.getFullYear(); const minMonth = this.data.minDateObj.getMonth() + 1; wx.showToast({ title: '已经是最早日期', icon: 'none' }); this.setData({ currentMonth: { year: minYear, month: minMonth }, yearPickerIndex: this.data.yearRange.indexOf(minYear), monthPickerIndex: minMonth - 1 }, () => { this.scrollToMonth(minYear, minMonth); }); } }, nextMonth() {console.log('nextMonth') const { year, month } = this.data.currentMonth; let newYear = year; let newMonth = month + 1; if (newMonth > 12) { newMonth = 1; newYear += 1; } const newDate = new Date(newYear, newMonth - 1, 1); if (this.isMonthInRange(newDate)) { this.navigateToMonth(newYear, newMonth); } else { const maxYear = this.data.maxDateObj.getFullYear(); const maxMonth = this.data.maxDateObj.getMonth() + 1; wx.showToast({ title: '已经是最晚日期', icon: 'none' }); this.setData({ currentMonth: { year: maxYear, month: maxMonth }, yearPickerIndex: this.data.yearRange.indexOf(maxYear), monthPickerIndex: maxMonth - 1 }, () => { this.scrollToMonth(maxYear, maxMonth); }); } }, prevYear() { const { year, month } = this.data.currentMonth; const newYear = year - 1; const newDate = new Date(newYear, month - 1, 1); if (this.isMonthInRange(newDate)) { this.navigateToMonth(newYear, month); } else { const minYear = this.data.minDateObj.getFullYear(); const minMonth = this.data.minDateObj.getMonth() + 1; wx.showToast({ title: '已经是最早年份', icon: 'none' }); this.setData({ currentMonth: { year: minYear, month: month < minMonth ? minMonth : month }, yearPickerIndex: this.data.yearRange.indexOf(minYear), monthPickerIndex: month < minMonth ? minMonth - 1 : month - 1 }, () => { this.scrollToMonth(minYear, month < minMonth ? minMonth : month); }); } }, nextYear() { const { year, month } = this.data.currentMonth; const newYear = year + 1; const newDate = new Date(newYear, month - 1, 1); if (this.isMonthInRange(newDate)) { this.navigateToMonth(newYear, month); } else { const maxYear = this.data.maxDateObj.getFullYear(); const maxMonth = this.data.maxDateObj.getMonth() + 1; wx.showToast({ title: '已经是最晚年份', icon: 'none' }); this.setData({ currentMonth: { year: maxYear, month: month > maxMonth ? maxMonth : month }, yearPickerIndex: this.data.yearRange.indexOf(maxYear), monthPickerIndex: month > maxMonth ? maxMonth - 1 : month - 1 }, () => { this.scrollToMonth(maxYear, month > maxMonth ? maxMonth : month); }); } }, navigateToMonth(year, month) { this.ensureMonthIsLoaded(year, month); if (!this.isMonthInRange(new Date(year, month - 1, 1))) { return; } this.setData({ currentMonth: { year, month }, yearPickerIndex: this.data.yearRange.indexOf(year), monthPickerIndex: month - 1 }, () => { this.scrollToMonth(year, month); }); }, onYearChange(e) { const index = e.detail.value; const year = this.data.yearRange[index]; const { month } = this.data.currentMonth; const newDate = new Date(year, month - 1, 1); if (this.isMonthInRange(newDate)) { this.navigateToMonth(year, month); } else { // 添加超出边界的提示 const { minDateObj, maxDateObj } = this.data; if (minDateObj && newDate < minDateObj) { wx.showToast({ title: '已经是最早日期', icon: 'none' }); } else if (maxDateObj && newDate > maxDateObj) { wx.showToast({ title: '已经是最晚日期', icon: 'none' }); } this.findNearestValidMonth(year, month); } }, onMonthChange(e) { const index = e.detail.value; const month = index*1 + 1; const { year } = this.data.currentMonth; const newDate = new Date(year, month - 1, 1); if (this.isMonthInRange(newDate)) { this.navigateToMonth(year, month); } else { // 添加超出边界的提示 const { minDateObj, maxDateObj } = this.data; if (minDateObj && newDate < minDateObj) { wx.showToast({ title: '已经是最早日期', icon: 'none' }); } else if (maxDateObj && newDate > maxDateObj) { wx.showToast({ title: '已经是最晚日期', icon: 'none' });