From 1fc9f4790f3613b6e3cfd04d501f967b750b443d Mon Sep 17 00:00:00 2001
From: zdl <3489966805@qq.com>
Date: Wed, 19 Nov 2025 20:03:55 +0800
Subject: [PATCH] =?UTF-8?q?pref:=20=E6=B8=85=E7=90=86=E5=BB=BA=E8=AE=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
6.1 立即可删除(安全)
以下文件可以立即删除,不会影响任何功能:
# 未使用的组件
src/views/Community/components/EventList.js
src/views/Community/components/EventListSection.js
src/views/Community/components/EventTimelineHeader.js
src/views/Community/components/MarketReviewCard.js
src/views/Community/components/UnifiedSearchBox.js
src/views/Community/components/ImportanceLegend.js
src/views/Community/components/IndustryCascader.js
src/views/Community/components/EventDetailModal.js
# 未使用的CSS
src/views/Community/components/EventList.css
# 备份文件
src/views/Community/components/EventList.js.bak
# 测试文档
src/views/Community/components/DynamicNewsDetail/1.md
预计减少代码量:~2000行代码
---
.../Community/components/EventDetailModal.js | 283 ------
src/views/Community/components/EventList.css | 387 --------
src/views/Community/components/EventList.js | 490 ----------
.../Community/components/EventList.js.bak | 818 ----------------
.../Community/components/EventListSection.js | 75 --
.../components/EventTimelineHeader.js | 42 -
.../Community/components/ImportanceLegend.js | 34 -
.../Community/components/IndustryCascader.js | 78 --
.../Community/components/MarketReviewCard.js | 300 ------
.../Community/components/UnifiedSearchBox.js | 898 ------------------
10 files changed, 3405 deletions(-)
delete mode 100644 src/views/Community/components/EventDetailModal.js
delete mode 100644 src/views/Community/components/EventList.css
delete mode 100644 src/views/Community/components/EventList.js
delete mode 100644 src/views/Community/components/EventList.js.bak
delete mode 100644 src/views/Community/components/EventListSection.js
delete mode 100644 src/views/Community/components/EventTimelineHeader.js
delete mode 100644 src/views/Community/components/ImportanceLegend.js
delete mode 100644 src/views/Community/components/IndustryCascader.js
delete mode 100644 src/views/Community/components/MarketReviewCard.js
delete mode 100644 src/views/Community/components/UnifiedSearchBox.js
diff --git a/src/views/Community/components/EventDetailModal.js b/src/views/Community/components/EventDetailModal.js
deleted file mode 100644
index 633de5c7..00000000
--- a/src/views/Community/components/EventDetailModal.js
+++ /dev/null
@@ -1,283 +0,0 @@
-// src/views/Community/components/EventDetailModal.js
-import React, { useState, useEffect } from 'react';
-import { Modal, Spin, Descriptions, Tag, List, Badge, Empty, Input, Button, message } from 'antd';
-import { eventService } from '../../../services/eventService';
-import { logger } from '../../../utils/logger';
-import dayjs from 'dayjs';
-
-const EventDetailModal = ({ visible, event, onClose }) => {
- const [loading, setLoading] = useState(false);
- const [eventDetail, setEventDetail] = useState(null);
- const [commentText, setCommentText] = useState('');
- const [submitting, setSubmitting] = useState(false);
- const [comments, setComments] = useState([]);
- const [commentsLoading, setCommentsLoading] = useState(false);
-
- const loadEventDetail = async () => {
- if (!event) return;
-
- setLoading(true);
- try {
- const response = await eventService.getEventDetail(event.id);
- if (response.success) {
- setEventDetail(response.data);
- }
- } catch (error) {
- logger.error('EventDetailModal', 'loadEventDetail', error, {
- eventId: event?.id
- });
- } finally {
- setLoading(false);
- }
- };
-
- const loadComments = async () => {
- if (!event) return;
-
- setCommentsLoading(true);
- try {
- // 使用统一的posts API获取评论
- const result = await eventService.getPosts(event.id);
- if (result.success) {
- setComments(result.data || []);
- }
- } catch (error) {
- logger.error('EventDetailModal', 'loadComments', error, {
- eventId: event?.id
- });
- } finally {
- setCommentsLoading(false);
- }
- };
-
- useEffect(() => {
- if (visible && event) {
- loadEventDetail();
- loadComments();
- }
- }, [visible, event]);
-
- const getImportanceColor = (importance) => {
- const colors = {
- S: 'red',
- A: 'orange',
- B: 'blue',
- C: 'green'
- };
- return colors[importance] || 'default';
- };
-
- const getRelationDesc = (relationDesc) => {
- // 处理空值
- if (!relationDesc) return '';
-
- // 如果是字符串,直接返回
- if (typeof relationDesc === 'string') {
- return relationDesc;
- }
-
- // 如果是对象且包含data数组
- if (typeof relationDesc === 'object' && relationDesc.data && Array.isArray(relationDesc.data)) {
- const firstItem = relationDesc.data[0];
- if (firstItem) {
- // 优先使用 query_part,其次使用 sentences
- return firstItem.query_part || firstItem.sentences || '';
- }
- }
-
- // 其他情况返回空字符串
- return '';
- };
-
- const renderPriceTag = (value, label) => {
- if (value === null || value === undefined) return `${label}: --`;
-
- const color = value > 0 ? '#ff4d4f' : '#52c41a';
- const prefix = value > 0 ? '+' : '';
-
- return (
-
- {label}: {prefix}{value.toFixed(2)}%
-
- );
- };
-
- const handleSubmitComment = async () => {
- if (!commentText.trim()) {
- message.warning('请输入评论内容');
- return;
- }
- setSubmitting(true);
- try {
- // 使用统一的createPost API
- const result = await eventService.createPost(event.id, {
- content: commentText.trim(),
- content_type: 'text'
- });
-
- if (result.success) {
- message.success('评论发布成功');
- setCommentText('');
- // 重新加载评论列表
- loadComments();
- } else {
- throw new Error(result.message || '评论失败');
- }
- } catch (e) {
- message.error(e.message || '评论失败');
- } finally {
- setSubmitting(false);
- }
- };
-
- return (
-
-
- {eventDetail && (
- <>
-
-
- {dayjs(eventDetail.created_at).format('YYYY-MM-DD HH:mm:ss')}
-
-
- {eventDetail.creator?.username || 'Anonymous'}
-
-
-
-
-
- {eventDetail.view_count || 0}
-
-
- {renderPriceTag(eventDetail.related_avg_chg, '平均涨幅')}
- {renderPriceTag(eventDetail.related_max_chg, '最大涨幅')}
- {renderPriceTag(eventDetail.related_week_chg, '周涨幅')}
-
-
- {eventDetail.description}(AI合成)
-
-
-
- {eventDetail.keywords && eventDetail.keywords.length > 0 && (
-
-
相关概念
- {eventDetail.keywords.map((keyword, index) => (
-
- {keyword}
-
- ))}
-
- )}
-
- {eventDetail.related_stocks && eventDetail.related_stocks.length > 0 && (
-
-
相关股票
- (
- {
- const stockCode = stock.stock_code.split('.')[0];
- window.open(`https://valuefrontier.cn/company?scode=${stockCode}`, '_blank');
- }}
- >
- 股票详情
-
- ]}
- >
-
- {stock.change !== null && (
- 0 ? 'red' : 'green'}>
- {stock.change > 0 ? '+' : ''}{stock.change.toFixed(2)}%
-
- )}
-
- )}
- />
-
- )}
-
- {/* 讨论区 */}
-
-
讨论区
-
- {/* 评论列表 */}
-
-
- {comments.length === 0 ? (
-
- ) : (
- (
-
-
- {comment.author?.username || 'Anonymous'}
-
- {dayjs(comment.created_at).format('MM-DD HH:mm')}
-
-
- }
- description={
-
- {comment.content}
-
- }
- />
-
- )}
- />
- )}
-
-
-
- {/* 评论输入框(登录后可用,未登录后端会返回401) */}
-
-
发表评论
-
setCommentText(e.target.value)}
- maxLength={500}
- showCount
- />
-
-
-
-
-
- >
- )}
-
-
- );
-};
-
-export default EventDetailModal;
\ No newline at end of file
diff --git a/src/views/Community/components/EventList.css b/src/views/Community/components/EventList.css
deleted file mode 100644
index a62d8b24..00000000
--- a/src/views/Community/components/EventList.css
+++ /dev/null
@@ -1,387 +0,0 @@
-/* src/views/Community/components/EventList.css */
-
-/* 时间轴容器样式 */
-.event-timeline {
- padding: 0 0 0 24px;
-}
-
-/* 时间轴圆点样式 */
-.timeline-dot {
- border: none !important;
- cursor: pointer;
- transition: all 0.3s;
-}
-
-/* 时间轴事件卡片 */
-.timeline-event-card {
- padding: 20px;
- margin-bottom: 16px;
- background: #fff;
- border-radius: 12px;
- border: 1px solid #f0f0f0;
- cursor: pointer;
- transition: all 0.3s;
- position: relative;
- overflow: hidden;
-}
-
-.timeline-event-card:hover {
- transform: translateX(8px);
- box-shadow: 0 6px 20px rgba(0,0,0,0.08);
- border-color: #d9d9d9;
-}
-
-/* 重要性标记线 */
-.importance-marker {
- position: absolute;
- left: 0;
- top: 0;
- width: 4px;
- height: 100%;
- transition: width 0.3s;
-}
-
-.timeline-event-card:hover .importance-marker {
- width: 6px;
-}
-
-/* 事件标题 */
-.event-title {
- margin-bottom: 8px;
-}
-
-.event-title a {
- color: #1890ff;
- font-size: 16px;
- font-weight: 500;
- text-decoration: none;
- transition: color 0.3s;
-}
-
-.event-title a:hover {
- color: #40a9ff;
- text-decoration: underline;
-}
-
-/* 事件元信息 */
-.event-meta {
- font-size: 12px;
- color: #999;
- margin-bottom: 12px;
- display: flex;
- align-items: center;
- gap: 16px;
-}
-
-.event-meta .anticon {
- margin-right: 4px;
-}
-
-.event-meta .separator {
- margin: 0;
- color: #e8e8e8;
-}
-
-/* 事件描述 */
-.event-description {
- margin: 0 0 12px;
- color: #666;
- line-height: 1.6;
- font-size: 14px;
-}
-
-/* 事件统计标签 */
-.event-stats {
- margin-bottom: 16px;
-}
-
-.event-stats .ant-tag {
- border-radius: 4px;
- padding: 2px 8px;
- font-size: 12px;
-}
-
-/* 事件操作区域 */
-.event-actions {
- display: flex;
- justify-content: space-between;
- align-items: center;
- font-size: 13px;
- color: #999;
-}
-
-.event-actions > span {
- margin-right: 0;
-}
-
-.event-actions .anticon {
- margin-right: 4px;
-}
-
-/* 事件按钮 */
-.event-buttons {
- margin-left: auto;
-}
-
-.event-buttons .ant-btn {
- font-size: 13px;
-}
-
-.event-buttons .ant-btn-sm {
- height: 28px;
- padding: 0 12px;
-}
-
-/* 重要性指示器 */
-.importance-indicator {
- text-align: right;
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 8px;
-}
-
-.importance-indicator .ant-badge {
- display: block;
-}
-
-.importance-indicator .ant-avatar {
- transition: all 0.3s;
-}
-
-.timeline-event-card:hover .importance-indicator .ant-avatar {
- transform: scale(1.1);
-}
-
-.importance-label {
- font-size: 12px;
- color: #666;
- font-weight: 500;
-}
-
-/* 分页容器 */
-.pagination-container {
- margin-top: 32px;
- text-align: center;
- padding-top: 24px;
- border-top: 1px solid #f0f0f0;
-}
-
-/* 空状态 */
-.empty-state {
- text-align: center;
- padding: 60px 0;
- color: #999;
- font-size: 14px;
-}
-
-/* 响应式设计 */
-@media (max-width: 768px) {
- .event-timeline {
- padding-left: 0;
- }
-
- .timeline-event-card {
- padding: 16px;
- }
-
- .event-title a {
- font-size: 15px;
- }
-
- .event-description {
- font-size: 13px;
- }
-
- .event-actions {
- flex-direction: column;
- align-items: flex-start;
- gap: 12px;
- }
-
- .event-buttons {
- margin-left: 0;
- width: 100%;
- }
-
- .event-buttons .ant-space {
- width: 100%;
- }
-
- .event-buttons .ant-btn {
- flex: 1;
- }
-
- .importance-indicator {
- position: absolute;
- top: 16px;
- right: 16px;
- }
-
- .importance-label {
- display: none;
- }
-}
-
-/* 深色主题支持(可选) */
-@media (prefers-color-scheme: dark) {
- .timeline-event-card {
- background: #1f1f1f;
- border-color: #303030;
- }
-
- .timeline-event-card:hover {
- border-color: #434343;
- }
-
- .event-title a {
- color: #4096ff;
- }
-
- .event-description {
- color: #bfbfbf;
- }
-
- .event-meta {
- color: #8c8c8c;
- }
-}
-
-/* 动画效果 */
-@keyframes fadeInLeft {
- from {
- opacity: 0;
- transform: translateX(-20px);
- }
- to {
- opacity: 1;
- transform: translateX(0);
- }
-}
-
-/* 时间轴项目动画 */
-.ant-timeline-item {
- animation: fadeInLeft 0.5s ease-out forwards;
- opacity: 0;
-}
-
-.ant-timeline-item:nth-child(1) { animation-delay: 0.1s; }
-.ant-timeline-item:nth-child(2) { animation-delay: 0.2s; }
-.ant-timeline-item:nth-child(3) { animation-delay: 0.3s; }
-.ant-timeline-item:nth-child(4) { animation-delay: 0.4s; }
-.ant-timeline-item:nth-child(5) { animation-delay: 0.5s; }
-.ant-timeline-item:nth-child(6) { animation-delay: 0.6s; }
-.ant-timeline-item:nth-child(7) { animation-delay: 0.7s; }
-.ant-timeline-item:nth-child(8) { animation-delay: 0.8s; }
-.ant-timeline-item:nth-child(9) { animation-delay: 0.9s; }
-.ant-timeline-item:nth-child(10) { animation-delay: 1s; }
-
-/* 时间轴连接线样式 */
-.ant-timeline-item-tail {
- border-left-style: dashed;
- border-left-width: 2px;
-}
-
-/* 涨跌幅标签特殊样式 */
-.event-stats .ant-tag[color="#ff4d4f"] {
- background-color: #fff1f0;
- border-color: #ffccc7;
-}
-
-.event-stats .ant-tag[color="#52c41a"] {
- background-color: #f6ffed;
- border-color: #b7eb8f;
-}
-
-/* 快速查看和详细信息按钮悬停效果 */
-.event-buttons .ant-btn-default:hover {
- color: #40a9ff;
- border-color: #40a9ff;
-}
-
-.event-buttons .ant-btn-primary {
- background: #1890ff;
- border-color: #1890ff;
-}
-
-.event-buttons .ant-btn-primary:hover {
- background: #40a9ff;
- border-color: #40a9ff;
-}
-
-/* 工具提示样式 */
-.ant-tooltip-inner {
- font-size: 12px;
-}
-
-/* 徽章计数样式 */
-.importance-indicator .ant-badge-count {
- font-size: 12px;
- font-weight: bold;
- min-width: 20px;
- height: 20px;
- line-height: 20px;
- border-radius: 10px;
- padding: 0 6px;
-}
-
-/* 加载状态动画 */
-.timeline-event-card.loading {
- background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
- background-size: 200% 100%;
- animation: loading 1.5s infinite;
-}
-
-@keyframes loading {
- 0% {
- background-position: 200% 0;
- }
- 100% {
- background-position: -200% 0;
- }
-}
-
-/* 特殊重要性等级样式增强 */
-.timeline-event-card[data-importance="S"] {
- border-left: 4px solid #722ed1;
-}
-
-.timeline-event-card[data-importance="A"] {
- border-left: 4px solid #ff4d4f;
-}
-
-.timeline-event-card[data-importance="B"] {
- border-left: 4px solid #faad14;
-}
-
-.timeline-event-card[data-importance="C"] {
- border-left: 4px solid #52c41a;
-}
-
-/* 时间轴左侧内容区域优化 */
-.ant-timeline-item-content {
- padding-bottom: 0;
- min-height: auto;
-}
-
-/* 确保最后一个时间轴项目没有连接线 */
-.ant-timeline-item:last-child .ant-timeline-item-tail {
- display: none;
-}
-
-/* 打印样式优化 */
-@media print {
- .timeline-event-card {
- page-break-inside: avoid;
- border: 1px solid #000;
- }
-
- .event-buttons {
- display: none;
- }
-
- .importance-marker {
- width: 2px !important;
- background: #000 !important;
- }
-}
\ No newline at end of file
diff --git a/src/views/Community/components/EventList.js b/src/views/Community/components/EventList.js
deleted file mode 100644
index 6e98a675..00000000
--- a/src/views/Community/components/EventList.js
+++ /dev/null
@@ -1,490 +0,0 @@
-// src/views/Community/components/EventList.js
-import React, { useState, useEffect } from 'react';
-import {
- Box,
- VStack,
- HStack,
- Text,
- Button,
- Badge,
- Flex,
- Container,
- useColorModeValue,
- Switch,
- FormControl,
- FormLabel,
- useToast,
- Center,
- Tooltip,
-} from '@chakra-ui/react';
-import { InfoIcon } from '@chakra-ui/icons';
-import { useNavigate } from 'react-router-dom';
-
-// 导入工具函数和常量
-import { logger } from '../../../utils/logger';
-import { getApiBase } from '../../../utils/apiConfig';
-import { useEventNotifications } from '../../../hooks/useEventNotifications';
-import { browserNotificationService } from '../../../services/browserNotificationService';
-import { useNotification } from '../../../contexts/NotificationContext';
-import { getImportanceConfig } from '../../../constants/importanceLevels';
-
-// 导入子组件
-import EventCard from './EventCard';
-
-// ========== 主组件 ==========
-const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetail }) => {
- const navigate = useNavigate();
- const toast = useToast();
- const [isCompactMode, setIsCompactMode] = useState(false); // 新增:紧凑模式状态
- const [followingMap, setFollowingMap] = useState({});
- const [followCountMap, setFollowCountMap] = useState({});
- const [localEvents, setLocalEvents] = useState(events); // 用于实时更新的本地事件列表
-
- // 从 NotificationContext 获取推送权限相关状态和方法
- const { browserPermission, requestBrowserPermission } = useNotification();
-
- // 实时事件推送集成
- const { isConnected } = useEventNotifications({
- eventType: 'all',
- importance: 'all',
- enabled: true,
- onNewEvent: (event) => {
- console.log('\n[EventList DEBUG] ========== EventList 收到新事件 ==========');
- console.log('[EventList DEBUG] 事件数据:', event);
- console.log('[EventList DEBUG] 事件 ID:', event?.id);
- console.log('[EventList DEBUG] 事件标题:', event?.title);
- logger.info('EventList', '收到新事件推送', event);
-
- // 发送浏览器原生通知
- console.log('[EventList DEBUG] 准备发送浏览器原生通知');
- console.log('[EventList DEBUG] 通知权限状态:', browserPermission);
- if (browserPermission === 'granted') {
- const importance = getImportanceConfig(event.importance);
- const notification = browserNotificationService.sendNotification({
- title: `🔔 ${importance.label}级事件`,
- body: event.title,
- tag: `event_${event.id}`,
- data: {
- link: `/event-detail/${event.id}`,
- eventId: event.id,
- },
- autoClose: 10000, // 10秒后自动关闭
- });
-
- if (notification) {
- browserNotificationService.setupClickHandler(notification, navigate);
- console.log('[EventList DEBUG] ✓ 浏览器原生通知已发送');
- } else {
- console.log('[EventList DEBUG] ⚠️ 浏览器原生通知发送失败');
- }
- } else {
- console.log('[EventList DEBUG] ⚠️ 浏览器通知权限未授予,跳过原生通知');
- }
-
- console.log('[EventList DEBUG] 准备更新事件列表');
- // 将新事件添加到列表顶部(防止重复)
- setLocalEvents((prevEvents) => {
- console.log('[EventList DEBUG] 当前事件列表数量:', prevEvents.length);
- const exists = prevEvents.some(e => e.id === event.id);
- console.log('[EventList DEBUG] 事件是否已存在:', exists);
- if (exists) {
- logger.debug('EventList', '事件已存在,跳过添加', { eventId: event.id });
- console.log('[EventList DEBUG] ⚠️ 事件已存在,跳过添加');
- return prevEvents;
- }
- logger.info('EventList', '新事件添加到列表顶部', { eventId: event.id });
- console.log('[EventList DEBUG] ✓ 新事件添加到列表顶部');
- // 添加到顶部,最多保留 100 个
- const updatedEvents = [event, ...prevEvents].slice(0, 100);
- console.log('[EventList DEBUG] 更新后事件列表数量:', updatedEvents.length);
- return updatedEvents;
- });
- console.log('[EventList DEBUG] ✓ 事件列表更新完成');
- console.log('[EventList DEBUG] ========== EventList 处理完成 ==========\n');
- }
- });
-
- // 同步外部 events 到 localEvents
- useEffect(() => {
- setLocalEvents(events);
- }, [events]);
-
- // 初始化关注状态与计数
- useEffect(() => {
- // 初始化计数映射
- const initCounts = {};
- localEvents.forEach(ev => {
- initCounts[ev.id] = ev.follower_count || 0;
- });
- setFollowCountMap(initCounts);
-
- const loadFollowing = async () => {
- try {
- const base = getApiBase();
- const res = await fetch(base + '/api/account/events/following', { credentials: 'include' });
- const data = await res.json();
- if (res.ok && data.success) {
- const map = {};
- (data.data || []).forEach(ev => { map[ev.id] = true; });
- setFollowingMap(map);
- logger.debug('EventList', '关注状态加载成功', {
- followingCount: Object.keys(map).length
- });
- }
- } catch (e) {
- logger.warn('EventList', '加载关注状态失败', { error: e.message });
- }
- };
- loadFollowing();
- // 仅在 localEvents 更新时重跑
- }, [localEvents]);
-
- const toggleFollow = async (eventId) => {
- try {
- const base = getApiBase();
- const res = await fetch(base + `/api/events/${eventId}/follow`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- credentials: 'include'
- });
- const data = await res.json();
- if (!res.ok || !data.success) throw new Error(data.error || '操作失败');
- const isFollowing = data.data?.is_following;
- const count = data.data?.follower_count ?? 0;
- setFollowingMap(prev => ({ ...prev, [eventId]: isFollowing }));
- setFollowCountMap(prev => ({ ...prev, [eventId]: count }));
- logger.debug('EventList', '关注状态切换成功', {
- eventId,
- isFollowing,
- followerCount: count
- });
- } catch (e) {
- logger.warn('EventList', '关注操作失败', {
- eventId,
- error: e.message
- });
- }
- };
-
- // 处理推送开关切换
- const handlePushToggle = async (e) => {
- const isChecked = e.target.checked;
-
- if (isChecked) {
- // 用户想开启推送
- logger.info('EventList', '用户请求开启推送');
- const permission = await requestBrowserPermission();
-
- if (permission === 'denied') {
- // 权限被拒绝,显示设置指引
- logger.warn('EventList', '用户拒绝了推送权限');
- toast({
- title: '推送权限被拒绝',
- description: '如需开启推送,请在浏览器设置中允许通知权限',
- status: 'warning',
- duration: 5000,
- isClosable: true,
- position: 'top',
- });
- } else if (permission === 'granted') {
- logger.info('EventList', '推送权限已授予');
- }
- } else {
- // 用户想关闭推送 - 提示需在浏览器设置中操作
- logger.info('EventList', '用户尝试关闭推送');
- toast({
- title: '关闭推送通知',
- description: '如需关闭,请在浏览器设置中撤销通知权限',
- status: 'info',
- duration: 5000,
- isClosable: true,
- position: 'top',
- });
- }
- };
-
- // 专业的金融配色方案
- const bgColor = useColorModeValue('gray.50', 'gray.900');
- const cardBg = useColorModeValue('white', 'gray.800');
- const borderColor = useColorModeValue('gray.200', 'gray.700');
- const textColor = useColorModeValue('gray.700', 'gray.200');
- const mutedColor = useColorModeValue('gray.500', 'gray.400');
- const linkColor = useColorModeValue('blue.600', 'blue.400');
- const hoverBg = useColorModeValue('gray.50', 'gray.700');
-
-
- const handleTitleClick = (e, event) => {
- e.preventDefault();
- e.stopPropagation();
- onEventClick(event);
- };
-
- const handleViewDetailClick = (e, eventId) => {
- e.stopPropagation();
- navigate(`/event-detail/${eventId}`);
- };
-
- // 时间轴样式配置(固定使用轻量卡片样式)
- const getTimelineBoxStyle = () => {
- return {
- bg: useColorModeValue('gray.50', 'gray.700'),
- borderColor: useColorModeValue('gray.400', 'gray.500'),
- borderWidth: '2px',
- textColor: useColorModeValue('blue.600', 'blue.400'),
- boxShadow: 'sm',
- };
- };
-
- // 分页组件
- const Pagination = ({ current, total, pageSize, onChange }) => {
- const totalPages = Math.ceil(total / pageSize);
-
- // 计算要显示的页码数组(智能分页)
- const getPageNumbers = () => {
- const delta = 2; // 当前页左右各显示2个页码
- const range = [];
- const rangeWithDots = [];
-
- // 始终显示第1页
- range.push(1);
-
- // 显示当前页附近的页码
- for (let i = current - delta; i <= current + delta; i++) {
- if (i > 1 && i < totalPages) {
- range.push(i);
- }
- }
-
- // 始终显示最后一页(如果总页数>1)
- if (totalPages > 1) {
- range.push(totalPages);
- }
-
- // 去重并排序
- const uniqueRange = [...new Set(range)].sort((a, b) => a - b);
-
- // 添加省略号
- let prev = 0;
- for (const page of uniqueRange) {
- if (page - prev === 2) {
- // 如果只差一个页码,直接显示
- rangeWithDots.push(prev + 1);
- } else if (page - prev > 2) {
- // 如果差距大于2,显示省略号
- rangeWithDots.push('...');
- }
- rangeWithDots.push(page);
- prev = page;
- }
-
- return rangeWithDots;
- };
-
- const pageNumbers = getPageNumbers();
-
- return (
-
-
-
-
- {pageNumbers.map((page, index) => {
- if (page === '...') {
- return (
-
- ...
-
- );
- }
- return (
-
- );
- })}
-
-
-
-
-
- 共 {total} 条
-
-
- );
- };
-
- return (
-
- {/* 顶部控制栏:左空白 + 中间分页器 + 右侧控制(固定sticky) - 铺满全宽 */}
-
-
-
- {/* 左侧占位 */}
-
-
- {/* 中间:分页器 */}
- {pagination.total > 0 && localEvents.length > 0 ? (
-
-
-
- 第 {pagination.current} / {Math.ceil(pagination.total / pagination.pageSize)} 页
-
-
-
- 共 {pagination.total} 条
-
-
- ) : (
-
- )}
-
- {/* 右侧:控制按钮 */}
-
- {/* WebSocket 连接状态 */}
-
- {isConnected ? '🟢 实时' : '🔴 离线'}
-
-
- {/* 桌面推送开关 */}
-
-
- 推送
-
-
-
-
-
-
- {/* 视图切换控制 */}
-
-
- 精简
-
- setIsCompactMode(e.target.checked)}
- colorScheme="blue"
- />
-
-
-
-
-
-
- {/* 事件列表内容 */}
-
- {localEvents.length > 0 ? (
-
- {localEvents.map((event, index) => (
-
-
-
- ))}
-
- ) : (
-
-
-
-
- 暂无事件数据
-
-
-
- )}
-
- {pagination.total > 0 && (
-
- )}
-
-
- );
-};
-
-export default EventList;
\ No newline at end of file
diff --git a/src/views/Community/components/EventList.js.bak b/src/views/Community/components/EventList.js.bak
deleted file mode 100644
index 36a2f947..00000000
--- a/src/views/Community/components/EventList.js.bak
+++ /dev/null
@@ -1,818 +0,0 @@
-// src/views/Community/components/EventList.js
-import React, { useState, useEffect } from 'react';
-import {
- Box,
- VStack,
- HStack,
- Text,
- Button,
- Badge,
- Tag,
- TagLabel,
- TagLeftIcon,
- Flex,
- Avatar,
- Tooltip,
- IconButton,
- Divider,
- Container,
- useColorModeValue,
- Circle,
- Stat,
- StatNumber,
- StatHelpText,
- StatArrow,
- ButtonGroup,
- Heading,
- SimpleGrid,
- Card,
- CardBody,
- Center,
- Link,
- Spacer,
- Switch,
- FormControl,
- FormLabel,
-} from '@chakra-ui/react';
-import {
- ViewIcon,
- ChatIcon,
- StarIcon,
- TimeIcon,
- InfoIcon,
- WarningIcon,
- WarningTwoIcon,
- CheckCircleIcon,
- TriangleUpIcon,
- TriangleDownIcon,
- ArrowForwardIcon,
- ExternalLinkIcon,
- ViewOffIcon,
-} from '@chakra-ui/icons';
-import { useNavigate } from 'react-router-dom';
-import moment from 'moment';
-import { logger } from '../../../utils/logger';
-
-// ========== 工具函数定义在组件外部 ==========
-// 涨跌颜色配置(中国A股配色:红涨绿跌)- 分档次显示
-const getPriceChangeColor = (value) => {
- if (value === null || value === undefined) return 'gray.500';
-
- const absValue = Math.abs(value);
-
- if (value > 0) {
- // 上涨用红色,根据涨幅大小使用不同深浅
- if (absValue >= 3) return 'red.600'; // 深红色:3%以上
- if (absValue >= 1) return 'red.500'; // 中红色:1-3%
- return 'red.400'; // 浅红色:0-1%
- } else if (value < 0) {
- // 下跌用绿色,根据跌幅大小使用不同深浅
- if (absValue >= 3) return 'green.600'; // 深绿色:3%以上
- if (absValue >= 1) return 'green.500'; // 中绿色:1-3%
- return 'green.400'; // 浅绿色:0-1%
- }
- return 'gray.500';
-};
-
-const getPriceChangeBg = (value) => {
- if (value === null || value === undefined) return 'gray.50';
-
- const absValue = Math.abs(value);
-
- if (value > 0) {
- // 上涨背景色
- if (absValue >= 3) return 'red.100'; // 深色背景:3%以上
- if (absValue >= 1) return 'red.50'; // 中色背景:1-3%
- return 'red.50'; // 浅色背景:0-1%
- } else if (value < 0) {
- // 下跌背景色
- if (absValue >= 3) return 'green.100'; // 深色背景:3%以上
- if (absValue >= 1) return 'green.50'; // 中色背景:1-3%
- return 'green.50'; // 浅色背景:0-1%
- }
- return 'gray.50';
-};
-
-const getPriceChangeBorderColor = (value) => {
- if (value === null || value === undefined) return 'gray.300';
-
- const absValue = Math.abs(value);
-
- if (value > 0) {
- // 上涨边框色
- if (absValue >= 3) return 'red.500'; // 深边框:3%以上
- if (absValue >= 1) return 'red.400'; // 中边框:1-3%
- return 'red.300'; // 浅边框:0-1%
- } else if (value < 0) {
- // 下跌边框色
- if (absValue >= 3) return 'green.500'; // 深边框:3%以上
- if (absValue >= 1) return 'green.400'; // 中边框:1-3%
- return 'green.300'; // 浅边框:0-1%
- }
- return 'gray.300';
-};
-
-// 重要性等级配置 - 金融配色方案
-const importanceLevels = {
- 'S': {
- color: 'purple.600',
- bgColor: 'purple.50',
- borderColor: 'purple.200',
- icon: WarningIcon,
- label: '极高',
- dotBg: 'purple.500',
- },
- 'A': {
- color: 'red.600',
- bgColor: 'red.50',
- borderColor: 'red.200',
- icon: WarningTwoIcon,
- label: '高',
- dotBg: 'red.500',
- },
- 'B': {
- color: 'orange.600',
- bgColor: 'orange.50',
- borderColor: 'orange.200',
- icon: InfoIcon,
- label: '中',
- dotBg: 'orange.500',
- },
- 'C': {
- color: 'green.600',
- bgColor: 'green.50',
- borderColor: 'green.200',
- icon: CheckCircleIcon,
- label: '低',
- dotBg: 'green.500',
- }
-};
-
-const getImportanceConfig = (importance) => {
- return importanceLevels[importance] || importanceLevels['C'];
-};
-
-// 自定义的涨跌箭头组件(修复颜色问题)
-const PriceArrow = ({ value }) => {
- if (value === null || value === undefined) return null;
-
- const Icon = value > 0 ? TriangleUpIcon : TriangleDownIcon;
- const color = value > 0 ? 'red.500' : 'green.500';
-
- return ;
-};
-
-// ========== 主组件 ==========
-const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetail }) => {
- const navigate = useNavigate();
- const [isCompactMode, setIsCompactMode] = useState(false); // 新增:紧凑模式状态
- const [followingMap, setFollowingMap] = useState({});
- const [followCountMap, setFollowCountMap] = useState({});
-
- // 初始化关注状态与计数
- useEffect(() => {
- // 初始化计数映射
- const initCounts = {};
- events.forEach(ev => {
- initCounts[ev.id] = ev.follower_count || 0;
- });
- setFollowCountMap(initCounts);
-
- const loadFollowing = async () => {
- try {
- const base = (process.env.NODE_ENV === 'production' ? '' : process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001');
- const res = await fetch(base + '/api/account/events/following', { credentials: 'include' });
- const data = await res.json();
- if (res.ok && data.success) {
- const map = {};
- (data.data || []).forEach(ev => { map[ev.id] = true; });
- setFollowingMap(map);
- logger.debug('EventList', '关注状态加载成功', {
- followingCount: Object.keys(map).length
- });
- }
- } catch (e) {
- logger.warn('EventList', '加载关注状态失败', { error: e.message });
- }
- };
- loadFollowing();
- // 仅在 events 更新时重跑
- }, [events]);
-
- const toggleFollow = async (eventId) => {
- try {
- const base = (process.env.NODE_ENV === 'production' ? '' : process.env.REACT_APP_API_URL || 'http://49.232.185.254:5001');
- const res = await fetch(base + `/api/events/${eventId}/follow`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- credentials: 'include'
- });
- const data = await res.json();
- if (!res.ok || !data.success) throw new Error(data.error || '操作失败');
- const isFollowing = data.data?.is_following;
- const count = data.data?.follower_count ?? 0;
- setFollowingMap(prev => ({ ...prev, [eventId]: isFollowing }));
- setFollowCountMap(prev => ({ ...prev, [eventId]: count }));
- logger.debug('EventList', '关注状态切换成功', {
- eventId,
- isFollowing,
- followerCount: count
- });
- } catch (e) {
- logger.warn('EventList', '关注操作失败', {
- eventId,
- error: e.message
- });
- }
- };
-
- // 专业的金融配色方案
- const bgColor = useColorModeValue('gray.50', 'gray.900');
- const cardBg = useColorModeValue('white', 'gray.800');
- const borderColor = useColorModeValue('gray.200', 'gray.700');
- const textColor = useColorModeValue('gray.700', 'gray.200');
- const mutedColor = useColorModeValue('gray.500', 'gray.400');
- const linkColor = useColorModeValue('blue.600', 'blue.400');
- const hoverBg = useColorModeValue('gray.50', 'gray.700');
-
- const renderPriceChange = (value, label) => {
- if (value === null || value === undefined) {
- return (
-
- {label}: --
-
- );
- }
-
- const absValue = Math.abs(value);
- const isPositive = value > 0;
-
- // 根据涨跌幅大小选择不同的颜色深浅
- let colorScheme = 'gray';
- let variant = 'solid';
-
- if (isPositive) {
- // 上涨用红色系
- if (absValue >= 3) {
- colorScheme = 'red';
- variant = 'solid'; // 深色
- } else if (absValue >= 1) {
- colorScheme = 'red';
- variant = 'subtle'; // 中等
- } else {
- colorScheme = 'red';
- variant = 'outline'; // 浅色
- }
- } else {
- // 下跌用绿色系
- if (absValue >= 3) {
- colorScheme = 'green';
- variant = 'solid'; // 深色
- } else if (absValue >= 1) {
- colorScheme = 'green';
- variant = 'subtle'; // 中等
- } else {
- colorScheme = 'green';
- variant = 'outline'; // 浅色
- }
- }
-
- const Icon = isPositive ? TriangleUpIcon : TriangleDownIcon;
-
- return (
-
-
-
- {label}: {isPositive ? '+' : ''}{value.toFixed(2)}%
-
-
- );
- };
-
- const handleTitleClick = (e, event) => {
- e.preventDefault();
- e.stopPropagation();
- onEventClick(event);
- };
-
- const handleViewDetailClick = (e, eventId) => {
- e.stopPropagation();
- navigate(`/event-detail/${eventId}`);
- };
-
- // 精简模式的事件渲染
- const renderCompactEvent = (event) => {
- const importance = getImportanceConfig(event.importance);
- const isFollowing = !!followingMap[event.id];
- const followerCount = followCountMap[event.id] ?? (event.follower_count || 0);
-
- return (
-
- {/* 时间线和重要性标记 */}
-
-
- {event.importance || 'C'}
-
-
-
-
- {/* 精简事件卡片 */}
- onEventClick(event)}
- mb={3}
- >
-
-
- {/* 左侧:标题和时间 */}
-
- handleTitleClick(e, event)}
- cursor="pointer"
- noOfLines={1}
- >
- {event.title}
-
-
-
- {moment(event.created_at).format('MM-DD HH:mm')}
- •
- {event.creator?.username || 'Anonymous'}
-
-
-
- {/* 右侧:涨跌幅指标 */}
-
-
-
-
-
-
- {event.related_avg_chg != null
- ? `${event.related_avg_chg > 0 ? '+' : ''}${event.related_avg_chg.toFixed(2)}%`
- : '--'}
-
-
-
-
-
-
- }
- onClick={(e) => {
- e.stopPropagation();
- toggleFollow(event.id);
- }}
- >
- {isFollowing ? '已关注' : '关注'} {followerCount ? `(${followerCount})` : ''}
-
-
-
-
-
-
- );
- };
-
- // 详细模式的事件渲染(原有的渲染方式,但修复了箭头颜色)
- const renderDetailedEvent = (event) => {
- const importance = getImportanceConfig(event.importance);
- const isFollowing = !!followingMap[event.id];
- const followerCount = followCountMap[event.id] ?? (event.follower_count || 0);
-
- return (
-
- {/* 时间线和重要性标记 */}
-
-
- {event.importance || 'C'}
-
-
-
-
- {/* 事件卡片 */}
- onEventClick(event)}
- mb={4}
- >
-
-
- {/* 标题和重要性标签 */}
-
-
- handleTitleClick(e, event)}
- cursor="pointer"
- >
- {event.title}
-
-
-
- {importance.label}优先级
-
-
-
- {/* 元信息 */}
-
-
-
- {moment(event.created_at).format('YYYY-MM-DD HH:mm')}
-
- •
- {event.creator?.username || 'Anonymous'}
-
-
- {/* 描述 */}
-
- {event.description}
-
-
- {/* 价格变化指标 */}
-
-
-
-
-
-
- 平均涨幅
-
-
- {event.related_avg_chg != null ? (
-
-
-
- {event.related_avg_chg > 0 ? '+' : ''}{event.related_avg_chg.toFixed(2)}%
-
-
- ) : (
- --
- )}
-
-
-
-
-
-
-
-
-
- 最大涨幅
-
-
- {event.related_max_chg != null ? (
-
-
-
- {event.related_max_chg > 0 ? '+' : ''}{event.related_max_chg.toFixed(2)}%
-
-
- ) : (
- --
- )}
-
-
-
-
-
-
-
-
-
- 周涨幅
-
-
- {event.related_week_chg != null ? (
-
-
-
- {event.related_week_chg > 0 ? '+' : ''}{event.related_week_chg.toFixed(2)}%
-
-
- ) : (
- --
- )}
-
-
-
-
-
-
-
-
-
- {/* 统计信息和操作按钮 */}
-
-
-
-
-
- {event.view_count || 0}
-
-
-
-
-
- {event.post_count || 0}
-
-
-
-
-
- {followerCount}
-
-
-
-
-
- }
- onClick={(e) => {
- e.stopPropagation();
- onEventClick(event);
- }}
- >
- 快速查看
-
- }
- onClick={(e) => handleViewDetailClick(e, event.id)}
- >
- 详细信息
-
- }
- onClick={(e) => {
- e.stopPropagation();
- toggleFollow(event.id);
- }}
- >
- {isFollowing ? '已关注' : '关注'}
-
-
-
-
-
-
-
- );
- };
-
- // 分页组件
- const Pagination = ({ current, total, pageSize, onChange }) => {
- const totalPages = Math.ceil(total / pageSize);
-
- return (
-
-
-
-
- {[...Array(Math.min(5, totalPages))].map((_, i) => {
- const pageNum = i + 1;
- return (
-
- );
- })}
- {totalPages > 5 && ...}
- {totalPages > 5 && (
-
- )}
-
-
-
-
-
- 共 {total} 条
-
-
- );
- };
-
- return (
-
-
- {/* 视图切换控制 */}
-
-
-
- 精简模式
-
- setIsCompactMode(e.target.checked)}
- colorScheme="blue"
- />
-
-
-
- {events.length > 0 ? (
-
- {events.map((event, index) => (
-
- {isCompactMode
- ? renderCompactEvent(event)
- : renderDetailedEvent(event)
- }
-
- ))}
-
- ) : (
-
-
-
-
- 暂无事件数据
-
-
-
- )}
-
- {pagination.total > 0 && (
-
- )}
-
-
- );
-};
-
-export default EventList;
\ No newline at end of file
diff --git a/src/views/Community/components/EventListSection.js b/src/views/Community/components/EventListSection.js
deleted file mode 100644
index 9a17759e..00000000
--- a/src/views/Community/components/EventListSection.js
+++ /dev/null
@@ -1,75 +0,0 @@
-// src/views/Community/components/EventListSection.js
-// 事件列表区域组件(包含Loading、Empty、List三种状态)
-
-import React from 'react';
-import {
- Box,
- Center,
- VStack,
- Spinner,
- Text
-} from '@chakra-ui/react';
-import EventList from './EventList';
-
-/**
- * 事件列表区域组件
- * @param {boolean} loading - 加载状态
- * @param {Array} events - 事件列表
- * @param {Object} pagination - 分页信息
- * @param {Function} onPageChange - 分页变化回调
- * @param {Function} onEventClick - 事件点击回调
- * @param {Function} onViewDetail - 查看详情回调
- */
-const EventListSection = ({
- loading,
- events,
- pagination,
- onPageChange,
- onEventClick,
- onViewDetail
-}) => {
- // ✅ 最小高度,避免加载后高度突变
- const minHeight = '600px';
-
- // Loading 状态
- if (loading) {
- return (
-
-
-
-
- 正在加载最新事件...
-
-
-
- );
- }
-
- // Empty 状态
- if (!events || events.length === 0) {
- return (
-
-
-
- 暂无事件数据
-
-
-
- );
- }
-
- // List 状态
- return (
-
-
-
- );
-};
-
-export default EventListSection;
diff --git a/src/views/Community/components/EventTimelineHeader.js b/src/views/Community/components/EventTimelineHeader.js
deleted file mode 100644
index 4ec577bd..00000000
--- a/src/views/Community/components/EventTimelineHeader.js
+++ /dev/null
@@ -1,42 +0,0 @@
-// src/views/Community/components/EventTimelineHeader.js
-// 事件时间轴标题组件
-
-import React from 'react';
-import {
- Flex,
- VStack,
- HStack,
- Heading,
- Text,
- Badge
-} from '@chakra-ui/react';
-import { TimeIcon } from '@chakra-ui/icons';
-
-/**
- * 事件时间轴标题组件
- * @param {Date} lastUpdateTime - 最后更新时间
- */
-const EventTimelineHeader = ({ lastUpdateTime }) => {
- return (
-
-
-
-
-
- 实时事件
-
-
-
- 全网监控
- 智能捕获
- 深度分析
-
-
-
- 最后更新: {lastUpdateTime.toLocaleTimeString()}
-
-
- );
-};
-
-export default EventTimelineHeader;
diff --git a/src/views/Community/components/ImportanceLegend.js b/src/views/Community/components/ImportanceLegend.js
deleted file mode 100644
index 7e3bfc5c..00000000
--- a/src/views/Community/components/ImportanceLegend.js
+++ /dev/null
@@ -1,34 +0,0 @@
-// src/views/Community/components/ImportanceLegend.js
-import React from 'react';
-import { Card, Space, Badge } from 'antd';
-
-const ImportanceLegend = () => {
- const levels = [
- { level: 'S', color: '#ff4d4f', description: '重大事件,市场影响深远' },
- { level: 'A', color: '#faad14', description: '重要事件,影响较大' },
- { level: 'B', color: '#1890ff', description: '普通事件,有一定影响' },
- { level: 'C', color: '#52c41a', description: '参考事件,影响有限' }
- ];
-
- return (
-
-
- {levels.map(item => (
-
-
- {item.level}级
- {item.description}
-
- }
- />
-
- ))}
-
-
- );
-};
-
-export default ImportanceLegend;
\ No newline at end of file
diff --git a/src/views/Community/components/IndustryCascader.js b/src/views/Community/components/IndustryCascader.js
deleted file mode 100644
index d5f0f41b..00000000
--- a/src/views/Community/components/IndustryCascader.js
+++ /dev/null
@@ -1,78 +0,0 @@
-// src/views/Community/components/IndustryCascader.js
-import React, { useState, useCallback } from 'react';
-import { Card, Form, Cascader } from 'antd';
-import { useSelector, useDispatch } from 'react-redux';
-import { fetchIndustryData, selectIndustryData, selectIndustryLoading } from '../../../store/slices/industrySlice';
-import { logger } from '../../../utils/logger';
-
-const IndustryCascader = ({ onFilterChange, loading }) => {
- const [industryCascaderValue, setIndustryCascaderValue] = useState([]);
-
- // 使用 Redux 获取行业数据
- const dispatch = useDispatch();
- const industryData = useSelector(selectIndustryData);
- const industryLoading = useSelector(selectIndustryLoading);
-
- // Cascader 获得焦点时加载数据
- const handleCascaderFocus = useCallback(async () => {
- if (!industryData || industryData.length === 0) {
- logger.debug('IndustryCascader', 'Cascader 获得焦点,开始加载行业数据');
- await dispatch(fetchIndustryData());
- }
- }, [dispatch, industryData]);
-
- // Cascader 选择变化
- const handleIndustryCascaderChange = (value, selectedOptions) => {
- setIndustryCascaderValue(value);
-
- if (value && value.length > 0) {
- // value[0] = 分类体系名称
- // value[1...n] = 行业代码(一级~四级)
- const industryCode = value[value.length - 1]; // 最后一级的 code
- const classification = value[0]; // 分类体系名称
-
- onFilterChange('industry_classification', classification);
- onFilterChange('industry_code', industryCode);
-
- logger.debug('IndustryCascader', 'Cascader 选择变化', {
- value,
- classification,
- industryCode,
- path: selectedOptions.map(o => o.label).join(' > ')
- });
- } else {
- // 清空
- onFilterChange('industry_classification', '');
- onFilterChange('industry_code', '');
- }
- };
-
- return (
-
-
- labels.join(' > ')}
- showSearch={{
- filter: (inputValue, path) =>
- path.some(option => option.label.toLowerCase().includes(inputValue.toLowerCase()))
- }}
- style={{ width: '100%' }}
- />
-
-
-
- );
-};
-
-export default IndustryCascader;
diff --git a/src/views/Community/components/MarketReviewCard.js b/src/views/Community/components/MarketReviewCard.js
deleted file mode 100644
index 77164be4..00000000
--- a/src/views/Community/components/MarketReviewCard.js
+++ /dev/null
@@ -1,300 +0,0 @@
-// src/views/Community/components/MarketReviewCard.js
-// 市场复盘组件(左右布局:事件列表 | 事件详情)
-
-import React, { forwardRef, useState } from 'react';
-import {
- Card,
- CardHeader,
- CardBody,
- Box,
- Flex,
- VStack,
- HStack,
- Heading,
- Text,
- Badge,
- Center,
- Spinner,
- useColorModeValue,
- Grid,
- GridItem,
-} from '@chakra-ui/react';
-import { TimeIcon, InfoIcon } from '@chakra-ui/icons';
-import dayjs from 'dayjs';
-import CompactEventCard from './EventCard/CompactEventCard';
-import EventHeader from './EventCard/EventHeader';
-import EventStats from './EventCard/EventStats';
-import EventFollowButton from './EventCard/EventFollowButton';
-import EventPriceDisplay from './EventCard/EventPriceDisplay';
-import EventDescription from './EventCard/EventDescription';
-import { getImportanceConfig } from '../../../constants/importanceLevels';
-
-/**
- * 市场复盘 - 左右布局卡片组件
- * @param {Array} events - 事件列表
- * @param {boolean} loading - 加载状态
- * @param {Date} lastUpdateTime - 最后更新时间
- * @param {Function} onEventClick - 事件点击回调
- * @param {Function} onViewDetail - 查看详情回调
- * @param {Function} onToggleFollow - 切换关注回调
- * @param {Object} ref - 用于滚动的ref
- */
-const MarketReviewCard = forwardRef(({
- events,
- loading,
- lastUpdateTime,
- onEventClick,
- onViewDetail,
- onToggleFollow,
- ...rest
-}, ref) => {
- const cardBg = useColorModeValue('white', 'gray.800');
- const borderColor = useColorModeValue('gray.200', 'gray.700');
- const linkColor = useColorModeValue('blue.600', 'blue.400');
- const mutedColor = useColorModeValue('gray.500', 'gray.400');
- const textColor = useColorModeValue('gray.700', 'gray.200');
- const selectedBg = useColorModeValue('blue.50', 'blue.900');
-
- // 选中的事件
- const [selectedEvent, setSelectedEvent] = useState(null);
-
- // 时间轴样式配置
- const getTimelineBoxStyle = () => {
- return {
- bg: useColorModeValue('gray.50', 'gray.700'),
- borderColor: useColorModeValue('gray.400', 'gray.500'),
- borderWidth: '2px',
- textColor: useColorModeValue('blue.600', 'blue.400'),
- boxShadow: 'sm',
- };
- };
-
- // 处理事件点击
- const handleEventClick = (event) => {
- setSelectedEvent(event);
- if (onEventClick) {
- onEventClick(event);
- }
- };
-
- // 渲染右侧事件详情
- const renderEventDetail = () => {
- if (!selectedEvent) {
- return (
-
-
-
-
- 请从左侧选择事件查看详情
-
-
-
- );
- }
-
- const importance = getImportanceConfig(selectedEvent.importance);
-
- return (
-
-
-
- {/* 第一行:标题+优先级 | 统计+关注 */}
-
- {/* 左侧:标题 + 优先级标签 */}
- {
- e.preventDefault();
- e.stopPropagation();
- if (onViewDetail) {
- onViewDetail(e, selectedEvent.id);
- }
- }}
- linkColor={linkColor}
- compact={false}
- size="lg"
- />
-
- {/* 右侧:统计数据 + 关注按钮 */}
-
- {/* 统计数据 */}
-
-
- {/* 关注按钮 */}
- onToggleFollow && onToggleFollow(selectedEvent.id)}
- size="sm"
- showCount={false}
- />
-
-
-
- {/* 第二行:价格标签 | 时间+作者 */}
-
- {/* 左侧:价格标签 */}
-
-
- {/* 右侧:时间 + 作者 */}
-
-
- {dayjs(selectedEvent.created_at).format('YYYY-MM-DD HH:mm')}
-
- •
- @{selectedEvent.creator?.username || 'Anonymous'}
-
-
-
- {/* 第三行:描述文字 */}
-
-
-
-
- );
- };
-
- return (
-
- {/* 标题部分 */}
-
-
-
-
-
-
- 市场复盘
-
-
-
- 复盘
- 总结
- 完整
-
-
-
- 最后更新: {lastUpdateTime?.toLocaleTimeString() || '未知'}
-
-
-
-
- {/* 主体内容 */}
-
- {/* Loading 状态 */}
- {loading && (
-
-
-
- 正在加载复盘数据...
-
-
- )}
-
- {/* Empty 状态 */}
- {!loading && (!events || events.length === 0) && (
-
-
- 暂无复盘数据
-
-
- )}
-
- {/* 左右布局:事件列表 | 事件详情 */}
- {!loading && events && events.length > 0 && (
-
- {/* 左侧:事件列表 (33.3%) */}
-
-
-
- {events.map((event, index) => (
- handleEventClick(event)}
- cursor="pointer"
- bg={selectedEvent?.id === event.id ? selectedBg : 'transparent'}
- borderRadius="md"
- transition="all 0.2s"
- _hover={{ bg: selectedBg }}
- >
- handleEventClick(event)}
- onTitleClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- handleEventClick(event);
- }}
- onViewDetail={onViewDetail}
- onToggleFollow={() => {}}
- timelineStyle={getTimelineBoxStyle()}
- borderColor={borderColor}
- />
-
- ))}
-
-
-
-
- {/* 右侧:事件详情 (66.7%) */}
-
- {renderEventDetail()}
-
-
- )}
-
-
- );
-});
-
-MarketReviewCard.displayName = 'MarketReviewCard';
-
-export default MarketReviewCard;
diff --git a/src/views/Community/components/UnifiedSearchBox.js b/src/views/Community/components/UnifiedSearchBox.js
deleted file mode 100644
index a514ba2d..00000000
--- a/src/views/Community/components/UnifiedSearchBox.js
+++ /dev/null
@@ -1,898 +0,0 @@
-// src/views/Community/components/UnifiedSearchBox.js
-// 搜索组件:三行布局(主搜索 + 热门概念 + 筛选区)
-import React, { useState, useMemo, useEffect, useCallback, useRef } from 'react';
-import {
- Card, Input, Cascader, Button, Space, Tag, AutoComplete, Select as AntSelect
-} from 'antd';
-import {
- SearchOutlined, CloseCircleOutlined, StockOutlined
-} from '@ant-design/icons';
-import dayjs from 'dayjs';
-import debounce from 'lodash/debounce';
-import { useSelector, useDispatch } from 'react-redux';
-import { fetchIndustryData, selectIndustryData, selectIndustryLoading } from '../../../store/slices/industrySlice';
-import { stockService } from '../../../services/stockService';
-import { logger } from '../../../utils/logger';
-import PopularKeywords from './PopularKeywords';
-import TradingTimeFilter from './TradingTimeFilter';
-
-const { Option } = AntSelect;
-
-const UnifiedSearchBox = ({
- onSearch,
- onSearchFocus,
- popularKeywords = [],
- filters = {},
- mode, // 显示模式(如:vertical, horizontal 等)
- pageSize, // 每页显示数量
- trackingFunctions = {} // PostHog 追踪函数集合
-}) => {
-
- // 其他状态
- const [stockOptions, setStockOptions] = useState([]); // 股票下拉选项列表
- const [allStocks, setAllStocks] = useState([]); // 所有股票数据
- const [industryValue, setIndustryValue] = useState([]);
-
- // 筛选条件状态
- const [sort, setSort] = useState('new'); // 排序方式
- const [importance, setImportance] = useState([]); // 重要性(数组,支持多选)
- const [tradingTimeRange, setTradingTimeRange] = useState(null); // 交易时段筛选
-
- // ✅ 本地输入状态 - 管理用户的实时输入
- const [inputValue, setInputValue] = useState('');
-
- // 使用 Redux 获取行业数据
- const dispatch = useDispatch();
- const industryData = useSelector(selectIndustryData);
- const industryLoading = useSelector(selectIndustryLoading);
-
- // 加载行业数据函数
- const loadIndustryData = useCallback(() => {
- if (!industryData) {
- dispatch(fetchIndustryData());
- }
- }, [dispatch, industryData]);
-
- // 搜索触发函数
- const triggerSearch = useCallback((params) => {
- logger.debug('UnifiedSearchBox', '【5/5】✅ 最终触发搜索 - 调用onSearch回调', {
- params: params,
- timestamp: new Date().toISOString()
- });
- onSearch(params);
- }, [onSearch]);
-
- // ✅ 创建防抖的搜索函数(300ms 延迟)
- const debouncedSearchRef = useRef(null);
-
- useEffect(() => {
- // 创建防抖函数,使用 triggerSearch 而不是直接调用 onSearch
- debouncedSearchRef.current = debounce((params) => {
- logger.debug('UnifiedSearchBox', '⏱️ 防抖延迟结束,执行搜索', {
- params: params,
- delayMs: 300
- });
- triggerSearch(params);
- }, 300);
-
- // 清理函数
- return () => {
- if (debouncedSearchRef.current) {
- debouncedSearchRef.current.cancel();
- }
- };
- }, [triggerSearch]);
-
- // 加载所有股票数据
- useEffect(() => {
- const loadStocks = async () => {
- const response = await stockService.getAllStocks();
- if (response.success && response.data) {
- setAllStocks(response.data);
- logger.debug('UnifiedSearchBox', '股票数据加载成功', {
- count: response.data.length
- });
- }
- };
-
- loadStocks();
- }, []);
-
- // Cascader 获得焦点时加载数据
- const handleCascaderFocus = async () => {
- if (!industryData || industryData.length === 0) {
- logger.debug('UnifiedSearchBox', 'Cascader 获得焦点,开始加载行业数据');
- await loadIndustryData();
- }
- };
-
- // 从 props.filters 初始化所有内部状态 (只在组件首次挂载时执行)
- // 辅助函数:递归查找行业代码的完整路径
- const findIndustryPath = React.useCallback((targetCode, data, currentPath = []) => {
- if (!data || data.length === 0) return null;
-
- for (const item of data) {
- const newPath = [...currentPath, item.value];
-
- if (item.value === targetCode) {
- return newPath;
- }
-
- if (item.children && item.children.length > 0) {
- const found = findIndustryPath(targetCode, item.children, newPath);
- if (found) return found;
- }
- }
- return null;
- }, []);
-
- // ✅ 从 props.filters 初始化筛选条件和输入框值
- useEffect(() => {
- if (!filters) return;
-
- // 初始化排序
- if (filters.sort) setSort(filters.sort);
-
- // 初始化重要性(字符串解析为数组)
- if (filters.importance) {
- const importanceArray = filters.importance === 'all'
- ? [] // 'all' 对应空数组(不显示任何选中)
- : filters.importance.split(',').map(v => v.trim()).filter(Boolean);
- setImportance(importanceArray);
- logger.debug('UnifiedSearchBox', '初始化重要性', {
- filters_importance: filters.importance,
- importanceArray
- });
- } else {
- setImportance([]);
- }
-
- // ✅ 初始化行业分类(需要 industryData 加载完成)
- // ⚠️ 只在 industryValue 为空时才从 filters 初始化,避免用户选择后被覆盖
- if (filters.industry_code && industryData && industryData.length > 0 && (!industryValue || industryValue.length === 0)) {
- const path = findIndustryPath(filters.industry_code, industryData);
- if (path) {
- setIndustryValue(path);
- logger.debug('UnifiedSearchBox', '初始化行业分类', {
- industry_code: filters.industry_code,
- path
- });
- }
- } else if (!filters.industry_code && industryValue && industryValue.length > 0) {
- // 如果 filters 中没有行业代码,但本地有值,清空本地值
- setIndustryValue([]);
- logger.debug('UnifiedSearchBox', '清空行业分类(filters中无值)');
- }
-
- // ✅ 同步 filters.q 到输入框显示值
- if (filters.q) {
- setInputValue(filters.q);
- } else if (!filters.q) {
- // 如果 filters 中没有搜索关键词,清空输入框
- setInputValue('');
- }
-
- // ✅ 初始化时间筛选(从 filters 中恢复)
- // ⚠️ 只在 tradingTimeRange 为空时才从 filters 初始化,避免用户选择后被覆盖
- const hasTimeInFilters = filters.start_date || filters.end_date || filters.recent_days;
-
- if (hasTimeInFilters && (!tradingTimeRange || !tradingTimeRange.key)) {
- // 根据参数推断按钮 key
- let inferredKey = 'custom';
- let inferredLabel = '';
-
- if (filters.recent_days) {
- // 推断是否是预设按钮
- if (filters.recent_days === '7') {
- inferredKey = 'week';
- inferredLabel = '近一周';
- } else if (filters.recent_days === '30') {
- inferredKey = 'month';
- inferredLabel = '近一月';
- } else {
- inferredLabel = `近${filters.recent_days}天`;
- }
- } else if (filters.start_date && filters.end_date) {
- inferredLabel = `${dayjs(filters.start_date).format('MM-DD HH:mm')} - ${dayjs(filters.end_date).format('MM-DD HH:mm')}`;
- }
-
- // 从 filters 重建 tradingTimeRange 状态
- const timeRange = {
- start_date: filters.start_date || '',
- end_date: filters.end_date || '',
- recent_days: filters.recent_days || '',
- label: inferredLabel,
- key: inferredKey
- };
- setTradingTimeRange(timeRange);
- logger.debug('UnifiedSearchBox', '初始化时间筛选', {
- filters_time: {
- start_date: filters.start_date,
- end_date: filters.end_date,
- recent_days: filters.recent_days
- },
- tradingTimeRange: timeRange
- });
- } else if (!hasTimeInFilters && tradingTimeRange) {
- // 如果 filters 中没有时间参数,但本地有值,清空本地值
- setTradingTimeRange(null);
- logger.debug('UnifiedSearchBox', '清空时间筛选(filters中无值)');
- }
- }, [filters.sort, filters.importance, filters.industry_code, filters.q, filters.start_date, filters.end_date, filters.recent_days, industryData, findIndustryPath, industryValue, tradingTimeRange]);
-
- // AutoComplete 搜索股票(模糊匹配 code 或 name)
- const handleSearch = (value) => {
- if (!value || !allStocks || allStocks.length === 0) {
- setStockOptions([]);
- return;
- }
-
- // 使用 stockService 进行模糊搜索
- const results = stockService.fuzzySearch(value, allStocks, 10);
-
- // 转换为 AutoComplete 选项格式
- const options = results.map(stock => ({
- value: stock.code,
- label: (
-
-
- {stock.code}
- {stock.name}
-
- ),
- // 保存完整的股票信息,用于选中后显示
- stockInfo: stock
- }));
-
- setStockOptions(options);
- logger.debug('UnifiedSearchBox', '股票模糊搜索', {
- query: value,
- resultCount: options.length
- });
- };
-
- // ✅ 选中股票(从下拉选择) - 更新输入框并触发搜索
- const handleStockSelect = (_value, option) => {
- const stockInfo = option.stockInfo;
- if (stockInfo) {
- logger.debug('UnifiedSearchBox', '选中股票', {
- code: stockInfo.code,
- name: stockInfo.name
- });
-
- // 🎯 追踪股票点击
- if (trackingFunctions.trackRelatedStockClicked) {
- trackingFunctions.trackRelatedStockClicked({
- stockCode: stockInfo.code,
- stockName: stockInfo.name,
- source: 'search_box_autocomplete',
- timestamp: new Date().toISOString(),
- });
- }
-
- // 更新输入框显示
- setInputValue(`${stockInfo.code} ${stockInfo.name}`);
-
- // 直接构建参数并触发搜索 - 使用股票代码作为 q 参数
- const params = buildFilterParams({
- q: stockInfo.code, // 使用股票代码作为搜索关键词
- industry_code: ''
- });
- logger.debug('UnifiedSearchBox', '自动触发股票搜索', params);
- triggerSearch(params);
- }
- };
-
- // ✅ 重要性变化(立即执行)- 支持多选
- const handleImportanceChange = (value) => {
- logger.debug('UnifiedSearchBox', '重要性值改变', {
- oldValue: importance,
- newValue: value
- });
-
- setImportance(value);
-
- // 取消之前的防抖搜索
- if (debouncedSearchRef.current) {
- debouncedSearchRef.current.cancel();
- }
-
- // 转换为逗号分隔字符串传给后端(空数组表示"全部")
- const importanceStr = value.length === 0 ? 'all' : value.join(',');
-
- // 🎯 追踪筛选操作
- if (trackingFunctions.trackNewsFilterApplied) {
- trackingFunctions.trackNewsFilterApplied({
- filterType: 'importance',
- filterValue: importanceStr,
- timestamp: new Date().toISOString(),
- });
- }
-
- // 立即触发搜索
- const params = buildFilterParams({ importance: importanceStr });
- logger.debug('UnifiedSearchBox', '重要性改变,立即触发搜索', params);
-
- triggerSearch(params);
- };
-
- // ✅ 排序变化(立即触发搜索)
- const handleSortChange = (value) => {
- logger.debug('UnifiedSearchBox', '排序值改变', {
- oldValue: sort,
- newValue: value
- });
- setSort(value);
-
- // 取消之前的防抖搜索
- if (debouncedSearchRef.current) {
- debouncedSearchRef.current.cancel();
- }
-
- // 🎯 追踪排序操作
- if (trackingFunctions.trackNewsSorted) {
- trackingFunctions.trackNewsSorted({
- sortBy: value,
- previousSortBy: sort,
- timestamp: new Date().toISOString(),
- });
- }
-
- // 立即触发搜索
- const params = buildFilterParams({ sort: value });
- logger.debug('UnifiedSearchBox', '排序改变,立即触发搜索', params);
- triggerSearch(params);
- };
-
- // ✅ 行业分类变化(立即触发搜索)
- const handleIndustryChange = (value) => {
- logger.debug('UnifiedSearchBox', '行业分类值改变', {
- oldValue: industryValue,
- newValue: value
- });
- setIndustryValue(value);
-
- // 取消之前的防抖搜索
- if (debouncedSearchRef.current) {
- debouncedSearchRef.current.cancel();
- }
-
- // 🎯 追踪行业筛选
- if (trackingFunctions.trackNewsFilterApplied) {
- trackingFunctions.trackNewsFilterApplied({
- filterType: 'industry',
- filterValue: value?.[value.length - 1] || '',
- timestamp: new Date().toISOString(),
- });
- }
-
- // 立即触发搜索
- const params = buildFilterParams({
- industry_code: value?.[value.length - 1] || ''
- });
- logger.debug('UnifiedSearchBox', '行业改变,立即触发搜索', params);
-
- triggerSearch(params);
- };
-
- // ✅ 热门概念点击处理(立即搜索,不使用防抖) - 更新输入框并触发搜索
- const handleKeywordClick = (keyword) => {
- // 更新输入框显示
- setInputValue(keyword);
-
- // 立即触发搜索(取消之前的防抖)
- if (debouncedSearchRef.current) {
- debouncedSearchRef.current.cancel();
- }
-
- // 🎯 追踪热门关键词点击
- if (trackingFunctions.trackNewsSearched) {
- trackingFunctions.trackNewsSearched({
- searchQuery: keyword,
- searchType: 'popular_keyword',
- timestamp: new Date().toISOString(),
- });
- }
-
- const params = buildFilterParams({
- q: keyword,
- industry_code: ''
- });
- logger.debug('UnifiedSearchBox', '热门概念点击,立即触发搜索', {
- keyword,
- params
- });
- triggerSearch(params);
- };
-
- // ✅ 交易时段筛选变化(立即触发搜索)
- const handleTradingTimeChange = (timeConfig) => {
- if (!timeConfig) {
- // 清空筛选
- setTradingTimeRange(null);
-
- // 🎯 追踪时间筛选清空
- if (trackingFunctions.trackNewsFilterApplied) {
- trackingFunctions.trackNewsFilterApplied({
- filterType: 'time_range',
- filterValue: 'cleared',
- timestamp: new Date().toISOString(),
- });
- }
-
- const params = buildFilterParams({
- start_date: '',
- end_date: '',
- recent_days: ''
- });
- triggerSearch(params);
- return;
- }
-
- const { range, type, label, key } = timeConfig;
- let params = {};
-
- if (type === 'recent_days') {
- // 近一周/近一月使用 recent_days
- params.recent_days = range;
- params.start_date = '';
- params.end_date = '';
- } else {
- // 其他使用 start_date + end_date
- params.start_date = range[0].format('YYYY-MM-DD HH:mm:ss');
- params.end_date = range[1].format('YYYY-MM-DD HH:mm:ss');
- params.recent_days = '';
- }
-
- setTradingTimeRange({ ...params, label, key });
-
- // 🎯 追踪时间筛选
- if (trackingFunctions.trackNewsFilterApplied) {
- trackingFunctions.trackNewsFilterApplied({
- filterType: 'time_range',
- filterValue: label,
- timeRangeType: type,
- timestamp: new Date().toISOString(),
- });
- }
-
- // 立即触发搜索
- const searchParams = buildFilterParams({ ...params, mode });
- logger.debug('UnifiedSearchBox', '交易时段筛选变化,立即触发搜索', {
- timeConfig,
- params: searchParams
- });
- triggerSearch(searchParams);
- };
-
- // 主搜索(点击搜索按钮或回车)
- const handleMainSearch = () => {
- // 取消之前的防抖
- if (debouncedSearchRef.current) {
- debouncedSearchRef.current.cancel();
- }
-
- // 构建参数并触发搜索 - 使用用户输入作为 q 参数
- const params = buildFilterParams({
- q: inputValue, // 使用用户输入(可能是话题、股票代码、股票名称等)
- industry_code: ''
- });
-
- // 🎯 追踪搜索操作
- if (trackingFunctions.trackNewsSearched && inputValue) {
- trackingFunctions.trackNewsSearched({
- searchQuery: inputValue,
- searchType: 'main_search',
- filters: params,
- timestamp: new Date().toISOString(),
- });
- }
-
- logger.debug('UnifiedSearchBox', '主搜索触发', {
- inputValue,
- params
- });
- triggerSearch(params);
- };
-
- // ✅ 处理输入变化 - 更新本地输入状态
- const handleInputChange = (value) => {
- logger.debug('UnifiedSearchBox', '输入变化', { value });
- setInputValue(value);
- };
-
- // ✅ 生成完整的筛选参数对象 - 直接从 filters 和本地筛选器状态构建
- const buildFilterParams = useCallback((overrides = {}) => {
- logger.debug('UnifiedSearchBox', '🔧 buildFilterParams - 输入参数', {
- overrides: overrides,
- currentState: {
- sort,
- importance,
- industryValue,
- 'filters.q': filters.q,
- mode,
- pageSize
- }
- });
-
- // 处理排序参数 - 将 returns_avg/returns_week 转换为 sort=returns + return_type
- const sortValue = overrides.sort ?? sort;
- let actualSort = sortValue;
- let returnType;
-
- if (sortValue === 'returns_avg') {
- actualSort = 'returns';
- returnType = 'avg';
- } else if (sortValue === 'returns_week') {
- actualSort = 'returns';
- returnType = 'week';
- }
-
- // 处理重要性参数:数组转换为逗号分隔字符串
- let importanceValue = overrides.importance ?? importance;
- if (Array.isArray(importanceValue)) {
- importanceValue = importanceValue.length === 0
- ? 'all'
- : importanceValue.join(',');
- }
-
- const result = {
- // 基础参数(overrides 优先级高于本地状态)
- sort: actualSort,
- importance: importanceValue,
-
-
- // 搜索参数: 统一使用 q 参数进行搜索(话题/股票/关键词)
- q: (overrides.q ?? filters.q) ?? '',
- // 行业代码: 取选中路径的最后一级(最具体的行业代码)
- industry_code: overrides.industry_code ?? (industryValue?.[industryValue.length - 1] || ''),
-
- // 交易时段筛选参数
- start_date: overrides.start_date ?? (tradingTimeRange?.start_date || ''),
- end_date: overrides.end_date ?? (tradingTimeRange?.end_date || ''),
- recent_days: overrides.recent_days ?? (tradingTimeRange?.recent_days || ''),
-
- // 最终 overrides 具有最高优先级
- ...overrides,
- page: 1,
- per_page: overrides.mode === 'four-row' ? 30: 10
- };
-
- // 删除可能来自 overrides 的旧 per_page 值(将由 pageSize 重新设置)
- delete result.per_page;
-
- // 添加 return_type 参数(如果需要)
- if (returnType) {
- result.return_type = returnType;
- }
-
- // 添加 mode 和 per_page 参数(如果提供了的话)
- if (mode !== undefined && mode !== null) {
- result.mode = mode;
- }
- if (pageSize !== undefined && pageSize !== null) {
- result.per_page = pageSize; // 后端实际使用的参数
- }
-
- logger.debug('UnifiedSearchBox', '🔧 buildFilterParams - 输出结果', result);
- return result;
- }, [sort, importance, filters.q, industryValue, tradingTimeRange, mode, pageSize]);
-
- // ✅ 重置筛选 - 清空所有筛选器并触发搜索
- const handleReset = () => {
- console.log('%c🔄 [重置] 开始重置筛选条件', 'color: #FF4D4F; font-weight: bold;');
-
- // 重置所有筛选器状态
- setInputValue(''); // 清空输入框
- setStockOptions([]);
- setIndustryValue([]);
- setSort('new');
- setImportance([]); // 改为空数组
- setTradingTimeRange(null); // 清空交易时段筛选
-
- // 🎯 追踪筛选重置
- if (trackingFunctions.trackNewsFilterApplied) {
- trackingFunctions.trackNewsFilterApplied({
- filterType: 'reset',
- filterValue: 'all_filters_cleared',
- timestamp: new Date().toISOString(),
- });
- }
-
- // 输出重置后的完整参数
- const resetParams = {
- q: '',
- industry_code: '',
- sort: 'new',
- importance: 'all', // 传给后端时转为'all'
- start_date: '',
- end_date: '',
- recent_days: '',
- page: 1,
- _forceRefresh: Date.now() // 添加强制刷新标志,确保每次重置都触发更新
- };
-
- console.log('%c🔄 [重置] 重置参数', 'color: #FF4D4F;', resetParams);
- logger.debug('UnifiedSearchBox', '重置筛选', resetParams);
-
- console.log('%c🔄 [重置] 调用 onSearch', 'color: #FF4D4F;', typeof onSearch);
- onSearch(resetParams);
-
- console.log('%c✅ [重置] 重置完成', 'color: #52C41A; font-weight: bold;');
- };
-
- // 生成已选条件标签(包含所有筛选条件) - 从 filters 和本地状态读取
- const filterTags = useMemo(() => {
- const tags = [];
-
- // 搜索关键词标签 - 从 filters.q 读取
- if (filters.q) {
- tags.push({ key: 'search', label: `搜索: ${filters.q}` });
- }
-
- // 行业标签
- if (industryValue && industryValue.length > 0 && industryData) {
- // 递归查找每个层级的 label
- const findLabel = (code, data) => {
- for (const item of data) {
- if (code.startsWith(item.value)) {
- if (item.value === code) {
- return item.label;
- } else {
- return findLabel(code, item.children);
- }
- }
- }
- return null;
- };
-
- // 只显示最后一级的 label
- const lastLevelCode = industryValue[industryValue.length - 1];
- const lastLevelLabel = findLabel(lastLevelCode, industryData);
-
- tags.push({
- key: 'industry',
- label: `行业: ${lastLevelLabel}`
- });
- }
-
- // 交易时段筛选标签
- if (tradingTimeRange?.label) {
- tags.push({
- key: 'trading_time',
- label: `时间: ${tradingTimeRange.label}`
- });
- }
-
- // 重要性标签(多选合并显示为单个标签)
- if (importance && importance.length > 0) {
- const importanceMap = { 'S': '极高', 'A': '高', 'B': '中', 'C': '低' };
- const importanceLabel = importance.map(imp => importanceMap[imp] || imp).join(', ');
- tags.push({ key: 'importance', label: `重要性: ${importanceLabel}` });
- }
-
- // 排序标签(排除默认值 'new')
- if (sort && sort !== 'new') {
- let sortLabel;
- if (sort === 'hot') sortLabel = '最热';
- else if (sort === 'importance') sortLabel = '重要性';
- else if (sort === 'returns_avg') sortLabel = '平均收益率';
- else if (sort === 'returns_week') sortLabel = '周收益率';
- else sortLabel = sort;
- tags.push({ key: 'sort', label: `排序: ${sortLabel}` });
- }
-
- return tags;
- }, [filters.q, industryValue, importance, sort, tradingTimeRange]);
-
- // ✅ 移除单个标签 - 构建新参数并触发搜索
- const handleRemoveTag = (key) => {
- logger.debug('UnifiedSearchBox', '移除标签', { key });
-
- // 取消所有待执行的防抖搜索(避免旧的防抖覆盖删除操作)
- if (debouncedSearchRef.current) {
- debouncedSearchRef.current.cancel();
- }
-
- if (key === 'search') {
- // 清除搜索关键词和输入框,立即触发搜索
- setInputValue(''); // 清空输入框
- const params = buildFilterParams({ q: '' });
- logger.debug('UnifiedSearchBox', '移除搜索标签后触发搜索', { key, params });
- triggerSearch(params);
- } else if (key === 'industry') {
- // 清除行业选择
- setIndustryValue([]);
- const params = buildFilterParams({ industry_code: '' });
- triggerSearch(params);
- } else if (key === 'trading_time') {
- // 清除交易时段筛选
- setTradingTimeRange(null);
- const params = buildFilterParams({
- start_date: '',
- end_date: '',
- recent_days: ''
- });
- triggerSearch(params);
- } else if (key === 'importance') {
- // 重置重要性为空数组(传给后端为'all')
- setImportance([]);
- const params = buildFilterParams({ importance: 'all' });
- triggerSearch(params);
- } else if (key === 'sort') {
- // 重置排序为默认值
- setSort('new');
- const params = buildFilterParams({ sort: 'new' });
- triggerSearch(params);
- }
- };
-
- return (
-
- {/* 第三行:行业 + 重要性 + 排序 */}
-
- {/* 左侧:筛选器组 */}
-
- 筛选:
- {/* 行业分类 */}
-
- path.some(option =>
- option.label.toLowerCase().includes(inputValue.toLowerCase())
- )
- }}
- allowClear
- expandTrigger="hover"
- displayRender={(labels) => labels.join(' > ')}
- disabled={industryLoading}
- style={{ width: 160 }}
- size="small"
- />
-
- {/* 重要性 */}
-
- 重要性:
-
-
-
-
-
-
-
-
- {/* 搜索图标(可点击) + 搜索框 */}
-
- {
- e.currentTarget.style.color = '#096dd9';
- e.currentTarget.style.background = '#bae7ff';
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.color = '#1890ff';
- e.currentTarget.style.background = '#e6f7ff';
- }}
- />
- {
- if (e.key === 'Enter') {
- handleMainSearch();
- }
- }}
- style={{ flex: 1 }}
- size="small"
- notFoundContent={inputValue && stockOptions.length === 0 ? "未找到匹配的股票" : null}
- />
-
-
- {/* 重置按钮 - 现代化设计 */}
- }
- onClick={handleReset}
- size="small"
- style={{
- borderRadius: 6,
- border: '1px solid #d9d9d9',
- backgroundColor: '#fff',
- color: '#666',
- fontWeight: 500,
- padding: '4px 10px',
- display: 'flex',
- alignItems: 'center',
- gap: 4,
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
- boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)'
- }}
- onMouseEnter={(e) => {
- e.currentTarget.style.borderColor = '#ff4d4f';
- e.currentTarget.style.color = '#ff4d4f';
- e.currentTarget.style.backgroundColor = '#fff1f0';
- e.currentTarget.style.boxShadow = '0 2px 8px rgba(255, 77, 79, 0.15)';
- e.currentTarget.style.transform = 'translateY(-1px)';
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.borderColor = '#d9d9d9';
- e.currentTarget.style.color = '#666';
- e.currentTarget.style.backgroundColor = '#fff';
- e.currentTarget.style.boxShadow = '0 1px 2px rgba(0, 0, 0, 0.05)';
- e.currentTarget.style.transform = 'translateY(0)';
- }}
- >
- 重置
-
-
-
- {/* 右侧:排序 */}
-
- 排序:
-
-
-
-
-
-
-
-
-
-
- {/* 第一行:筛选 + 时间按钮 + 搜索图标 + 搜索框 */}
-
- 时间筛选:
-
- {/* 交易时段筛选 */}
-
-
-
- {/* 第二行:热门概念 */}
-
-
- );
-};
-
-export default UnifiedSearchBox;