问题: - 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>
347 lines
11 KiB
JavaScript
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;
|