Files
vf_react/src/views/Community/components/StockDetailPanel.js
zdl ff0c4d65e1 fix: 修复 StockDetailPanel 导入路径错误
问题:
- StockDetailPanel.js 引用了不存在的相对路径
- ./hooks/... 和 ./components 找不到文件
- 导致编译失败: Module not found

根因:
- hooks 和 components 实际在 ./StockDetailPanel/ 子目录下
- 但导入路径缺少 StockDetailPanel/ 前缀

修复:
-  ./hooks/useEventStocks → ./StockDetailPanel/hooks/useEventStocks
-  ./hooks/useWatchlist → ./StockDetailPanel/hooks/useWatchlist
-  ./hooks/useStockMonitoring → ./StockDetailPanel/hooks/useStockMonitoring
-  ./components → ./StockDetailPanel/components

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 15:32:22 +08:00

347 lines
11 KiB
JavaScript

// src/views/Community/components/StockDetailPanel.js
import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
import { Drawer, Spin, Button, Alert } from 'antd';
import { CloseOutlined, LockOutlined, CrownOutlined } from '@ant-design/icons';
import { Tabs as AntdTabs } from 'antd';
import moment from 'moment';
// Services and Utils
import { eventService } from '../../../services/eventService';
import { logger } from '../../../utils/logger';
import { getApiBase } from '../../../utils/apiConfig';
// Custom Hooks
import { useSubscription } from '../../../hooks/useSubscription';
import { useEventStocks } from './StockDetailPanel/hooks/useEventStocks';
import { useWatchlist } from './StockDetailPanel/hooks/useWatchlist';
import { useStockMonitoring } from './StockDetailPanel/hooks/useStockMonitoring';
// Components
import { RelatedStocksTab, LockedContent } from './StockDetailPanel/components';
import RelatedConcepts from '../../EventDetail/components/RelatedConcepts';
import HistoricalEvents from '../../EventDetail/components/HistoricalEvents';
import TransmissionChainAnalysis from '../../EventDetail/components/TransmissionChainAnalysis';
import EventDiscussionModal from './EventDiscussionModal';
import SubscriptionUpgradeModal from '../../../components/SubscriptionUpgradeModal';
import StockChartAntdModal from '../../../components/StockChart/StockChartAntdModal';
import RiskDisclaimer from '../../../components/RiskDisclaimer';
// Styles
import './StockDetailPanel.css';
/**
* 股票详情 Drawer 组件
* 显示事件相关的股票、概念、历史事件、传导链等信息
*
* @param {boolean} visible - 是否显示
* @param {Object} event - 事件对象
* @param {Function} onClose - 关闭回调
*/
function StockDetailPanel({ visible, event, onClose }) {
logger.debug('StockDetailPanel', '组件加载', {
visible,
eventId: event?.id,
eventTitle: event?.title
});
// ==================== Hooks ====================
// 权限控制
const { hasFeatureAccess, getUpgradeRecommendation } = useSubscription();
// 事件数据管理 (Redux + Hooks)
const {
stocks,
stocksWithQuotes,
quotes,
eventDetail,
historicalEvents,
chainAnalysis,
expectationScore,
loading,
refreshAllData,
refreshQuotes
} = useEventStocks(event?.id, event?.start_time);
// 自选股管理
const {
watchlistSet,
toggleWatchlist
} = useWatchlist();
// 实时监控管理
const {
isMonitoring,
toggleMonitoring,
manualRefresh: refreshMonitoring
} = useStockMonitoring(stocks, event?.start_time);
// ==================== Local State ====================
const [activeTab, setActiveTab] = useState('stocks');
const [searchText, setSearchText] = useState('');
const [filteredStocks, setFilteredStocks] = useState([]);
const [fixedCharts, setFixedCharts] = useState([]);
const [discussionModalVisible, setDiscussionModalVisible] = useState(false);
const [discussionType, setDiscussionType] = useState('事件讨论');
const [upgradeModalOpen, setUpgradeModalOpen] = useState(false);
const [upgradeFeature, setUpgradeFeature] = useState('');
// ==================== Effects ====================
// 过滤股票列表
useEffect(() => {
if (!searchText.trim()) {
setFilteredStocks(stocks);
} else {
const filtered = stocks.filter(stock =>
stock.stock_code.toLowerCase().includes(searchText.toLowerCase()) ||
stock.stock_name.toLowerCase().includes(searchText.toLowerCase())
);
setFilteredStocks(filtered);
}
}, [searchText, stocks]);
// ==================== Event Handlers ====================
// 搜索处理
const handleSearch = useCallback((value) => {
setSearchText(value);
}, []);
// 刷新数据
const handleRefresh = useCallback(() => {
logger.debug('StockDetailPanel', '手动刷新数据');
refreshAllData();
refreshQuotes();
}, [refreshAllData, refreshQuotes]);
// 切换监控
const handleMonitoringToggle = useCallback(() => {
toggleMonitoring();
}, [toggleMonitoring]);
// 自选股切换
const handleWatchlistToggle = useCallback(async (stockCode, isInWatchlist) => {
const stockName = stocks.find(s => s.stock_code === stockCode)?.stock_name || '';
await toggleWatchlist(stockCode, stockName);
}, [stocks, toggleWatchlist]);
// 行点击 - 显示固定图表
const handleRowClick = useCallback((stock) => {
setFixedCharts((prev) => {
if (prev.find(item => item.stock.stock_code === stock.stock_code)) return prev;
return [...prev, { stock, chartType: 'timeline' }];
});
}, []);
// 移除固定图表
const handleUnfixChart = useCallback((stock) => {
setFixedCharts((prev) => prev.filter(item => item.stock.stock_code !== stock.stock_code));
}, []);
// 权限检查和升级提示
const handleUpgradeClick = useCallback((featureName) => {
const recommendation = getUpgradeRecommendation(featureName);
setUpgradeFeature(recommendation?.required || 'pro');
setUpgradeModalOpen(true);
}, [getUpgradeRecommendation]);
// 渲染锁定内容
const renderLockedContent = useCallback((featureName, description) => {
const recommendation = getUpgradeRecommendation(featureName);
const isProRequired = recommendation?.required === 'pro';
return (
<LockedContent
description={description}
isProRequired={isProRequired}
message={recommendation?.message}
onUpgradeClick={() => handleUpgradeClick(featureName)}
/>
);
}, [getUpgradeRecommendation, handleUpgradeClick]);
// 渲染固定图表
const renderFixedCharts = useMemo(() => {
if (fixedCharts.length === 0) return null;
const formattedEventTime = event?.start_time
? moment(event.start_time).format('YYYY-MM-DD HH:mm')
: undefined;
return fixedCharts.map(({ stock }, index) => (
<div key={`fixed-chart-${stock.stock_code}-${index}`}>
<StockChartAntdModal
open={true}
onCancel={() => handleUnfixChart(stock)}
stock={stock}
eventTime={formattedEventTime}
fixed={true}
width={800}
/>
</div>
));
}, [fixedCharts, event, handleUnfixChart]);
// ==================== Tab Items ====================
const tabItems = useMemo(() => [
{
key: 'stocks',
label: (
<span>
相关标的
{!hasFeatureAccess('related_stocks') && (
<LockOutlined style={{ marginLeft: '4px', fontSize: '12px', opacity: 0.6 }} />
)}
</span>
),
children: hasFeatureAccess('related_stocks') ? (
<RelatedStocksTab
stocks={filteredStocks}
quotes={quotes}
eventTime={event?.start_time}
watchlistSet={watchlistSet}
searchText={searchText}
loading={loading.stocks || loading.quotes}
isMonitoring={isMonitoring}
onSearch={handleSearch}
onRefresh={handleRefresh}
onMonitoringToggle={handleMonitoringToggle}
onWatchlistToggle={handleWatchlistToggle}
onRowClick={handleRowClick}
onDiscussionClick={() => {
setDiscussionType('事件讨论');
setDiscussionModalVisible(true);
}}
fixedChartsContent={renderFixedCharts}
/>
) : renderLockedContent('related_stocks', '相关标的')
},
{
key: 'concepts',
label: (
<span>
相关概念
{!hasFeatureAccess('related_concepts') && (
<LockOutlined style={{ marginLeft: '4px', fontSize: '12px', opacity: 0.6 }} />
)}
</span>
),
children: hasFeatureAccess('related_concepts') ? (
<Spin spinning={loading.eventDetail}>
<RelatedConcepts
eventTitle={event?.title}
eventDetail={eventDetail}
eventService={eventService}
/>
</Spin>
) : renderLockedContent('related_concepts', '相关概念')
},
{
key: 'historical',
label: (
<span>
历史事件对比
{!hasFeatureAccess('historical_events_full') && (
<LockOutlined style={{ marginLeft: '4px', fontSize: '12px', opacity: 0.6 }} />
)}
</span>
),
children: hasFeatureAccess('historical_events_full') ? (
<Spin spinning={loading.historicalEvents}>
<HistoricalEvents
eventId={event?.id}
eventTitle={event?.title}
historicalEvents={historicalEvents}
eventService={eventService}
/>
</Spin>
) : renderLockedContent('historical_events_full', '历史事件对比')
},
{
key: 'chain',
label: (
<span>
传导链分析
{!hasFeatureAccess('transmission_chain') && (
<CrownOutlined style={{ marginLeft: '4px', fontSize: '12px', opacity: 0.6 }} />
)}
</span>
),
children: hasFeatureAccess('transmission_chain') ? (
<TransmissionChainAnalysis
eventId={event?.id}
eventService={eventService}
/>
) : renderLockedContent('transmission_chain', '传导链分析')
}
], [
hasFeatureAccess,
filteredStocks,
quotes,
event,
watchlistSet,
searchText,
loading,
isMonitoring,
eventDetail,
historicalEvents,
handleSearch,
handleRefresh,
handleMonitoringToggle,
handleWatchlistToggle,
handleRowClick,
renderFixedCharts,
renderLockedContent
]);
// ==================== Render ====================
return (
<>
<Drawer
title={
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span>{event?.title}</span>
<CloseOutlined onClick={onClose} style={{ cursor: 'pointer' }} />
</div>
}
placement="right"
width={900}
open={visible}
onClose={onClose}
closable={false}
className="stock-detail-panel"
>
<AntdTabs activeKey={activeTab} onChange={setActiveTab} items={tabItems} />
{/* 风险提示 */}
<div style={{ marginTop: '24px', paddingBottom: '20px' }}>
<RiskDisclaimer variant="default" />
</div>
</Drawer>
{/* 事件讨论模态框 */}
<EventDiscussionModal
isOpen={discussionModalVisible}
onClose={() => setDiscussionModalVisible(false)}
eventId={event?.id}
eventTitle={event?.title}
discussionType={discussionType}
/>
{/* 订阅升级模态框 */}
<SubscriptionUpgradeModal
isOpen={upgradeModalOpen}
onClose={() => setUpgradeModalOpen(false)}
requiredLevel={upgradeFeature}
featureName={upgradeFeature === 'pro' ? '相关分析功能' : '传导链分析'}
/>
</>
);
}
export default StockDetailPanel;