From 401762dfba6f224b2b648fb84e78a54ba20f4f68 Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Wed, 7 Jan 2026 15:50:56 +0800 Subject: [PATCH] =?UTF-8?q?community=E5=A2=9E=E5=8A=A0=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Calendar/FullCalendarPro.tsx | 207 +++++++++++--------- 1 file changed, 117 insertions(+), 90 deletions(-) diff --git a/src/components/Calendar/FullCalendarPro.tsx b/src/components/Calendar/FullCalendarPro.tsx index 5ba33ea1..48eed01f 100644 --- a/src/components/Calendar/FullCalendarPro.tsx +++ b/src/components/Calendar/FullCalendarPro.tsx @@ -1,16 +1,16 @@ /** * FullCalendarPro - 炫酷黑金主题日历组件 * 支持跨天事件条、动画效果、悬浮提示 + * 使用 dayCellDidMount 钩子实现完整的单元格自定义内容 */ -import React, { useMemo, useRef, useCallback } from 'react'; +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 } from '@fullcalendar/core'; -import { Box, Text, HStack, VStack, Tooltip } from '@chakra-ui/react'; +import type { EventInput, EventClickArg, DatesSetArg, DayCellMountArg } from '@fullcalendar/core'; +import { Box, Text, VStack, Tooltip } from '@chakra-ui/react'; import { keyframes } from '@emotion/react'; -import { Flame } from 'lucide-react'; import dayjs from 'dayjs'; // 动画定义 @@ -170,11 +170,86 @@ const createEventInput = (event: { concept: string; startDate: string; endDate: daysCount: event.dates.length, gradient: color.bg, borderColor: color.border, - textColor: color.text, // 确保文字颜色传递到 extendedProps + 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 = `${sign}${indexChange.toFixed(2)}%`; + } + + // 涨停数据 + let ztDataHTML = ''; + if (hasZtData) { + const ztColor = dateData.count >= 60 ? '#EF4444' : '#F59E0B'; + ztDataHTML = ` +
+ + + + ${dateData.count} +
+ `; + } + + // 未来事件计数 + let eventCountHTML = ''; + if (hasEventCount) { + eventCountHTML = ` +
+
+ ${dateData.eventCount} +
+ 事件 +
+ `; + } + + return ` +
+
+ ${dayNum} + ${indexChangeHTML} +
+ ${ztDataHTML} + ${eventCountHTML} +
+ `; +}; + /** * FullCalendarPro 组件 */ @@ -187,6 +262,7 @@ export const FullCalendarPro: React.FC = ({ height = '600px', }) => { const calendarRef = useRef(null); + const dataMapRef = useRef>(new Map()); // 将数据转换为事件 const events = useMemo(() => mergeConsecutiveConcepts(data), [data]); @@ -198,12 +274,17 @@ export const FullCalendarPro: React.FC = ({ return map; }, [data]); + // 同步 dataMap 到 ref,供 dayCellDidMount 使用 + useEffect(() => { + dataMapRef.current = dataMap; + }, [dataMap]); + // 处理日期点击 const handleDateClick = useCallback((arg: { date: Date; dateStr: string }) => { const dateStr = dayjs(arg.date).format('YYYYMMDD'); - const dateData = dataMap.get(dateStr); + const dateData = dataMapRef.current.get(dateStr); onDateClick?.(arg.date, dateData); - }, [dataMap, onDateClick]); + }, [onDateClick]); // 处理事件点击 const handleEventClick = useCallback((arg: EventClickArg) => { @@ -224,77 +305,21 @@ export const FullCalendarPro: React.FC = ({ onMonthChange?.(visibleDate.getFullYear(), visibleDate.getMonth() + 1); }, [onMonthChange]); - // 自定义日期单元格内容 - 优化布局,增大字体 - const dayCellContent = useCallback((arg: { date: Date; dayNumberText: string; isToday: boolean }) => { - const dateStr = dayjs(arg.date).format('YYYYMMDD'); - const dateData = dataMap.get(dateStr); - const isWeekend = arg.date.getDay() === 0 || arg.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 handleDayCellDidMount = useCallback((arg: DayCellMountArg) => { + const { date, el, isToday } = arg; + const dateStr = dayjs(date).format('YYYYMMDD'); + const dateData = dataMapRef.current.get(dateStr); - return ( - - {/* 顶部:日期数字 + 上证涨跌幅 */} - - - {arg.date.getDate()} - - - {/* 上证指数涨跌幅 - 右上角显示 */} - {hasIndexChange && ( - = 0 ? '#EF4444' : '#22C55E'} - > - {dateData.indexChange! >= 0 ? '+' : ''}{dateData.indexChange!.toFixed(2)}% - - )} - - - {/* 中间区域:涨停数 + 事件数 */} - - {/* 涨停数据 */} - {hasZtData && ( - - = 60 ? '#EF4444' : '#F59E0B'} /> - = 60 ? '#EF4444' : '#F59E0B'}> - {dateData.count} - - - )} - - {/* 未来事件计数 */} - {hasEventCount && ( - - - - {dateData.eventCount} - - - - 事件 - - - )} - - - ); - }, [dataMap]); + // 找到 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 } }) => { @@ -371,6 +396,15 @@ export const FullCalendarPro: React.FC = ({ ); }, []); + // 当数据变化时,需要重新渲染日历 + useEffect(() => { + if (calendarRef.current) { + const api = calendarRef.current.getApi(); + // 触发重新渲染 + api.render(); + } + }, [data]); + return ( = ({ minHeight: '110px', }, '.fc-daygrid-day-top': { - // 保留容器,让 dayCellContent 正常显示 width: '100%', padding: '0 !important', - }, - // 隐藏默认的日期数字链接 - '.fc-daygrid-day-number': { - display: 'none !important', - }, - // dayCellContent 占满整个顶部区域 - '.fc-daygrid-day-top > a': { - display: 'none !important', // 隐藏默认链接 - }, - '.fc-daygrid-day-top > div': { - width: '100%', // 让自定义内容占满宽度 + flexDirection: 'column', }, // 非当月日期 '.fc-day-other': { opacity: 0.4, }, + // 自定义单元格内容样式 + '.fc-custom-cell-content': { + width: '100%', + }, // 日期内容区域 '.fc-daygrid-day-events': { marginTop: '0 !important', @@ -503,7 +530,7 @@ export const FullCalendarPro: React.FC = ({ dateClick={handleDateClick} eventClick={handleEventClick} datesSet={handleDatesSet} - dayCellContent={dayCellContent} + dayCellDidMount={handleDayCellDidMount} eventContent={eventContent} dayMaxEvents={3} moreLinkText={(n) => `+${n} 更多`}