feat: 股票详情页增加最新交易日PE/PB/ROE/换手率/市值等行情指标
- 后端tencent.py增加换手率/市盈率/市净率/总市值/流通市值字段提取 - 前端StockQuote接口新增对应字段类型定义 - 股票详情页增加"行情指标"卡片,展示PE/PB/ROE/换手率/总手/成交额/市值 - ROE取自财务分析数据的加权净资产收益率 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,11 @@ export interface StockQuote {
|
||||
time: string;
|
||||
change: number;
|
||||
changePercent: number;
|
||||
turnoverRate: number; // 换手率%
|
||||
pe: number; // 市盈率
|
||||
pb: number; // 市净率
|
||||
totalMarketCap: number; // 总市值
|
||||
circulatingMarketCap: number; // 流通市值
|
||||
}
|
||||
|
||||
export interface KLineData {
|
||||
|
||||
@@ -63,8 +63,14 @@ interface StockInfo {
|
||||
addedPrice: number | null;
|
||||
outerDisk: number; // 外盘(手)
|
||||
innerDisk: number; // 内盘(手)
|
||||
volume: number; // 成交量(股)
|
||||
amount: number; // 成交额(元)
|
||||
quoteDate: string; // 行情日期 YYYY-MM-DD
|
||||
turnoverRate: number; // 换手率%
|
||||
pe: number; // 市盈率
|
||||
pb: number; // 市净率
|
||||
totalMarketCap: number; // 总市值
|
||||
circulatingMarketCap: number; // 流通市值
|
||||
}
|
||||
|
||||
function StockDetail() {
|
||||
@@ -155,6 +161,12 @@ function StockDetail() {
|
||||
innerDisk: quote.innerDisk || 0,
|
||||
amount: (quote.amount || 0) * 10000,
|
||||
quoteDate: quote.date,
|
||||
volume: quote.volume || 0,
|
||||
turnoverRate: quote.turnoverRate ?? 0,
|
||||
pe: quote.pe ?? 0,
|
||||
pb: quote.pb ?? 0,
|
||||
totalMarketCap: quote.totalMarketCap ?? 0,
|
||||
circulatingMarketCap: quote.circulatingMarketCap ?? 0,
|
||||
});
|
||||
|
||||
// 基础信息已就绪,结束主loading,先渲染页面框架
|
||||
@@ -267,6 +279,14 @@ function StockDetail() {
|
||||
negativeDays: negative,
|
||||
};
|
||||
}, [fundFlowSlice]);
|
||||
|
||||
// 从财务数据中提取最新ROE(加权净资产收益率)
|
||||
const latestROE = useMemo(() => {
|
||||
if (!financialData?.data?.length) return null;
|
||||
const latest = financialData.data[0];
|
||||
const roe = latest.indicators["加权净资产收益率"];
|
||||
return typeof roe === "number" ? roe : null;
|
||||
}, [financialData]);
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
@@ -653,6 +673,53 @@ function StockDetail() {
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 行情指标:PE / PB / ROE / 换手率 / 总手 / 成交额 / 市值 */}
|
||||
<Card className="mt-4 md:mt-6 shadow-lg">
|
||||
<CardHeader className="pb-2 md:pb-4 px-3 md:px-6 pt-4 md:pt-6">
|
||||
<CardTitle className="text-base md:text-lg">行情指标</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-3 md:px-6 pb-3 md:pb-6">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4">
|
||||
<div className="text-center p-3 rounded-lg bg-muted/30">
|
||||
<p className="text-xs text-muted-foreground mb-1">市盈率(PE)</p>
|
||||
<p className="text-lg font-semibold">{stockInfo.pe > 0 ? stockInfo.pe.toFixed(2) : '-'}</p>
|
||||
</div>
|
||||
<div className="text-center p-3 rounded-lg bg-muted/30">
|
||||
<p className="text-xs text-muted-foreground mb-1">市净率(PB)</p>
|
||||
<p className="text-lg font-semibold">{stockInfo.pb > 0 ? stockInfo.pb.toFixed(2) : '-'}</p>
|
||||
</div>
|
||||
<div className="text-center p-3 rounded-lg bg-muted/30">
|
||||
<p className="text-xs text-muted-foreground mb-1">ROE(加权净资产收益率)</p>
|
||||
<p className={`text-lg font-semibold ${latestROE !== null ? (latestROE > 0 ? 'text-red-500' : 'text-green-500') : ''}`}>
|
||||
{latestROE !== null ? `${latestROE.toFixed(2)}%` : '加载中...'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center p-3 rounded-lg bg-muted/30">
|
||||
<p className="text-xs text-muted-foreground mb-1">换手率</p>
|
||||
<p className="text-lg font-semibold">{stockInfo.turnoverRate > 0 ? `${stockInfo.turnoverRate}%` : '-'}</p>
|
||||
</div>
|
||||
<div className="text-center p-3 rounded-lg bg-muted/30">
|
||||
<p className="text-xs text-muted-foreground mb-1">总手</p>
|
||||
<p className="text-lg font-semibold">
|
||||
{stockInfo.volume > 0 ? `${(stockInfo.volume / 100).toLocaleString()}手` : '-'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center p-3 rounded-lg bg-muted/30">
|
||||
<p className="text-xs text-muted-foreground mb-1">成交额</p>
|
||||
<p className="text-lg font-semibold">{formatMoney(stockInfo.amount)}</p>
|
||||
</div>
|
||||
<div className="text-center p-3 rounded-lg bg-muted/30">
|
||||
<p className="text-xs text-muted-foreground mb-1">总市值</p>
|
||||
<p className="text-lg font-semibold">{stockInfo.totalMarketCap > 0 ? formatMoney(stockInfo.totalMarketCap) : '-'}</p>
|
||||
</div>
|
||||
<div className="text-center p-3 rounded-lg bg-muted/30">
|
||||
<p className="text-xs text-muted-foreground mb-1">流通市值</p>
|
||||
<p className="text-lg font-semibold">{stockInfo.circulatingMarketCap > 0 ? formatMoney(stockInfo.circulatingMarketCap) : '-'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Daily Data Table - Independent from fund flow */}
|
||||
{chartData.length > 0 && (
|
||||
<Card className="mt-4 md:mt-6 shadow-lg">
|
||||
|
||||
Reference in New Issue
Block a user