diff --git a/MeAgent/src/screens/StockDetail/StockDetailScreen.js b/MeAgent/src/screens/StockDetail/StockDetailScreen.js index 40954836..1df9d18e 100644 --- a/MeAgent/src/screens/StockDetail/StockDetailScreen.js +++ b/MeAgent/src/screens/StockDetail/StockDetailScreen.js @@ -16,6 +16,7 @@ import { MinuteChart, KlineChart, OrderBook, + StockInfoPanel, RelatedInfoTabs, EventsPanel, ConceptsPanel, @@ -264,7 +265,7 @@ const StockDetailScreen = () => { return ( - {/* 价格头部 - Wind 风格 */} + {/* 价格头部 - Wind 风格(固定在顶部) */} { onBack={handleBack} /> - {/* 图表类型切换 */} - + {/* 可滚动内容区域 */} + + {/* 图表类型切换 */} + - {/* 图表区域 */} - - {chartType === 'minute' ? ( - - ) : ( - + {chartType === 'minute' ? ( + + ) : ( + + )} + + + {/* 5档盘口 - 优先 WebSocket 实时数据,降级到 API 数据 */} + {chartType === 'minute' && ( + )} - - {/* 5档盘口 - 优先 WebSocket 实时数据,降级到 API 数据 */} - {chartType === 'minute' && ( - - )} - {/* 相关信息 Tab */} - + {/* 相关信息 Tab */} + - {/* 相关信息内容 */} - - {renderInfoContent()} - + {/* 相关信息内容 */} + + {renderInfoContent()} + + {/* 涨幅分析弹窗 */} { + if (!num || num === 0) return '--'; + if (num >= 100000000) { + return `${(num / 100000000).toFixed(2)}亿${unit}`; + } + if (num >= 10000) { + return `${(num / 10000).toFixed(2)}万${unit}`; + } + return `${num.toFixed(2)}${unit}`; +}; + +// 格式化百分比 +const formatPercent = (num) => { + if (num === null || num === undefined) return '--'; + return `${Number(num).toFixed(2)}%`; +}; + +// 格式化价格 +const formatPrice = (num) => { + if (num === null || num === undefined || num === 0) return '--'; + return Number(num).toFixed(2); +}; + +/** + * 单个信息项 + */ +const InfoItem = memo(({ label, value, color = 'white' }) => ( + + + {label} + + + {value} + + +)); + +/** + * 股票详情信息面板 + */ +const StockInfoPanel = memo(({ data, loading }) => { + // 处理数据 + const infoRows = useMemo(() => { + if (!data) return []; + + return [ + // 第一行:成交量、成交额、振幅 + [ + { label: '成交量', value: formatLargeNumber(data.volume, '股') }, + { label: '成交额', value: formatLargeNumber(data.amount, '') }, + { label: '振幅', value: formatPercent(data.amplitude) }, + ], + // 第二行:换手率、市盈率、总市值 + [ + { label: '换手率', value: formatPercent(data.turnover_rate) }, + { label: '市盈率', value: data.pe ? formatPrice(data.pe) : '--' }, + { label: '总市值', value: data.total_mv ? `${data.total_mv}亿` : '--' }, + ], + // 第三行:流通市值、总股本、流通股 + [ + { label: '流通市值', value: data.circ_mv ? `${data.circ_mv}亿` : '--' }, + { label: '总股本', value: formatLargeNumber(data.total_shares, '股') }, + { label: '流通股', value: formatLargeNumber(data.float_shares, '股') }, + ], + // 第四行:涨跌额、总笔数、52周高/低 + [ + { + label: '涨跌额', + value: data.change_amount ? formatPrice(data.change_amount) : '--', + color: data.change_amount > 0 ? '#EF4444' : data.change_amount < 0 ? '#22C55E' : 'white', + }, + { label: '总笔数', value: data.total_trades ? `${data.total_trades}` : '--' }, + { + label: '52周高/低', + value: data.week52_high && data.week52_low + ? `${formatPrice(data.week52_high)}/${formatPrice(data.week52_low)}` + : '--', + }, + ], + ]; + }, [data]); + + if (loading) { + return ( + + 加载中... + + ); + } + + if (!data) { + return null; + } + + return ( + + {/* 标题栏 */} + + + 行情数据 + + {data.update_time && ( + + 更新于 {data.update_time} + + )} + + + {/* 数据行 */} + + {infoRows.map((row, rowIndex) => ( + 0 ? 1 : 0} + borderTopColor="rgba(255,255,255,0.06)" + > + {row.map((item, itemIndex) => ( + + ))} + + ))} + + + ); +}); + +StockInfoPanel.displayName = 'StockInfoPanel'; + +export default StockInfoPanel; diff --git a/MeAgent/src/screens/StockDetail/components/index.js b/MeAgent/src/screens/StockDetail/components/index.js index 375d5206..5d2b637c 100644 --- a/MeAgent/src/screens/StockDetail/components/index.js +++ b/MeAgent/src/screens/StockDetail/components/index.js @@ -14,3 +14,4 @@ export { default as ConceptsPanel } from './ConceptsPanel'; export { default as AnnouncementsPanel } from './AnnouncementsPanel'; export { default as RiseAnalysisModal } from './RiseAnalysisModal'; export { default as RiseAnalysisPanel } from './RiseAnalysisPanel'; +export { default as StockInfoPanel } from './StockInfoPanel'; diff --git a/MeAgent/src/services/stockService.js b/MeAgent/src/services/stockService.js index 0e69213b..b5dabea4 100644 --- a/MeAgent/src/services/stockService.js +++ b/MeAgent/src/services/stockService.js @@ -125,15 +125,24 @@ export const stockDetailService = { high: quote.today_high || 0, low: quote.today_low || 0, // 涨跌 - change_amount: (quote.current_price - quote.yesterday_close) || 0, + change_amount: quote.change_amount || (quote.current_price - quote.yesterday_close) || 0, change_percent: quote.change_percent || 0, - // 成交量/成交额(从 market/trade 获取) - volume: latestTrade?.volume || 0, - amount: latestTrade?.amount || 0, + // 成交量/成交额(优先用 API 返回的,否则用 market/trade) + volume: quote.volume || latestTrade?.volume || 0, + amount: quote.amount || latestTrade?.amount || 0, + // 新增:总笔数、振幅 + total_trades: quote.total_trades || 0, + amplitude: quote.amplitude || 0, // 换手率/市盈率/市值 turnover_rate: quote.turnover_rate || latestTrade?.turnover_rate || 0, pe_ratio: quote.pe || 0, + pe: quote.pe || 0, market_cap: quote.market_cap || quote.circ_mv || 0, + circ_mv: quote.circ_mv || 0, + total_mv: quote.total_mv || 0, + // 股本信息 + total_shares: quote.total_shares || 0, + float_shares: quote.float_shares || 0, // 52周高低 week52_high: quote.week52_high || 0, week52_low: quote.week52_low || 0, diff --git a/app.py b/app.py index 1c134fcc..af51e43b 100755 --- a/app.py +++ b/app.py @@ -9929,18 +9929,28 @@ def get_stock_quote_detail(stock_code): # 价格信息 'current_price': None, 'change_percent': None, + 'change_amount': None, # 涨跌额(元) 'today_open': None, 'yesterday_close': None, 'today_high': None, 'today_low': None, + # 成交信息 + 'volume': None, # 成交量(股) + 'amount': None, # 成交额(元) + 'total_trades': None, # 总笔数 + 'amplitude': None, # 振幅(%) + # 关键指标 'pe': None, 'pb': None, 'eps': None, 'market_cap': None, 'circ_mv': None, + 'total_mv': None, # 总市值(亿元) 'turnover_rate': None, + 'total_shares': None, # 总股本(股) + 'float_shares': None, # 流通股本(股) 'week52_high': None, 'week52_low': None, @@ -9953,21 +9963,25 @@ def get_stock_quote_detail(stock_code): } with engine.connect() as conn: - # 1. 获取最新交易数据(来自 ea_trade) + # 1. 获取最新交易数据(来自 ea_trade,包含完整字段) trade_query = text(""" SELECT t.SECCODE, t.SECNAME, t.TRADEDATE, + t.F001V as exchange, t.F002N as pre_close, t.F003N as open_price, t.F004N as volume, t.F005N as high, t.F006N as low, t.F007N as close_price, + t.F008N as total_trades, + t.F009N as change_amount, t.F010N as change_pct, t.F011N as amount, t.F012N as turnover_rate, + t.F013N as amplitude, t.F020N as total_shares, t.F021N as float_shares, t.F026N as pe_ratio, @@ -9988,10 +10002,19 @@ def get_stock_quote_detail(stock_code): result_data['name'] = row.get('SECNAME') or '' result_data['current_price'] = float(row.get('close_price') or 0) result_data['change_percent'] = float(row.get('change_pct') or 0) + result_data['change_amount'] = float(row.get('change_amount') or 0) result_data['today_open'] = float(row.get('open_price') or 0) result_data['yesterday_close'] = float(row.get('pre_close') or 0) result_data['today_high'] = float(row.get('high') or 0) result_data['today_low'] = float(row.get('low') or 0) + + # 成交信息 + result_data['volume'] = float(row.get('volume') or 0) + result_data['amount'] = float(row.get('amount') or 0) + result_data['total_trades'] = int(row.get('total_trades') or 0) if row.get('total_trades') else None + result_data['amplitude'] = float(row.get('amplitude') or 0) + + # 关键指标 result_data['pe'] = float(row.get('pe_ratio') or 0) if row.get('pe_ratio') else None result_data['turnover_rate'] = float(row.get('turnover_rate') or 0) result_data['sw_industry_l1'] = row.get('sw_industry_l1') or '' @@ -9999,13 +10022,21 @@ def get_stock_quote_detail(stock_code): result_data['industry_l1'] = row.get('industry_l1') or '' result_data['industry'] = row.get('sw_industry_l2') or row.get('sw_industry_l1') or '' - # 计算流通市值(亿元) + # 股本信息 + total_shares = float(row.get('total_shares') or 0) float_shares = float(row.get('float_shares') or 0) close_price = float(row.get('close_price') or 0) + result_data['total_shares'] = total_shares + result_data['float_shares'] = float_shares + + # 计算市值(亿元) if float_shares > 0 and close_price > 0: - circ_mv = (float_shares * close_price) / 100000000 # 转为亿 + circ_mv = (float_shares * close_price) / 100000000 # 流通市值(亿) result_data['circ_mv'] = round(circ_mv, 2) result_data['market_cap'] = f"{round(circ_mv, 2)}亿" + if total_shares > 0 and close_price > 0: + total_mv = (total_shares * close_price) / 100000000 # 总市值(亿) + result_data['total_mv'] = round(total_mv, 2) trade_date = row.get('TRADEDATE') if trade_date: