ReactJS基于MUI时间选择器实现均分时间槽功能
MUI时间选择器均分时间槽实现
核心计算逻辑
- 校验输入合法性:确认起止时间已选择、槽位数为大于0的正整数、结束时间晚于开始时间
- 计算起止时间的总毫秒差值,除以槽位数得到单个时间槽的时长
- 从开始时间逐次累加单槽时长,循环生成对应数量的连续时间区间
- 用moment格式化每段时间为
HH:mm格式,拼接成开始-结束的字符串
关键修改点
- 修正
startTime、endTime初始值为null,匹配TimePicker组件的value类型要求 - 新增槽位数量、计算结果两个响应式状态,绑定到输入框和结果展示区域
- 给按钮绑定计算点击事件,实现时间分割逻辑
- 新增结果列表区域,渲染生成后的时间槽
完整可运行代码
import * as React from "react"; import TextField from "@mui/material/TextField"; import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { TimePicker } from "@mui/x-date-pickers/TimePicker"; import { Box } from "@mui/system"; import moment from "moment"; import { Button, Grid, List, ListItem, ListItemText } from "@mui/material"; import Paper from "@mui/material/Paper"; export default function BasicDatePicker() { const [startTime, setStartTime] = React.useState(null); const [endTime, setEndTime] = React.useState(null); const [slotCount, setSlotCount] = React.useState(1); const [timeSlots, setTimeSlots] = React.useState([]); const calculateSlots = () => { if (!startTime || !endTime || slotCount < 1) { alert("请完善开始时间、结束时间和槽位数量"); return; } const startMoment = moment(startTime); const endMoment = moment(endTime); if (!endMoment.isAfter(startMoment)) { alert("结束时间必须晚于开始时间"); return; } const totalDuration = endMoment.diff(startMoment); const singleSlotDuration = totalDuration / slotCount; const slots = []; let current = startMoment; for (let i = 0; i < slotCount; i++) { const slotStart = current.clone(); const slotEnd = current.add(singleSlotDuration, "ms"); slots.push(`${slotStart.format("HH:mm")}-${slotEnd.format("HH:mm")}`); } setTimeSlots(slots); }; return ( <Box component="main" sx={{ flexGrow: 1, p: 3 }} style={{ marginTop: "100px" }} > <LocalizationProvider dateAdapter={AdapterDateFns} sx={{ flexGrow: 1, p: 3 }} > <Box sx={{ display: "flex", flexWrap: "wrap", "& > :not(style)": { m: "0 auto", width: 300, minHeight: 350, }, }} > <Paper elevation={3} sx={{p:2}}> <Grid container spacing={2}> <Grid item xs={12}> <TimePicker label="Start Time" value={startTime} onChange={(newValue) => setStartTime(newValue)} renderInput={(params) => <TextField {...params} fullWidth />} /> </Grid> <Grid item xs={12}> <TimePicker label="End Time" value={endTime} onChange={(newValue) => setEndTime(newValue)} renderInput={(params) => <TextField {...params} fullWidth />} /> </Grid> <Grid item xs={12}> <TextField label="Slot" variant="outlined" type="number" value={slotCount} onChange={(e) => setSlotCount(Number(e.target.value))} fullWidth /> </Grid> <Grid item xs={12} style={{ textAlign: "center" }}> <Button variant="contained" onClick={calculateSlots}>生成时间槽</Button> </Grid> {timeSlots.length > 0 && ( <Grid item xs={12}> <List dense> {timeSlots.map((slot, idx) => ( <ListItem key={idx}> <ListItemText primary={slot} /> </ListItem> ))} </List> </Grid> )} </Grid> </Paper> </Box> </LocalizationProvider> </Box> ); }
效果参考

注意事项
- moment的
add方法会修改原时间对象,生成每段开始时间时需要用clone()复制实例,避免时间计算偏移 - 槽位输入框要绑定
number类型,防止传入字符串导致计算异常 - 如果需要支持跨天时间分割,只需要在计算总时长时判断:如果结束时间早于开始时间,给结束时间加1天即可
内容的提问来源于stack exchange,提问作者Lokesh Bhardwaj
相关产品推荐
相关产品推荐

