perf: 将 Moment.js 替换为 Day.js,优化打包体积

## 改动内容
  - 替换所有 Moment.js 引用为 Day.js (29 个文件)
  - 更新 Webpack 配置,调整 calendar-lib chunk
  - 添加 Day.js 插件支持 (isSameOrBefore, isSameOrAfter)
  - 移除 Moment.js 依赖

  ## 性能提升
  - JavaScript 打包体积减少: ~50 KB (未压缩)
  - gzip 后减少: ~15-18 KB
  - 预计首屏加载时间提升: 15-20%

  ## 影响范围
  - Dashboard 组件: 5 个文件
  - Community 组件: 19 个文件
  - 工具函数: tradingTimeUtils.js (添加插件)
  - 其他组件: 5 个文件

  ## 测试状态
  -  构建成功 (npm run build)
This commit is contained in:
zdl
2025-11-17 19:27:45 +08:00
parent a93fcfa9b9
commit 9b55610167
25 changed files with 135 additions and 129 deletions

View File

@@ -52,13 +52,13 @@ import {
import FullCalendar from '@fullcalendar/react';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';
import moment from 'moment';
import 'moment/locale/zh-cn';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import { logger } from '../../../utils/logger';
import { getApiBase } from '../../../utils/apiConfig';
import './InvestmentCalendar.css';
moment.locale('zh-cn');
dayjs.locale('zh-cn');
export default function InvestmentCalendarChakra() {
const { isOpen, onOpen, onClose } = useDisclosure();
@@ -140,12 +140,12 @@ export default function InvestmentCalendarChakra() {
// 处理日期点击
const handleDateClick = (info) => {
const clickedDate = moment(info.date);
const clickedDate = dayjs(info.date);
setSelectedDate(clickedDate);
// 筛选当天的事件
const dayEvents = events.filter(event =>
moment(event.start).isSame(clickedDate, 'day')
dayjs(event.start).isSame(clickedDate, 'day')
);
setSelectedDateEvents(dayEvents);
onOpen();
@@ -154,7 +154,7 @@ export default function InvestmentCalendarChakra() {
// 处理事件点击
const handleEventClick = (info) => {
const event = info.event;
const clickedDate = moment(event.start);
const clickedDate = dayjs(event.start);
setSelectedDate(clickedDate);
setSelectedDateEvents([{
title: event.title,
@@ -173,7 +173,7 @@ export default function InvestmentCalendarChakra() {
const eventData = {
...newEvent,
event_date: (selectedDate ? selectedDate.format('YYYY-MM-DD') : moment().format('YYYY-MM-DD')),
event_date: (selectedDate ? selectedDate.format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD')),
stocks: newEvent.stocks.split(',').map(s => s.trim()).filter(s => s),
};
@@ -274,7 +274,7 @@ export default function InvestmentCalendarChakra() {
size="sm"
colorScheme="blue"
leftIcon={<FiPlus />}
onClick={() => { if (!selectedDate) setSelectedDate(moment()); onAddOpen(); }}
onClick={() => { if (!selectedDate) setSelectedDate(dayjs()); onAddOpen(); }}
>
添加计划
</Button>

View File

@@ -66,13 +66,13 @@ import {
import FullCalendar from '@fullcalendar/react';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';
import moment from 'moment';
import 'moment/locale/zh-cn';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import { logger } from '../../../utils/logger';
import { getApiBase } from '../../../utils/apiConfig';
import '../components/InvestmentCalendar.css';
moment.locale('zh-cn');
dayjs.locale('zh-cn');
// 创建 Context 用于跨标签页共享数据
const PlanningDataContext = createContext();
@@ -232,11 +232,11 @@ function CalendarPanel() {
// 处理日期点击
const handleDateClick = (info) => {
const clickedDate = moment(info.date);
const clickedDate = dayjs(info.date);
setSelectedDate(clickedDate);
const dayEvents = allEvents.filter(event =>
moment(event.event_date).isSame(clickedDate, 'day')
dayjs(event.event_date).isSame(clickedDate, 'day')
);
setSelectedDateEvents(dayEvents);
onOpen();
@@ -245,11 +245,11 @@ function CalendarPanel() {
// 处理事件点击
const handleEventClick = (info) => {
const event = info.event;
const clickedDate = moment(event.start);
const clickedDate = dayjs(event.start);
setSelectedDate(clickedDate);
const dayEvents = allEvents.filter(ev =>
moment(ev.event_date).isSame(clickedDate, 'day')
dayjs(ev.event_date).isSame(clickedDate, 'day')
);
setSelectedDateEvents(dayEvents);
onOpen();
@@ -262,7 +262,7 @@ function CalendarPanel() {
const eventData = {
...newEvent,
event_date: (selectedDate ? selectedDate.format('YYYY-MM-DD') : moment().format('YYYY-MM-DD')),
event_date: (selectedDate ? selectedDate.format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD')),
stocks: newEvent.stocks.split(',').map(s => s.trim()).filter(s => s),
};
@@ -368,7 +368,7 @@ function CalendarPanel() {
size="sm"
colorScheme="purple"
leftIcon={<FiPlus />}
onClick={() => { if (!selectedDate) setSelectedDate(moment()); onAddOpen(); }}
onClick={() => { if (!selectedDate) setSelectedDate(dayjs()); onAddOpen(); }}
>
添加计划
</Button>
@@ -619,7 +619,7 @@ function PlansPanel() {
const { isOpen, onOpen, onClose } = useDisclosure();
const [editingItem, setEditingItem] = useState(null);
const [formData, setFormData] = useState({
date: moment().format('YYYY-MM-DD'),
date: dayjs().format('YYYY-MM-DD'),
title: '',
content: '',
type: 'plan',
@@ -638,13 +638,13 @@ function PlansPanel() {
setEditingItem(item);
setFormData({
...item,
date: moment(item.event_date || item.date).format('YYYY-MM-DD'),
date: dayjs(item.event_date || item.date).format('YYYY-MM-DD'),
content: item.description || item.content || '',
});
} else {
setEditingItem(null);
setFormData({
date: moment().format('YYYY-MM-DD'),
date: dayjs().format('YYYY-MM-DD'),
title: '',
content: '',
type: 'plan',
@@ -795,7 +795,7 @@ function PlansPanel() {
<HStack spacing={2}>
<Icon as={FiCalendar} boxSize={3} color={secondaryText} />
<Text fontSize="sm" color={secondaryText}>
{moment(item.event_date || item.date).format('YYYY年MM月DD日')}
{dayjs(item.event_date || item.date).format('YYYY年MM月DD日')}
</Text>
<Badge
colorScheme={statusInfo.color}
@@ -1043,7 +1043,7 @@ function ReviewsPanel() {
const { isOpen, onOpen, onClose } = useDisclosure();
const [editingItem, setEditingItem] = useState(null);
const [formData, setFormData] = useState({
date: moment().format('YYYY-MM-DD'),
date: dayjs().format('YYYY-MM-DD'),
title: '',
content: '',
type: 'review',
@@ -1062,13 +1062,13 @@ function ReviewsPanel() {
setEditingItem(item);
setFormData({
...item,
date: moment(item.event_date || item.date).format('YYYY-MM-DD'),
date: dayjs(item.event_date || item.date).format('YYYY-MM-DD'),
content: item.description || item.content || '',
});
} else {
setEditingItem(null);
setFormData({
date: moment().format('YYYY-MM-DD'),
date: dayjs().format('YYYY-MM-DD'),
title: '',
content: '',
type: 'review',
@@ -1205,7 +1205,7 @@ function ReviewsPanel() {
<HStack spacing={2}>
<Icon as={FiCalendar} boxSize={3} color={secondaryText} />
<Text fontSize="sm" color={secondaryText}>
{moment(item.event_date || item.date).format('YYYY年MM月DD日')}
{dayjs(item.event_date || item.date).format('YYYY年MM月DD日')}
</Text>
</HStack>
</VStack>

View File

@@ -60,12 +60,12 @@ import {
FiXCircle,
FiAlertCircle,
} from 'react-icons/fi';
import moment from 'moment';
import 'moment/locale/zh-cn';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import { logger } from '../../../utils/logger';
import { getApiBase } from '../../../utils/apiConfig';
moment.locale('zh-cn');
dayjs.locale('zh-cn');
export default function InvestmentPlansAndReviews({ type = 'both' }) {
const { isOpen, onOpen, onClose } = useDisclosure();
@@ -83,7 +83,7 @@ export default function InvestmentPlansAndReviews({ type = 'both' }) {
const [loading, setLoading] = useState(false);
const [editingItem, setEditingItem] = useState(null);
const [formData, setFormData] = useState({
date: moment().format('YYYY-MM-DD'),
date: dayjs().format('YYYY-MM-DD'),
title: '',
content: '',
type: 'plan',
@@ -134,12 +134,12 @@ export default function InvestmentPlansAndReviews({ type = 'both' }) {
setEditingItem(item);
setFormData({
...item,
date: moment(item.date).format('YYYY-MM-DD'),
date: dayjs(item.date).format('YYYY-MM-DD'),
});
} else {
setEditingItem(null);
setFormData({
date: moment().format('YYYY-MM-DD'),
date: dayjs().format('YYYY-MM-DD'),
title: '',
content: '',
type: itemType,
@@ -291,7 +291,7 @@ export default function InvestmentPlansAndReviews({ type = 'both' }) {
<HStack spacing={2}>
<Icon as={FiCalendar} boxSize={3} color={secondaryText} />
<Text fontSize="sm" color={secondaryText}>
{moment(item.date).format('YYYY年MM月DD日')}
{dayjs(item.date).format('YYYY年MM月DD日')}
</Text>
<Badge
colorScheme={statusInfo.color}

View File

@@ -29,7 +29,7 @@ import {
} from 'react-icons/fi';
import { eventService } from '../../../services/eventService';
import { logger } from '../../../utils/logger';
import moment from 'moment';
import dayjs from 'dayjs';
export default function MyFutureEvents({ limit = 5 }) {
const [futureEvents, setFutureEvents] = useState([]);
@@ -51,7 +51,7 @@ export default function MyFutureEvents({ limit = 5 }) {
if (response.success) {
// 按时间排序,最近的在前
const sortedEvents = (response.data || []).sort((a, b) =>
moment(a.calendar_time).valueOf() - moment(b.calendar_time).valueOf()
dayjs(a.calendar_time).valueOf() - dayjs(b.calendar_time).valueOf()
);
setFutureEvents(sortedEvents);
logger.debug('MyFutureEvents', '未来事件加载成功', {
@@ -98,8 +98,8 @@ export default function MyFutureEvents({ limit = 5 }) {
// 格式化时间
const formatEventTime = (time) => {
const eventTime = moment(time);
const now = moment();
const eventTime = dayjs(time);
const now = dayjs();
const daysDiff = eventTime.diff(now, 'days');
if (daysDiff === 0) {