community增加事件详情
This commit is contained in:
@@ -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} 更多`}
|
||||
|
||||
Reference in New Issue
Block a user