如何仅针对特定日期从下拉菜单中移除预订时段?
按日期过滤已预订时段的实现方案
要实现选中日期的已预订时段自动隐藏,其他日期不受影响,需要完成以下几个核心步骤:
1. 新增状态存储已预订记录
在组件中添加状态,保存所有已提交的预订信息:
const [bookedSlots, setBookedSlots] = useState([]);
2. 实时获取并监听已预订数据
修改useEffect,同时获取可用时段和已预订记录,并且通过onSnapshot监听Firestore的实时变化,确保页面能及时更新:
useEffect(() => { // 获取基础可用时段 const fetchTimes = async () => { const timeData = await getDocs(timesRef); setTimes(timeData.docs.map(doc => ({ ...doc.data(), id: doc.id }))); }; // 监听预订记录的实时更新 const unsubscribe = onSnapshot(formRef, (snapshot) => { const bookedData = snapshot.docs.map(doc => ({ date: doc.data().date.toDate(), // 转换为JS Date对象方便对比 time: doc.data().time })); setBookedSlots(bookedData); }); fetchTimes(); return unsubscribe; // 组件卸载时取消监听,避免内存泄漏 }, []);
3. 根据选中日期过滤可用时段
创建计算属性,对比当前选中日期和已预订记录,过滤掉该日期已被占用的时段:
const availableTimes = date ? times.filter(timeItem => { // 检查当前时段是否在选中日期的已预订列表中 const isBooked = bookedSlots.some(slot => slot.date.toDateString() === date.toDateString() && slot.time === timeItem.time ); return !isBooked; }) : [];
4. 更新下拉菜单的渲染逻辑
根据计算出的availableTimes渲染下拉选项,同时处理未选日期和无可用时段的情况:
<div className="pt-10"> {date ? ( <select onChange={(e) => setTime(e.target.value)}> {availableTimes.length > 0 ? ( availableTimes.map((item) => ( <option key={item.id} value={item.time}> {item.time} </option> )) ) : ( <option value="">该日期暂无可用时段</option> )} </select> ) : ( <p className="text-gray-500">请先选择日期</p> )} </div>
5. 优化日期存储格式(可选)
提交预订时,将JS Date对象转换为Firestore的Timestamp类型,保证存储格式统一:
const createBooking = async () => { await addDoc(formRef, { name: name, date: Timestamp.fromDate(date), email: email, number: number, team: team, age: age, time: time, }); };
完整修改后的组件代码
function GThirtyminsp() { // Form values const [date, setDate] = useState(null); const [email, setEmail] = useState(""); const [name, setName] = useState(""); const [age, setAge] = useState(""); const [team, setTeam] = useState(""); const [number, setNumber] = useState(""); const [time, setTime] = useState(null); // To show times on screen const [times, setTimes] = useState([]); // 存储已预订的时段信息 const [bookedSlots, setBookedSlots] = useState([]); const timesRef = collection(db, "30min-goalie-semiprivate-times"); const formRef = collection(db, "30min-goalie-semiprivate"); useEffect(() => { const fetchTimes = async () => { const timeData = await getDocs(timesRef); setTimes(timeData.docs.map(doc => ({ ...doc.data(), id: doc.id }))); }; // 实时监听预订记录变化 const unsubscribe = onSnapshot(formRef, (snapshot) => { const bookedData = snapshot.docs.map(doc => ({ date: doc.data().date.toDate(), time: doc.data().time })); setBookedSlots(bookedData); }); fetchTimes(); return unsubscribe; }, []); // 计算当前选中日期的可用时段 const availableTimes = date ? times.filter(timeItem => { const isBooked = bookedSlots.some(slot => slot.date.toDateString() === date.toDateString() && slot.time === timeItem.time ); return !isBooked; }) : []; const createBooking = async () => { await addDoc(formRef, { name: name, date: Timestamp.fromDate(date), email: email, number: number, team: team, age: age, time: time, }); }; const handleSubmit = (e) => { e.preventDefault(); createBooking(); // 重置表单 setName(""); setEmail(""); setDate(null); setNumber(""); setTeam(""); setTime(null); setAge(""); }; return ( <div> <h1 className="text-3xl w-full text-center pt-10 font-bold"> Book 30min Semi-Private Goalie Clinic </h1> <div className="flex flex-col items-center"> <form onSubmit={handleSubmit} className="flex flex-col pt-10 w-3/4"> <input type="text" name="name" placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} className="border-2 p-3 mb-5 border-gray rounded-lg" /> <input type="text" name="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} className="border-2 p-3 mb-5 border-gray rounded-lg" /> <input type="text" name="number" placeholder="Phone Number" value={number} onChange={(e) => setNumber(e.target.value)} className="border-2 p-3 mb-5 border-gray rounded-lg" /> <input type="text" name="team" placeholder="Team Name" value={team} onChange={(e) => setTeam(e.target.value)} className="border-2 p-3 mb-5 border-gray rounded-lg" /> <input type="text" name="age" placeholder="Age" value={age} onChange={(e) => setAge(e.target.value)} className="border-2 p-3 mb-5 border-gray rounded-lg" /> <h2 className="text-center text-xl py-5 font-bold"> Pick a Date and Time </h2> <div className="pb-10"> <DatePicker className="border-2 p-3 w-full border-gray rounded-lg" selected={date} onChange={(date) => setDate(date)} placeholderText="Select a Date" /> <div className="pt-10"> {date ? ( <select onChange={(e) => setTime(e.target.value)}> {availableTimes.length > 0 ? ( availableTimes.map((item) => ( <option key={item.id} value={item.time}> {item.time} </option> )) ) : ( <option value="">该日期暂无可用时段</option> )} </select> ) : ( <p className="text-gray-500">请先选择日期</p> )} </div> </div> <button type="submit" className="bg-accent text-white font-semibold p-3 rounded-full" > Submit </button> </form> </div> </div> ); }
关键细节说明
- 使用
onSnapshot实现实时更新,当有新的预订提交时,页面会自动刷新可用时段 - 通过
toDateString()对比日期,只匹配年月日,忽略时间部分,确保日期判断准确 - 未选择日期时显示提示文本,避免下拉菜单为空的尴尬情况
- 提交时转换日期为
Timestamp,保证Firestore存储格式一致,避免后续读取时的格式问题
内容的提问来源于stack exchange,提问作者Dhruv
相关产品推荐
相关产品推荐

