Fullcalendar 日历 React+Ant Design项目

话不多说,直接附上效果图加源码!!!



 

日历展示效果图:

日历创建日程效果图:

日历创建日程选择事件颜色的效果图:

日历详情效果图:

源码部分:

/* eslint-disable @next/next/no-img-element */
import React, { useState, useEffect, useRef, useMemo } from 'react';
import  Calendar  from '@fullcalendar/react';
import interactionPlugin from '@fullcalendar/interaction';
import dayGridPlugin from '@fullcalendar/daygrid';
import "dayjs/locale/zh-cn";
import dayjs from 'dayjs';
import type { RadioChangeEvent } from 'antd';
import type {DatePickerProps, RangePickerProps} from 'antd/es/date-picker';
import zhCN from "antd/lib/locale/zh_CN";
import { Badge, Button, ColorPicker, ConfigProvider, DatePicker, Divider, Dropdown, Input, InputNumber, Modal, Radio, Select, Space } from 'antd';
import { DeleteOutlined, DownOutlined, EditOutlined } from '@ant-design/icons';
import'./calendar.scss'
interface CalendarType{
  title:string;//事件标签
  end:string;//开始时间
  start:string;//结束时间
  color?:string;//事件的颜色
  extendedProps?:CalendarObj;//事件备注对象
  allDay?:boolean;//事件备注详情
}
interface CalendarObj{
  wordsText: string;
  repetition:string;
}
export default function FullCalendarExample ({classify}:{classify:string}) {
  const { TextArea } = Input;//文本框
  const { RangePicker }=DatePicker//开始 结束时间
  const [calendarData, setCalendarData] = useState<CalendarType[]>([]);
const handleDateSelect = (start:string) => {
  if(start === ''  || repetition === "永不重复")return
  const events:CalendarType[] = [];//创建一个数组
  const currentDate = new Date(start);//将字符串转化一个时间格式
  // 在此处将事件数据添加到 FullCalendar 的事件源中
  //  每周特定一天的事件
  if(repetition === "每周重复"){
    for (let i = 0; i < 52; i++) {
      let weeklyEvent:any = {}
      if( i == 0) {
        weeklyEvent = {
          title: `第${words.toString()}话-${TagsEnum[wordsType]}`,
          start: start,
          end: start,
          color:bgColor,
          extendedProps :{
            wordsText,
            repetition,
          },
          allDay: true,
        };
      } else {
      currentDate.setDate(currentDate.getDate() + 7);//加七天
      const nextDay = currentDate.toISOString().slice(0, 10);//分钟转化时间
      weeklyEvent = {
        title: `第${words.toString()}话-${TagsEnum[wordsType]}`,
        start: nextDay,
        end: nextDay,
        color:bgColor,
        extendedProps :{
          wordsText,
          repetition,
        },
        allDay: true,
      };
    }
      events.push(weeklyEvent);
    }
  setCalendarData([...calendarData,...events])
  }
  // 每月特定日期的事件
  if(repetition === "每月重复"){
  currentDate.setDate(currentDate.getDate() + 1);//加一天
  const nextday = currentDate.toISOString().slice(0, 10);//分钟转化时间
  const dayOfMonth = new Date(nextday).getDate(); // 获取日期的号数
  const year = currentDate.getFullYear();//获取年限
    for (let month = 0; month < 12; month++) {
      let monthlyEvent:any={}
      const date = new Date(year, month, dayOfMonth);//将字符串转化一个时间格式
      const formattedDate = date.toISOString().split('T')[0];//获取年月日时间
      monthlyEvent = {
        title: `第${words.toString()}话-${TagsEnum[wordsType]}`,
        start: formattedDate,
        end: formattedDate,
        color:bgColor,
        extendedProps :{
          wordsText,
          repetition,
        },
        allDay: true,
      };
      events.push(monthlyEvent);
   }
   setCalendarData([...calendarData,...events])
  }
};

useEffect(() => {
    //初始化日历数据
  if(calendarData.length === 0){
    setCalendarData([
      { title: 'Event 1', start: '2024-01-01', end: '2024-01-05',color: 'red',extendedProps :{wordsText: 'Event 1 notes',repetition }},
      { title: 'Event 2', start: '2024-01-16',end: '2024-01-19', color: '#3694FF',extendedProps :{wordsText: 'Event 2 notes',repetition }}
    ])
  }
  }, [calendarData]);
  const [particulars,setParticulars]=useState(false)//详情弹窗开关
  const [details,setdetails]=useState<any>({})//保存点击详情数据
  // 点击事件详情弹窗
  const handleEventClick = (info: { event: any; }) => {
    // 处理事件点击逻辑
    // 在这里可以打开编辑事件的对话框或执行其他操作
    console.log('点击事件:', info.event);
    setdetails(info.event)
    setParticulars(true)
  };
  // 点击详情弹窗关闭
  const particularsCancel = () => {
    setParticulars(false)
  };
  // 点击详情弹窗确定
  const particularsOk = () => {
    setParticulars(false);
  };
  
  const [DateClick,setDateClick]=useState(false)//创建弹窗开关
  const [today,setToday]=useState('')//开始时间
  //点击日历日期创建事件
  const handleDateClick = (info: { dateStr: any; }) => {
    setDateClick(true)
    setToday(info.dateStr)
  };
  // 点击创建弹窗确定
  const handleOk = () => {
    setDateClick(false);
    if(TagsEnum[wordsType] && words){
      updata(today);
    }
  };
  // 提交数据同步日历事件
const updata=(date: any)=>{
    const newEvent = { 
      title:`第${words.toString()}话-${TagsEnum[wordsType]}`,
      start:date,
      end:wordsItem!='' ? wordsItem : date ,
      color:bgColor,
      extendedProps :{
        wordsText,
        repetition,
      }
    };
    console.log(newEvent);
    setCalendarData([...calendarData, newEvent]);
    setWords('')
    setWordsType('')
    setWordsItem('')
    handleDateSelect(date)

}
// 点击创建弹窗关闭
  const handleCancel = () => {
    setDateClick(false);
  };

const TagsEnum: any = {
    Outline: "简纲审批",
    Scenario: "剧本审批",
    Storyboard: "分镜审批",
    FineDraft: "精草审批",
    LineDraft: "线稿审批",
    ColourDraft: "色稿审批",
    CompleteDraft: "成稿审批",
    Settlement: "结算审批"
  };
  // 下拉菜单事件
  const [scheduleTpye,setScheduleTpye]=useState('')
  const itemsClick=(color:string,e:any)=>{
    // console.log(e.target.outerText,color);
    if(e.target.outerText == '')return
    setColor(color)
    setScheduleTpye(e.target.outerText)
    
  }
// 时间事件
const onChange = (
  value: DatePickerProps['value'] | RangePickerProps['value'],
  dateString: [string, string] | string,
) => {
  if(typeof(dateString)=="string"){
    setToday(dateString)
    setWordsItem(dateString)
  }else{
    const currentDate = new Date(dateString[1]);
    currentDate.setDate(currentDate.getDate() + 1);
    const nextDay = currentDate.toISOString().slice(0, 10);
    setToday(dateString[0])
    setWordsItem(nextDay)
    // console.log('Formatted Selected Time: ',typeof(dateString),nextDay);
  }
};
const [value, setValue] = useState(1);//同步单选
const [repetition, seTrepetition] = useState("永不重复");//重复单选
const [words,setWords]= useState('')//话数
const [wordsType,setWordsType]= useState('')//内容类型
const [wordsItem,setWordsItem]= useState('')//结束时间
const [color, setColor] = useState<any>('#1677ff');//颜色
const [wordsText, setWordsText] = useState('');//备注
//同步单选按钮
const RadioChange = (e: RadioChangeEvent) => {
  console.log('radio checked', e.target.value);
  setValue(e.target.value);
};
// 重复单选按钮
const repetitionChange = (e: RadioChangeEvent) => {
  console.log('radio checked', e.target.value);
  seTrepetition(e.target.value);
};
// 颜色函数
const bgColor = useMemo<string>(
  () => (typeof color === 'string' ? color : color!.toHexString()),
  [color],
);

  return (
    <div style={{marginTop:"24px",height:"770px"}}>
      <Calendar
        plugins={[interactionPlugin,dayGridPlugin]}
        height={770} //高度,可根据需求设置
        initialView="dayGridMonth"
        events={calendarData}//数据内容
        eventClick={handleEventClick}//详情点击事件
        editable={true}
        displayEventEnd={false}
        dateClick={handleDateClick}//日历点击事件
        locale="zh-cn" // 设置中文
        // 按钮显示中文
        buttonText={{
          today: '今天',
          dayGridMonth:'月',
          dayGridYear:'年'
        }}
        // 按钮显示位置
        headerToolbar={{
          start: 'dayGridMonth,dayGridYear', // 日历月份
          center: 'title',
          end: 'today,prev,next'
        }}
      />
      {/* 创建日程 */}
    {DateClick &&
    <Modal width={500} title="创建日程" style={{marginTop:"100px",height:"500px"}} open={DateClick} onOk={handleOk} onCancel={handleCancel}>
    <div>
      {/* 创建日程下拉菜单 */}
    <Space.Compact>
      <Dropdown  menu={{items: [
        { key: 'jack', label:<div onClick={(e)=>itemsClick('#3694FF',e)}><Badge color="#3694FF" text="项目排期" /></div>},
        { key: 'lucy', label: <div  onClick={(e)=>itemsClick(bgColor,e)}>
          <ColorPicker onChange={setColor} destroyTooltipOnHide={true} placement='bottomRight'>
          <Badge color={bgColor} text="自定义日程" />
         </ColorPicker>
        </div> },
      ]}}
      trigger={['click']}
      // destroyPopupOnHide={true}
      >
        <Button>
        <Space>
        <Badge color={bgColor} className='Badgetext'/>
        <DownOutlined />
        </Space>
      </Button>
      </Dropdown>
      {/* 创建日程话数 */}
      <InputNumber addonBefore="第" addonAfter="话" placeholder="*输入话数" style={{width:"185px"}} onChange={(e:any)=>{setWords(e)}}/>
    </Space.Compact>
    {/* 创建日程内容 */}
    <Select style={{width:"185px",marginLeft:18}} 
    onChange={(e)=>{setWordsType(e)}}
    placeholder="*请选择内容" 
    options={[
        {value: 'Outline', label: '简纲'},
        {value: 'Scenario', label: '剧本'},
        {value: 'Storyboard', label: '分镜'},
        {value: 'FineDraft', label: '精草'},
        {value: 'LineDraft', label: '线稿'},
        {value: 'ColourDraft', label: '色稿'},
        // {value: 'CompleteDraft', label: '成稿'},
      ]} />
      {/* 创建日程时间 */}
      <div style={{marginTop: 12, width: "100%"}}>
					<Space direction="vertical" size={12} style={{width: "99%"}}>
						<ConfigProvider locale={zhCN} >
            {(scheduleTpye === '项目排期' || scheduleTpye==='') && <
              DatePicker
								style={{width: "100%"}}
                placeholder="*请设置结束时间"
								format="YYYY-MM-DD"
								onChange={onChange}
							/>}
              {scheduleTpye==='自定义日程'&&<RangePicker
								style={{width: "100%"}}
								defaultValue={today
									? [dayjs(today, "YYYY-MM-DD"),
										dayjs(today, "YYYY-MM-DD")] : null}
								format="YYYY-MM-DD"
								onChange={onChange}
							/>}
						</ConfigProvider>
					</Space>
				</div>
        {/* 创建日程重复 */}
        <div style={{marginTop: 12}}>重复
        <Radio.Group onChange={repetitionChange} value={repetition} style={{marginLeft: "16px"}}>
            <Radio value={"永不重复"}>永不</Radio>
            <Radio value={"每周重复"}>每周</Radio>
            <Radio value={"每月重复"}>每月</Radio>
        </Radio.Group>
        </div>
        {/* 创建日程同步 */}
      <div style={{marginTop: 12}}>{classify=='calendar'?"同步日程至项目组":"同步日程至日历"}</div>
      {classify=='calendar'?<Radio.Group onChange={RadioChange} value={value} style={{marginTop: 12}}>
        <Space direction="vertical">
        <Radio value={1}>不同步(个人日程)</Radio>
        <Radio value={2}>同步日程</Radio>
        </Space>
      </Radio.Group> : 
      <Radio.Group onChange={RadioChange} value={value} style={{marginTop: 12}}>
        <Space direction="vertical">
        <Radio value={1}>不同步(本项目日程)</Radio>
        <Radio value={2}>同步至日历</Radio>
        </Space>
      </Radio.Group>}
      {classify=='calendar' && value == 2 &&<div style={{display:'inline-block',position:'relative',right:"55px",top:"30px"}}><Select
        style={{ width: 200 }}
        size={"small"}
        placeholder="*选择项目组"
        options={[
          {
            value: '1',
            label: 'Not Identified',
          },
        ]}
      /></div>}
    {/* 创建日程备注 */}
    <div style={{minHeight:"130px"}}>
    <TextArea style={{marginTop: 12,height:'100px',resize:'none'}} placeholder="备注" 
    onChange={(e)=>setWordsText(e.target.value)} 
    showCount maxLength={100}
    />
    </div>
    </div>
   </Modal>
     }
     {/* 详情弹窗 */}
     {particulars &&
      <Modal width={500} title="" style={{marginTop:"100px",height:"500px"}} open={particulars} footer={null} onOk={particularsOk} onCancel={particularsCancel}>
        <div>
           {/* 详情标题 */}
          <h4><Badge color={details?.borderColor} className='Badgetext' style={{marginRight:" 7px"}}/>{details?.title}</h4>
           {/* 详情时间 */}
          <div style={{color:"#858585",marginTop:5}}>
            <img src="/menu/calendar.svg" alt=""  style={{color:"#fff",marginRight:" 7px",width:"16px",display:"inline-block"}}/>
            {details?.startStr} {details?.endStr!=="" ? '- ' + details?.endStr : ''}
          </div>
          <div style={{color:"#A3E00F",marginTop:8}}>
          <img src="/menu/repetition.svg" alt=""  
				  style={{color:"#fff",width:"12px",marginRight:"7px",cursor: "pointer",display:"inline-block"}}/>
            {details?.extendedProps.repetition}
          </div>
           {/* 详情备注 */}
          <TextArea style={{marginTop: 12,height:'100px',resize:'none'}} 
          disabled 
          defaultValue={
            details?.extendedProps.wordsText !== '' ?
            details?.extendedProps.wordsText :
            '无备注'}
          />
          {/* 详情底部按钮 */}
        <div style={{display:"flex",justifyContent:"space-between",marginTop:12,borderTop:"1px solid rgba(5, 5, 5, 0.06)"}}>
          <div style={{textAlign:'center',width:"50%",height:"30px",lineHeight:"35px",cursor: "pointer"}} 
          onClick={()=>{console.log("编辑");
          }}><EditOutlined />编辑</div><Divider type="vertical" style={{height:"20px",marginTop:"5px"}}/>
          <div style={{textAlign:'center',width:"50%",height:"30px",lineHeight:"35px",cursor: "pointer"}}
          onClick={()=>{console.log("删除")}}
          ><DeleteOutlined />删除</div>
        </div>
        </div>
      </Modal>
     }
    </div>
  );
};


  • 15
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值