feat(calendar): 日历单元格改为两行布局

- 第一行:日期 + 上证涨跌
  - 第二行:热度(涨停数)+ 事件数
  - 周一至周日表头背景色修复为深色主题
  - 事件条高度固定为12px,防止不同行对齐问题
This commit is contained in:
zdl
2026-01-13 14:55:28 +08:00
parent b11c9e91d3
commit 098a88c5ba

View File

@@ -4,14 +4,19 @@
* 使用 dayCellDidMount 钩子实现完整的单元格自定义内容 * 使用 dayCellDidMount 钩子实现完整的单元格自定义内容
*/ */
import React, { useMemo, useRef, useCallback, useEffect } from 'react'; import React, { useMemo, useRef, useCallback, useEffect } from "react";
import FullCalendar from '@fullcalendar/react'; import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from '@fullcalendar/daygrid'; import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin from '@fullcalendar/interaction'; import interactionPlugin from "@fullcalendar/interaction";
import type { EventInput, EventClickArg, DatesSetArg, DayCellMountArg } from '@fullcalendar/core'; import type {
import { Box, Text, VStack, Tooltip } from '@chakra-ui/react'; EventInput,
import { keyframes } from '@emotion/react'; EventClickArg,
import dayjs from 'dayjs'; 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` const shimmer = keyframes`
@@ -44,7 +49,12 @@ export interface FullCalendarProProps {
/** 日期点击回调 */ /** 日期点击回调 */
onDateClick?: (date: Date, data?: CalendarEventData) => void; onDateClick?: (date: Date, data?: CalendarEventData) => void;
/** 事件点击回调(点击跨天条) */ /** 事件点击回调(点击跨天条) */
onEventClick?: (event: { title: string; start: Date; end: Date; dates: string[] }) => void; onEventClick?: (event: {
title: string;
start: Date;
end: Date;
dates: string[];
}) => void;
/** 月份变化回调 */ /** 月份变化回调 */
onMonthChange?: (year: number, month: number) => void; onMonthChange?: (year: number, month: number) => void;
/** 当前月份 */ /** 当前月份 */
@@ -56,18 +66,61 @@ export interface FullCalendarProProps {
/** /**
* 概念颜色映射 - 为不同概念生成不同的渐变色 * 概念颜色映射 - 为不同概念生成不同的渐变色
*/ */
const CONCEPT_COLORS: Record<string, { bg: string; border: string; text: string }> = {}; const CONCEPT_COLORS: Record<
string,
{ bg: string; border: string; text: string }
> = {};
const COLOR_PALETTE = [ 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, #FFD700 0%, #FFA500 100%)",
{ bg: 'linear-gradient(135deg, #FF6B6B 0%, #EE5A5A 100%)', border: '#FF6B6B', text: '#fff' }, // 红色 - 白色文字 border: "#FFD700",
{ bg: 'linear-gradient(135deg, #A855F7 0%, #9333EA 100%)', border: '#A855F7', text: '#fff' }, // 紫色 - 白色文字 text: "#1a1a2e",
{ 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, #00CED1 0%, #20B2AA 100%)",
{ bg: 'linear-gradient(135deg, #EC4899 0%, #DB2777 100%)', border: '#EC4899', text: '#fff' }, // 粉色 - 白色文字 border: "#00CED1",
{ bg: 'linear-gradient(135deg, #6366F1 0%, #4F46E5 100%)', border: '#6366F1', text: '#fff' }, // 靛蓝 - 白色文字 text: "#1a1a2e",
{ bg: 'linear-gradient(135deg, #14B8A6 0%, #0D9488 100%)', border: '#14B8A6', 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; let colorIndex = 0;
@@ -87,11 +140,17 @@ const mergeConsecutiveConcepts = (data: CalendarEventData[]): EventInput[] => {
// 按日期排序 // 按日期排序
const sorted = [...data] const sorted = [...data]
.filter(d => d.topSector) .filter((d) => d.topSector)
.sort((a, b) => a.date.localeCompare(b.date)); .sort((a, b) => a.date.localeCompare(b.date));
const events: EventInput[] = []; const events: EventInput[] = [];
let currentEvent: { concept: string; startDate: string; endDate: string; dates: string[]; totalCount: number } | null = null; let currentEvent: {
concept: string;
startDate: string;
endDate: string;
dates: string[];
totalCount: number;
} | null = null;
sorted.forEach((item, index) => { sorted.forEach((item, index) => {
const dateStr = item.date; const dateStr = item.date;
@@ -99,7 +158,8 @@ const mergeConsecutiveConcepts = (data: CalendarEventData[]): EventInput[] => {
// 检查是否与前一天连续且概念相同 // 检查是否与前一天连续且概念相同
const prevItem = sorted[index - 1]; const prevItem = sorted[index - 1];
const isConsecutive = prevItem && const isConsecutive =
prevItem &&
concept === prevItem.topSector && concept === prevItem.topSector &&
isNextDay(prevItem.date, dateStr); isNextDay(prevItem.date, dateStr);
@@ -136,11 +196,11 @@ const mergeConsecutiveConcepts = (data: CalendarEventData[]): EventInput[] => {
* 检查两个日期是否连续(跳过周末) * 检查两个日期是否连续(跳过周末)
*/ */
const isNextDay = (date1: string, date2: string): boolean => { const isNextDay = (date1: string, date2: string): boolean => {
const d1 = dayjs(date1, 'YYYYMMDD'); const d1 = dayjs(date1, "YYYYMMDD");
const d2 = dayjs(date2, 'YYYYMMDD'); const d2 = dayjs(date2, "YYYYMMDD");
// 简单判断相差1-3天内考虑周末 // 简单判断相差1-3天内考虑周末
const diff = d2.diff(d1, 'day'); const diff = d2.diff(d1, "day");
if (diff === 1) return true; if (diff === 1) return true;
if (diff === 2 && d1.day() === 5) return true; // 周五到周日 if (diff === 2 && d1.day() === 5) return true; // 周五到周日
if (diff === 3 && d1.day() === 5) return true; // 周五到周一 if (diff === 3 && d1.day() === 5) return true; // 周五到周一
@@ -150,18 +210,24 @@ const isNextDay = (date1: string, date2: string): boolean => {
/** /**
* 创建 FullCalendar 事件对象 * 创建 FullCalendar 事件对象
*/ */
const createEventInput = (event: { concept: string; startDate: string; endDate: string; dates: string[]; totalCount: number }): EventInput => { const createEventInput = (event: {
concept: string;
startDate: string;
endDate: string;
dates: string[];
totalCount: number;
}): EventInput => {
const color = getConceptColor(event.concept); const color = getConceptColor(event.concept);
const startDate = dayjs(event.startDate, 'YYYYMMDD'); const startDate = dayjs(event.startDate, "YYYYMMDD");
const endDate = dayjs(event.endDate, 'YYYYMMDD').add(1, 'day'); // FullCalendar 的 end 是 exclusive const endDate = dayjs(event.endDate, "YYYYMMDD").add(1, "day"); // FullCalendar 的 end 是 exclusive
return { return {
id: `${event.concept}-${event.startDate}`, id: `${event.concept}-${event.startDate}`,
title: event.concept, title: event.concept,
start: startDate.format('YYYY-MM-DD'), start: startDate.format("YYYY-MM-DD"),
end: endDate.format('YYYY-MM-DD'), end: endDate.format("YYYY-MM-DD"),
backgroundColor: 'transparent', backgroundColor: "transparent",
borderColor: 'transparent', borderColor: "transparent",
textColor: color.text, textColor: color.text,
extendedProps: { extendedProps: {
concept: event.concept, concept: event.concept,
@@ -187,66 +253,58 @@ const createCellContentHTML = (
const isWeekend = date.getDay() === 0 || date.getDay() === 6; const isWeekend = date.getDay() === 0 || date.getDay() === 6;
const hasZtData = dateData && dateData.count > 0; const hasZtData = dateData && dateData.count > 0;
const hasEventCount = dateData?.eventCount && dateData.eventCount > 0; const hasEventCount = dateData?.eventCount && dateData.eventCount > 0;
const hasIndexChange = dateData?.indexChange !== undefined && dateData?.indexChange !== null; const hasIndexChange =
dateData?.indexChange !== undefined && dateData?.indexChange !== null;
// 日期颜色 // 日期颜色
const dateColor = isToday ? '#FFD700' : isWeekend ? '#FB923C' : '#FFFFFF'; const dateColor = isToday ? "#FFD700" : isWeekend ? "#FB923C" : "#FFFFFF";
const dateFontWeight = isToday ? 'bold' : '600'; const dateFontWeight = isToday ? "bold" : "600";
// 上证涨跌幅 // 上证涨跌幅
let indexChangeHTML = ''; let indexChangeHTML = "";
if (hasIndexChange) { if (hasIndexChange) {
const indexChange = dateData.indexChange!; const indexChange = dateData.indexChange!;
const indexColor = indexChange >= 0 ? '#EF4444' : '#22C55E'; const indexColor = indexChange >= 0 ? "#EF4444" : "#22C55E";
const sign = indexChange >= 0 ? '+' : ''; const sign = indexChange >= 0 ? "+" : "";
indexChangeHTML = `<span style="font-size: 13px; font-weight: 700; color: ${indexColor};">${sign}${indexChange.toFixed(2)}%</span>`; indexChangeHTML = `<span style="font-size: 12px; font-weight: 700; color: ${indexColor};">${sign}${indexChange.toFixed(1)}%</span>`;
} }
// 涨停数据 // 涨停数据(热度)
let ztDataHTML = ''; let ztDataHTML = "";
if (hasZtData) { if (hasZtData) {
const ztColor = dateData.count >= 60 ? '#EF4444' : '#F59E0B'; const ztColor = dateData.count >= 60 ? "#EF4444" : "#F59E0B";
ztDataHTML = ` ztDataHTML = `
<div style="display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 4px;"> <span style="display: inline-flex; align-items: center; gap: 2px;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="${ztColor}" stroke-width="2"> <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"/> <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> </svg>
<span style="font-size: 15px; font-weight: bold; color: ${ztColor};">${dateData.count}</span> <span style="font-size: 12px; font-weight: bold; color: ${ztColor};">${dateData.count}</span>
</div> </span>
`; `;
} }
// 未来事件计数 // 未来事件计数
let eventCountHTML = ''; let eventCountHTML = "";
if (hasEventCount) { if (hasEventCount) {
eventCountHTML = ` eventCountHTML = `
<div style="display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 4px;"> <span style="display: inline-flex; align-items: center; gap: 1px;">
<div style=" <span style="font-size: 12px; font-weight: bold; color: #22C55E;">${dateData.eventCount}</span>
width: 18px; <span style="font-size: 10px; color: #22C55E;">事件</span>
height: 18px; </span>
border-radius: 50%;
background: linear-gradient(135deg, #22C55E 0%, #16A34A 100%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 6px rgba(34, 197, 94, 0.4);
">
<span style="font-size: 11px; font-weight: bold; color: white;">${dateData.eventCount}</span>
</div>
<span style="font-size: 13px; color: #22C55E; font-weight: 600;">事件</span>
</div>
`; `;
} }
return ` return `
<div class="fc-custom-cell-content" style="width: 100%; padding: 4px;"> <div class="fc-custom-cell-content" style="width: 100%; padding: 2px 4px;">
<div style="display: flex; justify-content: space-between; align-items: flex-start;"> <div style="display: flex; align-items: center; justify-content: space-between; gap: 3px;">
<span style="font-size: 18px; font-weight: ${dateFontWeight}; color: ${dateColor};">${dayNum}</span> <span style="font-size: 13px; font-weight: ${dateFontWeight}; color: ${dateColor};">${dayNum}</span>
${indexChangeHTML} ${indexChangeHTML}
</div> </div>
<div style="display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 2px; height: 12px;">
${ztDataHTML} ${ztDataHTML}
${eventCountHTML} ${eventCountHTML}
</div> </div>
</div>
`; `;
}; };
@@ -259,7 +317,7 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
onEventClick, onEventClick,
onMonthChange, onMonthChange,
currentMonth, currentMonth,
height = '600px', height = "auto",
}) => { }) => {
const calendarRef = useRef<FullCalendar>(null); const calendarRef = useRef<FullCalendar>(null);
const dataMapRef = useRef<Map<string, CalendarEventData>>(new Map()); const dataMapRef = useRef<Map<string, CalendarEventData>>(new Map());
@@ -270,7 +328,7 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
// 创建日期数据映射 // 创建日期数据映射
const dataMap = useMemo(() => { const dataMap = useMemo(() => {
const map = new Map<string, CalendarEventData>(); const map = new Map<string, CalendarEventData>();
data.forEach(d => map.set(d.date, d)); data.forEach((d) => map.set(d.date, d));
return map; return map;
}, [data]); }, [data]);
@@ -285,19 +343,19 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
// 获取所有日期单元格并更新内容 // 获取所有日期单元格并更新内容
const calendarEl = calendarRef.current.getApi().el; const calendarEl = calendarRef.current.getApi().el;
const dayCells = calendarEl?.querySelectorAll('.fc-daygrid-day'); const dayCells = calendarEl?.querySelectorAll(".fc-daygrid-day");
dayCells?.forEach((cell: Element) => { dayCells?.forEach((cell: Element) => {
const dateAttr = cell.getAttribute('data-date'); const dateAttr = cell.getAttribute("data-date");
if (!dateAttr) return; if (!dateAttr) return;
const date = new Date(dateAttr); const date = new Date(dateAttr);
const dateStr = dayjs(date).format('YYYYMMDD'); const dateStr = dayjs(date).format("YYYYMMDD");
const dateData = dataMapRef.current.get(dateStr); const dateData = dataMapRef.current.get(dateStr);
const isToday = dayjs(date).isSame(dayjs(), 'day'); const isToday = dayjs(date).isSame(dayjs(), "day");
// 找到 day-top 容器并更新内容 // 找到 day-top 容器并更新内容
const dayTop = cell.querySelector('.fc-daygrid-day-top'); const dayTop = cell.querySelector(".fc-daygrid-day-top");
if (dayTop) { if (dayTop) {
dayTop.innerHTML = createCellContentHTML(date, dateData, isToday); dayTop.innerHTML = createCellContentHTML(date, dateData, isToday);
} }
@@ -305,14 +363,18 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
}, [dataMap]); }, [dataMap]);
// 处理日期点击 // 处理日期点击
const handleDateClick = useCallback((arg: { date: Date; dateStr: string }) => { const handleDateClick = useCallback(
const dateStr = dayjs(arg.date).format('YYYYMMDD'); (arg: { date: Date; dateStr: string }) => {
const dateStr = dayjs(arg.date).format("YYYYMMDD");
const dateData = dataMapRef.current.get(dateStr); const dateData = dataMapRef.current.get(dateStr);
onDateClick?.(arg.date, dateData); onDateClick?.(arg.date, dateData);
}, [onDateClick]); },
[onDateClick]
);
// 处理事件点击 // 处理事件点击
const handleEventClick = useCallback((arg: EventClickArg) => { const handleEventClick = useCallback(
(arg: EventClickArg) => {
const { extendedProps } = arg.event; const { extendedProps } = arg.event;
if (arg.event.start && arg.event.end) { if (arg.event.start && arg.event.end) {
onEventClick?.({ onEventClick?.({
@@ -322,36 +384,44 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
dates: extendedProps.dates as string[], dates: extendedProps.dates as string[],
}); });
} }
}, [onEventClick]); },
[onEventClick]
);
// 处理月份变化 // 处理月份变化
const handleDatesSet = useCallback((arg: DatesSetArg) => { const handleDatesSet = useCallback(
(arg: DatesSetArg) => {
const visibleDate = arg.view.currentStart; const visibleDate = arg.view.currentStart;
onMonthChange?.(visibleDate.getFullYear(), visibleDate.getMonth() + 1); onMonthChange?.(visibleDate.getFullYear(), visibleDate.getMonth() + 1);
}, [onMonthChange]); },
[onMonthChange]
);
// 单元格挂载时插入自定义内容 // 单元格挂载时插入自定义内容
const handleDayCellDidMount = useCallback((arg: DayCellMountArg) => { const handleDayCellDidMount = useCallback((arg: DayCellMountArg) => {
const { date, el, isToday } = arg; const { date, el, isToday } = arg;
const dateStr = dayjs(date).format('YYYYMMDD'); const dateStr = dayjs(date).format("YYYYMMDD");
const dateData = dataMapRef.current.get(dateStr); const dateData = dataMapRef.current.get(dateStr);
// 找到 day-top 容器并插入自定义内容 // 找到 day-top 容器并插入自定义内容
const dayTop = el.querySelector('.fc-daygrid-day-top'); const dayTop = el.querySelector(".fc-daygrid-day-top");
if (dayTop) { if (dayTop) {
// 清空默认内容 // 清空默认内容
dayTop.innerHTML = ''; dayTop.innerHTML = "";
// 插入自定义内容 // 插入自定义内容
dayTop.innerHTML = createCellContentHTML(date, dateData, isToday); dayTop.innerHTML = createCellContentHTML(date, dateData, isToday);
} }
}, []); }, []);
// 自定义事件内容(跨天条) // 自定义事件内容(跨天条)
const eventContent = useCallback((arg: { event: { title: string; extendedProps: Record<string, unknown> } }) => { const eventContent = useCallback(
(arg: {
event: { title: string; extendedProps: Record<string, unknown> };
}) => {
const { extendedProps } = arg.event; const { extendedProps } = arg.event;
const daysCount = extendedProps.daysCount as number; const daysCount = extendedProps.daysCount as number;
const totalCount = extendedProps.totalCount as number; const totalCount = extendedProps.totalCount as number;
const textColor = (extendedProps.textColor as string) || '#fff'; const textColor = (extendedProps.textColor as string) || "#fff";
const gradient = extendedProps.gradient as string; const gradient = extendedProps.gradient as string;
const borderColor = extendedProps.borderColor as string; const borderColor = extendedProps.borderColor as string;
@@ -359,9 +429,9 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
<Tooltip <Tooltip
label={ label={
<VStack spacing={1} align="start" p={1}> <VStack spacing={1} align="start" p={1}>
<Text fontWeight="bold">{arg.event.title}</Text> <Text fontWeight="bold" color="white">{arg.event.title}</Text>
<Text fontSize="xs"> {daysCount} </Text> <Text fontSize="xs" color="white"> {daysCount} </Text>
<Text fontSize="xs"> {totalCount} </Text> <Text fontSize="xs" color="white"> {totalCount} </Text>
</VStack> </VStack>
} }
placement="top" placement="top"
@@ -372,9 +442,9 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
> >
<Box <Box
w="100%" w="100%"
h="26px" h="18px"
bg={gradient} bg={gradient}
borderRadius="lg" borderRadius="md"
border={`1px solid ${borderColor}`} border={`1px solid ${borderColor}`}
display="flex" display="flex"
alignItems="center" alignItems="center"
@@ -382,7 +452,7 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
cursor="pointer" cursor="pointer"
transition="all 0.2s" transition="all 0.2s"
_hover={{ _hover={{
transform: 'scale(1.02)', transform: "scale(1.02)",
boxShadow: `0 0 12px ${borderColor}`, boxShadow: `0 0 12px ${borderColor}`,
}} }}
overflow="hidden" overflow="hidden"
@@ -401,17 +471,17 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
opacity={0.5} opacity={0.5}
/> />
<Text <Text
fontSize="sm" fontSize="xs"
fontWeight="bold" fontWeight="bold"
color={textColor} color={textColor}
noOfLines={1} noOfLines={1}
px={3} px={2}
position="relative" position="relative"
zIndex={1} zIndex={1}
> >
{arg.event.title} {arg.event.title}
{daysCount > 1 && ( {daysCount > 1 && (
<Text as="span" fontSize="xs" ml={1} opacity={0.8}> <Text as="span" fontSize="2xs" ml={1} opacity={0.8}>
({daysCount}) ({daysCount})
</Text> </Text>
)} )}
@@ -419,7 +489,9 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
</Box> </Box>
</Tooltip> </Tooltip>
); );
}, []); },
[]
);
return ( return (
<Box <Box
@@ -427,108 +499,130 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
position="relative" position="relative"
sx={{ sx={{
// FullCalendar 深色主题样式 // FullCalendar 深色主题样式
'.fc': { ".fc": {
fontFamily: 'inherit', fontFamily: "inherit",
}, },
'.fc-theme-standard': { ".fc-theme-standard": {
bg: 'transparent', bg: "transparent",
}, },
'.fc-theme-standard td, .fc-theme-standard th': { ".fc-theme-standard td, .fc-theme-standard th": {
borderColor: 'rgba(212, 175, 55, 0.15)', borderColor: "rgba(212, 175, 55, 0.15)",
}, },
'.fc-theme-standard .fc-scrollgrid': { ".fc-theme-standard .fc-scrollgrid": {
borderColor: 'rgba(212, 175, 55, 0.2)', borderColor: "rgba(212, 175, 55, 0.2)",
}, },
// 工具栏 // 工具栏
'.fc-toolbar-title': { ".fc-toolbar": {
fontSize: '1.5rem !important', marginBottom: "0.5em !important",
fontWeight: 'bold !important',
background: 'linear-gradient(135deg, #FFD700 0%, #F5E6A3 100%)',
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
color: 'transparent',
}, },
'.fc-button': { ".fc-toolbar-title": {
bg: 'rgba(212, 175, 55, 0.2) !important', fontSize: "0.95rem !important",
border: '1px solid rgba(212, 175, 55, 0.4) !important', fontWeight: "bold !important",
color: '#FFD700 !important', background: "linear-gradient(135deg, #FFD700 0%, #F5E6A3 100%)",
borderRadius: '8px !important', backgroundClip: "text",
transition: 'all 0.2s !important', WebkitBackgroundClip: "text",
'&:hover': { color: "transparent",
bg: 'rgba(212, 175, 55, 0.3) !important',
transform: 'scale(1.05)',
}, },
'&:disabled': { ".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, opacity: 0.5,
}, },
}, },
'.fc-button-active': { ".fc-button-active": {
bg: 'rgba(212, 175, 55, 0.4) !important', bg: "rgba(212, 175, 55, 0.4) !important",
}, },
// 星期头 // 星期头
'.fc-col-header-cell': { ".fc-col-header": {
bg: 'rgba(212, 175, 55, 0.1)', bg: "rgba(15, 15, 22, 0.95) !important",
py: '12px !important',
}, },
'.fc-col-header-cell-cushion': { ".fc-col-header-cell": {
color: '#FFD700 !important', bg: "transparent !important",
fontWeight: '600 !important', py: "6px !important",
fontSize: '14px', 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': { ".fc-daygrid-day": {
bg: 'rgba(15, 15, 22, 0.4)', bg: "rgba(15, 15, 22, 0.4)",
transition: 'all 0.2s', transition: "all 0.2s",
cursor: 'pointer', cursor: "pointer",
'&:hover': { "&:hover": {
bg: 'rgba(212, 175, 55, 0.1)', bg: "rgba(212, 175, 55, 0.1)",
}, },
}, },
'.fc-daygrid-day.fc-day-today': { ".fc-daygrid-day.fc-day-today": {
bg: 'rgba(212, 175, 55, 0.15) !important', bg: "rgba(212, 175, 55, 0.15) !important",
animation: `${glow} 2s ease-in-out infinite`, animation: `${glow} 2s ease-in-out infinite`,
}, },
'.fc-daygrid-day-frame': { ".fc-daygrid-day-frame": {
minHeight: '110px', minHeight: "50px",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
}, },
'.fc-daygrid-day-top': { ".fc-daygrid-day-top": {
width: '100%', width: "100%",
padding: '0 !important', padding: "0 !important",
flexDirection: 'column', flexDirection: "column",
}, },
// 隐藏 FullCalendar 默认的日期数字链接 // 隐藏 FullCalendar 默认的日期数字链接
'.fc-daygrid-day-top a.fc-daygrid-day-number': { ".fc-daygrid-day-top a.fc-daygrid-day-number": {
display: 'none !important', display: "none !important",
}, },
// 非当月日期 // 非当月日期
'.fc-day-other': { ".fc-day-other": {
opacity: 0.4, opacity: 0.4,
}, },
// 自定义单元格内容样式 // 自定义单元格内容样式
'.fc-custom-cell-content': { ".fc-custom-cell-content": {
width: '100%', width: "100%",
}, },
// 日期内容区域 // 日期内容区域
'.fc-daygrid-day-events': { ".fc-daygrid-day-events": {
marginTop: '0 !important', marginTop: "auto !important",
marginBottom: "0 !important",
paddingBottom: "0 !important",
}, },
// 事件 // 事件
'.fc-daygrid-event': { ".fc-daygrid-event": {
borderRadius: '8px', borderRadius: "6px",
border: 'none !important', border: "none !important",
margin: '3px 4px !important', margin: "0 3px !important",
marginBottom: "0 !important",
}, },
'.fc-event-main': { ".fc-event-main": {
padding: '0 !important', padding: "0 !important",
}, },
'.fc-daygrid-event-harness': { ".fc-daygrid-event-harness": {
marginTop: '3px', marginTop: "2px",
}, },
// 更多事件链接 // 更多事件链接
'.fc-daygrid-more-link': { ".fc-daygrid-more-link": {
color: '#FFD700 !important', color: "#FFD700 !important",
fontWeight: '600', fontWeight: "600",
fontSize: '11px', fontSize: "11px",
}, },
}} }}
> >
@@ -539,12 +633,12 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
initialDate={currentMonth} initialDate={currentMonth}
locale="zh-cn" locale="zh-cn"
headerToolbar={{ headerToolbar={{
left: 'prev,next today', left: "prev,next today",
center: 'title', center: "title",
right: '', right: "",
}} }}
buttonText={{ buttonText={{
today: '今天', today: "今天",
}} }}
events={events} events={events}
dateClick={handleDateClick} dateClick={handleDateClick}
@@ -555,7 +649,8 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
dayMaxEvents={3} dayMaxEvents={3}
moreLinkText={(n) => `+${n} 更多`} moreLinkText={(n) => `+${n} 更多`}
fixedWeekCount={false} fixedWeekCount={false}
height="100%" height="auto"
contentHeight="auto"
/> />
</Box> </Box>
); );