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

如何用React DnD的useDrop实现FullCalendar事件拖拽至垃圾桶删除?

实现FullCalendar事件拖拽至垃圾桶删除(React Hooks + React DnD)

可以通过React DnD的useDrop实现这个需求,下面是具体的改造步骤和代码:


1. 定义统一的拖拽类型常量

先定义一个拖拽类型标识,避免硬编码,确保拖拽源和放置目标匹配:

const EVENT_ITEM_TYPE = 'fullcalendar-event';

2. 完善useDrop配置

修改你的useDrop钩子,指定accept为上面定义的类型,并在drop回调中获取要删除的事件ID:

const [draggedEventId, setDraggedEventId] = useState(null);

const [, dropTarget] = useDrop({
  accept: EVENT_ITEM_TYPE,
  drop: (item, monitor) => {
    // 获取拖拽的事件ID,优先从monitor的item中取,其次用状态记录的值
    const targetEventId = item?.eventId || draggedEventId;
    if (targetEventId) {
      dispatch({ type: "REMOVE_EVENT", payload: targetEventId });
    }
  },
  // 可选:拖拽悬停时给垃圾桶添加高亮样式
  hover: () => {
    // 这里可以修改垃圾桶的样式,比如添加active类
  }
});

3. 处理FullCalendar的拖拽事件

在eventDragStart中记录当前拖拽的事件ID,并适配React DnD的HTML5后端传递数据:

const handleDragStart = (info) => {
  setDraggedEventId(info.event.id);
  // 给React DnD传递拖拽数据,确保放置目标能识别
  if (info.jsEvent.dataTransfer) {
    info.jsEvent.dataTransfer.effectAllowed = 'move';
    info.jsEvent.dataTransfer.setData(
      `application/react-dnd-${EVENT_ITEM_TYPE}`,
      JSON.stringify({ eventId: info.event.id })
    );
  }
};

const handleDragEnd = () => {
  // 拖拽结束后清空状态
  setDraggedEventId(null);
};

4. 确保垃圾桶组件支持Ref

你的BinSection组件需要用forwardRef包装,才能接收useDrop返回的ref作为放置目标:

const BinSection = React.forwardRef((props, ref) => {
  return (
    <div 
      ref={ref} 
      style={{
        marginTop: '20px',
        padding: '12px',
        border: '2px dashed #e0e0e0',
        borderRadius: '4px',
        textAlign: 'center',
        color: '#666',
        cursor: 'move'
      }}
    >
      🗑️ 拖拽事件到此处删除
    </div>
  );
});

完整修改后的Schedule组件

import React, { useState, forwardRef } from 'react';
import { useDrop } from 'react-dnd';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import FunkFullCalendar from './FunkFullCalendar';

const EVENT_ITEM_TYPE = 'fullcalendar-event';

const BinSection = forwardRef((props, ref) => {
  return (
    <div 
      ref={ref} 
      style={{
        marginTop: '20px',
        padding: '12px',
        border: '2px dashed #e0e0e0',
        borderRadius: '4px',
        textAlign: 'center',
        color: '#666',
        cursor: 'move'
      }}
    >
      🗑️ 拖拽事件到此处删除
    </div>
  );
});

const Schedule = () => {
  const [draggedEventId, setDraggedEventId] = useState(null);
  // 保留你原有的状态:view, startDate, endDate, sessions等

  const [, dropTarget] = useDrop({
    accept: EVENT_ITEM_TYPE,
    drop: (item, monitor) => {
      const targetEventId = item?.eventId || draggedEventId;
      if (targetEventId) {
        dispatch({ type: "REMOVE_EVENT", payload: targetEventId });
      }
    }
  });

  const handleDragStart = (info) => {
    setDraggedEventId(info.event.id);
    if (info.jsEvent.dataTransfer) {
      info.jsEvent.dataTransfer.effectAllowed = 'move';
      info.jsEvent.dataTransfer.setData(
        `application/react-dnd-${EVENT_ITEM_TYPE}`,
        JSON.stringify({ eventId: info.event.id })
      );
    }
  };

  const handleDragEnd = () => {
    setDraggedEventId(null);
  };

  return (
    <DndProvider backend={HTML5Backend}>
      <FunkFullCalendar
        today={() => {
          let startDate = moment().tz(timeZone, true).startOf('week');
          let endDate = moment().tz(timeZone, true).endOf('week');
          changeInterval(startDate.toISOString(), endDate.toISOString());
        }}
        timeGridWeek={() => {
          setView('timeGridWeek');
        }}
        allDaySlot={false}
        defaultView='timeGridWeek'
        view={view}
        events={sessions}
        eventDragStart={handleDragStart}
        eventDragStop={handleDragEnd}
        dateClick={(evt) => {
          let correctedDate = moment(evt.date).utc().tz(timeZone, true);
          createSession(correctedDate);
        }}
        drop={(evt) => {
          let correctedDate = moment(evt.date).utc().tz(timeZone, true);
          createSession(correctedDate, evt.draggedEl.dataset.actorid);
        }}
        eventClick={(evt) => {
          openSession(evt.event.id);
        }}
        eventChange={(evt) => {
          let correctedStart = moment(evt.event.start).utc().tz(timeZone, true);
          let correctedEnd = moment(evt.event.end).utc().tz(timeZone, true);
          changeSessionTime(evt.event.id, correctedStart, correctedEnd);
        }}
        startDate={startDate}
        endDate={endDate}
      />
      <BinSection ref={dropTarget}/>
    </DndProvider>
  );
};

export default Schedule;

核心要点

  • accept属性值:必须和拖拽源的类型完全一致,这里用EVENT_ITEM_TYPE统一标识FullCalendar事件,确保垃圾桶只接收目标拖拽元素。
  • 拖拽数据传递:通过eventDragStart中的dataTransfer传递事件ID,或者用组件状态记录,保证drop回调能拿到要删除的事件ID。
  • 垃圾桶Ref:必须用forwardRef包装垃圾桶组件,否则无法接收useDrop返回的ref,无法成为有效的放置目标。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 07:20:53