公开页补齐自引用 Canonical 与按类型的结构化数据,图片补上说明文字。

后台设置和内容编辑改成可导航布局;部署用访客 origin 生成 Canonical,避免落到容器地址。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-20 00:11:24 +08:00
parent 2b3d122e56
commit 40bbad2c82
51 changed files with 3042 additions and 1318 deletions

View File

@@ -15,7 +15,7 @@ import { siteDocumentTitle } from "@/lib/brand";
import { isAdminOrAbove } from "@/lib/roles";
import Pagination from "@/components/Pagination";
import PostRow from "@/components/PostRow";
import FeedTabs, { type FeedSort } from "@/components/home/FeedTabs";
import FeedTabs from "@/components/home/FeedTabs";
import FeedNewTip from "@/components/home/FeedNewTip";
import { WorkspaceNav, WorkspaceNavMobile } from "@/components/home/WorkspaceNav";
import { SiteDocBreadcrumb } from "@/components/site-doc";
@@ -25,25 +25,24 @@ import {
BoardPanelsMobile,
BoardMissingPanel,
} from "@/components/home/BoardPanels";
export async function generateMetadata(): Promise<Metadata> {
const settings = await getPublicSettingsCached();
return {
title: { absolute: siteDocumentTitle(settings.site_name, settings.site_slogan) },
description: settings.site_description,
};
}
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 }>;
}
function normalizeSort(v: string | undefined, allowComments: boolean): FeedSort {
if (v === "recommended") return "recommended";
if (v === "new") return "new";
if (!allowComments) return "new";
if (v === "latest") return "latest";
return "latest";
export async function generateMetadata({ searchParams }: HomePageProps): Promise<Metadata> {
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))),
};
}
// 空结果面板(无帖 / 无搜索结果)
@@ -90,7 +89,7 @@ export default async function HomePage({ searchParams }: HomePageProps) {
const cookie = authCookieHeader(await cookies());
const publicSettings = await getPublicSettingsCached();
const allowComments = publicSettings.allow_comments !== false;
const sort = normalizeSort(sp.sort, allowComments);
const sort = normalizeFeedSort(sp.sort, allowComments);
// 与 layout 共享同一份 /me 请求(React cache 去重)
const mePromise = getMeCached(cookie || undefined);
const postsPromise = fetchPosts(
@@ -125,10 +124,31 @@ export default async function HomePage({ searchParams }: HomePageProps) {
// boardId 有效但接口失败 → 显示简卡;成功则板级右栏;否则全站右栏
const boardPanelsMissing = showBoardPanels && !boardSidebar;
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,
});
// 搜索态:单栏占满,无 Tabs/侧栏
if (isSearch) {
return (
<div className="max-w-3xl mx-auto">
<JsonLd data={listingLd} />
<div className="mb-5">
<h1 className="text-[24px] sm:text-[28px] font-extrabold tracking-tight" style={{ color: "var(--ink)" }}>
搜索“<span style={{ color: "var(--accent)" }}>{keyword}</span>”
@@ -149,27 +169,23 @@ export default async function HomePage({ searchParams }: HomePageProps) {
page={page}
size={20}
basePath="/"
query={{ q: keyword }}
query={{
q: keyword,
...(sort !== defaultFeedSort(allowComments) ? { sort } : {}),
}}
/>
</div>
);
}
// 置顶帖直接按后端 pinned DESC 顺序并入统一信息流;标题仅供屏幕阅读器
const heading = activeBoard
? activeBoard.name
: sort === "new"
? "最新发布"
: sort === "recommended"
? "推荐帖"
: "最新讨论";
const pageQuery: Record<string, string> = {};
if (boardId > 0) pageQuery.board = String(boardId);
if (sort !== "latest") pageQuery.sort = sort;
if (sort !== defaultFeedSort(allowComments)) pageQuery.sort = sort;
return (
<div>
<JsonLd data={listingLd} />
<WorkspaceNavMobile boards={boards} user={user} />
<div className={`flex items-start gap-6 xl:gap-7 ${activeBoard ? "" : "mt-5 lg:mt-0"}`}>
@@ -177,7 +193,7 @@ export default async function HomePage({ searchParams }: HomePageProps) {
{/* 中栏:tab 栏与帖子流为同一个整体卡片(左栏切板块只替换这一列) */}
<div className="flex-1 min-w-0">
<h1 className="sr-only">{heading}</h1>
<h1 className="sr-only">{pageName}</h1>
<div className="panel overflow-hidden">
{activeBoard ? (