260 lines
7.2 KiB
React
260 lines
7.2 KiB
React
|
|
import React, { useState, useEffect } from 'react';
|
|||
|
|
import { Calendar, Clock } from 'lucide-react';
|
|||
|
|
import './DateTimePicker.css';
|
|||
|
|
|
|||
|
|
const DateTimePicker = ({ value, onChange, placeholder = "选择会议时间" }) => {
|
|||
|
|
const [date, setDate] = useState('');
|
|||
|
|
const [time, setTime] = useState('');
|
|||
|
|
const [showQuickSelect, setShowQuickSelect] = useState(false);
|
|||
|
|
const [isInitialized, setIsInitialized] = useState(false);
|
|||
|
|
|
|||
|
|
// 组件卸载时清理状态
|
|||
|
|
useEffect(() => {
|
|||
|
|
return () => {
|
|||
|
|
setShowQuickSelect(false);
|
|||
|
|
};
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
// 初始化时间值
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (value && !isInitialized) {
|
|||
|
|
const dateObj = new Date(value);
|
|||
|
|
if (!isNaN(dateObj.getTime())) {
|
|||
|
|
// 转换为本地时间字符串
|
|||
|
|
const timeZoneOffset = dateObj.getTimezoneOffset() * 60000;
|
|||
|
|
const localDate = new Date(dateObj.getTime() - timeZoneOffset);
|
|||
|
|
const isoString = localDate.toISOString();
|
|||
|
|
|
|||
|
|
setDate(isoString.split('T')[0]);
|
|||
|
|
setTime(isoString.split('T')[1].slice(0, 5));
|
|||
|
|
}
|
|||
|
|
setIsInitialized(true);
|
|||
|
|
} else if (!value && !isInitialized) {
|
|||
|
|
setDate('');
|
|||
|
|
setTime('');
|
|||
|
|
setIsInitialized(true);
|
|||
|
|
}
|
|||
|
|
}, [value, isInitialized]);
|
|||
|
|
|
|||
|
|
// 当日期或时间改变时,更新父组件的值
|
|||
|
|
useEffect(() => {
|
|||
|
|
// 只在初始化完成后才触发onChange
|
|||
|
|
if (!isInitialized) return;
|
|||
|
|
|
|||
|
|
if (date && time) {
|
|||
|
|
const dateTimeString = `${date}T${time}`;
|
|||
|
|
onChange?.(dateTimeString);
|
|||
|
|
} else if (!date && !time) {
|
|||
|
|
onChange?.('');
|
|||
|
|
}
|
|||
|
|
}, [date, time, isInitialized]); // 移除onChange依赖
|
|||
|
|
|
|||
|
|
// 快速选择时间的选项
|
|||
|
|
const timeOptions = [
|
|||
|
|
{ label: '09:00', value: '09:00' },
|
|||
|
|
{ label: '10:00', value: '10:00' },
|
|||
|
|
{ label: '11:00', value: '11:00' },
|
|||
|
|
{ label: '14:00', value: '14:00' },
|
|||
|
|
{ label: '15:00', value: '15:00' },
|
|||
|
|
{ label: '16:00', value: '16:00' },
|
|||
|
|
{ label: '17:00', value: '17:00' },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 快速选择日期的选项
|
|||
|
|
const getQuickDateOptions = () => {
|
|||
|
|
const today = new Date();
|
|||
|
|
const options = [];
|
|||
|
|
|
|||
|
|
// 今天
|
|||
|
|
options.push({
|
|||
|
|
label: '今天',
|
|||
|
|
value: today.toISOString().split('T')[0]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 明天
|
|||
|
|
const tomorrow = new Date(today);
|
|||
|
|
tomorrow.setDate(today.getDate() + 1);
|
|||
|
|
options.push({
|
|||
|
|
label: '明天',
|
|||
|
|
value: tomorrow.toISOString().split('T')[0]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 后天
|
|||
|
|
const dayAfterTomorrow = new Date(today);
|
|||
|
|
dayAfterTomorrow.setDate(today.getDate() + 2);
|
|||
|
|
options.push({
|
|||
|
|
label: '后天',
|
|||
|
|
value: dayAfterTomorrow.toISOString().split('T')[0]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return options;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const quickDateOptions = getQuickDateOptions();
|
|||
|
|
|
|||
|
|
const formatDisplayText = () => {
|
|||
|
|
if (!date && !time) return placeholder;
|
|||
|
|
|
|||
|
|
if (date && time) {
|
|||
|
|
const dateObj = new Date(`${date}T${time}`);
|
|||
|
|
return dateObj.toLocaleString('zh-CN', {
|
|||
|
|
year: 'numeric',
|
|||
|
|
month: 'long',
|
|||
|
|
day: 'numeric',
|
|||
|
|
hour: '2-digit',
|
|||
|
|
minute: '2-digit'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (date) {
|
|||
|
|
const dateObj = new Date(date);
|
|||
|
|
return dateObj.toLocaleDateString('zh-CN', {
|
|||
|
|
year: 'numeric',
|
|||
|
|
month: 'long',
|
|||
|
|
day: 'numeric'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return placeholder;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const clearDateTime = () => {
|
|||
|
|
setDate('');
|
|||
|
|
setTime('');
|
|||
|
|
// 重置初始化状态,允许后续值的设定
|
|||
|
|
setIsInitialized(false);
|
|||
|
|
onChange?.('');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="datetime-picker">
|
|||
|
|
<div className="datetime-display" onClick={(e) => {
|
|||
|
|
e.stopPropagation();
|
|||
|
|
setShowQuickSelect(!showQuickSelect);
|
|||
|
|
}}>
|
|||
|
|
<Calendar size={18} />
|
|||
|
|
<span className={`display-text ${(!date && !time) ? 'placeholder' : ''}`}>
|
|||
|
|
{formatDisplayText()}
|
|||
|
|
</span>
|
|||
|
|
{(date || time) && (
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
className="clear-btn"
|
|||
|
|
onClick={(e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
e.stopPropagation();
|
|||
|
|
clearDateTime();
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
×
|
|||
|
|
</button>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{showQuickSelect && (
|
|||
|
|
<div className="datetime-picker-panel">
|
|||
|
|
<div className="picker-section">
|
|||
|
|
<h4>选择日期</h4>
|
|||
|
|
<div className="quick-date-options">
|
|||
|
|
{quickDateOptions.map((option) => (
|
|||
|
|
<button
|
|||
|
|
key={option.value}
|
|||
|
|
type="button"
|
|||
|
|
className={`quick-option ${date === option.value ? 'selected' : ''}`}
|
|||
|
|
onClick={(e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
e.stopPropagation();
|
|||
|
|
setDate(option.value);
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
{option.label}
|
|||
|
|
</button>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
<div className="custom-date-input">
|
|||
|
|
<input
|
|||
|
|
type="date"
|
|||
|
|
value={date}
|
|||
|
|
onChange={(e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
e.stopPropagation();
|
|||
|
|
setDate(e.target.value);
|
|||
|
|
}}
|
|||
|
|
onClick={(e) => e.stopPropagation()}
|
|||
|
|
className="date-input"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="picker-section">
|
|||
|
|
<h4>选择时间</h4>
|
|||
|
|
<div className="quick-time-options">
|
|||
|
|
{timeOptions.map((option) => (
|
|||
|
|
<button
|
|||
|
|
key={option.value}
|
|||
|
|
type="button"
|
|||
|
|
className={`quick-option ${time === option.value ? 'selected' : ''}`}
|
|||
|
|
onClick={(e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
e.stopPropagation();
|
|||
|
|
setTime(option.value);
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
{option.label}
|
|||
|
|
</button>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
<div className="custom-time-input">
|
|||
|
|
<Clock size={16} />
|
|||
|
|
<input
|
|||
|
|
type="time"
|
|||
|
|
value={time}
|
|||
|
|
onChange={(e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
e.stopPropagation();
|
|||
|
|
setTime(e.target.value);
|
|||
|
|
}}
|
|||
|
|
onClick={(e) => e.stopPropagation()}
|
|||
|
|
className="time-input"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="picker-actions">
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
className="action-btn cancel"
|
|||
|
|
onClick={(e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
e.stopPropagation();
|
|||
|
|
setShowQuickSelect(false);
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
取消
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
className="action-btn confirm"
|
|||
|
|
onClick={(e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
e.stopPropagation();
|
|||
|
|
setShowQuickSelect(false);
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
确认
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{showQuickSelect && (
|
|||
|
|
<div
|
|||
|
|
className="datetime-picker-overlay"
|
|||
|
|
onClick={() => setShowQuickSelect(false)}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default DateTimePicker;
|