数据源修改
This commit is contained in:
@@ -45,10 +45,13 @@ export default function MacroPage() {
|
||||
if (next) navigate(next.path)
|
||||
}
|
||||
|
||||
// 详情页 /macro/articles/:slug 单独走 React Router 渲染, 不参与滑屏
|
||||
if (location.pathname.match(/^\/macro\/articles\/[^/]+$/)) {
|
||||
// 详情页 /macro/articles/:slug 和 /macro/data/:name 单独走 React Router 渲染, 不参与滑屏
|
||||
// 这样浏览器/手机物理返回键也能正常回到列表
|
||||
if (location.pathname.match(/^\/macro\/(articles|data|strategy)\/[^/]+$/)) {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="data/:name" element={<DataView />} />
|
||||
<Route path="strategy/:name" element={<StrategyView />} />
|
||||
<Route path="articles/:slug" element={<ArticleDetail />} />
|
||||
</Routes>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
|
||||
import { Card, List, NavBar } from 'antd-mobile'
|
||||
import { api } from '../../api/client.js'
|
||||
@@ -7,6 +8,9 @@ import TrendBadge, { calcTrend, lastPoint } from '../../components/TrendBadge.js
|
||||
// 经济数据
|
||||
// 列表态 (默认): 列出所有指标, 每行显示最新值 + 趋势徽标, 点击进入详情
|
||||
// 详情态: 顶部 NavBar (返回) + 折线图 + 数据表
|
||||
// 频率中文标签 (对应 Indicator.frequency)
|
||||
const FREQ_LABEL = { year: '年', quarter: '季', month: '月', day: '日' }
|
||||
|
||||
// 统一数据行的时间字段: 兼容 {month}/{year}/{period} 三种存储形状
|
||||
function periodOf(r) {
|
||||
return (r && (r.period || r.month || r.year)) || ''
|
||||
@@ -16,7 +20,9 @@ export default function DataView() {
|
||||
const [indicators, setIndicators] = useState([])
|
||||
const [err, setErr] = useState('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [selected, setSelected] = useState(null) // null = 列表态, 名称 = 详情态
|
||||
// 路由驱动: /macro/data 列表, /macro/data/:name 详情, 浏览器/手机返回键天然可用
|
||||
const { name } = useParams()
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
@@ -35,41 +41,33 @@ export default function DataView() {
|
||||
if (err) return <div className="error-banner">{err}</div>
|
||||
if (!indicators.length) return <div className="empty">暂无指标数据, 请先执行 npm run db:seed</div>
|
||||
|
||||
// 详情态
|
||||
if (selected) {
|
||||
const cur = indicators.find((i) => i.name === selected)
|
||||
if (cur) return <DataDetail indicator={cur} onBack={() => setSelected(null)} />
|
||||
// 详情态 (路径 /macro/data/:name)
|
||||
if (name) {
|
||||
const cur = indicators.find((i) => i.name === name)
|
||||
if (cur) return <DataDetail indicator={cur} onBack={() => navigate('/macro/data')} />
|
||||
}
|
||||
|
||||
// 列表态
|
||||
const labelOf = (i) => (i.data && i.data.title) || i.name
|
||||
const labelOf = (i) => i.title || i.name
|
||||
|
||||
return (
|
||||
<div className="list-page">
|
||||
<div className="page-title">经济数据</div>
|
||||
<List>
|
||||
{indicators.map((i) => {
|
||||
const lp = lastPoint(i.data)
|
||||
const { trend, change } = calcTrend(i.data && i.data.rows)
|
||||
const unit = (i.data && i.data.unit) || ''
|
||||
const lp = lastPoint({ rows: i.points || [] })
|
||||
const { trend, change } = calcTrend(i.points || [])
|
||||
const unit = i.unit || ''
|
||||
return (
|
||||
<List.Item
|
||||
key={i.name}
|
||||
title={labelOf(i)}
|
||||
description={lp ? <><b>{lp.value}{unit}</b> · {periodOf(lp)}</> : '暂无数据'}
|
||||
extra={
|
||||
<div className="row-meta">
|
||||
{lp && Math.abs(change) >= 1 && (
|
||||
<span className="row-change">
|
||||
{change > 0 ? '+' : ''}{change.toFixed(1)}%
|
||||
</span>
|
||||
)}
|
||||
<TrendBadge trend={trend} />
|
||||
</div>
|
||||
}
|
||||
extra={<TrendBadge trend={trend} />}
|
||||
|
||||
arrowIcon
|
||||
clickable
|
||||
onClick={() => setSelected(i.name)}
|
||||
onClick={() => navigate('/macro/data/' + i.name)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
@@ -81,19 +79,19 @@ export default function DataView() {
|
||||
// 详情: 折线图 + 数据表
|
||||
function DataDetail({ indicator, onBack }) {
|
||||
const i = indicator
|
||||
const rows = (i.data && i.data.rows) || []
|
||||
const rows = (i.points || [])
|
||||
// 图表 X 轴统一用 period 字段
|
||||
const chartRows = rows.map((r) => ({ ...r, period: periodOf(r) }))
|
||||
const lp = lastPoint(i.data)
|
||||
const lp = lastPoint({ rows })
|
||||
const { trend } = calcTrend(rows)
|
||||
const title = (i.data && i.data.title) || i.name
|
||||
const title = i.title || i.name
|
||||
return (
|
||||
<div className="detail-page">
|
||||
<NavBar onBack={onBack}>{title}</NavBar>
|
||||
<Card className="detail-card">
|
||||
<div className="detail-meta">
|
||||
单位 {i.data.unit || '-'} · 来源 {i.data.source || 'seed'} ·
|
||||
{lp && <> 最新 <b style={{ color: 'var(--color-text)' }}>{lp.value}{i.data.unit || ''}</b> ({periodOf(lp)}) </>}
|
||||
单位 {i.unit || '-'} · 频率 {FREQ_LABEL[i.frequency] || i.frequency || '-'} · 来源 {i.source || 'seed'} ·
|
||||
{lp && <> 最新 <b style={{ color: 'var(--color-text)' }}>{lp.value}{i.unit || ''}</b> ({periodOf(lp)}) </>}
|
||||
<TrendBadge trend={trend} />
|
||||
</div>
|
||||
<div className="chart-wrap">
|
||||
@@ -104,7 +102,7 @@ function DataDetail({ indicator, onBack }) {
|
||||
<YAxis tick={{ fontSize: 11 }} />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line type="monotone" dataKey="value" name={i.data.unit || '值'} stroke="#1677ff" strokeWidth={2} dot={{ r: 3 }} />
|
||||
<Line type="monotone" dataKey="value" name={i.unit || '值'} stroke="#1677ff" strokeWidth={2} dot={{ r: 3 }} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
|
||||
import { Card, List, NavBar } from 'antd-mobile'
|
||||
import { api } from '../../api/client.js'
|
||||
@@ -26,7 +27,9 @@ export default function StrategyView() {
|
||||
const [strategies, setStrategies] = useState([])
|
||||
const [err, setErr] = useState('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [selected, setSelected] = useState(null)
|
||||
// 路由驱动: /macro/strategy 列表, /macro/strategy/:name 详情, 浏览器/手机返回键天然可用
|
||||
const { name } = useParams()
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
@@ -42,10 +45,10 @@ export default function StrategyView() {
|
||||
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)} />
|
||||
// 详情态 (路径 /macro/strategy/:name)
|
||||
if (name) {
|
||||
const cur = strategies.find((s) => s.name === name)
|
||||
if (cur) return <StrategyDetail strategy={cur} onBack={() => navigate('/macro/strategy')} />
|
||||
}
|
||||
|
||||
// 列表态: 主指标 = 债务/GDP, 趋势基于其最近变化
|
||||
@@ -64,19 +67,10 @@ export default function StrategyView() {
|
||||
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>
|
||||
}
|
||||
extra={<TrendBadge trend={trend} />}
|
||||
arrowIcon
|
||||
clickable
|
||||
onClick={() => setSelected(s.name)}
|
||||
onClick={() => navigate('/macro/strategy/' + s.name)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user