合并 HeroPanel 模块化重构及多个组件优化: - HeroPanel: 3000+ 行拆分为模块化子组件 (~219行) - ThemeCometChart/MarketOverviewBanner: 提取常量和子组件 - CompactSearchBox/TradingTimeFilter: 提取工具函数 - MainlineTimeline: 提取时间线子组件 - StockChangeIndicators: 修复 React Hooks 规则 冲突解决:保留重构后的精简版 HeroPanel.js 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
665 lines
20 KiB
TypeScript
665 lines
20 KiB
TypeScript
/**
|
||
* FullCalendarPro - 炫酷黑金主题日历组件
|
||
* 支持跨天事件条、动画效果、悬浮提示
|
||
* 使用 dayCellDidMount 钩子实现完整的单元格自定义内容
|
||
*/
|
||
|
||
import React, { useMemo, useRef, useCallback, useEffect } from "react";
|
||
import FullCalendar from "@fullcalendar/react";
|
||
import dayGridPlugin from "@fullcalendar/daygrid";
|
||
import interactionPlugin from "@fullcalendar/interaction";
|
||
import type {
|
||
EventInput,
|
||
EventClickArg,
|
||
DatesSetArg,
|
||
DayCellMountArg,
|
||
} from "@fullcalendar/core";
|
||
import { Box, Text, VStack, Tooltip } from "@chakra-ui/react";
|
||
import { keyframes } from "@emotion/react";
|
||
import dayjs from "dayjs";
|
||
|
||
// 动画定义
|
||
const shimmer = keyframes`
|
||
0% { background-position: -200% 0; }
|
||
100% { background-position: 200% 0; }
|
||
`;
|
||
|
||
const glow = keyframes`
|
||
0%, 100% { box-shadow: 0 0 5px rgba(212, 175, 55, 0.3); }
|
||
50% { box-shadow: 0 0 20px rgba(212, 175, 55, 0.6); }
|
||
`;
|
||
|
||
/**
|
||
* 事件数据接口
|
||
*/
|
||
export interface CalendarEventData {
|
||
date: string; // YYYYMMDD 格式
|
||
count: number; // 涨停数
|
||
topSector: string; // 最热概念
|
||
eventCount?: number; // 未来事件数
|
||
indexChange?: number; // 上证指数涨跌幅
|
||
}
|
||
|
||
/**
|
||
* FullCalendarPro Props
|
||
*/
|
||
export interface FullCalendarProProps {
|
||
/** 日历数据 */
|
||
data: CalendarEventData[];
|
||
/** 日期点击回调 */
|
||
onDateClick?: (date: Date, data?: CalendarEventData) => void;
|
||
/** 事件点击回调(点击跨天条) */
|
||
onEventClick?: (event: {
|
||
title: string;
|
||
start: Date;
|
||
end: Date;
|
||
dates: string[];
|
||
}) => void;
|
||
/** 月份变化回调 */
|
||
onMonthChange?: (year: number, month: number) => void;
|
||
/** 当前月份 */
|
||
currentMonth?: Date;
|
||
/** 高度 */
|
||
height?: string | number;
|
||
}
|
||
|
||
/**
|
||
* 概念颜色映射 - 为不同概念生成不同的渐变色
|
||
*/
|
||
const CONCEPT_COLORS: Record<
|
||
string,
|
||
{ bg: string; border: string; text: string }
|
||
> = {};
|
||
const COLOR_PALETTE = [
|
||
{
|
||
bg: "linear-gradient(135deg, #FFD700 0%, #FFA500 100%)",
|
||
border: "#FFD700",
|
||
text: "#1a1a2e",
|
||
}, // 金色 - 深色文字
|
||
{
|
||
bg: "linear-gradient(135deg, #00CED1 0%, #20B2AA 100%)",
|
||
border: "#00CED1",
|
||
text: "#1a1a2e",
|
||
}, // 青色 - 深色文字
|
||
{
|
||
bg: "linear-gradient(135deg, #FF6B6B 0%, #EE5A5A 100%)",
|
||
border: "#FF6B6B",
|
||
text: "#fff",
|
||
}, // 红色 - 白色文字
|
||
{
|
||
bg: "linear-gradient(135deg, #A855F7 0%, #9333EA 100%)",
|
||
border: "#A855F7",
|
||
text: "#fff",
|
||
}, // 紫色 - 白色文字
|
||
{
|
||
bg: "linear-gradient(135deg, #3B82F6 0%, #2563EB 100%)",
|
||
border: "#3B82F6",
|
||
text: "#fff",
|
||
}, // 蓝色 - 白色文字
|
||
{
|
||
bg: "linear-gradient(135deg, #10B981 0%, #059669 100%)",
|
||
border: "#10B981",
|
||
text: "#1a1a2e",
|
||
}, // 绿色 - 深色文字
|
||
{
|
||
bg: "linear-gradient(135deg, #F59E0B 0%, #D97706 100%)",
|
||
border: "#F59E0B",
|
||
text: "#1a1a2e",
|
||
}, // 橙色 - 深色文字
|
||
{
|
||
bg: "linear-gradient(135deg, #EC4899 0%, #DB2777 100%)",
|
||
border: "#EC4899",
|
||
text: "#fff",
|
||
}, // 粉色 - 白色文字
|
||
{
|
||
bg: "linear-gradient(135deg, #6366F1 0%, #4F46E5 100%)",
|
||
border: "#6366F1",
|
||
text: "#fff",
|
||
}, // 靛蓝 - 白色文字
|
||
{
|
||
bg: "linear-gradient(135deg, #14B8A6 0%, #0D9488 100%)",
|
||
border: "#14B8A6",
|
||
text: "#1a1a2e",
|
||
}, // 青绿 - 深色文字
|
||
];
|
||
|
||
let colorIndex = 0;
|
||
const getConceptColor = (concept: string) => {
|
||
if (!CONCEPT_COLORS[concept]) {
|
||
CONCEPT_COLORS[concept] = COLOR_PALETTE[colorIndex % COLOR_PALETTE.length];
|
||
colorIndex++;
|
||
}
|
||
return CONCEPT_COLORS[concept];
|
||
};
|
||
|
||
/**
|
||
* 将连续相同概念的日期合并成跨天事件
|
||
* 注意:在周六截断,避免跨周显示导致布局问题
|
||
*/
|
||
const mergeConsecutiveConcepts = (data: CalendarEventData[]): EventInput[] => {
|
||
if (!data.length) return [];
|
||
|
||
// 按日期排序
|
||
const sorted = [...data]
|
||
.filter((d) => d.topSector)
|
||
.sort((a, b) => a.date.localeCompare(b.date));
|
||
|
||
const events: EventInput[] = [];
|
||
let currentEvent: {
|
||
concept: string;
|
||
startDate: string;
|
||
endDate: string;
|
||
dates: string[];
|
||
totalCount: number;
|
||
} | null = null;
|
||
|
||
sorted.forEach((item, index) => {
|
||
const dateStr = item.date;
|
||
const concept = item.topSector;
|
||
|
||
// 检查是否与前一天连续且概念相同
|
||
const prevItem = sorted[index - 1];
|
||
const isConsecutive =
|
||
prevItem &&
|
||
concept === prevItem.topSector &&
|
||
isNextDay(prevItem.date, dateStr);
|
||
|
||
// 检查前一天是否是周六(day() === 6),如果是则需要截断,开始新事件
|
||
const prevDate = prevItem ? dayjs(prevItem.date, 'YYYYMMDD') : null;
|
||
const shouldBreakAtWeekend = prevDate && prevDate.day() === 6; // 周六
|
||
|
||
if (isConsecutive && currentEvent && !shouldBreakAtWeekend) {
|
||
// 延续当前事件
|
||
currentEvent.endDate = dateStr;
|
||
currentEvent.dates.push(dateStr);
|
||
currentEvent.totalCount += item.count;
|
||
} else {
|
||
// 保存之前的事件
|
||
if (currentEvent) {
|
||
events.push(createEventInput(currentEvent));
|
||
}
|
||
// 开始新事件
|
||
currentEvent = {
|
||
concept,
|
||
startDate: dateStr,
|
||
endDate: dateStr,
|
||
dates: [dateStr],
|
||
totalCount: item.count,
|
||
};
|
||
}
|
||
});
|
||
|
||
// 保存最后一个事件
|
||
if (currentEvent) {
|
||
events.push(createEventInput(currentEvent));
|
||
}
|
||
|
||
return events;
|
||
};
|
||
|
||
/**
|
||
* 检查两个日期是否连续(跳过周末)
|
||
*/
|
||
const isNextDay = (date1: string, date2: string): boolean => {
|
||
const d1 = dayjs(date1, "YYYYMMDD");
|
||
const d2 = dayjs(date2, "YYYYMMDD");
|
||
|
||
// 简单判断:相差1-3天内(考虑周末)
|
||
const diff = d2.diff(d1, "day");
|
||
if (diff === 1) return true;
|
||
if (diff === 2 && d1.day() === 5) return true; // 周五到周日
|
||
if (diff === 3 && d1.day() === 5) return true; // 周五到周一
|
||
return false;
|
||
};
|
||
|
||
/**
|
||
* 创建 FullCalendar 事件对象
|
||
*/
|
||
const createEventInput = (event: {
|
||
concept: string;
|
||
startDate: string;
|
||
endDate: string;
|
||
dates: string[];
|
||
totalCount: number;
|
||
}): EventInput => {
|
||
const color = getConceptColor(event.concept);
|
||
const startDate = dayjs(event.startDate, "YYYYMMDD");
|
||
const endDate = dayjs(event.endDate, "YYYYMMDD").add(1, "day"); // FullCalendar 的 end 是 exclusive
|
||
|
||
return {
|
||
id: `${event.concept}-${event.startDate}`,
|
||
title: event.concept,
|
||
start: startDate.format("YYYY-MM-DD"),
|
||
end: endDate.format("YYYY-MM-DD"),
|
||
backgroundColor: "transparent",
|
||
borderColor: "transparent",
|
||
textColor: color.text,
|
||
extendedProps: {
|
||
concept: event.concept,
|
||
dates: event.dates,
|
||
totalCount: event.totalCount,
|
||
daysCount: event.dates.length,
|
||
gradient: color.bg,
|
||
borderColor: color.border,
|
||
textColor: color.text,
|
||
},
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 创建自定义单元格内容的 HTML
|
||
*/
|
||
const createCellContentHTML = (
|
||
date: Date,
|
||
dateData: CalendarEventData | undefined,
|
||
isToday: boolean
|
||
): string => {
|
||
const dayNum = date.getDate();
|
||
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
|
||
const hasZtData = dateData && dateData.count > 0;
|
||
const hasEventCount = dateData?.eventCount && dateData.eventCount > 0;
|
||
const hasIndexChange =
|
||
dateData?.indexChange !== undefined && dateData?.indexChange !== null;
|
||
|
||
// 日期颜色
|
||
const dateColor = isToday ? "#FFD700" : isWeekend ? "#FB923C" : "#FFFFFF";
|
||
const dateFontWeight = isToday ? "bold" : "600";
|
||
|
||
// 上证涨跌幅
|
||
let indexChangeHTML = "";
|
||
if (hasIndexChange) {
|
||
const indexChange = dateData.indexChange!;
|
||
const indexColor = indexChange >= 0 ? "#EF4444" : "#22C55E";
|
||
const sign = indexChange >= 0 ? "+" : "";
|
||
indexChangeHTML = `<span style="font-size: 12px; font-weight: 700; color: ${indexColor};">${sign}${indexChange.toFixed(1)}%</span>`;
|
||
}
|
||
|
||
// 涨停数据(热度)
|
||
let ztDataHTML = "";
|
||
if (hasZtData) {
|
||
const ztColor = dateData.count >= 60 ? "#EF4444" : "#F59E0B";
|
||
ztDataHTML = `
|
||
<span style="display: inline-flex; align-items: center; gap: 2px;">
|
||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="${ztColor}" stroke-width="2">
|
||
<path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/>
|
||
</svg>
|
||
<span style="font-size: 12px; font-weight: bold; color: ${ztColor};">${dateData.count}</span>
|
||
</span>
|
||
`;
|
||
}
|
||
|
||
// 未来事件计数
|
||
let eventCountHTML = "";
|
||
if (hasEventCount) {
|
||
eventCountHTML = `
|
||
<span style="display: inline-flex; align-items: center; gap: 1px;">
|
||
<span style="font-size: 12px; font-weight: bold; color: #22C55E;">${dateData.eventCount}</span>
|
||
<span style="font-size: 10px; color: #22C55E;">事件</span>
|
||
</span>
|
||
`;
|
||
}
|
||
|
||
return `
|
||
<div class="fc-custom-cell-content" style="width: 100%; padding: 2px 4px;">
|
||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 3px;">
|
||
<span style="font-size: 13px; font-weight: ${dateFontWeight}; color: ${dateColor};">${dayNum}</span>
|
||
${indexChangeHTML}
|
||
</div>
|
||
<div style="display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 2px; height: 12px;">
|
||
${ztDataHTML}
|
||
${eventCountHTML}
|
||
</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
/**
|
||
* FullCalendarPro 组件
|
||
*/
|
||
export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
|
||
data,
|
||
onDateClick,
|
||
onEventClick,
|
||
onMonthChange,
|
||
currentMonth,
|
||
height = "auto",
|
||
}) => {
|
||
const calendarRef = useRef<FullCalendar>(null);
|
||
const dataMapRef = useRef<Map<string, CalendarEventData>>(new Map());
|
||
|
||
// 将数据转换为事件
|
||
const events = useMemo(() => mergeConsecutiveConcepts(data), [data]);
|
||
|
||
// 创建日期数据映射
|
||
const dataMap = useMemo(() => {
|
||
const map = new Map<string, CalendarEventData>();
|
||
data.forEach((d) => map.set(d.date, d));
|
||
return map;
|
||
}, [data]);
|
||
|
||
// 同步 dataMap 到 ref,供 dayCellDidMount 使用
|
||
useEffect(() => {
|
||
dataMapRef.current = dataMap;
|
||
}, [dataMap]);
|
||
|
||
// 当数据变化时,更新所有已挂载的单元格内容
|
||
useEffect(() => {
|
||
if (!calendarRef.current) return;
|
||
|
||
// 获取所有日期单元格并更新内容
|
||
const calendarEl = calendarRef.current.getApi().el;
|
||
const dayCells = calendarEl?.querySelectorAll(".fc-daygrid-day");
|
||
|
||
dayCells?.forEach((cell: Element) => {
|
||
const dateAttr = cell.getAttribute("data-date");
|
||
if (!dateAttr) return;
|
||
|
||
const date = new Date(dateAttr);
|
||
const dateStr = dayjs(date).format("YYYYMMDD");
|
||
const dateData = dataMapRef.current.get(dateStr);
|
||
const isToday = dayjs(date).isSame(dayjs(), "day");
|
||
|
||
// 找到 day-top 容器并更新内容
|
||
const dayTop = cell.querySelector(".fc-daygrid-day-top");
|
||
if (dayTop) {
|
||
dayTop.innerHTML = createCellContentHTML(date, dateData, isToday);
|
||
}
|
||
});
|
||
}, [dataMap]);
|
||
|
||
// 处理日期点击
|
||
const handleDateClick = useCallback(
|
||
(arg: { date: Date; dateStr: string }) => {
|
||
const dateStr = dayjs(arg.date).format("YYYYMMDD");
|
||
const dateData = dataMapRef.current.get(dateStr);
|
||
onDateClick?.(arg.date, dateData);
|
||
},
|
||
[onDateClick]
|
||
);
|
||
|
||
// 处理事件点击
|
||
const handleEventClick = useCallback(
|
||
(arg: EventClickArg) => {
|
||
const { extendedProps } = arg.event;
|
||
if (arg.event.start && arg.event.end) {
|
||
onEventClick?.({
|
||
title: arg.event.title,
|
||
start: arg.event.start,
|
||
end: arg.event.end,
|
||
dates: extendedProps.dates as string[],
|
||
});
|
||
}
|
||
},
|
||
[onEventClick]
|
||
);
|
||
|
||
// 处理月份变化
|
||
const handleDatesSet = useCallback(
|
||
(arg: DatesSetArg) => {
|
||
const visibleDate = arg.view.currentStart;
|
||
onMonthChange?.(visibleDate.getFullYear(), visibleDate.getMonth() + 1);
|
||
},
|
||
[onMonthChange]
|
||
);
|
||
|
||
// 单元格挂载时插入自定义内容
|
||
const handleDayCellDidMount = useCallback((arg: DayCellMountArg) => {
|
||
const { date, el, isToday } = arg;
|
||
const dateStr = dayjs(date).format("YYYYMMDD");
|
||
const dateData = dataMapRef.current.get(dateStr);
|
||
|
||
// 找到 day-top 容器并插入自定义内容
|
||
const dayTop = el.querySelector(".fc-daygrid-day-top");
|
||
if (dayTop) {
|
||
// 清空默认内容
|
||
dayTop.innerHTML = "";
|
||
// 插入自定义内容
|
||
dayTop.innerHTML = createCellContentHTML(date, dateData, isToday);
|
||
}
|
||
}, []);
|
||
|
||
// 自定义事件内容(跨天条)
|
||
const eventContent = useCallback(
|
||
(arg: {
|
||
event: { title: string; extendedProps: Record<string, unknown> };
|
||
}) => {
|
||
const { extendedProps } = arg.event;
|
||
const daysCount = extendedProps.daysCount as number;
|
||
const totalCount = extendedProps.totalCount as number;
|
||
const textColor = (extendedProps.textColor as string) || "#fff";
|
||
const gradient = extendedProps.gradient as string;
|
||
const borderColor = extendedProps.borderColor as string;
|
||
|
||
return (
|
||
<Tooltip
|
||
label={
|
||
<VStack spacing={1} align="start" p={1}>
|
||
<Text fontWeight="bold" color="white">{arg.event.title}</Text>
|
||
<Text fontSize="xs" color="white">连续 {daysCount} 天</Text>
|
||
<Text fontSize="xs" color="white">累计涨停 {totalCount} 家</Text>
|
||
</VStack>
|
||
}
|
||
placement="top"
|
||
hasArrow
|
||
bg="rgba(15, 15, 22, 0.95)"
|
||
border="1px solid rgba(212, 175, 55, 0.3)"
|
||
borderRadius="md"
|
||
>
|
||
<Box
|
||
w="100%"
|
||
h="18px"
|
||
bg={gradient}
|
||
borderRadius="md"
|
||
border={`1px solid ${borderColor}`}
|
||
display="flex"
|
||
alignItems="center"
|
||
justifyContent="center"
|
||
cursor="pointer"
|
||
transition="all 0.2s"
|
||
_hover={{
|
||
transform: "scale(1.02)",
|
||
boxShadow: `0 0 12px ${borderColor}`,
|
||
}}
|
||
overflow="hidden"
|
||
position="relative"
|
||
>
|
||
{/* 闪光效果 */}
|
||
<Box
|
||
position="absolute"
|
||
top="0"
|
||
left="0"
|
||
right="0"
|
||
bottom="0"
|
||
bgGradient="linear(to-r, transparent, rgba(255,255,255,0.3), transparent)"
|
||
backgroundSize="200% 100%"
|
||
animation={`${shimmer} 3s linear infinite`}
|
||
opacity={0.5}
|
||
/>
|
||
<Text
|
||
fontSize="xs"
|
||
fontWeight="bold"
|
||
color={textColor}
|
||
noOfLines={1}
|
||
px={2}
|
||
position="relative"
|
||
zIndex={1}
|
||
>
|
||
{arg.event.title}
|
||
{daysCount > 1 && (
|
||
<Text as="span" fontSize="2xs" ml={1} opacity={0.8}>
|
||
({daysCount}天)
|
||
</Text>
|
||
)}
|
||
</Text>
|
||
</Box>
|
||
</Tooltip>
|
||
);
|
||
},
|
||
[]
|
||
);
|
||
|
||
return (
|
||
<Box
|
||
height={height}
|
||
position="relative"
|
||
sx={{
|
||
// FullCalendar 深色主题样式
|
||
".fc": {
|
||
fontFamily: "inherit",
|
||
},
|
||
".fc-theme-standard": {
|
||
bg: "transparent",
|
||
},
|
||
".fc-theme-standard td, .fc-theme-standard th": {
|
||
borderColor: "rgba(212, 175, 55, 0.15)",
|
||
},
|
||
".fc-theme-standard .fc-scrollgrid": {
|
||
borderColor: "rgba(212, 175, 55, 0.2)",
|
||
},
|
||
// 工具栏
|
||
".fc-toolbar": {
|
||
marginBottom: "0.5em !important",
|
||
},
|
||
".fc-toolbar-title": {
|
||
fontSize: "0.95rem !important",
|
||
fontWeight: "bold !important",
|
||
background: "linear-gradient(135deg, #FFD700 0%, #F5E6A3 100%)",
|
||
backgroundClip: "text",
|
||
WebkitBackgroundClip: "text",
|
||
color: "transparent",
|
||
},
|
||
".fc-button": {
|
||
bg: "rgba(212, 175, 55, 0.2) !important",
|
||
border: "1px solid rgba(212, 175, 55, 0.4) !important",
|
||
color: "#FFD700 !important",
|
||
borderRadius: "6px !important",
|
||
padding: "4px 8px !important",
|
||
fontSize: "12px !important",
|
||
transition: "all 0.2s !important",
|
||
"&:hover": {
|
||
bg: "rgba(212, 175, 55, 0.3) !important",
|
||
transform: "scale(1.05)",
|
||
},
|
||
"&:disabled": {
|
||
opacity: 0.5,
|
||
},
|
||
},
|
||
".fc-button-active": {
|
||
bg: "rgba(212, 175, 55, 0.4) !important",
|
||
},
|
||
// 星期头
|
||
".fc-col-header": {
|
||
bg: "rgba(15, 15, 22, 0.95) !important",
|
||
},
|
||
".fc-col-header-cell": {
|
||
bg: "transparent !important",
|
||
py: "6px !important",
|
||
borderColor: "rgba(255, 215, 0, 0.1) !important",
|
||
},
|
||
".fc-col-header-cell-cushion": {
|
||
color: "white !important",
|
||
fontWeight: "600 !important",
|
||
fontSize: "12px",
|
||
},
|
||
".fc-scrollgrid-section-header": {
|
||
bg: "rgba(15, 15, 22, 0.95) !important",
|
||
},
|
||
".fc-scrollgrid-section-header > td": {
|
||
bg: "rgba(15, 15, 22, 0.95) !important",
|
||
borderColor: "rgba(255, 215, 0, 0.1) !important",
|
||
},
|
||
// 日期格子
|
||
".fc-daygrid-day": {
|
||
bg: "rgba(15, 15, 22, 0.4)",
|
||
transition: "all 0.2s",
|
||
cursor: "pointer",
|
||
"&:hover": {
|
||
bg: "rgba(212, 175, 55, 0.1)",
|
||
},
|
||
},
|
||
".fc-daygrid-day.fc-day-today": {
|
||
bg: "rgba(212, 175, 55, 0.15) !important",
|
||
animation: `${glow} 2s ease-in-out infinite`,
|
||
},
|
||
".fc-daygrid-day-frame": {
|
||
minHeight: "50px",
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
justifyContent: "space-between",
|
||
},
|
||
".fc-daygrid-day-top": {
|
||
width: "100%",
|
||
padding: "0 !important",
|
||
flexDirection: "column",
|
||
},
|
||
// 隐藏 FullCalendar 默认的日期数字链接
|
||
".fc-daygrid-day-top a.fc-daygrid-day-number": {
|
||
display: "none !important",
|
||
},
|
||
// 非当月日期
|
||
".fc-day-other": {
|
||
opacity: 0.4,
|
||
},
|
||
// 自定义单元格内容样式
|
||
".fc-custom-cell-content": {
|
||
width: "100%",
|
||
},
|
||
// 日期内容区域
|
||
".fc-daygrid-day-events": {
|
||
marginTop: "auto !important",
|
||
marginBottom: "0 !important",
|
||
paddingBottom: "0 !important",
|
||
},
|
||
// 事件
|
||
".fc-daygrid-event": {
|
||
borderRadius: "6px",
|
||
border: "none !important",
|
||
margin: "0 3px !important",
|
||
marginBottom: "0 !important",
|
||
},
|
||
".fc-event-main": {
|
||
padding: "0 !important",
|
||
},
|
||
".fc-daygrid-event-harness": {
|
||
marginTop: "2px",
|
||
},
|
||
// 更多事件链接
|
||
".fc-daygrid-more-link": {
|
||
color: "#FFD700 !important",
|
||
fontWeight: "600",
|
||
fontSize: "11px",
|
||
},
|
||
}}
|
||
>
|
||
<FullCalendar
|
||
ref={calendarRef}
|
||
plugins={[dayGridPlugin, interactionPlugin]}
|
||
initialView="dayGridMonth"
|
||
initialDate={currentMonth}
|
||
locale="zh-cn"
|
||
headerToolbar={{
|
||
left: "prev,next today",
|
||
center: "title",
|
||
right: "",
|
||
}}
|
||
buttonText={{
|
||
today: "今天",
|
||
}}
|
||
events={events}
|
||
dateClick={handleDateClick}
|
||
eventClick={handleEventClick}
|
||
datesSet={handleDatesSet}
|
||
dayCellDidMount={handleDayCellDidMount}
|
||
eventContent={eventContent}
|
||
dayMaxEvents={3}
|
||
moreLinkText={(n) => `+${n} 更多`}
|
||
fixedWeekCount={false}
|
||
height="auto"
|
||
contentHeight="auto"
|
||
/>
|
||
</Box>
|
||
);
|
||
};
|
||
|
||
export default FullCalendarPro;
|