react实现携程日历
实现携程日历的React组件
创建一个类似携程的日历组件需要处理日期选择、区间高亮、禁用日期等逻辑。以下是关键实现步骤:
日期选择与区间高亮
使用date-fns库处理日期操作,实现开始/结束日期选择和区间高亮效果:

import { format, addDays, isBefore, isAfter, isSameDay } from 'date-fns';
function DateRangePicker({ startDate, endDate, onDateSelect }) {
const handleDateClick = (date) => {
if (!startDate || (startDate && endDate)) {
onDateSelect({ startDate: date, endDate: null });
} else if (isBefore(date, startDate)) {
onDateSelect({ startDate: date, endDate: startDate });
} else {
onDateSelect({ startDate, endDate: date });
}
};
const renderDays = () => {
const days = [];
const currentDate = new Date();
for (let i = 0; i < 42; i++) { // 6周
const date = addDays(currentDate, i);
const isSelected =
(startDate && isSameDay(date, startDate)) ||
(endDate && isSameDay(date, endDate));
const isInRange =
startDate && endDate &&
isAfter(date, startDate) &&
isBefore(date, endDate);
days.push(
<div
key={date}
className={`day ${isSelected ? 'selected' : ''} ${isInRange ? 'in-range' : ''}`}
onClick={() => handleDateClick(date)}
>
{format(date, 'd')}
</div>
);
}
return days;
};
return <div className="date-range-picker">{renderDays()}</div>;
}
禁用日期处理
添加禁用日期逻辑,比如限制可选日期范围或排除特定日期:
const isDateDisabled = (date) => {
const today = new Date();
const maxSelectableDate = addDays(today, 180); // 限制半年内
return isBefore(date, today) || isAfter(date, maxSelectableDate);
};
// 在renderDays中修改
const disabled = isDateDisabled(date);
<div
className={`day ${disabled ? 'disabled' : ''}`}
onClick={!disabled ? () => handleDateClick(date) : undefined}
>
{format(date, 'd')}
</div>
月份切换与布局
实现月份导航和符合携程视觉风格的布局:

function Calendar() {
const [currentMonth, setCurrentMonth] = useState(new Date());
const prevMonth = () => setCurrentMonth(subMonths(currentMonth, 1));
const nextMonth = () => setCurrentMonth(addMonths(currentMonth, 1));
return (
<div className="ctrip-calendar">
<div className="header">
<button onClick={prevMonth}><</button>
<h3>{format(currentMonth, 'yyyy年MM月')}</h3>
<button onClick={nextMonth}>></button>
</div>
<div className="weekdays">
{['日', '一', '二', '三', '四', '五', '六'].map(day => (
<div key={day}>{day}</div>
))}
</div>
<DateRangePicker
startDate={startDate}
endDate={endDate}
onDateSelect={setDates}
/>
</div>
);
}
样式设计
添加CSS实现携程风格的视觉效果:
.ctrip-calendar {
width: 350px;
border: 1px solid #eee;
border-radius: 8px;
padding: 10px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.weekdays {
display: grid;
grid-template-columns: repeat(7, 1fr);
text-align: center;
color: #666;
}
.date-range-picker {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 5px;
}
.day {
padding: 8px;
text-align: center;
cursor: pointer;
border-radius: 4px;
}
.day.selected {
background-color: #008489;
color: white;
}
.day.in-range {
background-color: #e6f7ff;
}
.day.disabled {
color: #ccc;
cursor: not-allowed;
}
完整组件集成
将所有功能集成到完整组件中:
import React, { useState } from 'react';
import {
format, addDays, subMonths, addMonths,
isBefore, isAfter, isSameDay
} from 'date-fns';
export default function CtripCalendar() {
const [currentMonth, setCurrentMonth] = useState(new Date());
const [selectedDates, setSelectedDates] = useState({
startDate: null,
endDate: null
});
return (
<div className="ctrip-calendar-container">
<Calendar
currentMonth={currentMonth}
onMonthChange={setCurrentMonth}
selectedDates={selectedDates}
onDateSelect={setSelectedDates}
/>
</div>
);
}
优化与扩展
- 添加动画过渡效果提升用户体验
- 实现多个月份并排显示
- 集成价格日历功能(显示每日价格)
- 添加节假日特殊标记
- 实现移动端触摸滑动切换月份
以上实现提供了携程日历的核心功能,可根据实际需求调整样式和交互细节。使用date-fns处理日期操作比原生Date API更可靠,组件设计遵循了React的最佳实践。






