remove frontend directory from git tracking
This commit is contained in:
@@ -1,40 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { ArrowLeft, CalendarDays, LineChart as LineChartIcon, ShieldCheck } from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { PublicHeader } from "@/components/public-header";
|
||||
import { apiFetch } from "@/lib/api";
|
||||
import type { FundProduct, FundProductList } from "@/types/api";
|
||||
|
||||
interface HistoryPoint { nav_date: string; unit_nav: number; daily_growth?: number | null; cum_return_pct?: number; }
|
||||
interface HistoryResponse { items: HistoryPoint[]; total_growth_rate: number; max_return_rate: number; max_drawdown_rate: number; }
|
||||
|
||||
export default function FundDetailPage({ params }: { params: Promise<{ code: string }> }) {
|
||||
const [product, setProduct] = useState<FundProduct | null>(null);
|
||||
const [history, setHistory] = useState<HistoryResponse | null>(null);
|
||||
const [state, setState] = useState<"loading" | "ready" | "error">("loading");
|
||||
|
||||
useEffect(() => {
|
||||
void params.then(({ code }) => Promise.all([
|
||||
apiFetch<FundProductList>(`/products?keyword=${encodeURIComponent(code)}&page_size=10`),
|
||||
apiFetch<HistoryResponse>(`/products/${encodeURIComponent(code)}/history`),
|
||||
]).then(([list, chart]) => {
|
||||
setProduct(list.items.find((item) => item.product_code === code) ?? null);
|
||||
setHistory(chart); setState("ready");
|
||||
}).catch(() => setState("error")));
|
||||
}, [params]);
|
||||
|
||||
return <main className="min-h-screen bg-slate-50 text-slate-900"><PublicHeader /><div className="mx-auto max-w-7xl px-8 pb-24 pt-32"><Link href="/products" className="inline-flex items-center gap-2 text-sm text-slate-500 hover:text-[var(--brand-primary)]"><ArrowLeft className="h-4 w-4" />返回基金列表</Link>{state === "loading" && <div className="mt-10 h-96 animate-pulse rounded-2xl bg-slate-200" />}{state === "error" && <Notice text="基金详情暂时无法加载,请稍后重试。" />}{state === "ready" && product && <><div className="mt-10 rounded-2xl bg-[var(--brand-navy)] p-10 text-white"><div className="flex flex-wrap items-start justify-between gap-8"><div><span className="rounded-full bg-white/10 px-3 py-1 text-xs text-blue-100">{product.risk_level}</span><h1 className="mt-5 text-4xl font-semibold">{product.product_name}</h1><p className="mt-2 text-sm text-blue-100">{product.product_code} · {product.product_type}</p></div><div className="text-right"><p className="text-xs text-blue-200">最新单位净值</p><p className="mt-2 text-4xl font-semibold">{product.nav ?? "--"}</p><p className="mt-2 text-xs text-blue-200">更新于 {product.nav_date ?? "暂无日期"}</p></div></div></div><div className="mt-6 grid gap-5 md:grid-cols-3"><Metric label="区间收益" value={history?.total_growth_rate} tone="green" /><Metric label="区间最大收益" value={history?.max_return_rate} tone="gold" /><Metric label="最大回撤" value={history?.max_drawdown_rate} tone="red" /></div><div className="mt-6 rounded-2xl border border-slate-200 bg-white p-8"><div className="flex items-center justify-between"><div><h2 className="flex items-center gap-2 text-lg font-semibold text-[var(--brand-navy)]"><LineChartIcon className="h-5 w-5 text-[var(--brand-primary)]" />历史净值走势</h2><p className="mt-1 text-sm text-slate-400">数据来自基金历史净值接口 · {history?.items.length ?? 0} 个数据点</p></div><CalendarDays className="h-5 w-5 text-slate-300" /></div><NavLineChart points={history?.items ?? []} /><p className="mt-4 flex items-center gap-2 text-xs text-slate-400"><ShieldCheck className="h-4 w-4" />基金投资有风险,过往业绩不代表未来表现。</p></div></>}{state === "ready" && !product && <Notice text="未找到该基金产品。" />}</div></main>;
|
||||
}
|
||||
|
||||
function Metric({ label, value, tone }: { label: string; value?: number; tone: "green" | "gold" | "red" }) { const color = tone === "green" ? "text-emerald-600" : tone === "gold" ? "text-[var(--brand-gold)]" : "text-rose-600"; return <div className="rounded-2xl border border-slate-200 bg-white p-6"><p className="text-xs text-slate-400">{label}</p><p className={`mt-3 text-2xl font-semibold ${color}`}>{value ?? "--"}{value !== undefined && "%"}</p></div>; }
|
||||
|
||||
function NavLineChart({ points }: { points: HistoryPoint[] }) {
|
||||
const chartPoints = useMemo(() => points.slice(-60), [points]);
|
||||
if (!chartPoints.length) return <div className="mt-8 flex h-64 items-center justify-center rounded-xl bg-slate-50 text-sm text-slate-400">暂无历史净值数据</div>;
|
||||
const values = chartPoints.map((point) => point.unit_nav); const min = Math.min(...values); const max = Math.max(...values); const range = Math.max(max - min, 0.000001); const coords = chartPoints.map((point, index) => { const x = (index / Math.max(chartPoints.length - 1, 1)) * 100; const y = 92 - ((point.unit_nav - min) / range) * 76; return `${x},${y}`; }).join(" ");
|
||||
return <div className="mt-8"><div className="relative h-64 overflow-hidden rounded-xl border border-slate-100 bg-slate-50 px-4 py-5"><svg viewBox="0 0 100 100" preserveAspectRatio="none" className="h-full w-full" role="img" aria-label="基金历史净值折线图"><line x1="0" y1="16" x2="100" y2="16" stroke="#e2e8f0" strokeWidth="0.4" /><line x1="0" y1="54" x2="100" y2="54" stroke="#e2e8f0" strokeWidth="0.4" /><line x1="0" y1="92" x2="100" y2="92" stroke="#cbd5e1" strokeWidth="0.5" /><polyline points={coords} fill="none" stroke="var(--brand-primary)" strokeWidth="1.2" vectorEffect="non-scaling-stroke" strokeLinecap="round" strokeLinejoin="round" /></svg><div className="pointer-events-none absolute inset-x-4 bottom-2 flex justify-between text-[10px] text-slate-400"><span>{chartPoints[0].nav_date}</span><span>{chartPoints[chartPoints.length - 1].nav_date}</span></div></div><div className="mt-3 flex justify-between text-xs text-slate-400"><span>最低净值 {min.toFixed(4)}</span><span>最高净值 {max.toFixed(4)}</span></div></div>;
|
||||
}
|
||||
|
||||
function Notice({ text }: { text: string }) { return <div className="mt-10 rounded-2xl border border-amber-200 bg-amber-50 p-8 text-sm text-amber-800">{text}</div>; }
|
||||
Reference in New Issue
Block a user