s''
This commit is contained in:
109
app/[locale]/ebooks/[slug]/page.tsx
Normal file
109
app/[locale]/ebooks/[slug]/page.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
"use client";
|
||||
|
||||
import { use, useEffect, useState } from "react";
|
||||
import { Link, useLocale } from "@/i18n/navigation";
|
||||
import { apiFetch } from "@/app/lib/api-client";
|
||||
import Footer from "@/app/components/Footer";
|
||||
|
||||
interface ContentItem {
|
||||
slug: string;
|
||||
title: string;
|
||||
titleEn?: string;
|
||||
subtitle: string;
|
||||
subtitleEn?: string;
|
||||
description: string;
|
||||
descriptionEn?: string;
|
||||
coverImage: string;
|
||||
mediaUrl?: string;
|
||||
chapters?: { title: string; minutes?: number }[];
|
||||
durationMinutes?: number;
|
||||
}
|
||||
|
||||
interface CityLite {
|
||||
slug: string;
|
||||
name: string;
|
||||
nameEn?: string;
|
||||
icon: string;
|
||||
costPerMonth: number;
|
||||
internetSpeed: number;
|
||||
}
|
||||
|
||||
export default function EbookPage({ params }: { params: Promise<{ slug: string }> }) {
|
||||
const { slug } = use(params);
|
||||
const locale = useLocale();
|
||||
const [item, setItem] = useState<ContentItem | null>(null);
|
||||
const [cities, setCities] = useState<CityLite[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
apiFetch<{ item: ContentItem; cities: CityLite[] }>(`/api/content/${slug}`)
|
||||
.then((data) => {
|
||||
setItem(data.item);
|
||||
setCities(data.cities || []);
|
||||
})
|
||||
.catch(() => setItem(null));
|
||||
}, [slug]);
|
||||
|
||||
if (!item) {
|
||||
return <div className="min-h-screen bg-[#fafafa] dark:bg-gray-950 p-10 text-gray-500">加载中...</div>;
|
||||
}
|
||||
|
||||
const title = locale === "zh" ? item.title : item.titleEn || item.title;
|
||||
const subtitle = locale === "zh" ? item.subtitle : item.subtitleEn || item.subtitle;
|
||||
const description = locale === "zh" ? item.description : item.descriptionEn || item.description;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#fafafa] dark:bg-gray-950">
|
||||
<main className="mx-auto max-w-6xl px-4 py-8 sm:px-6 sm:py-12">
|
||||
<div className="grid gap-8 lg:grid-cols-[360px_1fr]">
|
||||
<div className="overflow-hidden rounded-2xl bg-white shadow-lg dark:bg-gray-900">
|
||||
<img src={item.coverImage} alt={title} className="h-96 w-full object-cover" />
|
||||
</div>
|
||||
<section>
|
||||
<p className="text-sm font-semibold text-[#ff4d4f]">电子书</p>
|
||||
<h1 className="mt-2 text-3xl font-bold text-gray-900 dark:text-gray-100 sm:text-4xl">{title}</h1>
|
||||
<p className="mt-3 text-lg text-gray-500 dark:text-gray-400">{subtitle}</p>
|
||||
<p className="mt-6 leading-8 text-gray-700 dark:text-gray-300">{description}</p>
|
||||
|
||||
<div className="mt-8 rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-900">
|
||||
<h2 className="font-bold text-gray-900 dark:text-gray-100">目录</h2>
|
||||
<div className="mt-4 space-y-3">
|
||||
{(item.chapters || []).map((chapter, index) => (
|
||||
<div key={chapter.title} className="flex items-center justify-between rounded-xl bg-gray-50 px-4 py-3 dark:bg-gray-800">
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">{index + 1}. {chapter.title}</span>
|
||||
<span className="text-xs text-gray-400">{chapter.minutes || 8} min</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex flex-wrap gap-3">
|
||||
<a href={item.mediaUrl || "#"} className="rounded-full bg-[#ff4d4f] px-5 py-3 text-sm font-semibold text-white hover:bg-[#ff7a45]">
|
||||
下载电子书
|
||||
</a>
|
||||
<Link href="/dashboard" className="rounded-full bg-[#ff4d4f] px-5 py-3 text-sm font-semibold text-white hover:bg-[#ff7a45]">
|
||||
用城市数据规划路线
|
||||
</Link>
|
||||
<Link href="/" className="rounded-full border border-gray-300 px-5 py-3 text-sm font-semibold text-gray-700 hover:bg-gray-100 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800">
|
||||
返回首页
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section className="mt-12">
|
||||
<h2 className="mb-4 text-xl font-bold text-gray-900 dark:text-gray-100">书中关联城市</h2>
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{cities.map((city) => (
|
||||
<Link key={city.slug} href={`/city/${city.slug}`} className="rounded-2xl bg-white p-5 shadow-sm dark:bg-gray-900">
|
||||
<div className="text-3xl">{city.icon}</div>
|
||||
<h3 className="mt-3 font-bold text-gray-900 dark:text-gray-100">{locale === "zh" ? city.name : city.nameEn || city.name}</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">¥{city.costPerMonth}/月 · {city.internetSpeed}Mbps</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user