perf(Calendar): FullCalendar 懒加载与代码分割优化
- HeroPanel: 使用 React.lazy + Suspense 懒加载 FullCalendarPro - craco.config: 添加 @fullcalendar 独立分包配置(~15KB gzip) - event mock: 生成连续概念数据(2-4天同概念)便于测试跨天效果 - LimitAnalyse: 文案优化(高潮→高涨) - ForceGraphView: 层级图优化 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -81,6 +81,13 @@ module.exports = {
|
||||
priority: 18,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
// FullCalendar 日历组件(单独分包,约 60KB gzip)
|
||||
fullcalendar: {
|
||||
test: /[\\/]node_modules[\\/]@fullcalendar[\\/]/,
|
||||
name: 'fullcalendar-lib',
|
||||
priority: 19,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
// 其他第三方库
|
||||
vendor: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
|
||||
@@ -1812,6 +1812,12 @@ export const eventHandlers = [
|
||||
'固态电池', '量子计算', '低空经济', '智能驾驶', '光伏', '储能'
|
||||
];
|
||||
|
||||
// 预生成概念连续段(用于测试跨天连接效果)
|
||||
// 每段 2-4 天使用相同概念
|
||||
let currentConcept = hotConcepts[0];
|
||||
let conceptDaysLeft = 0;
|
||||
let conceptIndex = 0;
|
||||
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const date = new Date(year, month - 1, day);
|
||||
const dayOfWeek = date.getDay();
|
||||
@@ -1831,6 +1837,14 @@ export const eventHandlers = [
|
||||
return x - Math.floor(x);
|
||||
};
|
||||
|
||||
// 概念连续段逻辑:每段 2-4 天使用相同概念
|
||||
if (conceptDaysLeft <= 0) {
|
||||
conceptIndex = Math.floor(seededRandom(dateSeed + 100) * hotConcepts.length);
|
||||
currentConcept = hotConcepts[conceptIndex];
|
||||
conceptDaysLeft = Math.floor(seededRandom(dateSeed + 101) * 3) + 2; // 2-4 天
|
||||
}
|
||||
conceptDaysLeft--;
|
||||
|
||||
const item = {
|
||||
date: dateStr,
|
||||
zt_count: 0,
|
||||
@@ -1842,7 +1856,7 @@ export const eventHandlers = [
|
||||
// 历史数据:涨停 + 上证涨跌幅
|
||||
if (isPast || isToday) {
|
||||
item.zt_count = Math.floor(seededRandom(dateSeed) * 80 + 30); // 30-110
|
||||
item.top_sector = hotConcepts[Math.floor(seededRandom(dateSeed + 1) * hotConcepts.length)];
|
||||
item.top_sector = currentConcept; // 使用连续概念
|
||||
item.index_change = parseFloat((seededRandom(dateSeed + 2) * 4 - 2).toFixed(2)); // -2% ~ +2%
|
||||
}
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ const generateAvailableDates = (): MockDateItem[] => {
|
||||
|
||||
// 返回包含 date 和 count 字段的对象
|
||||
// 生成 15-110 范围的涨停数,确保各阶段都有数据
|
||||
// <40: 偏冷, >=40: 温和, >=60: 高潮, >=80: 超级高潮
|
||||
// <40: 偏冷, >=40: 温和, >=60: 高涨, >=80: 超级高涨
|
||||
let limitCount: number;
|
||||
const rand = Math.random();
|
||||
if (rand < 0.2) {
|
||||
@@ -187,9 +187,9 @@ const generateAvailableDates = (): MockDateItem[] => {
|
||||
} else if (rand < 0.5) {
|
||||
limitCount = Math.floor(Math.random() * 20) + 40; // 40-59 温和日
|
||||
} else if (rand < 0.8) {
|
||||
limitCount = Math.floor(Math.random() * 20) + 60; // 60-79 高潮日
|
||||
limitCount = Math.floor(Math.random() * 20) + 60; // 60-79 高涨日
|
||||
} else {
|
||||
limitCount = Math.floor(Math.random() * 30) + 80; // 80-109 超级高潮日
|
||||
limitCount = Math.floor(Math.random() * 30) + 80; // 80-109 超级高涨日
|
||||
}
|
||||
|
||||
dates.push({
|
||||
@@ -512,9 +512,9 @@ const generateDatesJson = (): { dates: MockDateItem[] } => {
|
||||
} else if (rand < 0.5) {
|
||||
limitCount = Math.floor(Math.random() * 20) + 40; // 40-59 温和日
|
||||
} else if (rand < 0.8) {
|
||||
limitCount = Math.floor(Math.random() * 20) + 60; // 60-79 高潮日
|
||||
limitCount = Math.floor(Math.random() * 20) + 60; // 60-79 高涨日
|
||||
} else {
|
||||
limitCount = Math.floor(Math.random() * 30) + 80; // 80-109 超级高潮日
|
||||
limitCount = Math.floor(Math.random() * 30) + 80; // 80-109 超级高涨日
|
||||
}
|
||||
|
||||
dates.push({
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// 综合日历面板:融合涨停分析 + 投资日历
|
||||
// 点击日期弹出详情弹窗(TAB切换历史涨停/未来事件)
|
||||
|
||||
import React, { useEffect, useState, useCallback, useMemo, memo } from 'react';
|
||||
import React, { useEffect, useState, useCallback, useMemo, memo, lazy, Suspense } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { loadWatchlist, toggleWatchlist } from '@store/slices/stockSlice';
|
||||
import {
|
||||
@@ -56,7 +56,10 @@ import { getApiBase } from '@utils/apiConfig';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import dayjs from 'dayjs';
|
||||
import KLineChartModal from '@components/StockChart/KLineChartModal';
|
||||
import { FullCalendarPro } from '@components/Calendar';
|
||||
// 懒加载 FullCalendar(约 60KB gzip,延迟加载提升首屏性能)
|
||||
const FullCalendarPro = lazy(() =>
|
||||
import('@components/Calendar').then(module => ({ default: module.FullCalendarPro }))
|
||||
);
|
||||
import ThemeCometChart from './ThemeCometChart';
|
||||
import EventDailyStats from './EventDailyStats';
|
||||
|
||||
@@ -2477,14 +2480,23 @@ const CombinedCalendar = () => {
|
||||
backgroundSize="200% 100%"
|
||||
/>
|
||||
|
||||
{/* FullCalendar Pro - 炫酷跨天事件条日历 */}
|
||||
<FullCalendarPro
|
||||
data={calendarData}
|
||||
currentMonth={currentMonth}
|
||||
onDateClick={handleDateClick}
|
||||
onMonthChange={handleMonthChange}
|
||||
height="650px"
|
||||
/>
|
||||
{/* FullCalendar Pro - 炫酷跨天事件条日历(懒加载) */}
|
||||
<Suspense fallback={
|
||||
<Center h="650px">
|
||||
<VStack spacing={4}>
|
||||
<Spinner size="xl" color="gold" thickness="3px" />
|
||||
<Text color="whiteAlpha.600" fontSize="sm">加载日历组件...</Text>
|
||||
</VStack>
|
||||
</Center>
|
||||
}>
|
||||
<FullCalendarPro
|
||||
data={calendarData}
|
||||
currentMonth={currentMonth}
|
||||
onDateClick={handleDateClick}
|
||||
onMonthChange={handleMonthChange}
|
||||
height="650px"
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
{/* 图例说明 */}
|
||||
<HStack spacing={4} mt={4} justify="center" flexWrap="wrap">
|
||||
|
||||
@@ -296,34 +296,42 @@ const ForceGraphView = ({
|
||||
}, []);
|
||||
|
||||
// 生成概念节点的 label 配置(确保详情按钮始终显示)
|
||||
const getConceptLabel = useCallback((conceptName, changePct) => {
|
||||
// isTopLevel: 钻取到最后一级时为 true,字体放大
|
||||
const getConceptLabel = useCallback((conceptName, changePct, isTopLevel = false) => {
|
||||
const changeStr = changePct !== undefined && changePct !== null
|
||||
? ` {change|${formatChangePercent(changePct)}}`
|
||||
: '';
|
||||
|
||||
// 根据是否顶层调整字体大小
|
||||
const nameSize = isTopLevel ? 16 : 11;
|
||||
const changeSize = isTopLevel ? 14 : 10;
|
||||
const btnSize = isTopLevel ? 13 : 11;
|
||||
const btnPadding = isTopLevel ? [4, 10] : [3, 8];
|
||||
|
||||
return {
|
||||
show: true,
|
||||
position: 'insideTopLeft',
|
||||
fontSize: 11,
|
||||
padding: [4, 6],
|
||||
fontSize: nameSize,
|
||||
padding: isTopLevel ? [8, 12] : [4, 6],
|
||||
overflow: 'break',
|
||||
formatter: `{name|${conceptName}}${changeStr}\n{btn|详情 >}`,
|
||||
rich: {
|
||||
name: {
|
||||
fontSize: 11,
|
||||
fontSize: nameSize,
|
||||
fontWeight: 'bold',
|
||||
lineHeight: 16,
|
||||
lineHeight: nameSize + 6,
|
||||
},
|
||||
change: {
|
||||
fontSize: 10,
|
||||
lineHeight: 16,
|
||||
fontSize: changeSize,
|
||||
lineHeight: changeSize + 6,
|
||||
},
|
||||
btn: {
|
||||
fontSize: 9,
|
||||
color: '#A78BFA',
|
||||
backgroundColor: 'rgba(139, 92, 246, 0.3)',
|
||||
borderRadius: 3,
|
||||
padding: [2, 6],
|
||||
lineHeight: 18,
|
||||
fontSize: btnSize,
|
||||
color: '#FFFFFF',
|
||||
backgroundColor: 'rgba(139, 92, 246, 0.6)',
|
||||
borderRadius: 4,
|
||||
padding: btnPadding,
|
||||
lineHeight: btnSize + 8,
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -662,7 +670,7 @@ const ForceGraphView = ({
|
||||
return result;
|
||||
}
|
||||
|
||||
// 钻取到某个三级分类
|
||||
// 钻取到某个三级分类(最后一级,字体放大)
|
||||
if (drillPath.lv1 && drillPath.lv2 && drillPath.lv3) {
|
||||
const lv1 = hierarchy.find(h => h.name === drillPath.lv1);
|
||||
if (!lv1) return [];
|
||||
@@ -687,7 +695,7 @@ const ForceGraphView = ({
|
||||
borderWidth: 2,
|
||||
borderRadius: 12,
|
||||
},
|
||||
label: getConceptLabel(conceptName, conceptPrice.avg_change_pct),
|
||||
label: getConceptLabel(conceptName, conceptPrice.avg_change_pct, true),
|
||||
data: {
|
||||
level: 'concept',
|
||||
parentLv1: lv1.name,
|
||||
|
||||
@@ -34,7 +34,7 @@ export const HEAT_LEVELS: HeatLevel[] = [
|
||||
{
|
||||
key: 'high',
|
||||
threshold: 80,
|
||||
label: '超级高潮日',
|
||||
label: '超级高涨日',
|
||||
icon: Flame,
|
||||
colors: {
|
||||
bg: 'rgba(147, 51, 234, 0.55)',
|
||||
@@ -45,7 +45,7 @@ export const HEAT_LEVELS: HeatLevel[] = [
|
||||
{
|
||||
key: 'medium',
|
||||
threshold: 60,
|
||||
label: '高潮日',
|
||||
label: '高涨日',
|
||||
icon: Zap,
|
||||
colors: {
|
||||
bg: 'rgba(239, 68, 68, 0.50)',
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 优化点:
|
||||
* 1. 日历格子直接显示涨停数、趋势箭头、主要板块
|
||||
* 2. 左侧显示AI复盘总结、核心指标
|
||||
* 3. 快速筛选器(只看高潮日等)
|
||||
* 3. 快速筛选器(只看高涨日等)
|
||||
* 4. 悬浮提示卡片显示详细信息
|
||||
*/
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
Reference in New Issue
Block a user