You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React周历拖拽功能异常:位置偏移+数据不持久

拖拽日历盒子的问题修复方案

问题说明

  • 拖拽盒子到指定时间槽时,会落到该日期的首个空位置,而非目标位置
  • 页面刷新后,所有拖拽调整过的盒子都会恢复初始状态

修复方案

问题1:拖拽位置错误修复

当前代码在拖拽完成后,是删除原订单再将修改后的订单push到数组末尾,导致渲染时会被填充到第一个空槽位。需要给每个订单绑定明确的时间槽索引,直接更新目标订单的日期和槽位信息。

问题2:刷新数据丢失修复

使用localStorage持久化订单数据,初始化时优先读取本地存储的内容,更新数据时同步保存到本地。


修改后的完整代码

import React, { useState, useRef, useEffect } from "react";
import { startOfWeek, endOfWeek, format, addDays, subDays } from "date-fns";
import "../assets/css/RunSheetPDF.css";

const DraggableBox = ({
  day,
  order,
  setOrderData,
  setChangeData
}) => {
  const [showTooltip, setShowTooltip] = useState(false);
  const boxRef = useRef(null);

  const handleDragStart = (e) => {
    setShowTooltip(false);
    setChangeData(order);
    boxRef.current.classList.add("dragging");
  };

  const handleDragEnd = () => {
    boxRef.current.classList.remove("dragging");
  };

  const handleDragOver = (e) => {
    e.preventDefault();
  };

  return (
    <div
      className="drag-box"
      ref={boxRef}
      draggable
      onDragStart={handleDragStart}
      onDragEnd={handleDragEnd}
      onDragOver={handleDragOver}
      onMouseEnter={() => setShowTooltip(true)}
      onMouseLeave={() => setShowTooltip(false)}
      style={{
        width: "70px",
        height: "80px",
        textAlign: "center",
        backgroundColor: "#F5F5F5",
        color: "#333333",
        marginTop: "5px",
        position: "relative",
        cursor: "move",
      }}
      data-day={day}
    >
      <div style={{ margin: 0, lineHeight: "40px" }}>
        <p style={{ margin: 0 }}>{order.Id}</p>
        <p style={{ margin: 0 }}>{order.DelCity}</p>
      </div>
    </div>
  );
};

const RunSheetPreview = ({
  initialOrderData,
  selectedDate: propSelectedDate,
}) => {
  const [changeData, setChangeData] = useState(null);
  const [selectedDate, setSelectedDate] = useState(propSelectedDate || new Date());

  // 从localStorage读取持久化数据
  const [orderData, setOrderData] = useState(() => {
    const savedData = localStorage.getItem("runSheetOrders");
    return savedData ? JSON.parse(savedData) : initialOrderData;
  });

  // 数据更新时同步保存到localStorage
  useEffect(() => {
    localStorage.setItem("runSheetOrders", JSON.stringify(orderData));
  }, [orderData]);

  const formatDate = (date) => {
    const dateVal = new Date(date);
    const year = dateVal.getFullYear();
    const month = String(dateVal.getMonth() + 1).padStart(2, "0");
    const day = String(dateVal.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
  };

  const handleDragOver = (e) => {
    e.preventDefault();
  };

  const handleDrop = (e, targetTimeSlot, targetDay, targetYear, targetMonth) => {
    e.preventDefault();
    e.stopPropagation();

    if (!changeData) return;

    // 创建新的日期对象,设置目标日期
    const newDate = new Date(changeData.OrderDate);
    newDate.setFullYear(targetYear);
    newDate.setMonth(targetMonth - 1); // 月份是0-based
    newDate.setDate(targetDay);

    // 更新订单的日期和时间槽索引
    const updatedOrders = orderData.order.map(order => {
      if (order.Id === changeData.Id) {
        return {
          ...order,
          OrderDate: newDate.toISOString(),
          RequiredDateAndTime: newDate.toISOString(),
          timeSlotIndex: targetTimeSlot
        };
      }
      return order;
    });

    setOrderData({ ...orderData, order: updatedOrders });
    setChangeData(null);
  };

  const generateCalendar = () => {
    const startDate = startOfWeek(selectedDate, { weekStartsOn: 0 });
    const endDate = endOfWeek(selectedDate, { weekStartsOn: 0 });

    const rows = [];
    let day = startDate;

    while (day <= endDate) {
      const formattedDate = formatDate(day);
      const dateObject = new Date(formattedDate);
      const year = dateObject.getFullYear();
      const month = dateObject.getMonth() + 1;
      const dayName = format(day, "EEEE");
      const dayOfMonth = format(day, "d");

      // 按时间槽索引排序当天的订单
      const ordersForDay = orderData.order
        .filter(order => {
          const orderDate = new Date(order.RequiredDateAndTime).getDate();
          return orderDate === parseInt(dayOfMonth);
        })
        .sort((a, b) => (a.timeSlotIndex || 0) - (b.timeSlotIndex || 0));

      const row = (
        <tr key={day}>
          <td className="calendar-cell">
            <div className="day-name">{dayName}</div>
            <div className="day-number">{dayOfMonth}</div>
          </td>
          {[...Array(10)].map((_, slotIndex) => {
            const matchedOrder = ordersForDay.find(order => order.timeSlotIndex === slotIndex);
            return (
              <td key={slotIndex} className={`grid-cell`}>
                {matchedOrder ? (
                  <DraggableBox
                    day={parseInt(dayOfMonth)}
                    order={matchedOrder}
                    setOrderData={setOrderData}
                    setChangeData={setChangeData}
                  />
                ) : (
                  <p
                    style={{
                      height: "90px",
                      display: "flex",
                      alignItems: "center",
                      justifyContent: "center",
                    }}
                    onDragOver={handleDragOver}
                    onDrop={(e) => handleDrop(e, slotIndex, parseInt(dayOfMonth), year, month)}
                    data-year={year}
                    data-month={month}
                  ></p>
                )}
              </td>
            );
          })}
        </tr>
      );

      rows.push(row);
      day = addDays(day, 1);
    }

    return rows;
  };

  const handleNextWeek = () => {
    setSelectedDate(addDays(selectedDate, 7));
  };
  const handlePreviousWeek = () => {
    setSelectedDate(subDays(selectedDate, 7));
  };

  return (
    <div style={{ width: "100%", display: "flex", flexDirection: "column", alignItems: "center", marginTop: "20px" }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "center", marginBottom: "0px" }}>
        <h2 style={{ color: "#333333", margin: 0 }}>{format(startOfWeek(selectedDate), "MMMM d")} - {format(endOfWeek(selectedDate), "MMMM d, yyyy")}</h2>
      </div>
      <div className="calendar-container">
        <table className="calendar-table">
          <thead>
            <tr>
              <th>{/* Empty header cell for labels */}</th>
              {[...Array(10)].map((_, index) => (
                <th key={index}>t{index + 1}</th>
              ))}
            </tr>
          </thead>
          <tbody>
            {generateCalendar()}
          </tbody>
        </table>
      </div>
      <div style={{ marginTop: "10px" }}>
        <button onClick={handlePreviousWeek}>上一周</button>
        <button onClick={handleNextWeek} style={{ marginLeft: "10px" }}>下一周</button>
      </div>
    </div>
  );
};

export default RunSheetPreview;

内容的提问来源于stack exchange,提问作者user022yufjb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 13:19:50