import type { Metadata } from "next"; import { cookies } from "next/headers"; import Link from "next/link"; import { PenSquare, SearchX, MessagesSquare } from "lucide-react"; import { fetchPosts, fetchOverview, fetchBoardSidebar, fetchSidebar, fetchFriendLinks, type FriendLink, type OverviewResponse, type BoardSidebarResponse, type SidebarPublicPayload, } from "@/lib/api"; import { defaultSidebarConfig, sidebarScopeLists } from "@/lib/sidebarMeta"; import SidebarRenderer, { type SidebarBlockProps } from "@/components/sidebar/SidebarRenderer"; import { authCookieHeader } from "@/lib/cookies"; import { getMeCached, getPublicSettingsCached } from "@/lib/serverData"; import { siteDocumentTitle } from "@/lib/brand"; import { isAdminOrAbove } from "@/lib/roles"; import Pagination from "@/components/Pagination"; import PostRow from "@/components/PostRow"; import FeedTabs from "@/components/home/FeedTabs"; import FeedNewTip from "@/components/home/FeedNewTip"; import { WorkspaceNavMobile } from "@/components/home/WorkspaceNav"; import { BoardMissingPanel } from "@/components/home/BoardPanels"; import { requestCanonical } from "@/lib/canonicalMeta"; import { defaultFeedSort, homeCanonicalQuery, normalizeFeedSort } from "@/lib/siteUrl"; import { listingJsonLd } from "@/lib/jsonLd"; import JsonLd from "@/components/JsonLd"; interface HomePageProps { searchParams: Promise<{ q?: string; page?: string; sort?: string; board?: string }>; } export async function generateMetadata({ searchParams }: HomePageProps): Promise { const settings = await getPublicSettingsCached(); const sp = await searchParams; const allowComments = settings.allow_comments !== false; return { title: { absolute: siteDocumentTitle(settings.site_name, settings.site_slogan) }, description: settings.site_description, ...(await requestCanonical("/", homeCanonicalQuery(sp, allowComments))), }; } // 空结果面板(无帖 / 无搜索结果) function EmptyState({ keyword }: { keyword: string }) { return (
{keyword ? : }

{keyword ? "没有找到相关帖子" : "还没有帖子"}

{keyword ? `换个关键词试试,或者发起“${keyword}”相关的话题` : "成为第一个发起讨论的人吧"}

{keyword ? (
清除搜索 发起这个话题
) : ( 写新帖 )}
); } export default async function HomePage({ searchParams }: HomePageProps) { const sp = await searchParams; const keyword = sp.q?.trim() || ""; const page = Math.max(1, parseInt(sp.page || "1", 10) || 1); const boardId = Math.max(0, parseInt(sp.board || "0", 10) || 0); const isSearch = keyword.length > 0; const cookie = authCookieHeader(await cookies()); const publicSettings = await getPublicSettingsCached(); const allowComments = publicSettings.allow_comments !== false; const sort = normalizeFeedSort(sp.sort, allowComments); // 与 layout 共享同一份 /me 请求(React cache 去重) const mePromise = getMeCached(cookie || undefined); const postsPromise = fetchPosts( page, 20, isSearch || boardId <= 0 ? undefined : boardId, sort, keyword, sort === "recommended", cookie ); // 聚合面板失败不阻塞帖子流 const overviewPromise: Promise = fetchOverview(cookie).catch(() => null); const boardSidebarPromise: Promise = boardId > 0 && !isSearch ? fetchBoardSidebar(boardId, cookie).catch(() => null) : Promise.resolve(null); // 侧栏布局配置失败不阻塞页面,回落默认布局 const sidebarPromise: Promise = isSearch ? Promise.resolve(null) : fetchSidebar(cookie).catch(() => null); // 友情链接列表失败不阻塞页面 const friendLinksPromise: Promise<{ links: FriendLink[] } | null> = isSearch ? Promise.resolve(null) : fetchFriendLinks(cookie).catch(() => null); const [me, postsData, overview, boardSidebar, sidebarData, friendLinksData] = await Promise.all([ mePromise, postsPromise, overviewPromise, boardSidebarPromise, sidebarPromise, friendLinksPromise, ]); const sidebar = sidebarData ?? { config: defaultSidebarConfig(), widgets: [] }; const user = me.user; const boards = overview?.boards ?? []; const canManageAnnouncements = !!user && isAdminOrAbove(user.role); const canManagePages = canManageAnnouncements; const sidebarPages = overview?.sidebar_pages ?? []; const activeBoard = boards.find((b) => b.id === boardId) || boardSidebar?.board || null; const showBoardPanels = boardId > 0 && !isSearch; // boardId 有效但接口失败 → 显示简卡;成功则板级右栏;否则全站右栏 const boardPanelsMissing = showBoardPanels && !boardSidebar; // 侧栏布局:板块态右栏走 board 配置(左栏跟随首页),其余走 home 配置 const scopeLists = sidebarScopeLists(sidebar.config, showBoardPanels ? "board" : "home"); // 三栏竖向分隔线跟随左右栏显隐(空配置不画悬空线) const hasLeftRail = scopeLists.left.length > 0; const hasRightRail = showBoardPanels ? boardSidebar ? scopeLists.right.length > 0 : boardPanelsMissing : !!(overview && scopeLists.right.length > 0); const sidebarProps: SidebarBlockProps = { allowComments, user, boards, checkin: (boardSidebar ?? overview)?.checkin ?? undefined, // 友链列表独立于 overview:即使聚合接口失败也保留「申请」入口 friendLinks: friendLinksData?.links ?? [], ...(overview ? { stats: overview.stats, announcements: overview.announcements, canManageAnnouncements, ads: overview.ads ?? [], adsPanelTitle: overview.ads_panel_title || "自助推广", adsEnabled: overview.ads_enabled !== false, sponsors: overview.sponsors ?? [], sponsorsPanelTitle: overview.sponsors_panel_title || "赞助商", sponsorsEnabled: overview.sponsors_enabled !== false, hot: overview.hot, newUsers: overview.new_users, activeUsers: overview.active_users, } : {}), canManagePages, sidebarPages, siteDescription: publicSettings.site_description, ...(boardSidebar ? { board: boardSidebar.board, moderators: boardSidebar.moderators, boardStats: boardSidebar.stats, boardHot: boardSidebar.hot, boardActiveUsers: boardSidebar.active_users, } : {}), }; const pageName = isSearch ? `搜索“${keyword}”` : activeBoard ? activeBoard.name : sort === "new" ? "最新发布" : sort === "recommended" ? "推荐帖" : "最新讨论"; const listingLd = listingJsonLd({ siteName: publicSettings.site_name || "姜十三论坛", siteDescription: publicSettings.site_description, pageName, path: "/", query: homeCanonicalQuery(sp, allowComments), items: postsData.posts.map((p) => ({ id: p.id, name: p.title })), page, pageSize: 20, urlStyle: publicSettings.url_style, }); // 搜索态:单栏占满,无 Tabs/侧栏 if (isSearch) { return (

搜索“{keyword}”

共 {postsData.total} 篇结果 · 按相关度与时间排序

{postsData.posts.length > 0 ? ( postsData.posts.map((post, i) => ( )) ) : ( )}
); } // 置顶帖直接按后端 pinned DESC 顺序并入统一信息流;标题仅供屏幕阅读器 const pageQuery: Record = {}; if (boardId > 0) pageQuery.board = String(boardId); if (sort !== defaultFeedSort(allowComments)) pageQuery.sort = sort; return (
{/* 移动端:顶部横向板块条 = 左右抽屉的触发行—— ☰ 打开左抽屉(桌面左栏:板块目录 + 工作台),▦ 打开右抽屉(桌面右栏面板) */} 0 ? ( ) : undefined } right={ scopeLists.right.length > 0 ? ( ) : undefined } />
{/* 左栏:按配置渲染(缺省 = 板块目录) */} {scopeLists.left.length > 0 && ( )} {/* 中栏:tab 栏与帖子流为同一个整体(左栏切板块只替换这一列);self-stretch 撑满行高供分隔线贯通 */}

{pageName}

共 {postsData.total} 篇
{user && ( )} {postsData.posts.length > 0 ? ( postsData.posts.map((post, i) => ( )) ) : ( )}
0 ? pageQuery : undefined} />
{/* 右栏:选中板块时切板级面板,否则全站社区面板;均按配置渲染 */} {showBoardPanels ? ( boardSidebar ? ( scopeLists.right.length > 0 ? ( ) : null ) : boardPanelsMissing ? ( ) : null ) : overview && scopeLists.right.length > 0 ? ( ) : null}
); }