wx-mini-weview
Version:
weixin UI
1,359 lines (1,238 loc) • 63.5 kB
JavaScript
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'
});