128 lines
4.9 KiB
TypeScript
128 lines
4.9 KiB
TypeScript
import { useEffect, useState } from "react";
|
||
import { Boxes, Search, PlusCircle, Code2, KeyRound } from "lucide-react";
|
||
import Home from "./components/Home";
|
||
import ProductView from "./components/ProductView";
|
||
import Contribute from "./components/Contribute";
|
||
import ApiDocs from "./components/ApiDocs";
|
||
import Account from "./components/Account";
|
||
import { api } from "./api";
|
||
|
||
type View =
|
||
| { name: "home" }
|
||
| { name: "product"; id: string }
|
||
| { name: "contribute" }
|
||
| { name: "api" }
|
||
| { name: "account" };
|
||
|
||
const NAV: { key: View["name"]; label: string; icon: typeof Search }[] = [
|
||
{ key: "home", label: "检索", icon: Search },
|
||
{ key: "contribute", label: "贡献档案", icon: PlusCircle },
|
||
{ key: "api", label: "API", icon: Code2 },
|
||
{ key: "account", label: "API 密钥", icon: KeyRound },
|
||
];
|
||
|
||
export default function App() {
|
||
const [view, setView] = useState<View>({ name: "home" });
|
||
const [qualified, setQualified] = useState<number | null>(null);
|
||
|
||
useEffect(() => {
|
||
api
|
||
.stats()
|
||
.then((s) => setQualified(s.qualified))
|
||
.catch(() => setQualified(null));
|
||
}, []);
|
||
|
||
return (
|
||
<div className="min-h-full flex flex-col">
|
||
<header className="sticky top-0 z-30 border-b border-gray-200/70 bg-white/80 backdrop-blur-md">
|
||
<div className="max-w-5xl mx-auto px-4 h-16 flex items-center justify-between gap-4">
|
||
<button
|
||
className="flex items-center gap-2.5 font-semibold text-gray-800 group"
|
||
onClick={() => setView({ name: "home" })}
|
||
>
|
||
<span className="grid h-9 w-9 place-items-center rounded-xl bg-gradient-to-br from-brand-500 to-brand-700 text-white shadow-glow transition group-hover:scale-105">
|
||
<Boxes className="w-5 h-5" />
|
||
</span>
|
||
<span className="flex items-baseline gap-1.5">
|
||
<span className="text-lg tracking-tight">天工</span>
|
||
<span className="hidden sm:inline text-gray-400 font-normal text-xs">
|
||
商品档案公共仓
|
||
</span>
|
||
</span>
|
||
</button>
|
||
<nav className="flex items-center gap-0.5 text-sm">
|
||
{NAV.map(({ key, label, icon: Icon }) => {
|
||
const active =
|
||
view.name === key || (key === "home" && view.name === "product");
|
||
return (
|
||
<button
|
||
key={key}
|
||
className={`px-3 py-1.5 rounded-lg flex items-center gap-1.5 font-medium transition ${
|
||
active
|
||
? "bg-brand-50 text-brand-700"
|
||
: "text-gray-600 hover:bg-gray-100"
|
||
}`}
|
||
onClick={() => setView({ name: key } as View)}
|
||
>
|
||
<Icon className="w-4 h-4" />
|
||
<span className="hidden sm:inline">{label}</span>
|
||
</button>
|
||
);
|
||
})}
|
||
</nav>
|
||
</div>
|
||
</header>
|
||
|
||
<main className="flex-1 max-w-5xl w-full mx-auto px-4 py-8">
|
||
{view.name === "home" && (
|
||
<Home
|
||
onOpen={(id) => setView({ name: "product", id })}
|
||
onContribute={() => setView({ name: "contribute" })}
|
||
/>
|
||
)}
|
||
{view.name === "product" && (
|
||
<ProductView id={view.id} onBack={() => setView({ name: "home" })} />
|
||
)}
|
||
{view.name === "contribute" && (
|
||
<Contribute onDone={() => setView({ name: "home" })} />
|
||
)}
|
||
{view.name === "api" && <ApiDocs onRegister={() => setView({ name: "account" })} />}
|
||
{view.name === "account" && <Account />}
|
||
</main>
|
||
|
||
<footer className="border-t border-gray-200/70 bg-white/60">
|
||
<div className="max-w-5xl mx-auto px-4 py-6 text-xs text-gray-400 leading-relaxed text-center">
|
||
{qualified != null && (
|
||
<div className="mb-3 inline-flex items-center gap-1.5 rounded-full border border-brand-100 bg-brand-50 px-3 py-1 text-gray-500">
|
||
已收录
|
||
<span className="font-semibold text-brand-600">
|
||
{qualified.toLocaleString()}
|
||
</span>
|
||
条合格商品档案
|
||
</div>
|
||
)}
|
||
<p className="max-w-2xl mx-auto">
|
||
天工是一个公益性商品档案库,主要收录商品名称、条码、品类、配料等官方快照,不涉及任何交易行为。
|
||
<button
|
||
onClick={() => setView({ name: "api" })}
|
||
className="ml-1 text-brand-600 font-medium hover:underline"
|
||
>
|
||
API 调用说明
|
||
</button>
|
||
</p>
|
||
<div className="mt-3">
|
||
<a
|
||
href="https://beian.miit.gov.cn/"
|
||
target="_blank"
|
||
rel="noreferrer"
|
||
className="hover:text-gray-600"
|
||
>
|
||
鲁ICP备2025185218号-6
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
</div>
|
||
);
|
||
}
|