community增加事件详情

This commit is contained in:
2026-01-07 15:50:56 +08:00
parent 56c8de352d
commit 401762dfba

View File

@@ -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 = `<span style="font-size: 13px; font-weight: 700; color: ${indexColor};">${sign}${indexChange.toFixed(2)}%</span>`;
}
// 涨停数据
let ztDataHTML = '';
if (hasZtData) {
const ztColor = dateData.count >= 60 ? '#EF4444' : '#F59E0B';
ztDataHTML = `
<div style="display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 4px;">
<svg width="16" height="16" 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: 15px; font-weight: bold; color: ${ztColor};">${dateData.count}</span>
</div>
`;
}
// 未来事件计数
let eventCountHTML = '';
if (hasEventCount) {
eventCountHTML = `
<div style="display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 4px;">
<div style="
width: 18px;
height: 18px;
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 `
<div class="fc-custom-cell-content" style="width: 100%; padding: 4px;">
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
<span style="font-size: 18px; font-weight: ${dateFontWeight}; color: ${dateColor};">${dayNum}</span>
${indexChangeHTML}
</div>
${ztDataHTML}
${eventCountHTML}
</div>
`;
};
/**
* FullCalendarPro 组件
*/
@@ -187,6 +262,7 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
height = '600px',
}) => {
const calendarRef = useRef<FullCalendar>(null);
const dataMapRef = useRef<Map<string, CalendarEventData>>(new Map());
// 将数据转换为事件
const events = useMemo(() => mergeConsecutiveConcepts(data), [data]);
@@ -198,12 +274,17 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
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<FullCalendarProProps> = ({
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 (
<Box position="relative" w="100%" h="100%" py={1}>
{/* 顶部:日期数字 + 上证涨跌幅 */}
<HStack justify="space-between" align="flex-start" px={1}>
<Text
fontSize="xl"
fontWeight={arg.isToday ? 'bold' : '600'}
color={arg.isToday ? '#FFD700' : isWeekend ? 'orange.300' : 'white'}
>
{arg.date.getDate()}
</Text>
{/* 上证指数涨跌幅 - 右上角显示 */}
{hasIndexChange && (
<Text
fontSize="sm"
fontWeight="700"
color={dateData.indexChange! >= 0 ? '#EF4444' : '#22C55E'}
>
{dateData.indexChange! >= 0 ? '+' : ''}{dateData.indexChange!.toFixed(2)}%
</Text>
)}
</HStack>
{/* 中间区域:涨停数 + 事件数 */}
<VStack spacing={1} mt={1} align="stretch">
{/* 涨停数据 */}
{hasZtData && (
<HStack spacing={1.5} justify="center">
<Flame size={16} color={dateData.count >= 60 ? '#EF4444' : '#F59E0B'} />
<Text fontSize="md" fontWeight="bold" color={dateData.count >= 60 ? '#EF4444' : '#F59E0B'}>
{dateData.count}
</Text>
</HStack>
)}
{/* 未来事件计数 */}
{hasEventCount && (
<HStack spacing={1.5} justify="center">
<Box
w="18px"
h="18px"
borderRadius="full"
bg="linear-gradient(135deg, #22C55E 0%, #16A34A 100%)"
display="flex"
alignItems="center"
justifyContent="center"
boxShadow="0 0 6px rgba(34, 197, 94, 0.4)"
>
<Text fontSize="xs" fontWeight="bold" color="white">
{dateData.eventCount}
</Text>
</Box>
<Text fontSize="sm" color="#22C55E" fontWeight="600">
</Text>
</HStack>
)}
</VStack>
</Box>
);
}, [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<string, unknown> } }) => {
@@ -371,6 +396,15 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
);
}, []);
// 当数据变化时,需要重新渲染日历
useEffect(() => {
if (calendarRef.current) {
const api = calendarRef.current.getApi();
// 触发重新渲染
api.render();
}
}, [data]);
return (
<Box
height={height}
@@ -442,25 +476,18 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
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<FullCalendarProProps> = ({
dateClick={handleDateClick}
eventClick={handleEventClick}
datesSet={handleDatesSet}
dayCellContent={dayCellContent}
dayCellDidMount={handleDayCellDidMount}
eventContent={eventContent}
dayMaxEvents={3}
moreLinkText={(n) => `+${n} 更多`}