feat: 友链/书库/移动端导航/排行榜等多模块功能

新增模块:
- 友链(friendlink)后端处理 + 前端管理页与友链板
- 书库(library):导入、章节、封面、阅读页、横竖版自适应 AdaptiveCoverSlot
- 顶栏导航(header_nav)配置与 MobileTabBar/MobileRailDrawers 移动端抽屉
- 排行榜 service 测试、站点页面测试、时间线发布(timeline_release)

其它改动:
- 后端 handlers/services 全量小幅调整
- 前端组件、库函数、URL/品牌/站点 URL 工具更新
- Lightbox 图片、MdEntries 条目卡、coverColor 派生色相等前端能力
This commit is contained in:
2026-09-30 16:30:07 +08:00
parent e487bc2e92
commit 4f3f3a265c
105 changed files with 12371 additions and 852 deletions

View File

@@ -26,13 +26,13 @@ import type {
SidebarPageItem,
PublicAdItem,
PublicSponsorItem,
FriendLink,
} from "@/lib/api";
import Avatar from "@/components/Avatar";
import { AdText } from "@/components/AdText";
import { getPublicSettingsCached } from "@/lib/serverData";
import { announcementPath, postPath, userPath, pagePath } from "@/lib/urlStyle";
import { PulseCheckin } from "./CheckinCard";
import { FRIEND_LINKS } from "@/lib/friendLinks";
import { formatCompact, formatShortDate } from "@/lib/format";
import { pageGlyph } from "@/lib/pageGlyph";
import { AboutSiteHeader } from "@/components/site-doc";
@@ -396,38 +396,53 @@ export async function ActiveUsers({ users, allowComments }: { users: ActiveUser[
/* ---------- 友情链接 ---------- */
export function FriendLinks() {
if (FRIEND_LINKS.length === 0) return null;
export function FriendLinks({ links }: { links: FriendLink[] }) {
// 块显隐由侧栏布局配置控制;列表为空时仍渲染(保留「申请」入口),仅展示占位提示
return (
<section className="j13-module" aria-label="友情链接">
<p className="j13-module-title mb-3">
<Link2 size={15} style={{ color: "var(--accent)" }} /> 友情链接
<Link
href="/compose"
className="meta ml-auto text-[12px] font-normal transition-colors hover:text-[var(--accent)]"
>
申请
</Link>
<span className="ml-auto inline-flex items-center gap-3 text-[12px] font-normal">
<Link href="/links" className="meta transition-colors hover:text-[var(--accent)]">
全部
</Link>
<Link
href="/links#apply"
className="meta transition-colors hover:text-[var(--accent)]"
>
申请
</Link>
</span>
</p>
<div className="flex flex-wrap gap-2">
{FRIEND_LINKS.map((l) =>
l.href.startsWith("http") ? (
{links.length === 0 ? (
<p className="meta text-[12.5px]">暂无友链,欢迎申请</p>
) : (
<div className="flex flex-wrap gap-2">
{links.map((l) => (
<a
key={l.name}
href={l.href}
key={l.id}
href={l.url}
target="_blank"
rel="noopener noreferrer"
className="j13-chip"
className="j13-chip inline-flex items-center gap-1.5"
title={l.description || l.name}
>
{l.logo_url ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={l.logo_url}
alt=""
width={14}
height={14}
className="object-contain"
style={{ width: 14, height: 14 }}
/>
) : null}
{l.name}
</a>
) : (
<span key={l.name} className="j13-chip" data-tip="友链待配置">
{l.name}
</span>
)
)}
</div>
))}
</div>
)}
</section>
);
}
@@ -452,6 +467,7 @@ type PanelsProps = {
sponsors?: PublicSponsorItem[];
sponsorsPanelTitle?: string;
sponsorsEnabled?: boolean;
friendLinks?: FriendLink[];
};
export function AdsPanel({
@@ -615,6 +631,7 @@ function CommunityPanelsBody({
sponsors = [],
sponsorsPanelTitle = "赞助商",
sponsorsEnabled = true,
friendLinks = [],
}: PanelsProps) {
return (
<>
@@ -625,7 +642,7 @@ function CommunityPanelsBody({
<HotRanking posts={hot} allowComments={allowComments} />
<NewUsers users={newUsers} />
<ActiveUsers users={activeUsers} allowComments={allowComments} />
<FriendLinks />
<FriendLinks links={friendLinks} />
<AboutSite
pages={sidebarPages}
siteDescription={siteDescription}