mirror of
https://github.com/KivotosRailway/website.git
synced 2026-11-03 11:06:32 +09:00
优化代码
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"nodeVersion": "22.11.0",
|
||||
"redirects": [
|
||||
{ "source": "/zh-CN", "destination": "/zh-Hans", "statusCode": 301 },
|
||||
{ "source": "/zh-CN/*", "destination": "/zh-Hans/:splat", "statusCode": 301 },
|
||||
|
||||
+12
-12
@@ -13,20 +13,20 @@
|
||||
"preview": "wrangler dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/noto-sans": "^5.3.0",
|
||||
"@fontsource-variable/noto-sans-sc": "^5.3.0",
|
||||
"marked": "^18.0.11",
|
||||
"next": "latest",
|
||||
"react": "latest",
|
||||
"react-dom": "latest"
|
||||
"@fontsource-variable/noto-sans": "5.3.0",
|
||||
"@fontsource-variable/noto-sans-sc": "5.3.0",
|
||||
"marked": "18.0.11",
|
||||
"next": "16.3.5",
|
||||
"react": "19.3.0",
|
||||
"react-dom": "19.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "latest",
|
||||
"@types/react": "latest",
|
||||
"@types/react-dom": "latest",
|
||||
"eslint": "latest",
|
||||
"eslint-config-next": "latest",
|
||||
"typescript": "latest",
|
||||
"@types/node": "22.20.2",
|
||||
"@types/react": "19.3.0",
|
||||
"@types/react-dom": "19.3.0",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-config-next": "16.3.5",
|
||||
"typescript": "5.9.3",
|
||||
"wrangler": "4.131.1"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+298
-434
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import { FriendlyLinksPage } from "@/components/links/friendly-links-page";
|
||||
import { getLocaleMessages, isLocale } from "@/lib/i18n";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale?: string }> }): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
return { title: getLocaleMessages(isLocale(locale) ? locale : "zh-Hans").footer.legal[2] };
|
||||
}
|
||||
|
||||
export default async function LinksPage({ params }: { params: Promise<{ locale?: string }> }) {
|
||||
const { locale } = await params;
|
||||
return <FriendlyLinksPage locale={isLocale(locale) ? locale : "zh-Hans"} />;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import { getNewsBySlug, getNewsCatalog, getNewsList } from "@/data/news";
|
||||
import { NewsDetailClient } from "@/components/news/news-detail-client";
|
||||
import { isLocale } from "@/lib/i18n";
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const news = await getNewsList();
|
||||
|
||||
return news.map((item) => ({ slug: item.slug }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string; locale?: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { slug, locale } = await params;
|
||||
const item = await getNewsBySlug(slug, isLocale(locale) ? locale : "zh-Hans");
|
||||
|
||||
return {
|
||||
title: item?.title ?? "新闻",
|
||||
};
|
||||
}
|
||||
|
||||
export default async function NewsDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
return <NewsDetailClient catalog={await getNewsCatalog()} slug={slug} />;
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import { getNewsCatalog } from "@/data/news";
|
||||
import { PageBreadcrumb } from "@/components/layout/page-breadcrumb";
|
||||
import { NewsList } from "@/components/news/news-list";
|
||||
import { NewsPageHeading } from "@/components/news/news-page-heading";
|
||||
import { getLocaleMessages, isLocale } from "@/lib/i18n";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale?: string }> }): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
return { title: getLocaleMessages(isLocale(locale) ? locale : "zh-Hans").common.news };
|
||||
}
|
||||
|
||||
export default async function NewsPage({ params }: { params: Promise<{ locale?: string }> }) {
|
||||
const { locale } = await params;
|
||||
const messages = getLocaleMessages(isLocale(locale) ? locale : "zh-Hans");
|
||||
const catalog = await getNewsCatalog();
|
||||
|
||||
return (
|
||||
<main className="news-page">
|
||||
<div className="news-page-inner">
|
||||
<header className="news-page-header">
|
||||
<NewsPageHeading />
|
||||
</header>
|
||||
|
||||
<NewsList catalog={catalog} />
|
||||
</div>
|
||||
<div className="page-bottom-nav">
|
||||
<div className="page-bottom-nav-inner">
|
||||
<PageBreadcrumb items={[{ label: messages.common.home, href: "/" }, { label: messages.common.news }]} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { PolicyPageContent } from "@/components/policy/policy-page-content";
|
||||
import { getPolicyCatalog, getPolicySlugs } from "@/data/policy";
|
||||
import { isLocale } from "@/lib/i18n";
|
||||
|
||||
export const dynamicParams = false;
|
||||
|
||||
export function generateStaticParams() {
|
||||
return getPolicySlugs();
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ slug: string; locale?: string }> }): Promise<Metadata> {
|
||||
const { slug, locale } = await params;
|
||||
const catalog = await getPolicyCatalog(slug);
|
||||
return { title: catalog[isLocale(locale) ? locale : "zh-Hans"]?.title ?? "法律信息" };
|
||||
}
|
||||
|
||||
export default async function PolicyPage({ params }: { params: Promise<{ slug: string }> }) {
|
||||
const { slug } = await params;
|
||||
const catalog = await getPolicyCatalog(slug);
|
||||
if (!catalog["zh-Hans"]) notFound();
|
||||
|
||||
return (
|
||||
<main className="policy-page">
|
||||
<div className="policy-page-inner">
|
||||
<PolicyPageContent catalog={catalog} />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1 +1,15 @@
|
||||
export { default, generateMetadata } from "@/app/(legacy)/links/page";
|
||||
import { notFound } from "next/navigation";
|
||||
import { FriendlyLinksPage } from "@/components/links/friendly-links-page";
|
||||
import { getLocaleMessages, isLocale } from "@/lib/i18n";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
if (!isLocale(locale)) notFound();
|
||||
return { title: getLocaleMessages(locale).footer.legal[2] };
|
||||
}
|
||||
|
||||
export default async function LinksPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
if (!isLocale(locale)) notFound();
|
||||
return <FriendlyLinksPage locale={locale} />;
|
||||
}
|
||||
|
||||
@@ -1 +1,21 @@
|
||||
export { default, generateMetadata, generateStaticParams } from "@/app/(legacy)/news/[slug]/page";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getNewsBySlug, getNewsList } from "@/data/news";
|
||||
import { NewsDetailClient } from "@/components/news/news-detail-client";
|
||||
import { isLocale } from "@/lib/i18n";
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return (await getNewsList()).map((item) => ({ slug: item.slug }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ slug: string; locale: string }> }) {
|
||||
const { slug, locale } = await params;
|
||||
if (!isLocale(locale)) notFound();
|
||||
const item = await getNewsBySlug(slug, locale);
|
||||
return { title: item?.title ?? "新闻" };
|
||||
}
|
||||
|
||||
export default async function NewsDetailPage({ params }: { params: Promise<{ slug: string; locale: string }> }) {
|
||||
const { slug, locale } = await params;
|
||||
if (!isLocale(locale)) notFound();
|
||||
return <NewsDetailClient item={await getNewsBySlug(slug, locale)} locale={locale} />;
|
||||
}
|
||||
|
||||
@@ -1 +1,28 @@
|
||||
export { default, generateMetadata } from "@/app/(legacy)/news/page";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getNewsList } from "@/data/news";
|
||||
import { PageBreadcrumb } from "@/components/layout/page-breadcrumb";
|
||||
import { NewsList } from "@/components/news/news-list";
|
||||
import { NewsPageHeading } from "@/components/news/news-page-heading";
|
||||
import { getLocaleMessages, isLocale } from "@/lib/i18n";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
if (!isLocale(locale)) notFound();
|
||||
return { title: getLocaleMessages(locale).common.news };
|
||||
}
|
||||
|
||||
export default async function NewsPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
if (!isLocale(locale)) notFound();
|
||||
const messages = getLocaleMessages(locale);
|
||||
|
||||
return (
|
||||
<main className="news-page">
|
||||
<div className="news-page-inner">
|
||||
<header className="news-page-header"><NewsPageHeading /></header>
|
||||
<NewsList news={await getNewsList(locale)} />
|
||||
</div>
|
||||
<div className="page-bottom-nav"><div className="page-bottom-nav-inner"><PageBreadcrumb items={[{ label: messages.common.home, href: "/" }, { label: messages.common.news }]} /></div></div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1 +1,26 @@
|
||||
export { default, generateMetadata, generateStaticParams } from "@/app/(legacy)/policy/[slug]/page";
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { PolicyPageContent } from "@/components/policy/policy-page-content";
|
||||
import { getPolicyCatalog, getPolicySlugs } from "@/data/policy";
|
||||
import { isLocale } from "@/lib/i18n";
|
||||
|
||||
export const dynamicParams = false;
|
||||
|
||||
export function generateStaticParams() {
|
||||
return getPolicySlugs();
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ slug: string; locale: string }> }): Promise<Metadata> {
|
||||
const { slug, locale } = await params;
|
||||
if (!isLocale(locale)) notFound();
|
||||
const catalog = await getPolicyCatalog(slug);
|
||||
return { title: catalog[locale]?.title ?? "法律信息" };
|
||||
}
|
||||
|
||||
export default async function PolicyPage({ params }: { params: Promise<{ slug: string; locale: string }> }) {
|
||||
const { slug, locale } = await params;
|
||||
if (!isLocale(locale)) notFound();
|
||||
const catalog = await getPolicyCatalog(slug);
|
||||
if (!catalog["zh-Hans"]) notFound();
|
||||
return <main className="policy-page"><div className="policy-page-inner"><PolicyPageContent catalog={catalog} /></div></main>;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ function isIcpVisibleHost(hostname: string) {
|
||||
|
||||
export function SiteFooter() {
|
||||
const [locale, setLocale] = useState<Locale>(useRouteLocale());
|
||||
const [showIcp, setShowIcp] = useState(false);
|
||||
const [showIcp] = useState(() => typeof window !== "undefined" && isIcpVisibleHost(window.location.hostname));
|
||||
|
||||
useEffect(() => {
|
||||
const syncLocale = (nextLocale: Locale) => setLocale(nextLocale);
|
||||
@@ -37,7 +37,6 @@ export function SiteFooter() {
|
||||
const currentLocale = getInitialLocale();
|
||||
syncLocale(currentLocale);
|
||||
applyLocale(currentLocale);
|
||||
setShowIcp(isIcpVisibleHost(window.location.hostname));
|
||||
window.addEventListener("kr-locale-change", handleLocaleChange);
|
||||
window.addEventListener("storage", handleStorage);
|
||||
|
||||
|
||||
@@ -27,7 +27,10 @@ function FriendlyLinkCard({ link, locale, visitLabel }: { link: LinkEntry; local
|
||||
<>
|
||||
<span className="friendly-link-card-content">
|
||||
<span className="friendly-link-card-heading">
|
||||
<span className="friendly-link-card-logo"><img src={link.logo} alt="" width={66} height={66} /></span>
|
||||
<span className="friendly-link-card-logo">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element -- Static export has no Image Optimization API. */}
|
||||
<img src={link.logo} alt="" width={66} height={66} />
|
||||
</span>
|
||||
<span>{name}</span>
|
||||
</span>
|
||||
<span className="friendly-link-card-description">{description}</span>
|
||||
|
||||
@@ -1,50 +1,32 @@
|
||||
"use client";
|
||||
|
||||
import { useRouteLocale } from "@/components/layout/locale-context";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { NewsItem } from "@/data/news";
|
||||
import { getAiTranslationNotice, getInitialLocale, withLocale, getLocaleMessages, type Locale } from "@/lib/i18n";
|
||||
import { getAiTranslationNotice, getLocaleMessages, withLocale, type Locale } from "@/lib/i18n";
|
||||
import { markdownToHtml } from "@/lib/markdown";
|
||||
import { PageBreadcrumb } from "@/components/layout/page-breadcrumb";
|
||||
import { NewsShare } from "@/components/news/news-share";
|
||||
|
||||
export function NewsDetailClient({ catalog, slug }: { catalog: Record<Locale, NewsItem[]>; slug: string }) {
|
||||
const [locale, setLocale] = useState<Locale>(useRouteLocale());
|
||||
export function NewsDetailClient({ item, locale }: { item: NewsItem | null; locale: Locale }) {
|
||||
const messages = getLocaleMessages(locale);
|
||||
const item = catalog[locale].find((entry) => entry.slug === slug) ?? catalog["zh-Hans"].find((entry) => entry.slug === slug);
|
||||
|
||||
useEffect(() => {
|
||||
const syncLocale = () => setLocale(getInitialLocale());
|
||||
syncLocale();
|
||||
window.addEventListener("kr-locale-change", syncLocale);
|
||||
window.addEventListener("storage", syncLocale);
|
||||
return () => {
|
||||
window.removeEventListener("kr-locale-change", syncLocale);
|
||||
window.removeEventListener("storage", syncLocale);
|
||||
};
|
||||
}, []);
|
||||
if (!item) {
|
||||
return <main className="news-detail-page"><div className="news-detail-inner"><p>未找到该文章。</p><Link href={withLocale("/news", locale)}>{messages.common.backToNews}</Link></div></main>;
|
||||
}
|
||||
|
||||
if (!item) return <main className="news-detail-page"><div className="news-detail-inner"><p>未找到该文章。</p><Link href={withLocale("/news", locale)}>{messages.common.backToNews}</Link></div></main>;
|
||||
|
||||
return <main className="news-detail-page">
|
||||
<div className="news-detail-inner">
|
||||
<Link href={withLocale("/news", locale)} className="back-link">← {messages.common.backToNews}</Link>
|
||||
<article className="news-detail-card">
|
||||
<p className="news-detail-tag">{item.category}</p>
|
||||
<h1>{item.title}</h1>
|
||||
<time>{item.date}</time>
|
||||
{locale !== "zh-Hans" && (
|
||||
<div className="news-detail-ai-banner" aria-label="AI translated content">
|
||||
{getAiTranslationNotice(locale)}
|
||||
</div>
|
||||
)}
|
||||
<div className="news-detail-cover" style={{ backgroundImage: `url(${item.cover})` }} />
|
||||
<div className="news-detail-body" dangerouslySetInnerHTML={{ __html: markdownToHtml(item.body) }} />
|
||||
<NewsShare title={item.title} />
|
||||
</article>
|
||||
</div>
|
||||
<div className="page-bottom-nav"><div className="page-bottom-nav-inner"><PageBreadcrumb ariaLabel={messages.common.pagePosition} items={[{ label: messages.common.home, href: "/" }, { label: messages.common.news, href: "/news" }, { label: item.title }]} /></div></div>
|
||||
</main>;
|
||||
}
|
||||
return (
|
||||
<main className="news-detail-page">
|
||||
<div className="news-detail-inner">
|
||||
<Link href={withLocale("/news", locale)} className="back-link">← {messages.common.backToNews}</Link>
|
||||
<article className="news-detail-card">
|
||||
<p className="news-detail-tag">{item.category}</p>
|
||||
<h1>{item.title}</h1>
|
||||
<time>{item.date}</time>
|
||||
{locale !== "zh-Hans" && <div className="news-detail-ai-banner" aria-label="AI translated content">{getAiTranslationNotice(locale)}</div>}
|
||||
<div className="news-detail-cover" style={{ backgroundImage: `url(${item.cover})` }} />
|
||||
<div className="news-detail-body" dangerouslySetInnerHTML={{ __html: markdownToHtml(item.body) }} />
|
||||
<NewsShare title={item.title} />
|
||||
</article>
|
||||
</div>
|
||||
<div className="page-bottom-nav"><div className="page-bottom-nav-inner"><PageBreadcrumb ariaLabel={messages.common.pagePosition} items={[{ label: messages.common.home, href: "/" }, { label: messages.common.news, href: "/news" }, { label: item.title }]} /></div></div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,12 +9,11 @@ import { getInitialLocale, withLocale, getLocaleMessages, type Locale } from "@/
|
||||
|
||||
const ITEMS_PER_PAGE = 12;
|
||||
|
||||
export function NewsList({ catalog }: { catalog: Record<Locale, NewsItem[]> }) {
|
||||
export function NewsList({ news }: { news: NewsItem[] }) {
|
||||
const [activeCategory, setActiveCategory] = useState<string | null>(null);
|
||||
const [locale, setLocale] = useState<Locale>(useRouteLocale());
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const messages = getLocaleMessages(locale);
|
||||
const news = catalog[locale];
|
||||
|
||||
useEffect(() => {
|
||||
const syncLocale = () => setLocale(getInitialLocale());
|
||||
@@ -27,10 +26,6 @@ export function NewsList({ catalog }: { catalog: Record<Locale, NewsItem[]> }) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [activeCategory]);
|
||||
|
||||
const categories = Array.from(new Set(news.map((item) => item.category).filter(Boolean)));
|
||||
const visibleNews = activeCategory
|
||||
? news.filter((item) => item.category === activeCategory)
|
||||
@@ -48,6 +43,11 @@ export function NewsList({ catalog }: { catalog: Record<Locale, NewsItem[]> }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleCategoryChange = (category: string | null) => {
|
||||
setActiveCategory(category);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (currentPage < totalPages) {
|
||||
setCurrentPage(currentPage + 1);
|
||||
@@ -67,7 +67,7 @@ export function NewsList({ catalog }: { catalog: Record<Locale, NewsItem[]> }) {
|
||||
<button
|
||||
type="button"
|
||||
className={`news-filter-button${activeCategory === null ? " active" : ""}`}
|
||||
onClick={() => setActiveCategory(null)}
|
||||
onClick={() => handleCategoryChange(null)}
|
||||
>
|
||||
{messages.common.all}
|
||||
</button>
|
||||
@@ -76,7 +76,7 @@ export function NewsList({ catalog }: { catalog: Record<Locale, NewsItem[]> }) {
|
||||
key={category}
|
||||
type="button"
|
||||
className={`news-filter-button${activeCategory === category ? " active" : ""}`}
|
||||
onClick={() => setActiveCategory(category)}
|
||||
onClick={() => handleCategoryChange(category)}
|
||||
>
|
||||
{category}
|
||||
</button>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useRouteLocale } from "@/components/layout/locale-context";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { getInitialLocale, withLocale, type Locale } from "@/lib/i18n";
|
||||
|
||||
export type HttpStatusCode = 403 | 404 | 500 | 502 | 503 | 504;
|
||||
@@ -72,7 +72,6 @@ function parseLocalizedMessages(source: string): Partial<Record<Locale, string[]
|
||||
export function HttpStatusPage({ code }: { code: HttpStatusCode }) {
|
||||
const [locale, setLocale] = useState<Locale>(useRouteLocale());
|
||||
const [randomMessages, setRandomMessages] = useState<Partial<Record<Locale, string[]>>>({});
|
||||
const [randomMessage, setRandomMessage] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const syncLocale = () => setLocale(getInitialLocale());
|
||||
@@ -94,10 +93,11 @@ export function HttpStatusPage({ code }: { code: HttpStatusCode }) {
|
||||
.catch(() => setRandomMessages({}));
|
||||
}, [code]);
|
||||
|
||||
useEffect(() => {
|
||||
const randomMessage = useMemo(() => {
|
||||
const messages = randomMessages[locale] ?? randomMessages["zh-Hans"];
|
||||
if (!messages?.length) return;
|
||||
setRandomMessage(messages[Math.floor(Math.random() * messages.length)]);
|
||||
if (!messages?.length) return null;
|
||||
const index = Array.from(`${locale}:${messages.join("|")}`).reduce((total, character) => total + character.codePointAt(0)!, 0) % messages.length;
|
||||
return messages[index];
|
||||
}, [locale, randomMessages]);
|
||||
|
||||
const returnToPreviousPage = () => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { localeContentDirectory, type Locale } from "@/lib/i18n";
|
||||
import { markdownToHtml } from "@/lib/markdown";
|
||||
import { imageMap } from "@/lib/image-map.generated";
|
||||
|
||||
export type NewsItem = {
|
||||
|
||||
Reference in New Issue
Block a user