Files
Mutual_Fund/frontend/app/products/page.tsx
T
2026-09-14 11:54:20 +08:00

34 lines
4.2 KiB
TypeScript

"use client";
import Link from "next/link";
import { ArrowLeft, ArrowRight, Search } from "lucide-react";
import { useEffect, useState } from "react";
import { PublicHeader } from "@/components/public-header";
import { apiFetch } from "@/lib/api";
import type { FundProduct, FundProductList } from "@/types/api";
export default function ProductsPage() {
const [items, setItems] = useState<FundProduct[]>([]);
const [keyword, setKeyword] = useState("");
const [riskLevel, setRiskLevel] = useState("");
const [state, setState] = useState<"loading" | "ready" | "error">("loading");
useEffect(() => {
const params = new URLSearchParams({ page: "1", page_size: "24" });
if (keyword.trim()) params.set("keyword", keyword.trim());
if (riskLevel) params.set("risk_level", riskLevel);
setState("loading");
apiFetch<FundProductList>(`/products?${params.toString()}`)
.then((data) => { setItems(data.items); setState("ready"); })
.catch(() => setState("error"));
}, [keyword, riskLevel]);
return (
<main className="min-h-screen bg-slate-50 text-slate-900">
<PublicHeader />
<section className="bg-[var(--brand-navy)] px-8 pb-16 pt-36 text-white"><div className="mx-auto max-w-7xl"><Link href="/" className="inline-flex items-center gap-2 text-sm text-blue-100 hover:text-white"><ArrowLeft className="h-4 w-4" />返回首页</Link><p className="mt-12 text-sm font-semibold uppercase tracking-[0.22em] text-blue-200">Fund Marketplace</p><h1 className="mt-4 text-5xl font-semibold">基金产品</h1><p className="mt-4 max-w-xl text-base leading-7 text-blue-100">基于真实产品数据,按基金类型、风险等级和投资目标了解更多选择。</p></div></section>
<section className="mx-auto max-w-7xl px-8 py-12"><div className="flex flex-wrap gap-3"><div className="relative w-80"><Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" /><input value={keyword} onChange={(event) => setKeyword(event.target.value)} placeholder="搜索基金名称或代码" className="w-full rounded-xl border border-slate-200 bg-white py-3 pl-10 pr-4 text-sm outline-none focus:border-[var(--brand-primary)]" /></div><select value={riskLevel} onChange={(event) => setRiskLevel(event.target.value)} className="rounded-xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 outline-none focus:border-[var(--brand-primary)]"><option value="">全部风险等级</option><option value="低风险">低风险</option><option value="中风险">中风险</option><option value="高风险">高风险</option></select></div><div className="mt-8 grid gap-5 md:grid-cols-2 lg:grid-cols-3">{state === "loading" && [1, 2, 3, 4, 5, 6].map((item) => <div key={item} className="h-64 animate-pulse rounded-2xl bg-slate-200" />)}{state === "error" && <div className="rounded-2xl border border-amber-200 bg-amber-50 p-6 text-sm text-amber-800 lg:col-span-3">基金数据暂时无法加载,请确认后端服务已启动后重试。</div>}{state === "ready" && items.map((item) => <Link href={`/products/${item.product_code}`} key={item.id} className="group rounded-2xl border border-slate-200 bg-white p-6 transition hover:-translate-y-1 hover:border-[var(--brand-primary)] hover:shadow-xl hover:shadow-slate-200/50"><div className="flex items-start justify-between"><span className="rounded-full bg-indigo-50 px-3 py-1 text-xs font-semibold text-[var(--brand-primary)]">{item.risk_level}</span><ArrowRight className="h-4 w-4 text-slate-300 group-hover:text-[var(--brand-primary)]" /></div><h2 className="mt-8 text-lg font-semibold text-[var(--brand-navy)]">{item.product_name}</h2><p className="mt-1 text-xs text-slate-400">{item.product_code} · {item.product_type}</p><div className="mt-8 flex items-end justify-between"><div><p className="text-xs text-slate-400">最新净值</p><p className="mt-1 text-2xl font-semibold text-[var(--brand-navy)]">{item.nav ?? "--"}</p></div><span className="text-xs text-slate-400">{item.nav_date ?? "暂无日期"}</span></div></Link>)}{state === "ready" && items.length === 0 && <div className="rounded-2xl border border-slate-200 bg-white p-10 text-center text-sm text-slate-500 lg:col-span-3">没有找到匹配的基金产品。</div>}</div></section>
</main>
);
}