如何用React+Material UI ProgressBar实现关联Firestore的预约倒计时功能
第一步:修正Firestore文档写入逻辑
你现有的expiryDate计算逻辑存在错误,既没有声明event变量,setHours(72)也不是「当前时间加72小时」的正确写法,同时推荐使用Firebase服务器时间避免本地时间误差,修正后的写入代码如下:
import { serverTimestamp } from 'firebase/firestore'; // 写入文档逻辑 reserveDB.doc(item.productID).set({ productID: item.productID, image: item.image, price: item.price, productName: item.productName, isReserved: true, startDate: serverTimestamp(), // 用服务器时间戳避免本地时间误差 expiryDate: new Date(Date.now() + 72 * 60 * 60 * 1000), // 72小时后的时间戳 });
如果需要完全避免本地时间篡改,可以配合Firebase云函数在文档创建时自动计算expiryDate。
第二步:实现绑定Firestore的Material UI进度条倒计时
完整实现逻辑如下,包含实时监听Firestore数据、进度计算、到期自动重置状态的功能:
import { useState, useEffect } from 'react'; import { LinearProgress } from '@mui/material'; import { doc, onSnapshot, updateDoc } from 'firebase/firestore'; import { reserveDB } from './你的firebase配置文件路径'; const ReserveProgress = ({ productID }) => { const [progress, setProgress] = useState(0); const [isReserved, setIsReserved] = useState(false); const TOTAL_RESERVE_TIME = 72 * 60 * 60 * 1000; // 总预留时长72小时,单位毫秒 useEffect(() => { // 监听对应商品的Firestore文档实时变化 const unsub = onSnapshot(doc(reserveDB, productID), (docSnapshot) => { if (!docSnapshot.exists()) return; const data = docSnapshot.data(); setIsReserved(data.isReserved); // 如果商品处于预留状态,启动倒计时 if (data.isReserved && data.expiryDate) { // 兼容Firestore Timestamp类型和毫秒数字格式 const expiryTime = data.expiryDate.toMillis ? data.expiryDate.toMillis() : data.expiryDate; // 每秒更新一次进度 const timer = setInterval(() => { const now = Date.now(); const remainingTime = expiryTime - now; if (remainingTime <= 0) { // 到期自动重置预留状态 updateDoc(doc(reserveDB, productID), { isReserved: false }); setProgress(0); clearInterval(timer); return; } // 计算进度值,限制在0-100区间 const calculatedProgress = Math.max(0, Math.min(100, (remainingTime / TOTAL_RESERVE_TIME) * 100)); setProgress(calculatedProgress); }, 1000); // 状态变化时清理旧定时器 return () => clearInterval(timer); } else { setProgress(0); } }); // 组件卸载时取消Firestore监听 return () => unsub(); }, [productID]); return isReserved ? ( <LinearProgress variant="determinate" value={progress} /> ) : null; }; export default ReserveProgress;
补充说明
如果需要更高的安全性,建议新增Firebase云函数定时扫描所有isReserved=true的文档,把expiryDate小于当前时间的文档批量重置isReserved为false,避免前端定时器失效或者用户篡改本地时间导致的状态异常。
内容的提问来源于stack exchange,提问作者B_M
相关产品推荐
相关产品推荐

