如何基于MUI X非Pro版实现单日历双输入框日期范围选择
用MUI X免费版实现单日历日期范围选择器
要实现两个输入框共用单个日历选择日期范围,你可以通过状态管理控制单个日历的显示,同时区分「选择起始日期」和「选择结束日期」两个阶段,以下是具体实现方案:
核心思路
- 用状态记录起始日期、结束日期、日历显示状态,以及当前处于选择起始还是结束日期的阶段
- 两个输入框点击时触发同一个日历弹窗,并切换对应的选择阶段
- 在日历中完成日期选择:先选起始日,自动切换到选结束日;选完结束日自动关闭日历
- 自定义日历单元格样式,高亮选中的日期范围
完整代码示例
import React, { useState } from 'react'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { TextField, Box, styled } from '@mui/material'; // 自定义日期单元格样式 const StyledDay = styled('div')<{ isStart?: boolean; isEnd?: boolean; isInRange?: boolean }>(({ theme, isStart, isEnd, isInRange }) => ({ width: 36, height: 36, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: '50%', backgroundColor: isStart || isEnd ? theme.palette.primary.main : isInRange ? theme.palette.primary.light : 'transparent', color: isStart || isEnd ? theme.palette.common.white : theme.palette.text.primary, cursor: 'pointer', '&:hover': { backgroundColor: theme.palette.primary.light, }, })); export default function App() { const [startDate, setStartDate] = useState<Date | null>(null); const [endDate, setEndDate] = useState<Date | null>(null); const [calendarOpen, setCalendarOpen] = useState(false); const [isSelectingStart, setIsSelectingStart] = useState(true); // 处理输入框点击,打开日历并切换选择阶段 const handleInputClick = (isStart: boolean) => { setIsSelectingStart(isStart); setCalendarOpen(true); }; // 处理日历日期选择 const handleDateChange = (newDate: Date | null) => { if (!newDate) return; if (isSelectingStart) { // 先选起始日期,自动切换到选结束日期 setStartDate(newDate); setIsSelectingStart(false); // 如果之前的结束日期早于新起始日,重置结束日期 if (endDate && endDate < newDate) { setEndDate(null); } } else { // 选结束日期,选完关闭日历 setEndDate(newDate); setCalendarOpen(false); } }; // 自定义日历日期渲染,高亮范围 const renderDay = (day: Date) => { const isStart = startDate && startDate.toDateString() === day.toDateString(); const isEnd = endDate && endDate.toDateString() === day.toDateString(); const isInRange = startDate && endDate && day >= startDate && day <= endDate; return ( <StyledDay isStart={isStart} isEnd={isEnd} isInRange={isInRange}> {day.getDate()} </StyledDay> ); }; return ( <LocalizationProvider dateAdapter={AdapterDateFns}> <Box sx={{ display: 'flex', gap: 2, p: 3 }}> <TextField label="起始日期" value={startDate ? startDate.toLocaleDateString() : ''} onClick={() => handleInputClick(true)} sx={{ width: 200 }} readOnly /> <TextField label="结束日期" value={endDate ? endDate.toLocaleDateString() : ''} onClick={() => handleInputClick(false)} sx={{ width: 200 }} readOnly disabled={!startDate} // 起始日未选时禁用结束日输入框 /> <DatePicker open={calendarOpen} onClose={() => setCalendarOpen(false)} value={isSelectingStart ? startDate : endDate} onChange={handleDateChange} renderDay={renderDay} slotProps={{ popper: { placement: 'bottom-start', }, }} /> </Box> </LocalizationProvider> ); }
关键细节说明
- 状态联动:通过
isSelectingStart标记当前选择阶段,确保日历知道要更新起始还是结束日期 - 输入框交互:设置输入框为只读,点击时触发日历弹窗,避免手动输入导致的格式问题
- 日期范围校验:选择新的起始日期时,如果原结束日期早于它,自动重置结束日期,保证范围合理性
- 视觉反馈:通过
renderDay自定义单元格样式,清晰展示选中的起始日、结束日以及中间的日期范围
内容的提问来源于stack exchange,提问作者Renaud is Not Bill Gates
相关产品推荐
相关产品推荐

