react ts 多段时间选择

已经封装好的组件 直接拿去用就好了,先效果

 


// TimeSelect.tsx import React, { useState, useRef, useEffect } from 'react'; import './TimePicker.css'; interface TimeUnit { class: string | null; timeData: number; } interface TimeSelectProps { value?: number[][][]; onChange?: (timeSection: number[][][]) => void; } const TimeSelect: React.FC
<TimeSelectProps> = ({ value, onChange }) => { const [tableHeader] = useState([ "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23" ]); // 初始化状态 const { initialRowUnit, initialTimeContent } = (() => { const rowUnit = Array(7).fill(null).map(() => Array(48).fill(null).map((_, j) => ({ class: null, timeData: j })) ); const timeContent = Array(7).fill(null).map(() => ({ arr: [] as number[] })); if (value) { value.forEach((dayData, dayIndex) => { dayData.forEach(([start, end]) => { const startUnit = Math.floor(start * 2); const endUnit = Math.ceil(end * 2) - 1; for (let i = startUnit; i <= endUnit; i++) { rowUnit[dayIndex][i].class = 'ui-selected'; if (!timeContent[dayIndex].arr.includes(i)) { timeContent[dayIndex].arr.push(i); } } }); }); } return { initialRowUnit: rowUnit, initialTimeContent: timeContent }; })(); const [rowUnit, setRowUnit] = useState<TimeUnit[][]>(initialRowUnit); const [timeContent, setTimeContent] = useState(initialTimeContent); const [timeStr, setTimeStr] = useState<string[]>(Array(7).fill('')); const selectionState = useRef({ beginDay: 0, beginTime: 0, downEvent: false }); const weekDate = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]; // 时间格式化 const formatTimeNumber = (num: number) => num.toString().padStart(2, '0'); // 时间转换 const timeToStr = (num: number) => { const hours = Math.floor(num); const minutes = num % 1 === 0 ? '00' : '30'; return `${formatTimeNumber(hours)}:${minutes}`; }; // 鼠标事件处理 const handleMouseDown = (i: number, day: number) => { selectionState.current = { ...selectionState.current, downEvent: true, beginDay: day, beginTime: i }; }; const handleMouseUp = (i: number, day: number) => { if (!selectionState.current.downEvent) return; const { beginDay, beginTime } = selectionState.current; const start = Math.min(beginTime, i); const end = Math.max(beginTime, i); const dayStart = Math.min(beginDay, day); const dayEnd = Math.max(beginDay, day); const isAdd = () => { for (let x = dayStart; x <= dayEnd; x++) { for (let y = start; y <= end; y++) { if (!rowUnit[x][y].class) return true; } } return false; }; const add = isAdd(); const newRowUnit = rowUnit.map(row => [...row]); const newTimeContent = timeContent.map(tc => ({ arr: [...tc.arr] })); for (let x = dayStart; x <= dayEnd; x++) { for (let y = start; y <= end; y++) { const cell = newRowUnit[x][y]; if (add) { if (!cell.class) { cell.class = 'ui-selected'; if (!newTimeContent[x].arr.includes(y)) { newTimeContent[x].arr.push(y); } } } else { if (cell.class) { cell.class = null; const index = newTimeContent[x].arr.indexOf(y); if (index > -1) { newTimeContent[x].arr.splice(index, 1); } } } } } setRowUnit(newRowUnit); setTimeContent(newTimeContent); updateTimeDisplay(newTimeContent); selectionState.current.downEvent = false; }; // 更新时间显示 const updateTimeDisplay = (tc: typeof timeContent) => { const newTimeStr = tc.map(({ arr }) => { const sorted = [...arr].sort((a, b) => a - b); const groups: number[][] = []; sorted.forEach(num => { const lastGroup = groups[groups.length - 1]; if (lastGroup && lastGroup[lastGroup.length - 1] === num - 1) { lastGroup.push(num); } else { groups.push([num]); } }); return groups.map(group => { const start = group[0] / 2; const end = group[group.length - 1] / 2 + 0.5; return `${timeToStr(start)}~${timeToStr(end)}`; }).join(', '); }); setTimeStr(newTimeStr); onChange?.(tc.map(({ arr }) => { const sorted = [...arr].sort((a, b) => a - b); const result: number[][] = []; sorted.forEach(num => { const last = result[result.length - 1]; if (last && last[1] === num / 2) { last[1] = num / 2 + 0.5; } else { result.push([num / 2, num / 2 + 0.5]); } }); return result; })); }; // 清空选择 const clear = () => { setRowUnit(initialRowUnit); setTimeContent(initialTimeContent); setTimeStr(Array(7).fill('')); }; // 监听外部数据变化 useEffect(() => { if (value) { const newRowUnit = initialRowUnit.map(day => [...day]); const newTimeContent = initialTimeContent.map(tc => ({ arr: [...tc.arr] })); value.forEach((dayData, dayIndex) => { dayData.forEach(([start, end]) => { const startUnit = Math.floor(start * 2); const endUnit = Math.ceil(end * 2) - 1; for (let i = startUnit; i <= endUnit; i++) { newRowUnit[dayIndex][i].class = 'ui-selected'; if (!newTimeContent[dayIndex].arr.includes(i)) { newTimeContent[dayIndex].arr.push(i); } } }); }); setRowUnit(newRowUnit); setTimeContent(newTimeContent); } }, [value]); return ( <div className="byted-weektime"> <div className="calendar"> <table className="calendar-table" style={{ width: 610 }}> <thead className="calendar-head"> <tr> <th rowSpan={6} className="week-td">星期/时间</th> <th colSpan={24}>00:00 - 12:00</th> <th colSpan={24}>12:00 - 24:00</th> </tr> <tr> {tableHeader.map((item, index) => ( <td key={index} colSpan={2}>{item}</td> ))} </tr> </thead> <tbody> {weekDate.map((dayName, day) => ( <tr key={day}> <td>{dayName}</td> {rowUnit[day].map((item, i) => ( <td key={i} onMouseDown={(e) => { e.preventDefault(); handleMouseDown(i, day); }} onMouseUp={(e) => { e.preventDefault(); handleMouseUp(i, day); }} className={`calendar-atom-time ${item.class || ''}`} /> ))} </tr> ))} <tr> <td colSpan={49} className="td-table-tip"> <div className="clearfix"> <span className="pull-left tip-text">请用鼠标点选时间段</span> <a className="pull-right" onClick={(e) => { e.preventDefault(); clear(); }}>清空</a> </div> <div className="time-result"> {timeStr.map((str, i) => ( str && <div key={i}>{weekDate[i]}:{str}</div> ))} </div> </td> </tr> </tbody> </table> </div> </div> ); }; export default TimeSelect;





以下是css:
.byted-weektime .calendar {
  -webkit-user-select: none;
  position: relative;
  display: inline-block;
}
#tableBody {
  /* position: relative; */
}
/*.byted-weektime .calendar .schedule{background:#2F88FF;width:0;height:0;position:fixed;display:none;top:0;left:0;pointer-events:none;-webkit-transition:all 400ms ease;-moz-transition:all 400ms ease;-ms-transition:all 400ms ease;transition:all 400ms ease}*/
.byted-weektime .calendar .calendar-table {
  border-collapse: collapse;
  border-radius: 4px;
}
.byted-weektime .calendar .calendar-table tr .calendar-atom-time:hover {
  background: #ccc;
}
.byted-weektime .calendar .calendar-table tr .ui-selected {
  background: #2f88ff;
}
.byted-weektime .calendar .calendar-table tr .ui-selected:hover {
  background: #2f88ff;
}
.byted-weektime .calendar .calendar-table tr,
.byted-weektime .calendar .calendar-table td,
.byted-weektime .calendar .calendar-table th {
  border: 1px solid #ccc;
  font-size: 12px;
  text-align: center;

  line-height: 1.8em;
  -webkit-transition: background 200ms ease;
  -moz-transition: background 200ms ease;
  -ms-transition: background 200ms ease;
  transition: background 200ms ease;
}
.byted-weektime .calendar .calendar-table tbody tr {
  height: 30px;
}
.byted-weektime .calendar .calendar-table tbody tr td:first-child {
  background: #f8f9fa;
}
.byted-weektime .calendar .calendar-table thead th,
.byted-weektime .calendar .calendar-table thead td {
  background: #f8f9fa;
}
.byted-weektime .calendar .calendar-table .td-table-tip {
  line-height: 2.4em;
  padding: 0 12px 0 19px;
  background: #fff !important;
}
.byted-weektime .calendar .calendar-table .td-table-tip .clearfix {
  height: 46px;
  line-height: 46px;
}
.byted-weektime .calendar .calendar-table .td-table-tip .pull-left {
  font-size: 14px;
  color: #333333;
}
.byted-weektime .week-td {
  width: 75px;
  padding: 20px 0;
}
.byted-weektime a {
  cursor: pointer;
  color: #2f88ff;
  font-size: 14px;
}
#kuang {
  position: absolute;
  background-color: blue;
  opacity: 0.3;
}

 

 

posted @ 2025-04-24 10:42  现世中的素人  阅读(30)  评论(0)    收藏  举报