You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于MUI X非Pro版实现单日历双输入框日期范围选择

用MUI X免费版实现单日历日期范围选择器

要实现两个输入框共用单个日历选择日期范围,你可以通过状态管理控制单个日历的显示,同时区分「选择起始日期」和「选择结束日期」两个阶段,以下是具体实现方案:

核心思路

  1. 用状态记录起始日期、结束日期、日历显示状态,以及当前处于选择起始还是结束日期的阶段
  2. 两个输入框点击时触发同一个日历弹窗,并切换对应的选择阶段
  3. 在日历中完成日期选择:先选起始日,自动切换到选结束日;选完结束日自动关闭日历
  4. 自定义日历单元格样式,高亮选中的日期范围

完整代码示例

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 06:41:19