jQuery UI日期范围日历显示异常问题求助
jQuery UI 多日历组件问题修复
问题描述
使用jQuery UI实现从当前月份开始显示本年后续6个月的日历,但出现两个异常:
- 实际生成6组各6个月的日历,总计36个月,不符合预期;
- 点击日历的前后切换箭头时,日历组件会消失,无法正常切换月份。
代码示例
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Date and Time Range Selector</title> <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script> <style> .date-time-range-selector { display: flex; flex-direction: column; align-items: center; margin: 20px; } .calendar-container { display: flex; flex-wrap: wrap; justify-content: center; } .month-container { margin: 10px; } .time-slider { width: 200px; margin-top: 10px; } </style> </head> <body> <div class="date-time-range-selector"> <label for="dateTimeRange">Select Date and Time Range:</label> <input type="text" id="dateTimeRange" readonly> <div id="calendar-popup" style="display: none;"> <div class="calendar-container"></div> </div> </div> </body> </html>
JavaScript代码
$(function() { const $input = $("#dateTimeRange"); const $calendarPopup = $("#calendar-popup"); const $calendarContainer = $calendarPopup.find(".calendar-container"); let startDate, endDate, startTime, endTime; function updateInputValue() { if (startDate && endDate && startTime && endTime) { $input.val(`${startDate.toLocaleDateString()} ${startTime} - ${endDate.toLocaleDateString()} ${endTime}`); } else if (startDate && startTime) { $input.val(`${startDate.toLocaleDateString()} ${startTime} - Select end date`); } else { $input.val(''); } } function createCalendar(date) { const $monthContainer = $("<div>").addClass("month-container"); const $calendar = $("<div>").datepicker({ defaultDate: date, numberOfMonths: [2,3], // 问题1根源:单个日历显示6个月 showOtherMonths: true, selectOtherMonths: true, beforeShowDay: function(date) { const isInRange = startDate && endDate && date >= startDate && date <= endDate; const isStart = startDate && date.getTime() === startDate.getTime(); const isEnd = endDate && date.getTime() === endDate.getTime(); let cssClass = ''; if (isStart) cssClass = 'in-range'; if (isEnd) cssClass = 'in-range'; if (isInRange) cssClass = 'in-range'; return [true, cssClass]; }, onSelect: function(selectedDate, instance) { const selected = new Date(instance.selectedYear, instance.selectedMonth, instance.selectedDay); if (!startDate || (startDate && endDate) || (startDate && selected < startDate)) { startDate = selected; endDate = null; startTime = '00:00'; endTime = null; } else { endDate = selected; endTime = '23:59'; } updateInputValue(); $calendarContainer.find(".ui-datepicker").datepicker("refresh"); } }); const $timeSlider = $("<div>").addClass("time-slider").slider({ range: true, min: 0, max: 1440, step: 15, values: [540, 1020], create: function() { const $handles = $(this).find('.ui-slider-handle'); $handles.each(function(index) { const $tooltip = $('<div class="ui-slider-tooltip">').appendTo(this); $(this).data('tooltip', $tooltip); }); updateTooltips($(this)); }, slide: function(event, ui) { startTime = minutesToTime(ui.values[0]); endTime = minutesToTime(ui.values[1]); updateInputValue(); updateTooltips($(this)); } }); $monthContainer.append($calendar, $timeSlider); return $monthContainer; } function updateTooltips($slider) { const values = $slider.slider('values'); $slider.find('.ui-slider-handle').each(function(index) { const $tooltip = $(this).data('tooltip'); $tooltip.text(minutesToTime(values[index])); }); } function minutesToTime(minutes) { const hours = Math.floor(minutes / 60); const mins = minutes % 60; return `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}`; } $input.on("click", function() { if ($calendarContainer.children().length === 0) { const today = new Date(); const monthsToShow = 6; const startDateOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); $calendarContainer.empty(); for (let i = 0; i < monthsToShow; i++) { const date = new Date(startDateOfMonth.getFullYear(), startDateOfMonth.getMonth() + i, 1); const $monthContainer = createCalendar(date); $calendarContainer.append($monthContainer); const $calendar = $monthContainer.find(".ui-datepicker"); // 问题2根源:直接绑定事件,日历刷新后DOM重建导致事件失效 $calendar.find(".ui-datepicker-prev, .ui-datepicker-next").off("click").on("click", function(e) { e.preventDefault(); e.stopPropagation(); const isNext = $(this).hasClass("ui-datepicker-next"); $calendar.datepicker(isNext ? "nextMonth" : "prevMonth"); }); } } $calendarPopup.show(); }); $(document).on("click", function(event) { if (!$(event.target).closest(".date-time-range-selector").length && !$(event.target).closest(".ui-datepicker-header").length) { $calendarPopup.hide(); } }); const $clearButton = $("<button>").text("Clear").click(function() { startDate = null; endDate = null; startTime = null; endTime = null; updateInputValue(); $calendarContainer.find(".ui-datepicker").datepicker("refresh"); $calendarPopup.hide(); }); $calendarPopup.append($clearButton); });
CSS代码
.ui-datepicker .in-range a, .ui-datepicker .range-start a, .ui-datepicker .range-end a { background-color: #FF0000; color: #FFFFFF; } .ui-datepicker .range-start a, .ui-datepicker .range-end a { font-weight: bold; } .ui-datepicker-other-month { visibility: visible; } .ui-datepicker-today { background: none !important; }
当前效果截图

问题修复方案
1. 解决重复生成36个月的问题
原因:createCalendar函数中初始化datepicker时设置了numberOfMonths: [2,3],该参数会让单个datepicker组件显示2行3列共6个月,再加上循环调用6次createCalendar,最终生成6*6=36个月。
修复:将numberOfMonths改为[1,1],让每个datepicker只显示1个月份:
const $calendar = $("<div>").datepicker({ defaultDate: date, numberOfMonths: [1,1], // 修改为单个月份显示 showOtherMonths: true, selectOtherMonths: true, // 其余配置不变 });
2. 解决切换箭头点击后日历消失的问题
原因:jQuery UI的datepicker在切换月份时会重新渲染DOM结构,原来直接绑定在箭头按钮上的点击事件会失效;同时直接调用nextMonth/prevMonth时,没有处理DOM重建后的事件绑定。
修复:使用事件委托的方式绑定箭头点击事件,将事件绑定到父容器$calendarContainer上,确保DOM重建后事件依然有效:
// 移除原循环内的箭头事件绑定代码,添加以下代码到$(function(){})范围内 $calendarContainer.on("click", ".ui-datepicker-prev, .ui-datepicker-next", function(e) { e.preventDefault(); e.stopPropagation(); const $calendar = $(this).closest(".ui-datepicker"); const isNext = $(this).hasClass("ui-datepicker-next"); $calendar.datepicker(isNext ? "nextMonth" : "prevMonth"); });
修改后完整JavaScript代码
$(function() { const $input = $("#dateTimeRange"); const $calendarPopup = $("#calendar-popup"); const $calendarContainer = $calendarPopup.find(".calendar-container"); let startDate, endDate, startTime, endTime; function updateInputValue() { if (startDate && endDate && startTime && endTime) { $input.val(`${startDate.toLocaleDateString()} ${startTime} - ${endDate.toLocaleDateString()} ${endTime}`); } else if (startDate && startTime) { $input.val(`${startDate.toLocaleDateString()} ${startTime} - Select end date`); } else { $input.val(''); } } function createCalendar(date) { const $monthContainer = $("<div>").addClass("month-container"); const $calendar = $("<div>").datepicker({ defaultDate: date, numberOfMonths: [1,1], // 修复问题1:单个日历只显示1个月 showOtherMonths: true, selectOtherMonths: true, beforeShowDay: function(date) { const isInRange = startDate && endDate && date >= startDate && date <= endDate; const isStart = startDate && date.getTime() === startDate.getTime(); const isEnd = endDate && date.getTime() === endDate.getTime(); let cssClass = ''; if (isStart) cssClass = 'in-range'; if (isEnd) cssClass = 'in-range'; if (isInRange) cssClass = 'in-range'; return [true, cssClass]; }, onSelect: function(selectedDate, instance) { const selected = new Date(instance.selectedYear, instance.selectedMonth, instance.selectedDay); if (!startDate || (startDate && endDate) || (startDate && selected < startDate)) { startDate = selected; endDate = null; startTime = '00:00'; endTime = null; } else { endDate = selected; endTime = '23:59'; } updateInputValue(); $calendarContainer.find(".ui-datepicker").datepicker("refresh"); } }); const $timeSlider = $("<div>").addClass("time-slider").slider({ range: true, min: 0, max: 1440, step: 15, values: [540, 1020], create: function() { const $handles = $(this).find('.ui-slider-handle'); $handles.each(function(index) { const $tooltip = $('<div class="ui-slider-tooltip">').appendTo(this); $(this).data('tooltip', $tooltip); }); updateTooltips($(this)); }, slide: function(event, ui) { startTime = minutesToTime(ui.values[0]); endTime = minutesToTime(ui.values[1]); updateInputValue(); updateTooltips($(this)); } }); $monthContainer.append($calendar, $timeSlider); return $monthContainer; } function updateTooltips($slider) { const values = $slider.slider('values'); $slider.find('.ui-slider-handle').each(function(index) { const $tooltip = $(this).data('tooltip'); $tooltip.text(minutesToTime(values[index])); }); } function minutesToTime(minutes) { const hours = Math.floor(minutes / 60); const mins = minutes % 60; return `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}`; } $input.on("click", function() { if ($calendarContainer.children().length === 0) { const today = new Date(); const monthsToShow = 6; const startDateOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); $calendarContainer.empty(); for (let i = 0; i < monthsToShow; i++) { const date = new Date(startDateOfMonth.getFullYear(), startDateOfMonth.getMonth() + i, 1); const $monthContainer = createCalendar(date); $calendarContainer.append($monthContainer); } } $calendarPopup.show(); }); // 修复问题2:使用事件委托绑定切换箭头事件 $calendarContainer.on("click", ".ui-datepicker-prev, .ui-datepicker-next", function(e) { e.preventDefault(); e.stopPropagation(); const $calendar = $(this).closest(".ui-datepicker"); const isNext = $(this).hasClass("ui-datepicker-next"); $calendar.datepicker(isNext ? "nextMonth" : "prevMonth"); }); $(document).on("click", function(event) { if (!$(event.target).closest(".date-time-range-selector").length && !$(event.target).closest(".ui-datepicker-header").length) { $calendarPopup.hide(); } }); const $clearButton = $("<button>").text("Clear").click(function() { startDate = null; endDate = null; startTime = null; endTime = null; updateInputValue(); $calendarContainer.find(".ui-datepicker").datepicker("refresh"); $calendarPopup.hide(); }); $calendarPopup.append($clearButton); });
内容的提问来源于stack exchange,提问作者Notion
相关产品推荐
相关产品推荐

