话不多说,直接附上效果图加源码!!!
日历展示效果图:
日历创建日程效果图:
日历创建日程选择事件颜色的效果图:
日历详情效果图:
源码部分:
/* 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>
);
};