Files
jiang13-bbs/frontend/app/about/page.tsx
freefire 4f3f3a265c feat: 友链/书库/移动端导航/排行榜等多模块功能
新增模块:
- 友链(friendlink)后端处理 + 前端管理页与友链板
- 书库(library):导入、章节、封面、阅读页、横竖版自适应 AdaptiveCoverSlot
- 顶栏导航(header_nav)配置与 MobileTabBar/MobileRailDrawers 移动端抽屉
- 排行榜 service 测试、站点页面测试、时间线发布(timeline_release)

其它改动:
- 后端 handlers/services 全量小幅调整
- 前端组件、库函数、URL/品牌/站点 URL 工具更新
- Lightbox 图片、MdEntries 条目卡、coverColor 派生色相等前端能力
2026-09-30 16:30:07 +08:00

153 lines
4.9 KiB
TypeScript

import type { Metadata } from "next";
import { cookies } from "next/headers";
import Link from "next/link";
import { Info } from "lucide-react";
import {
fetchPublishedSitePages,
fetchOverview,
type SitePageSummary,
} from "@/lib/api";
import { authCookieHeader } from "@/lib/cookies";
import { getMeCached, getPublicSettingsCached } from "@/lib/serverData";
import { isAdminOrAbove } from "@/lib/roles";
import { formatDate } from "@/lib/format";
import { pageGlyph } from "@/lib/pageGlyph";
import {
SiteDocShell,
SiteDocIdentity,
SiteDocAnnoNav,
} from "@/components/site-doc";
import { requestCanonical } from "@/lib/canonicalMeta";
import { pagePath, type UrlStyle } from "@/lib/urlStyle";
export async function generateMetadata(): Promise<Metadata> {
return {
title: "关于本站",
description: "站点说明、政策与帮助文档",
...(await requestCanonical("/about")),
};
}
function PageRow({
p,
showDivider,
urlStyle,
}: {
p: SitePageSummary;
showDivider: boolean;
urlStyle: UrlStyle;
}) {
const Icon = pageGlyph(p.slug);
return (
<Link
href={pagePath(urlStyle, p.slug)}
className="block px-5 sm:px-6 py-4 sm:py-5 transition-colors hover:bg-[var(--panel-2)] focus-visible:bg-[var(--panel-2)]"
style={showDivider ? { borderTop: "1px solid var(--line)" } : undefined}
>
<div className="flex items-start gap-3 min-w-0">
<span
className="w-9 h-9 rounded-lg flex items-center justify-center shrink-0 mt-0.5"
style={{ background: "var(--accent-soft)", color: "var(--accent)" }}
aria-hidden
>
<Icon size={16} strokeWidth={2} />
</span>
<span className="min-w-0 flex-1">
<span className="flex items-center gap-2 flex-wrap">
<h2
className="text-[16px] sm:text-[17px] font-semibold leading-snug tracking-tight"
style={{ color: "var(--ink)" }}
>
{p.title}
</h2>
<time className="meta text-[12px] tabular-nums ml-auto" dateTime={p.updated_at}>
{formatDate(p.updated_at || p.created_at)}
</time>
</span>
{p.excerpt ? (
<p className="mt-1.5 text-[13.5px] leading-relaxed line-clamp-2" style={{ color: "var(--ink-2)" }}>
{p.excerpt}
</p>
) : null}
</span>
</div>
</Link>
);
}
export default async function AboutIndexPage() {
const cookie = authCookieHeader(await cookies());
let pages: SitePageSummary[] = [];
let error = "";
try {
const res = await fetchPublishedSitePages();
pages = res.pages ?? [];
} catch (e) {
error = e instanceof Error ? e.message : "加载页面失败";
}
const [overview, settings, me] = await Promise.all([
fetchOverview().catch(() => null),
getPublicSettingsCached(),
getMeCached(cookie || undefined),
]);
const announcements = overview?.announcements ?? [];
const siteLead = settings.site_description?.trim() || undefined;
const urlStyle = settings.url_style;
const canManagePages = !!me.user && isAdminOrAbove(me.user.role);
const left = <SiteDocIdentity kind="about" lead={siteLead} />;
const right = <SiteDocAnnoNav items={announcements} />;
return (
<SiteDocShell left={left} right={right}>
<div className="panel overflow-hidden">
<header
className="px-5 sm:px-6 pt-5 pb-4"
style={{ borderBottom: "1px solid var(--line)" }}
>
<div className="flex items-end justify-between gap-3">
<div>
<h1
className="text-[24px] sm:text-[28px] font-extrabold tracking-tight flex items-center gap-2"
style={{ color: "var(--ink)" }}
>
<Info size={26} style={{ color: "var(--accent)" }} aria-hidden />
关于本站
</h1>
<p className="meta mt-2">
{error
? "加载失败"
: pages.length > 0
? `共 ${pages.length} 篇站点文档`
: "站点说明与政策文档"}
</p>
</div>
{canManagePages ? (
<Link
href="/admin/pages"
className="meta text-[13px] shrink-0 mb-0.5 transition-colors hover:text-[var(--accent)]"
>
管理
</Link>
) : null}
</div>
</header>
{error ? (
<p className="alert-error m-5">{error}</p>
) : pages.length === 0 ? (
<div className="p-10 text-center">
<p className="font-medium" style={{ color: "var(--ink)" }}>
暂无介绍
</p>
<p className="meta mt-1.5">有新文档时会显示在这里</p>
</div>
) : (
pages.map((p, i) => <PageRow key={p.id} p={p} showDivider={i > 0} urlStyle={urlStyle} />)
)}
</div>
</SiteDocShell>
);
}