根据fullcalendar实现企业微信的拖动式预约会议

发布于:2025-09-05 ⋅ 阅读:(18) ⋅ 点赞:(0)

先上效果图

这边是根据日历从当天开始,生成7个Calendar实例,并且根据数据数据进行回显,并且禁用当前时间段之前的操作,和在拖动后进行鼠标附近的modal展示,并且按照第几周来进行筛选和获取数据,直接上关键代码⬇️

import { Calendar } from '@fullcalendar/core';
import resourceTimelinePlugin from '@fullcalendar/resource-timeline';
import interactionPlugin from '@fullcalendar/interaction';
import ProFormDatePickerWeek from "@ant-design/pro-form/es/components/DatePicker/WeekPicker"
import dayjs from "dayjs"

  const [modalPosition, setModalPosition] = useState({ top: 0, left: 0 });
    const [modalVisible, setModalVisible] = useState(false);
  const [filterTimeValue,setFilterTimeValue]=useState(dayjs())
  const [currentWeek, setCurrentWeek] = useState(dayjs().weekday(1));
  const [condition, setCondition] = useState([{
    condition: {
      field: MeetConfig.f_1744783951543,
      op: "between",
      value:[dayjs().startOf('week').valueOf(),dayjs().endOf('week').valueOf()],
    },
    nextop: "and",
  },
  {
    condition: {
      field: MeetConfig.f_1744783955010,
      op: "between",
      value:[dayjs().startOf('week').valueOf(),dayjs().endOf('week').valueOf()],
    },
    nextop: "and",
  },])

  const modalRef =useRef()
const getWeekDays = () => {
  // 确保始终从周一开始计算
  let startOfWeek = currentWeek;
  if(startOfWeek.day()!==1){
    startOfWeek= startOfWeek.day(1)
  }
  console.log(startOfWeek.format('YYYY-MM-DD'),startOfWeek.day(),'currentWeek');
  
  // 验证是否为周一
  // console.assert(startOfWeek.day() === 1, 'Start day should be Monday');
  
  const days = [];
  for (let i = 0; i < 7; i++) {
    // if (isWorkday(startOfWeek.add(i, 'day'))) {
      console.log(startOfWeek.add(i, 'day'),'startOfWeek.add');
      days.push(startOfWeek.add(i, 'day'));
    // }
  }
    
  return days;
};
// 导航函数
const prevWeek = () => {
  const weekStart = dayjs(currentWeek.subtract(1, 'week')).startOf('week'); // 强制转为周一
  const weekEnd = weekStart.add(6, 'day').endOf('day');

  let submitCondition = condition.filter((item)=>item.condition.field!==MeetConfig.f_1744783951543&&item.condition.field!==MeetConfig.f_1744783955010)
  .concat(
    {
      condition: {
        field: MeetConfig.f_1744783951543,
        op: "between",
       value: [weekStart.valueOf(), weekEnd.valueOf()]
      },
      nextop: "and",
    },
  )
  .concat(
    {
      condition: {
        field: MeetConfig.f_1744783955010,
        op: "between",
       value: [weekStart.valueOf(), weekEnd.valueOf()]
      },
      nextop: "and",
    },
  )
  .filter((item2) => !isEmptyValues(item2?.condition?.value))
  setCondition(submitCondition)
  setFilterTimeValue(currentWeek.subtract(1, 'week'))
  setCurrentWeek(prev => prev.subtract(1, 'week'));
};

const nextWeek = () => {
  console.log(dayjs(currentWeek.add(1, 'week')).format('YYYY-MM-DD'),'lkkl');
  const weekStart = dayjs(currentWeek.add(1, 'week')).startOf('week'); // 强制转为周一
  const weekEnd = weekStart.add(6, 'day').endOf('day');

  let submitCondition = condition.filter((item)=>item.condition.field!==MeetConfig.f_1744783951543&&item.condition.field!==MeetConfig.f_1744783955010)
  .concat(
    {
      condition: {
        field: MeetConfig.f_1744783951543,
        op: "between",
       value: [weekStart.valueOf(), weekEnd.valueOf()]
      },
      nextop: "and",
    },
  )
  .concat(
    {
      condition: {
        field: MeetConfig.f_1744783955010,
        op: "between",
       value: [weekStart.valueOf(), weekEnd.valueOf()]
      },
      nextop: "and",
    },
  )
  .filter((item2) => !isEmptyValues(item2?.condition?.value))
  setCondition(submitCondition)
  setFilterTimeValue(currentWeek.add(1, 'week'))
  setCurrentWeek(prev => prev.add(1, 'week'));
};

const goToToday = () => {
  const weekStart = dayjs().startOf('week'); // 强制转为周一
  const weekEnd = weekStart.add(6, 'day').endOf('day');

  let submitCondition = condition.filter((item)=>item.condition.field!==MeetConfig.f_1744783951543&&item.condition.field!==MeetConfig.f_1744783955010)
  .concat(
    {
      condition: {
        field: MeetConfig.f_1744783951543,
        op: "between",
       value: [weekStart.valueOf(), weekEnd.valueOf()]
      },
      nextop: "and",
    },
  )
  .concat(
    {
      condition: {
        field: MeetConfig.f_1744783955010,
        op: "between",
       value: [weekStart.valueOf(), weekEnd.valueOf()]
      },
      nextop: "and",
    },
  )
  .filter((item2) => !isEmptyValues(item2?.condition?.value))
  setCondition(submitCondition)
  setCurrentWeek(dayjs());
      setFilterTimeValue(dayjs().startOf('week'));
};

 useEffect(() => {
  function handleClickOutside(event) {
      if (modalRef.current && !modalRef.current.contains(event.target)) {
          setModalVisible(false)
      }
  }

  document.addEventListener('mousedown', handleClickOutside);
  return () => {
      document.removeEventListener('mousedown', handleClickOutside);
  };
}, []);

useEffect(() => {
  // 清空之前的日历实例
  calendarApis.current.forEach(api => api && api.destroy());
  calendarApis.current = [];
  
  // 为每一天初始化日历
  getWeekDays().forEach((day, index) => {
    if (!calendarRefs.current[index]) return;
    const calendar = new Calendar(calendarRefs.current[index], {
      plugins: [resourceTimelinePlugin, interactionPlugin],
      initialView: 'resourceTimelineDay',
      initialDate: day.toDate(),
      headerToolbar: false,
      resourceAreaWidth: '0',
      selectAllow: (selectInfo) => {
      console.log(selectInfo,'selet');
      const now = new Date(); // 当前时间
      return  selectInfo.start >= now; // 只允许选择当前或未来的时间
    },
      slotLabelFormat: {
        hour: '2-digit',
        minute: '2-digit',
        hour12: false
      },
     timeline: {
        slotHeight: 30  // 设置与CSS一致的行高
      },
      headerToolbar:false,
      //设置时间间隔
      slotDuration: '00:30:00',
      slotMinTime: '08:00:00',
      slotMaxTime: '18:00:00',
      height: 'auto',
      editable: false,
      selectable: true,
      selectMirror: true,
      resources: resources,
      events: events.filter(event =>dayjs(event.start).isSame(day, 'day')),
      select:(selectInfo) => {
        const newEvent = {
          id: String(Math.random()),
          start: selectInfo.start,
          end: selectInfo.end,
          resourceId: selectInfo.resource.id
        };
        
        if (hasTimeConflict(newEvent, events)) {
          message.warning('该时间段已被占用');
          calendar.unselect();
          return;
        }
        
        // 计算点击位置(相对于日历容器)
        const position = {
          top: selectInfo.jsEvent.clientY - 50,
          left: selectInfo.jsEvent.clientX>1600? selectInfo.jsEvent.clientX-500:selectInfo.jsEvent.clientX-200
        };
      
        setModalPosition(position);
        setConfirmInfo(newEvent);
        setModalVisible(true);
      },
      eventClick: (info) => {
        setRecord({
          [MeetConfig.f_1744783951543]: dayjs(info.event.start).valueOf(),
          [MeetConfig.f_1744783955010]: dayjs(info.event.end).valueOf(),
          [MeetConfig.f_1745485256233]: info.event.title,
          [MeetConfig.f_1745485270386]: initialState?.currentUser?.name,
          id: info.event.id
        });
        setOpen(true);
      },
      dateClick: (selectInfo) => {
        console.log(selectInfo,22,'kkk');
        const calendarEl = calendarRef.current;
     if (dayjs(selectInfo?.date).valueOf()<dayjs().valueOf()) {
          message.warning('该时间段已禁用')
          return;
        }
        // 计算点击位置(相对于日历容器)
        const position = {
          top: selectInfo.jsEvent.clientY - 50,
          left: selectInfo.jsEvent.clientX>1600? selectInfo.jsEvent.clientX-500:selectInfo.jsEvent.clientX-200
        };
        const newEvent = {
          id: String(Math.random()),
          start: selectInfo.date,
          end:dayjs(selectInfo.date).add(30, 'minute'),
          resourceId: selectInfo.resource.id
        };
        setConfirmInfo(newEvent)
        setModalPosition(position);
        setModalVisible(true);
      },
    });

    calendar.render();
    calendarApis.current[index] = calendar;
  });

  return () => {
    calendarApis.current.forEach(api => api && api.destroy());
  };
}, [events, resources, currentWeek]);
  // 获取数据
  const getList = async () => {
    const res = await getFormData({
      constant: MeetConfig,
      filter_cond: { conditions: [
        ...condition,
      ] },
      page: 1,
      page_size: 999,
    })
    console.log("获取数据", res)
    if (res.ret === 0) {
      console.log(res?.msg?.data?.map((e)=>{
        return {
          ...e,
          id:e.id,
          resourceId: 'room',
          title: e?.[MeetConfig.f_1745485256233],
          start: new Date(e?.['f_1744783951543']),
          end: new Date(e?.['f_1744783955010']),
          backgroundColor: '#3788d8'
        }
      }),'123');
      setEvents(res?.msg?.data?.map((e)=>{
        return {
          ...e,
          id:e.id,
          resourceId: 'room',
          title: e?.[MeetConfig.f_1745485256233],
          start: new Date(e?.['f_1744783951543']),
          end: new Date(e?.['f_1744783955010']),
          backgroundColor: '#3788d8'
        }
      }))

 
    }
  }
  useEffect(() => {
    getList()
  }, [condition])
        //切换日期和筛选
    <ProForm
          title={null}
          layout="horizontal"
          onReset={() => {
            setCondition([])
          }}
          submitter={{
            render: (props, doms) => {
              return [

              ]
            },
          }}
          onFinish={(values) => {
            console.log(values,'123');
            let submitCondition = condition.filter((e)=>e.condition.field!==MeetConfig.f_1744783951543&&e.condition.field!==MeetConfig.f_1744783955010)
              .concat(
                {
                  condition: {
                    field: MeetConfig.f_1744783951543,
                    op: "between",
                    value:[dayjs( values['time']).startOf('day').valueOf(),dayjs( values['time']).endOf('day').valueOf()],
                  },
                  nextop: "and",
                },
              )
              .concat(
                {
                  condition: {
                    field: MeetConfig.f_1744783955010,
                    op: "between",
                    value:[dayjs( values['time']).startOf('day').valueOf(),dayjs( values['time']).endOf('day').valueOf()],
                  },
                  nextop: "and",
                },
              )
              .filter((item) => !isEmptyValues(item?.condition?.value))
            setPage(1)
            setCondition(submitCondition)
          }}
        >
          <ProFormGroup size={8}>
                    <ProFormDatePickerWeek
  name="time"
  fieldProps={{
    disabledDate: (current) => current && current < dayjs().startOf('day'),
    // 显式设置周一开始(兼容不同地区设置)
    value: filterTimeValue,
    onChange: (e) => {
      let date  

      if(e){
        date = e
      }else{
        date = dayjs()
      }

     
      // 统一按周一开始计算(ISO标准周)
      const weekStart = dayjs(date).startOf('week'); // 强制转为周一
      const weekEnd = weekStart.add(6, 'day').endOf('day');

      // 更新状态(保持同步)
      setCurrentWeek(dayjs(date).startOf('week'));
      setFilterTimeValue(date.startOf('week'));
      let submitCondition = condition.filter((item)=>item.condition.field!==MeetConfig.f_1744783951543&&item.condition.field!==MeetConfig.f_1744783955010)
      .concat(
        {
          condition: {
            field: MeetConfig.f_1744783951543,
            op: "between",
           value: [weekStart.valueOf(), weekEnd.valueOf()]
          },
          nextop: "and",
        },
      )
      .concat(
        {
          condition: {
            field: MeetConfig.f_1744783955010,
            op: "between",
           value: [weekStart.valueOf(), weekEnd.valueOf()]
          },
          nextop: "and",
        },
      )
      .filter((item2) => !isEmptyValues(item2?.condition?.value))
      setCondition(submitCondition)
    }
  }}
  allowClear
  // 清除时重置逻辑
  onClear={() => {
    const now = dayjs();
    setCurrentWeek(now.startOf('week').add(1, 'day'));
    setFilterTimeValue(now);
    setCondition([]);
  }}
/>
                    <Col>
      <Button.Group>
      <Button icon={<LeftOutlined />} onClick={prevWeek}   disabled={filterTimeValue<=dayjs()}/>
        <Button onClick={goToToday}>本周</Button>
        <Button icon={<RightOutlined />} onClick={nextWeek} />
      </Button.Group>
    </Col>
    <Col>
    </Col>
          </ProFormGroup>
        </ProForm>

  {getWeekDays().map((day, index) => (
        <div key={day.format('YYYY-MM-DD')}>
          <div  style={{marginBottom:2,marginTop:8,fontSize:14,fontWeight:700,display:'flex'}}> 
         <div style={{marginRight:10}}>{dayjs(day).format('YYYY-MM-DD')}</div>
          <div>{dayjs(day).format('dddd')=='Monday'?'周一':dayjs(day).format('dddd')=='Tuesday'?'周二':dayjs(day).format('dddd')=='Wednesday'?'周三':dayjs(day).format('dddd')=='Thursday'?'周四':dayjs(day).format('dddd')=='Friday'?'周五':dayjs(day).format('dddd')=='Saturday'?'周六':dayjs(day).format('dddd')=='Sunday'?'周日':dayjs(day).format('dddd')||'周日'}</div>
          </div>
          <div 
            ref={el => calendarRefs.current[index] = el}
            style={{ height: '500px' }}
          />
        </div>
      ))}

    {modalVisible && (
        <div 
        ref={modalRef}
          style={{
            position: 'absolute',
            top: `${modalPosition.top}px`,
            left: `${modalPosition.left}px`,
            zIndex: 1001,
            background: 'white',
            padding: '16px',
            borderRadius: '4px',
            boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
            minWidth: '300px'
          }}
        >
          {/* <h4>选择时间段</h4>
          <p>{clickedTime.date.toLocaleString()}</p>
          <p>资源: {clickedTime.resourceId || '无'}</p> */}
          <div style={{fontSize:20,fontWeight:700}}>会议室申请</div>
          <div style={{display:'flex',fontSize:16,fontWeight:500}}>
      <div style={{marginRight:10}}> {dayjs(confirmInfo?.start).format('YYYY-MM-DD')}</div>
      <div style={{marginRight:10}}> {dayjs(confirmInfo?.start).format('dddd')}</div>
      <div>{dayjs(confirmInfo?.start).format('HH:mm')+'-'+dayjs(confirmInfo?.end).format('HH:mm')}</div>
    </div>
          <div style={{ marginTop: '16px', display: 'flex', gap: '8px'}}>
            <Button 
              type="primary"
              style={{width:'100%'}}
              onClick={() => {
                // 创建新事件逻辑
              }}
            >
              预定
            </Button>
            {/* <Button onClick={() => setModalVisible(false)}>
              取消
            </Button> */}
          </div>
        </div>
      )}

如果想只创建一个实例代码,并且按照天筛选

//修改创建实例代码
  useEffect(() => {
    const calendar = new Calendar(calendarRef.current, {
     ....同上
  }, [events, resources,condition]);

      <div ref={calendarRef}  />


网站公告

今日签到

点亮在社区的每一天
去签到