更新ios
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
MinuteChart,
|
||||
KlineChart,
|
||||
OrderBook,
|
||||
StockInfoPanel,
|
||||
RelatedInfoTabs,
|
||||
EventsPanel,
|
||||
ConceptsPanel,
|
||||
@@ -264,7 +265,7 @@ const StockDetailScreen = () => {
|
||||
return (
|
||||
<Box flex={1} bg="#0A0A0F">
|
||||
<SafeAreaView style={styles.container} edges={['top']}>
|
||||
{/* 价格头部 - Wind 风格 */}
|
||||
{/* 价格头部 - Wind 风格(固定在顶部) */}
|
||||
<PriceHeader
|
||||
stock={{ stock_code: stockCode, stock_name: displayStockName }}
|
||||
quote={quote}
|
||||
@@ -273,51 +274,64 @@ const StockDetailScreen = () => {
|
||||
onBack={handleBack}
|
||||
/>
|
||||
|
||||
{/* 图表类型切换 */}
|
||||
<ChartTypeTabs
|
||||
activeType={chartType}
|
||||
onChange={handleChartTypeChange}
|
||||
/>
|
||||
{/* 可滚动内容区域 */}
|
||||
<ScrollView
|
||||
flex={1}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
>
|
||||
{/* 图表类型切换 */}
|
||||
<ChartTypeTabs
|
||||
activeType={chartType}
|
||||
onChange={handleChartTypeChange}
|
||||
/>
|
||||
|
||||
{/* 图表区域 */}
|
||||
<Box mt={2}>
|
||||
{chartType === 'minute' ? (
|
||||
<MinuteChart
|
||||
data={currentChartData}
|
||||
preClose={minutePrevClose || quote.pre_close}
|
||||
loading={isChartLoading}
|
||||
/>
|
||||
) : (
|
||||
<KlineChart
|
||||
data={currentChartData}
|
||||
type={chartType}
|
||||
loading={isChartLoading}
|
||||
riseAnalysisData={riseAnalysisData}
|
||||
onAnalysisPress={handleAnalysisPress}
|
||||
{/* 图表区域 */}
|
||||
<Box mt={2}>
|
||||
{chartType === 'minute' ? (
|
||||
<MinuteChart
|
||||
data={currentChartData}
|
||||
preClose={minutePrevClose || quote.pre_close}
|
||||
loading={isChartLoading}
|
||||
/>
|
||||
) : (
|
||||
<KlineChart
|
||||
data={currentChartData}
|
||||
type={chartType}
|
||||
loading={isChartLoading}
|
||||
riseAnalysisData={riseAnalysisData}
|
||||
onAnalysisPress={handleAnalysisPress}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* 5档盘口 - 优先 WebSocket 实时数据,降级到 API 数据 */}
|
||||
{chartType === 'minute' && (
|
||||
<OrderBook
|
||||
askPrices={realtimeQuote?.ask_prices || fallbackOrderBook?.ask_prices || orderBook.askPrices || []}
|
||||
askVolumes={realtimeQuote?.ask_volumes || fallbackOrderBook?.ask_volumes || orderBook.askVolumes || []}
|
||||
bidPrices={realtimeQuote?.bid_prices || fallbackOrderBook?.bid_prices || orderBook.bidPrices || []}
|
||||
bidVolumes={realtimeQuote?.bid_volumes || fallbackOrderBook?.bid_volumes || orderBook.bidVolumes || []}
|
||||
preClose={realtimeQuote?.pre_close || fallbackOrderBook?.prev_close || quote.pre_close}
|
||||
updateTime={realtimeQuote?.update_time || fallbackOrderBook?.trade_time}
|
||||
isConnected={wsConnected}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* 5档盘口 - 优先 WebSocket 实时数据,降级到 API 数据 */}
|
||||
{chartType === 'minute' && (
|
||||
<OrderBook
|
||||
askPrices={realtimeQuote?.ask_prices || fallbackOrderBook?.ask_prices || orderBook.askPrices || []}
|
||||
askVolumes={realtimeQuote?.ask_volumes || fallbackOrderBook?.ask_volumes || orderBook.askVolumes || []}
|
||||
bidPrices={realtimeQuote?.bid_prices || fallbackOrderBook?.bid_prices || orderBook.bidPrices || []}
|
||||
bidVolumes={realtimeQuote?.bid_volumes || fallbackOrderBook?.bid_volumes || orderBook.bidVolumes || []}
|
||||
preClose={realtimeQuote?.pre_close || fallbackOrderBook?.prev_close || quote.pre_close}
|
||||
updateTime={realtimeQuote?.update_time || fallbackOrderBook?.trade_time}
|
||||
isConnected={wsConnected}
|
||||
{/* 股票详情信息面板 */}
|
||||
<StockInfoPanel
|
||||
data={currentStock}
|
||||
loading={loading.detail}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 相关信息 Tab */}
|
||||
<RelatedInfoTabs activeTab={infoTab} onChange={setInfoTab} />
|
||||
{/* 相关信息 Tab */}
|
||||
<RelatedInfoTabs activeTab={infoTab} onChange={setInfoTab} />
|
||||
|
||||
{/* 相关信息内容 */}
|
||||
<Box flex={1}>
|
||||
{renderInfoContent()}
|
||||
</Box>
|
||||
{/* 相关信息内容 */}
|
||||
<Box minH={300} pb={4}>
|
||||
{renderInfoContent()}
|
||||
</Box>
|
||||
</ScrollView>
|
||||
|
||||
{/* 涨幅分析弹窗 */}
|
||||
<RiseAnalysisModal
|
||||
@@ -334,6 +348,9 @@ const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
});
|
||||
|
||||
export default StockDetailScreen;
|
||||
|
||||
159
MeAgent/src/screens/StockDetail/components/StockInfoPanel.js
Normal file
159
MeAgent/src/screens/StockDetail/components/StockInfoPanel.js
Normal file
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* 股票详情信息面板
|
||||
* 展示成交量、成交额、振幅、换手率、市盈率、市值等详细数据
|
||||
*/
|
||||
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { Box, HStack, VStack, Text } from 'native-base';
|
||||
|
||||
// 格式化大数字(带单位)
|
||||
const formatLargeNumber = (num, unit = '') => {
|
||||
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' }) => (
|
||||
<VStack alignItems="center" flex={1} py={2}>
|
||||
<Text color="gray.500" fontSize={10} mb={1}>
|
||||
{label}
|
||||
</Text>
|
||||
<Text color={color} fontSize={12} fontWeight="medium">
|
||||
{value}
|
||||
</Text>
|
||||
</VStack>
|
||||
));
|
||||
|
||||
/**
|
||||
* 股票详情信息面板
|
||||
*/
|
||||
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 (
|
||||
<Box
|
||||
mx={4}
|
||||
mt={3}
|
||||
p={4}
|
||||
bg="rgba(30, 41, 59, 0.6)"
|
||||
borderRadius={16}
|
||||
borderWidth={1}
|
||||
borderColor="rgba(255,255,255,0.08)"
|
||||
>
|
||||
<Text color="gray.500" textAlign="center">加载中...</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
mx={4}
|
||||
mt={3}
|
||||
bg="rgba(30, 41, 59, 0.6)"
|
||||
borderRadius={16}
|
||||
borderWidth={1}
|
||||
borderColor="rgba(255,255,255,0.08)"
|
||||
overflow="hidden"
|
||||
>
|
||||
{/* 标题栏 */}
|
||||
<HStack px={4} py={2.5} alignItems="center" justifyContent="space-between">
|
||||
<Text color="white" fontSize={14} fontWeight="bold">
|
||||
行情数据
|
||||
</Text>
|
||||
{data.update_time && (
|
||||
<Text color="gray.600" fontSize={10}>
|
||||
更新于 {data.update_time}
|
||||
</Text>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
{/* 数据行 */}
|
||||
<Box px={2} pb={2}>
|
||||
{infoRows.map((row, rowIndex) => (
|
||||
<HStack
|
||||
key={rowIndex}
|
||||
borderTopWidth={rowIndex > 0 ? 1 : 0}
|
||||
borderTopColor="rgba(255,255,255,0.06)"
|
||||
>
|
||||
{row.map((item, itemIndex) => (
|
||||
<InfoItem
|
||||
key={itemIndex}
|
||||
label={item.label}
|
||||
value={item.value}
|
||||
color={item.color || 'white'}
|
||||
/>
|
||||
))}
|
||||
</HStack>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
StockInfoPanel.displayName = 'StockInfoPanel';
|
||||
|
||||
export default StockInfoPanel;
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
37
app.py
37
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:
|
||||
|
||||
Reference in New Issue
Block a user