feat: 友链/书库/移动端导航/排行榜等多模块功能
新增模块: - 友链(friendlink)后端处理 + 前端管理页与友链板 - 书库(library):导入、章节、封面、阅读页、横竖版自适应 AdaptiveCoverSlot - 顶栏导航(header_nav)配置与 MobileTabBar/MobileRailDrawers 移动端抽屉 - 排行榜 service 测试、站点页面测试、时间线发布(timeline_release) 其它改动: - 后端 handlers/services 全量小幅调整 - 前端组件、库函数、URL/品牌/站点 URL 工具更新 - Lightbox 图片、MdEntries 条目卡、coverColor 派生色相等前端能力
This commit is contained in:
86
frontend/app/links/page.tsx
Normal file
86
frontend/app/links/page.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import type { Metadata } from "next";
|
||||
import { cookies } from "next/headers";
|
||||
import { fetchFriendLinks, fetchSiteState, type FriendLink } from "@/lib/api";
|
||||
import { authCookieHeader } from "@/lib/cookies";
|
||||
import { brandAliasPath } from "@/lib/brand";
|
||||
import { getMeCached, getPublicSettingsCached } from "@/lib/serverData";
|
||||
import { isAdminOrAbove } from "@/lib/roles";
|
||||
import { requestCanonical } from "@/lib/canonicalMeta";
|
||||
import { resolveSiteOrigin } from "@/lib/siteUrl";
|
||||
import LinksBoard from "./LinksBoard";
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
return {
|
||||
title: "友情链接",
|
||||
description: "本站友情链接收录与申请",
|
||||
...(await requestCanonical("/links")),
|
||||
};
|
||||
}
|
||||
|
||||
export default async function FriendLinksPage() {
|
||||
const cookie = authCookieHeader(await cookies());
|
||||
let links: FriendLink[] = [];
|
||||
let error = "";
|
||||
try {
|
||||
const res = await fetchFriendLinks(cookie || undefined);
|
||||
links = res.links ?? [];
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : "加载友情链接失败";
|
||||
}
|
||||
|
||||
const [settings, siteState, me] = await Promise.all([
|
||||
getPublicSettingsCached(),
|
||||
fetchSiteState(),
|
||||
getMeCached(cookie || undefined),
|
||||
]);
|
||||
const isLoggedIn = !!me.user;
|
||||
const canManage = !!me.user && isAdminOrAbove(me.user.role);
|
||||
// 站点 LOGO 用固定对外地址(换图不变链,对方收藏的地址永不失效),补全为绝对地址供申请人直接复制
|
||||
const siteBase = siteState.site_url?.trim() || resolveSiteOrigin() || "";
|
||||
const logoAlias = settings.logo_light_url
|
||||
? brandAliasPath("logo_light")
|
||||
: settings.favicon_url
|
||||
? brandAliasPath("favicon")
|
||||
: "";
|
||||
const siteLogo = logoAlias && siteBase ? new URL(logoAlias, siteBase).toString() : "";
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-[1200px] px-4 sm:px-6 py-8">
|
||||
{/* 标题 + 申请须知 */}
|
||||
<header className="text-center">
|
||||
<h1
|
||||
className="text-[26px] sm:text-[30px] font-extrabold tracking-tight inline-flex flex-col items-center"
|
||||
style={{ color: "var(--ink)" }}
|
||||
>
|
||||
友情链接
|
||||
<span
|
||||
className="mt-1.5 h-[3px] w-10 rounded-full"
|
||||
style={{ background: "var(--accent)" }}
|
||||
aria-hidden
|
||||
/>
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
{/* 搜索 + 卡片墙 + 我的申请 + 申请弹窗 */}
|
||||
<div className="mt-5">
|
||||
{error ? (
|
||||
<p
|
||||
className="rounded-lg px-3 py-2 text-[13px] text-center"
|
||||
style={{ background: "var(--panel-2)", color: "var(--ink)", border: "1px solid var(--line)" }}
|
||||
>
|
||||
{error}
|
||||
</p>
|
||||
) : (
|
||||
<LinksBoard
|
||||
links={links}
|
||||
isLoggedIn={isLoggedIn}
|
||||
canManage={canManage}
|
||||
siteName={settings.site_name}
|
||||
siteUrl={siteState.site_url}
|
||||
siteLogo={siteLogo}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user