React工时表组件周切换数据加载功能实现求助
React工时表组件修复方案
核心问题梳理
- 重复维护
weekStartDate和startDate两个日期状态,导致数据同步混乱 - 本地存储的
timeSheetData未关联到实际渲染的data状态,加载历史数据逻辑失效 - 未来周处理逻辑不严谨,未判断当前切换的周是否属于未来时间
- 多个冗余状态(
previousSubmissions、savedData、timeSheetData)重复存储数据,引发不一致 - 初始周未对齐到周一(与表头
Mon起始逻辑不符)
修复后的完整代码
import React, { useState, useEffect } from "react"; import Modal from "react-modal"; const TimeSheet = () => { const customStyles = { content: { top: "50%", left: "50%", right: "auto", bottom: "auto", marginRight: "-50%", transform: "translate(-50%, -50%)", }, }; // 统一用startDate管理周起始日期,初始化时对齐到周一 const [startDate, setStartDate] = useState(() => { const today = new Date(); const dayOfWeek = today.getDay(); // 0=周日,1=周一...6=周六 const offset = dayOfWeek === 0 ? -6 : 1 - dayOfWeek; // 对齐到最近的周一 return new Date(today.setDate(today.getDate() + offset)); }); // 活跃项目模板(仅作为初始空数据模板) const activeProjectTemplate = [ { project: "Project A", hours: [0, 0, 0, 0, 0, 0, 0], total: 0 }, { project: "Project B", hours: [0, 0, 0, 0, 0, 0, 0], total: 0 }, { project: "Project C", hours: [0, 0, 0, 0, 0, 0, 0], total: 0 }, { project: "Project D", hours: [0, 0, 0, 0, 0, 0, 0], total: 0 }, { project: "Project F", hours: [0, 0, 0, 0, 0, 0, 0], total: 0 }, { project: "Project G", hours: [0, 0, 0, 0, 0, 0, 0], total: 0 }, ]; // 当前展示的工时数据 const [data, setData] = useState([]); const days = ["Mon", "Tue", "Wed", "Thurs", "Fri", "Sat", "Sun"]; // 日期偏移工具函数 const dateOffset = (date, offset) => { const newDate = new Date(date); newDate.setDate(date.getDate() + offset); return newDate; }; // 判断当前周是否为未来周 const isFutureWeek = () => { const currentWeekEnd = dateOffset(startDate, 6); const today = new Date(); today.setHours(0, 0, 0, 0); return currentWeekEnd > today; }; // 从本地存储加载对应周的数据 const loadWeekData = () => { const weekKey = `timeSheet_${startDate.toISOString().slice(0, 10)}`; const savedData = localStorage.getItem(weekKey); if (savedData) { setData(JSON.parse(savedData)); } else { // 当前周无数据:显示空的活跃项目表;未来周:显示空表 setData(isFutureWeek() ? [] : [...activeProjectTemplate]); } }; // 保存当前周数据到本地存储 const saveWeekData = () => { const weekKey = `timeSheet_${startDate.toISOString().slice(0, 10)}`; localStorage.setItem(weekKey, JSON.stringify(data)); }; // 切换周时自动加载对应数据 useEffect(() => { loadWeekData(); }, [startDate]); // 上一周切换 const handlePreviousWeek = () => { setStartDate((curr) => dateOffset(curr, -7)); }; // 下一周切换 const handleNextWeek = () => { setStartDate((curr) => dateOffset(curr, 7)); // 未来周自动清空数据(loadWeekData会处理) }; // 添加新项目行 const handleAddRow = () => { setData([ ...data, { project: `Project ${data.length + 1}`, hours: [0, 0, 0, 0, 0, 0, 0], total: 0, }, ]); }; // 删除项目行 const handleDeleteRow = (index) => { const updatedData = [...data]; updatedData.splice(index, 1); setData(updatedData); }; // 计算单项目总工时 const calculateTotal = (hours) => { return hours.reduce((sum, hour) => sum + (parseInt(hour) || 0), 0); }; // 工时输入变更 const handleHoursChange = (projectIndex, dayIndex, value) => { const updatedData = [...data]; updatedData[projectIndex].hours[dayIndex] = value; updatedData[projectIndex].total = calculateTotal(updatedData[projectIndex].hours); setData(updatedData); }; // 保存工时(与提交合并,避免重复逻辑) const handleSaveSubmit = () => { saveWeekData(); console.log(`已保存周数据:${startDate.toLocaleDateString('de-DE')}`); }; // Modal相关逻辑(保留原功能) let subtitle; const [modalIsOpen, setIsOpen] = React.useState(false); function openModal() { setIsOpen(true); } function afterOpenModal() { subtitle.style.color = "#f00"; } function closeModal() { setIsOpen(false); } return ( <div> <h1>项目工时管理表</h1> <div> <button onClick={handlePreviousWeek}>上一周</button> <span>{`${startDate.toLocaleDateString('de-DE')} - ${dateOffset(startDate, 6).toLocaleDateString('de-DE')}`}</span> <button onClick={handleNextWeek}>下一周</button> </div> <table border="1"> <thead> <tr> <th>项目</th> {days.map((day) => ( <th key={day}> {day} <br /> {dateOffset(startDate, days.indexOf(day)).toLocaleDateString('de-DE')} </th> ))} <th>总计</th> <th>操作</th> </tr> </thead> <tbody> {data.length === 0 ? ( <tr> <td colSpan={9}>无数据</td> </tr> ) : ( data.map((item, index) => ( <tr key={index}> <td>{item.project}</td> {item.hours.map((hour, dayIndex) => ( <td key={dayIndex}> <input type="number" value={hour} onChange={(e) => handleHoursChange(index, dayIndex, e.target.value)} min="0" /> </td> ))} <td>{item.total}</td> <td> <button onClick={() => handleDeleteRow(index)}>删除</button> </td> </tr> )) )} </tbody> </table> <button onClick={handleAddRow}>添加新项目</button> <button onClick={handleSaveSubmit}>保存并提交</button> <button onClick={openModal}>打开弹窗</button> <Modal isOpen={modalIsOpen} onAfterOpen={afterOpenModal} onRequestClose={closeModal} style={customStyles} contentLabel="示例弹窗" > <h2 ref={(_subtitle) => (subtitle = _subtitle)}>Hello</h2> <button onClick={closeModal}>关闭</button> <div>我是弹窗</div> <form> <input /> <button>Tab导航</button> <button>保留在</button> <button>弹窗内</button> </form> </Modal> </div> ); }; export default TimeSheet;
关键修改说明
- 统一日期状态:删除冗余的
weekStartDate,只用startDate管理周起始,初始化时自动对齐到周一,符合表头的周一起始逻辑。 - 数据加载与展示关联:切换周时自动调用
loadWeekData,直接将本地存储的数据同步到data状态,确保展示的是对应周的内容。 - 未来周处理:通过
isFutureWeek判断,未来周直接显示空表,当前周无保存数据时显示预设的活跃项目空表。 - 简化存储逻辑:删除
previousSubmissions、savedData等冗余状态,直接用data作为唯一数据源,存储时仅保存当前周的data。 - 优化交互体验:空数据时显示提示,输入框添加
min="0"限制,合并保存与提交按钮避免重复逻辑。
内容的提问来源于stack exchange,提问作者Codeworld
相关产品推荐
相关产品推荐

