iboard/src/pages/macro/StrategyView.jsx
2026-08-06 11:26:54 +08:00

148 lines
5.6 KiB
JavaScript

import { useEffect, useState } from 'react'
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
import { Card, List, NavBar } from 'antd-mobile'
import { api } from '../../api/client.js'
import TrendBadge, { calcTrend } from '../../components/TrendBadge.jsx'
// 策略研究
// 列表态 (默认): 列出所有策略, 每行显示主指标 (债务/GDP) 最新值 + 趋势
// 详情态: 顶部 NavBar (返回) + 4 张图 (debt/gdp/debtToGdp/interestToGdp) + 表格
const SERIES = [
{ key: 'debt', label: '政府债务 (万亿美元)', color: '#8884d8' },
{ key: 'gdp', label: 'GDP (万亿美元)', color: '#3cb44b' },
{ key: 'debtToGdp', label: '债务/GDP (%)', color: '#4363d8' },
{ key: 'interestToGdp', label: '债务利息/GDP (%)', color: '#f58231' },
]
// 取最后一个有效数据点
function lastRow(rows) {
for (let i = rows.length - 1; i >= 0; i--) {
if (rows[i] && typeof rows[i].debtToGdp === 'number') return rows[i]
}
return null
}
export default function StrategyView() {
const [strategies, setStrategies] = useState([])
const [err, setErr] = useState('')
const [loading, setLoading] = useState(true)
const [selected, setSelected] = useState(null)
useEffect(() => {
let cancelled = false
setLoading(true)
api('/strategies')
.then((data) => { if (!cancelled) setStrategies(data.strategies || []) })
.catch((e) => !cancelled && setErr(e.message))
.finally(() => !cancelled && setLoading(false))
return () => { cancelled = true }
}, [])
if (loading) return <div className="loading">加载中...</div>
if (err) return <div className="error-banner">{err}</div>
if (!strategies.length) return <div className="empty">暂无策略数据</div>
// 详情态
if (selected) {
const cur = strategies.find((s) => s.name === selected)
if (cur) return <StrategyDetail strategy={cur} onBack={() => setSelected(null)} />
}
// 列表态: 主指标 = 债务/GDP, 趋势基于其最近变化
return (
<div className="list-page">
<div className="page-title">策略研究</div>
<List>
{strategies.map((s) => {
const rows = (s.data && s.data.rows) || []
const last = lastRow(rows)
const { trend, change } = calcTrend(rows)
return (
<List.Item
key={s.name}
title={s.description || s.name}
description={last
? <><b>{last.debtToGdp}%</b> · /GDP {last.interestToGdp}% · {last.year}</>
: '暂无数据'}
extra={
<div className="row-meta">
{last && Math.abs(change) >= 1 && (
<span className="row-change">
{change > 0 ? '+' : ''}{change.toFixed(1)}%
</span>
)}
<TrendBadge trend={trend} />
</div>
}
arrowIcon
clickable
onClick={() => setSelected(s.name)}
/>
)
})}
</List>
</div>
)
}
// 详情: 4 张图 + 表格
function StrategyDetail({ strategy, onBack }) {
const s = strategy
const rows = (s.data && s.data.rows) || []
const last = lastRow(rows)
const { trend } = calcTrend(rows)
return (
<div className="detail-page">
<NavBar onBack={onBack}>{s.description || s.name}</NavBar>
<Card className="detail-card">
<div className="detail-meta">
数据 {rows.length} · 来源 {s.data.source || 'seed'} ·
{last && <> 最新 <b style={{ color: 'var(--color-text)' }}>{last.year}</b> /GDP <b style={{ color: 'var(--color-text)' }}>{last.debtToGdp}%</b> </>}
<TrendBadge trend={trend} />
</div>
{SERIES.map((s2) => (
<div key={s2.key} style={{ marginBottom: 12 }}>
<div style={{ fontSize: 13, fontWeight: 500, marginBottom: 4 }}>{s2.label}</div>
<div className="chart-wrap">
<ResponsiveContainer width="100%" height={220}>
<LineChart data={rows} margin={{ top: 8, right: 12, left: 0, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="year" tick={{ fontSize: 11 }} />
<YAxis tick={{ fontSize: 11 }} />
<Tooltip />
<Legend />
<Line type="monotone" dataKey={s2.key} name={s2.label} stroke={s2.color} strokeWidth={2} dot={{ r: 3 }} />
</LineChart>
</ResponsiveContainer>
</div>
</div>
))}
<div style={{ maxHeight: 220, overflow: 'auto', marginTop: 8 }}>
<table className="data-table">
<thead>
<tr>
<th>年份</th>
<th style={{ textAlign: 'right' }}>债务</th>
<th style={{ textAlign: 'right' }}>GDP</th>
<th style={{ textAlign: 'right' }}>债务/GDP</th>
<th style={{ textAlign: 'right' }}>利息/GDP</th>
</tr>
</thead>
<tbody>
{rows.map((r) => (
<tr key={r.year}>
<td>{r.year}</td>
<td style={{ textAlign: 'right' }}>{r.debt}</td>
<td style={{ textAlign: 'right' }}>{r.gdp}</td>
<td style={{ textAlign: 'right' }}>{r.debtToGdp}</td>
<td style={{ textAlign: 'right' }}>{r.interestToGdp}</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
</div>
)
}