修改架构,移动端优先。

save
This commit is contained in:
cheney
2026-08-03 18:05:50 +08:00
committed by Cheney
parent 1c07b417ce
commit 08b3655a38
71 changed files with 2524 additions and 290 deletions
+109
View File
@@ -0,0 +1,109 @@
import { useEffect, useState } from 'react'
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
import { api } from '../../api/client.js'
// 经济数据: 从 /api/indicators 拉所有指标, 顶上一个 chip 行切换, 下面渲染图表+表格
export default function DataView() {
const [indicators, setIndicators] = useState([])
const [selected, setSelected] = useState(null)
const [err, setErr] = useState('')
const [loading, setLoading] = useState(true)
useEffect(() => {
let cancelled = false
setLoading(true)
api('/indicators')
.then((data) => {
if (cancelled) return
setIndicators(data.indicators || [])
if (data.indicators && data.indicators[0]) setSelected(data.indicators[0].name)
})
.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 (!indicators.length) return <div className="empty">暂无指标数据, 请先执行 npm run db:seed</div>
// 按 groupName 分组, chip 行按组排列
const groups = groupBy(indicators, 'groupName')
const cur = indicators.find((i) => i.name === selected)
const chartData = cur ? buildMonthlySeries(cur.data) : []
return (
<div>
<div className="page-title">经济数据</div>
{Object.entries(groups).map(([groupName, list]) => (
<div key={groupName}>
<div className="section-title">{groupName}</div>
<div className="chip-row">
{list.map((i) => (
<button
key={i.name}
className={'chip' + (selected === i.name ? ' active' : '')}
onClick={() => setSelected(i.name)}
>
{shortTitle(i)}
</button>
))}
</div>
</div>
))}
{cur && (
<div style={{ padding: '0 12px 12px' }}>
<div className="card">
<div style={{ fontWeight: 600, marginBottom: 4 }}>{cur.data.title || cur.name}</div>
<div style={{ fontSize: 12, color: 'var(--color-muted)', marginBottom: 8 }}>
单位 {cur.data.unit || '-'} · 来源 {cur.data.source || 'seed'}
</div>
<div className="chart-wrap">
<ResponsiveContainer width="100%" height={260}>
<LineChart data={chartData} margin={{ top: 10, right: 16, left: 0, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" tick={{ fontSize: 11 }} />
<YAxis tick={{ fontSize: 11 }} />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="value" name={cur.data.unit || '值'} stroke="#2563eb" strokeWidth={2} dot={{ r: 3 }} />
</LineChart>
</ResponsiveContainer>
</div>
<div style={{ maxHeight: 240, overflow: 'auto' }}>
<table className="data-table">
<thead>
<tr><th>月份</th><th style={{ textAlign: 'right' }}>数值</th></tr>
</thead>
<tbody>
{chartData.map((r) => (
<tr key={r.month}><td>{r.month}</td><td style={{ textAlign: 'right' }}>{r.value}</td></tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)}
</div>
)
}
function groupBy(arr, key) {
return arr.reduce((acc, x) => {
const k = x[key] || '其它'
;(acc[k] = acc[k] || []).push(x)
return acc
}, {})
}
function shortTitle(i) {
if (i.data && i.data.title) return i.data.title
return i.name
}
// 把 data.rows: [{month, value}, ...] 直接返回, 服务端已经按月排好
function buildMonthlySeries(data) {
return (data && data.rows) || []
}