diff --git a/src/components/Calendar/FullCalendarPro.tsx b/src/components/Calendar/FullCalendarPro.tsx index afcfe3ac..1b042778 100644 --- a/src/components/Calendar/FullCalendarPro.tsx +++ b/src/components/Calendar/FullCalendarPro.tsx @@ -32,6 +32,7 @@ export interface CalendarEventData { count: number; // 涨停数 topSector: string; // 最热概念 eventCount?: number; // 未来事件数 + indexChange?: number; // 上证指数涨跌幅 } /** @@ -57,16 +58,16 @@ export interface FullCalendarProProps { */ const CONCEPT_COLORS: Record = {}; const COLOR_PALETTE = [ - { bg: 'linear-gradient(135deg, #FFD700 0%, #FFA500 100%)', border: '#FFD700', text: '#000' }, // 金色 - { bg: 'linear-gradient(135deg, #00CED1 0%, #20B2AA 100%)', border: '#00CED1', text: '#000' }, // 青色 - { 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: '#fff' }, // 绿色 - { bg: 'linear-gradient(135deg, #F59E0B 0%, #D97706 100%)', border: '#F59E0B', text: '#000' }, // 橙色 - { 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: '#fff' }, // 青绿 + { 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; @@ -169,6 +170,7 @@ const createEventInput = (event: { concept: string; startDate: string; endDate: daysCount: event.dates.length, gradient: color.bg, borderColor: color.border, + textColor: color.text, // 确保文字颜色传递到 extendedProps }, }; }; @@ -227,6 +229,9 @@ export const FullCalendarPro: React.FC = ({ 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; return ( @@ -240,12 +245,25 @@ export const FullCalendarPro: React.FC = ({ {arg.date.getDate()} + {/* 上证指数涨跌幅 */} + {hasIndexChange && ( + = 0 ? '#EF4444' : '#22C55E'} + textAlign="center" + mt={0.5} + > + {dateData.indexChange! >= 0 ? '+' : ''}{dateData.indexChange!.toFixed(2)}% + + )} + {/* 涨停数据指示器 */} - {dateData && ( + {hasZtData && ( = 60 ? '#EF4444' : '#F59E0B'} /> = 60 ? '#EF4444' : '#F59E0B'}> @@ -254,17 +272,30 @@ export const FullCalendarPro: React.FC = ({ )} - {/* 未来事件指示 */} - {dateData?.eventCount && dateData.eventCount > 0 && ( - + {/* 未来事件计数 - 显示具体数字而非小圆点 */} + {hasEventCount && ( + + + + {dateData.eventCount} + + + + 事件 + + )} ); @@ -274,6 +305,10 @@ export const FullCalendarPro: React.FC = ({ const eventContent = useCallback((arg: { event: { title: string; extendedProps: Record } }) => { 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 ( = ({ {arg.event.title} 连续 {daysCount} 天 - 累计涨停 {extendedProps.totalCount as number} 家 + 累计涨停 {totalCount} 家 } placement="top" @@ -293,9 +328,9 @@ export const FullCalendarPro: React.FC = ({ = ({ transition="all 0.2s" _hover={{ transform: 'scale(1.02)', - boxShadow: `0 0 12px ${extendedProps.borderColor}`, + boxShadow: `0 0 12px ${borderColor}`, }} overflow="hidden" position="relative" @@ -323,7 +358,7 @@ export const FullCalendarPro: React.FC = ({ { const [eventCounts, setEventCounts] = useState([]); const [selectedEvents, setSelectedEvents] = useState([]); + // 上证指数涨跌幅数据 + const [indexChangeMap, setIndexChangeMap] = useState({}); + const [detailLoading, setDetailLoading] = useState(false); const [modalOpen, setModalOpen] = useState(false); @@ -2361,6 +2364,35 @@ const CombinedCalendar = () => { loadEventCounts(); }, [currentMonth]); + // 加载上证指数历史涨跌幅数据 + useEffect(() => { + const loadIndexData = async () => { + try { + const response = await fetch(`${getApiBase()}/api/index/000001.SH/kline?type=daily`); + if (response.ok) { + const result = await response.json(); + if (result.success && result.data) { + // 构建日期到涨跌幅的映射 + const changeMap = {}; + result.data.forEach(item => { + // date 格式是 YYYY-MM-DD,转为 YYYYMMDD + const yyyymmdd = item.date.replace(/-/g, ''); + // 计算涨跌幅 = (close - prev_close) / prev_close * 100 + if (item.close && item.prev_close) { + const change = ((item.close - item.prev_close) / item.prev_close) * 100; + changeMap[yyyymmdd] = change; + } + }); + setIndexChangeMap(changeMap); + } + } + } catch (error) { + console.error('Failed to load index data:', error); + } + }; + loadIndexData(); + }, []); + // 获取涨停板块详情(加载所有数据,不限于当月) useEffect(() => { const loadZtDetails = async () => { @@ -2409,19 +2441,45 @@ const CombinedCalendar = () => { }, [ztDatesData]); // 构建 FullCalendarPro 所需的数据格式 + // 需要合并涨停数据、未来事件数据和上证指数涨跌幅 const calendarData = useMemo(() => { - return ztDatesData.map(d => { + // 创建日期到数据的映射 + const dataMap = new Map(); + + // 先添加涨停数据 + ztDatesData.forEach(d => { const detail = ztDailyDetails[d.date] || {}; - const eventDateStr = `${d.date.slice(0,4)}-${d.date.slice(4,6)}-${d.date.slice(6,8)}`; - const eventCount = eventCounts.find(e => e.date === eventDateStr)?.count || 0; - return { + dataMap.set(d.date, { date: d.date, count: d.count, topSector: detail.top_sector || '', - eventCount, - }; + eventCount: 0, + indexChange: indexChangeMap[d.date] ?? null, + }); }); - }, [ztDatesData, ztDailyDetails, eventCounts]); + + // 再添加/合并未来事件数据 + eventCounts.forEach(e => { + // e.date 格式是 YYYY-MM-DD,需要转为 YYYYMMDD + const yyyymmdd = e.date.replace(/-/g, ''); + if (dataMap.has(yyyymmdd)) { + // 已有涨停数据,只更新事件数 + const existing = dataMap.get(yyyymmdd); + existing.eventCount = e.count; + } else { + // 纯未来事件日期,没有涨停数据 + dataMap.set(yyyymmdd, { + date: yyyymmdd, + count: 0, + topSector: '', + eventCount: e.count, + indexChange: indexChangeMap[yyyymmdd] ?? null, + }); + } + }); + + return Array.from(dataMap.values()); + }, [ztDatesData, ztDailyDetails, eventCounts, indexChangeMap]); // 处理日期点击 - 打开弹窗 const handleDateClick = useCallback(async (date) => { @@ -2521,8 +2579,24 @@ const CombinedCalendar = () => { 涨停<60 - - 有未来事件 + + N + + 未来事件数 + + + +0.5% + / + -0.5% + 上证涨跌