整体UI调整

This commit is contained in:
谢宇宁
2026-10-26 19:51:59 +07:00
parent c8895f407c
commit eeb9986923
112 changed files with 1507 additions and 4255 deletions
+2 -2
View File
@@ -8,7 +8,7 @@ export async function generateMetadata(): Promise<Metadata> {
return resolvePageMetadata({
seoInfo: {
title: "关于我们",
description: "了解海角社区的品牌、产品与服务。",
description: "了解黄果黑料的品牌、产品与服务。",
},
canonical: "/about",
});
@@ -22,7 +22,7 @@ export default async function Page() {
data={buildWebPageJSONLD({
path: "/about",
title: "关于我们",
description: "了解海角社区的品牌、产品与服务。",
description: "了解黄果黑料的品牌、产品与服务。",
type: "AboutPage",
breadcrumbs: [
["首页", "/"],
-31
View File
@@ -1,31 +0,0 @@
import type { Metadata } from "next";
import { FeedbackPage } from "@/components/feedback/feedback-page";
import { JsonLd } from "@/components/seo/json-ld";
import { buildWebPageJSONLD, getHomeBaseSEOCached } from "@/lib/seo";
export const metadata: Metadata = {
title: "问题反馈 - 海角社区",
robots: { index: false, follow: false },
};
export default async function Page() {
const siteSeo = await getHomeBaseSEOCached();
return (
<>
<JsonLd
data={buildWebPageJSONLD({
path: "/feedback",
title: "问题反馈",
description: "向海角社区提交问题反馈与建议。",
breadcrumbs: [
["首页", "/"],
["问题反馈", "/feedback"],
],
siteName: siteSeo.siteName,
})}
/>
<FeedbackPage gmailAddr={siteSeo.config?.gmailAddr} />
</>
);
}
-58
View File
@@ -1,58 +0,0 @@
import type { Metadata } from "next";
import { Suspense } from "react";
import { PageLoading } from "@/components/common/page-loading";
import { HelpPage } from "@/components/help/help-page";
import { HELP_RICHTEXT_TITLES, loadRichTextFirst } from "@/components/richtext/load-richtext";
import { JsonLd } from "@/components/seo/json-ld";
import { buildWebPageJSONLD, getHomeBaseSEOCached, mergeSEO, resolvePageMetadata } from "@/lib/seo";
export async function generateMetadata(): Promise<Metadata> {
const data = await loadRichTextFirst([...HELP_RICHTEXT_TITLES]);
return resolvePageMetadata({
seoInfo: mergeSEO(data?.seoInfo, {
title: "帮助中心",
description:
data?.seoInfo?.description?.trim() ||
data?.desc?.trim() ||
"查看海角社区常见问题、播放帮助与客服支持信息。",
}),
canonical: "/help",
});
}
async function HelpRouteContent() {
const data = await loadRichTextFirst([...HELP_RICHTEXT_TITLES]);
const siteSeo = await getHomeBaseSEOCached();
const seo = mergeSEO(data?.seoInfo, siteSeo.seo);
return (
<>
<JsonLd
data={buildWebPageJSONLD({
path: "/help",
title: seo.title || "帮助中心",
description:
seo.description ||
data?.desc ||
data?.title ||
"查看海角社区常见问题、播放帮助与客服支持信息。",
type: "WebPage",
breadcrumbs: [
["首页", "/"],
["帮助中心", "/help"],
],
siteName: siteSeo.siteName,
})}
/>
<HelpPage data={data} />
</>
);
}
export default function Page() {
return (
<Suspense fallback={<PageLoading />}>
<HelpRouteContent />
</Suspense>
);
}
+4 -27
View File
@@ -5,7 +5,6 @@ import { Suspense } from "react";
import { AdDialogSlot } from "@/components/ad/ad-dialog-slot";
import { AddressDialogSlot } from "@/components/address/address-dialog-slot";
import { AgeGateDialogHost } from "@/components/age-gate/age-gate-dialog";
import { TrackerHost } from "@/components/analytics/tracker-host";
import { RequestHeadersPrime } from "@/components/api/request-headers-prime";
import { AuthUnauthorizedBridge } from "@/components/auth/auth-unauthorized-bridge";
import { GuestAuthBootstrap } from "@/components/auth/guest-auth-bootstrap";
@@ -40,20 +39,17 @@ const pageScaleScript = `(function () {
window.addEventListener("resize", update);
})();`;
/** GA4 测量 ID(全站 head 注入) */
const GA4_MEASUREMENT_ID = "G-748CPNEVP5";
/** metadataBase 随请求 Host 变化,保证 rel=canonical 与访问地址一致 */
export async function generateMetadata(): Promise<Metadata> {
const origin = await getRequestOrigin();
return {
metadataBase: new URL(origin),
title: {
default: "海角社区",
default: "黄果黑料",
template: "%s",
},
description: "发现好故事,开启追剧时光",
applicationName: "海角社区",
applicationName: "黄果黑料",
verification: {
google: "0Bby47n1AEIB3W8wtLZ-oFQ3l8xRqRT78oA0JVRJ5pM",
yandex: "69b55a6aa9ee9a5f",
@@ -64,8 +60,8 @@ export async function generateMetadata(): Promise<Metadata> {
openGraph: {
type: "website",
locale: "zh_CN",
siteName: "海角社区",
images: [{ url: OG_DEFAULT_IMAGE, alt: "海角社区" }],
siteName: "黄果黑料",
images: [{ url: OG_DEFAULT_IMAGE, alt: "黄果黑料" }],
},
twitter: {
card: "summary_large_image",
@@ -93,29 +89,10 @@ export default function RootLayout({ children }: LayoutProps<"/">) {
// beforeInteractive 会给 html 加 is-cn-browser 等 class,需压制告警
suppressHydrationWarning
>
<head>
{/* Google Analytics (GA4) */}
<Script
async
src={`https://www.googletagmanager.com/gtag/js?id=${GA4_MEASUREMENT_ID}`}
strategy="afterInteractive"
/>
<Script id="ga4-gtag" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA4_MEASUREMENT_ID}');
`.trim()}
</Script>
</head>
<body className="flex min-h-full flex-col">
<Suspense fallback={null}>
<RequestHeadersPrime />
</Suspense>
<Suspense fallback={null}>
<TrackerHost />
</Suspense>
<Suspense fallback={null}>
<HomeBaseDebugSlot />
</Suspense>
+3 -3
View File
@@ -30,8 +30,8 @@ export async function generateMetadata({ params }: { params: Params }): Promise<
return resolvePageMetadata({
seoInfo: {
title: data.title,
description: data.subtitle || `浏览海角社区「${data.title}」更多精选内容。`,
keyworld: ["海角社区", data.title],
description: data.subtitle || `浏览黄果黑料「${data.title}」更多精选内容。`,
keyworld: ["黄果黑料", data.title],
},
page: pageNum,
canonical: moreListHref(pathKey, pageNum),
@@ -72,7 +72,7 @@ export default async function Page({ params }: { params: Params }) {
data={buildWebPageJSONLD({
path: moreListHref(pathKey, page),
title: data.title,
description: data.subtitle || `浏览海角社区「${data.title}」更多精选内容。`,
description: data.subtitle || `浏览黄果黑料「${data.title}」更多精选内容。`,
type: "CollectionPage",
breadcrumbs: [
["首页", "/"],
+1 -1
View File
@@ -9,7 +9,7 @@ import { moreListHref } from "@/components/more/route";
/** 无 id 的空壳页;正式内容在 /more/{id},本页不索引 */
export const metadata: Metadata = {
title: "更多内容 - 海角社区",
title: "更多内容 - 黄果黑料",
robots: { index: false, follow: false },
};
+6 -6
View File
@@ -4,7 +4,7 @@ import { NavPage } from "@/components/nav/nav-page";
import { JsonLd } from "@/components/seo/json-ld";
import { getHomeBaseCached } from "@/lib/api/home";
import type { AppTranslationInfo, ConfigInfo } from "@/lib/api/types";
import { buildWebPageJSONLD, mergeSEO, resolvePageMetadata } from "@/lib/seo";
import { buildWebPageJSONLD, mergeSEO, resolvePageMetadata, SITE_NAME } from "@/lib/seo";
async function loadNavBase(): Promise<{
config?: ConfigInfo;
@@ -31,17 +31,17 @@ export async function generateMetadata(): Promise<Metadata> {
description: config?.dhDescription,
keyworld: config?.dhKeyworld,
},
siteName: config?.dhSiteName?.trim() || undefined,
fallbackTitle: `${config?.dhSiteName?.trim() || "海角社区"} · 官方导航`,
fallbackDescription: `${config?.dhSiteName?.trim() || "海角社区"}官方导航页,提供最新访问线路、永久地址、APP 下载与官方联系方式。`,
fallbackKeywords: [config?.dhSiteName || "海角社区", "官方导航", "最新地址"].filter(Boolean) as string[],
siteName: SITE_NAME,
fallbackTitle: `${SITE_NAME} · 官方导航`,
fallbackDescription: `${SITE_NAME}官方导航页,提供最新访问线路、永久地址、APP 下载与官方联系方式。`,
fallbackKeywords: [SITE_NAME, "官方导航", "最新地址"],
canonical: "/nav",
});
}
export default async function Page() {
const { config, bottonTranslations } = await loadNavBase();
const siteName = config?.dhSiteName?.trim() || config?.webSiteName?.trim() || "海角社区";
const siteName = SITE_NAME;
const seo = mergeSEO(
{ title: config?.dhTitle, description: config?.dhDescription, keyworld: config?.dhKeyworld },
{ title: config?.webTitle, description: config?.webDescription, keyworld: config?.webKeyworld },
+1 -1
View File
@@ -5,7 +5,7 @@ import { GoBackButton } from "@/components/not-found/go-back-button";
import { SeoBreadcrumb } from "@/components/seo/seo-breadcrumb";
export const metadata: Metadata = {
title: "页面不存在 - 海角社区",
title: "页面不存在 - 黄果黑料",
robots: { index: false, follow: false },
};
+5 -5
View File
@@ -48,9 +48,9 @@ export async function generateMetadata(): Promise<Metadata> {
// seoInfo → home/base 公共 web* → 代码兜底
return resolvePageMetadata({
seoInfo: home?.seoInfo,
fallbackTitle: "海角社区 - 热门短剧在线看",
fallbackDescription: "海角社区汇集热门短剧、最近上新、排行榜与精选专题推荐,支持高清在线播放。",
fallbackKeywords: ["海角社区", "短剧", "在线短剧", "热门短剧"],
fallbackTitle: "黄果黑料 - 热门短剧在线看",
fallbackDescription: "黄果黑料汇集热门短剧、最近上新、排行榜与精选专题推荐,支持高清在线播放。",
fallbackKeywords: ["黄果黑料", "短剧", "在线短剧", "热门短剧"],
canonical: "/",
});
}
@@ -64,9 +64,9 @@ export default async function Home() {
: null;
const seo = mergeSEO(home?.seoInfo, siteSeo.seo);
const homeTitle = seo.title || "海角社区 - 热门短剧在线看";
const homeTitle = seo.title || "黄果黑料 - 热门短剧在线看";
const homeDescription =
seo.description || "海角社区汇集热门短剧、最近上新、排行榜与精选专题推荐,支持高清在线播放。";
seo.description || "黄果黑料汇集热门短剧、最近上新、排行榜与精选专题推荐,支持高清在线播放。";
const itemListUrls =
home?.homeBannerData
?.filter((item): item is PlayletData & { id: number } => Boolean(item?.id))
+2 -2
View File
@@ -8,7 +8,7 @@ export async function generateMetadata(): Promise<Metadata> {
return resolvePageMetadata({
seoInfo: {
title: "隐私政策",
description: "查看海角社区隐私政策与个人信息保护说明。",
description: "查看黄果黑料隐私政策与个人信息保护说明。",
},
canonical: "/privacy",
});
@@ -22,7 +22,7 @@ export default async function Page() {
data={buildWebPageJSONLD({
path: "/privacy",
title: "隐私政策",
description: "查看海角社区隐私政策与个人信息保护说明。",
description: "查看黄果黑料隐私政策与个人信息保护说明。",
type: "WebPage",
breadcrumbs: [
["首页", "/"],
+3 -3
View File
@@ -39,8 +39,8 @@ export async function generateMetadata({ params }: { params: Params }): Promise<
return resolvePageMetadata({
seoInfo: {
title: `${tabText}排行榜`,
description: `查看海角社区实时更新的${tabText}排行榜,浏览热播内容与精选推荐。`,
keyworld: ["海角社区", "排行榜", tabText],
description: `查看黄果黑料实时更新的${tabText}排行榜,浏览热播内容与精选推荐。`,
keyworld: ["黄果黑料", "排行榜", tabText],
},
page: pageNum,
canonical: rankingHref(tab, pageNum),
@@ -100,7 +100,7 @@ async function RankingContent({ params }: { params: Params }) {
data={buildWebPageJSONLD({
path: rankingHref(tab, page),
title: `${tabText}排行榜`,
description: `查看海角社区实时更新的${tabText}排行榜,浏览热播内容与精选推荐。`,
description: `查看黄果黑料实时更新的${tabText}排行榜,浏览热播内容与精选推荐。`,
type: "CollectionPage",
breadcrumbs: [
["首页", "/"],
+2 -22
View File
@@ -1,37 +1,17 @@
import type { MetadataRoute } from "next";
import { getHomeBaseCached } from "@/lib/api/home";
import { getRequestOrigin, getSiteOrigin } from "@/lib/seo/site";
/** 从地址字符串解析 origin(支持无协议域名) */
function originFromAddr(value?: string | null) {
const text = value?.trim();
if (!text) return undefined;
try {
const withProto = /^https?:\/\//i.test(text) ? text : `https://${text}`;
return new URL(withProto).origin;
} catch {
return undefined;
}
}
/** 基础 robots:私有页 / 搜索 disallow,并声明 sitemap(永久域名取 config.foreverUrlAddr) */
/** 基础 robots:私有页 / 搜索 disallow,并声明 sitemap(取本站域名) */
export default async function robots(): Promise<MetadataRoute.Robots> {
const forever = originFromAddr((await getHomeBaseCached()).config?.foreverUrlAddr);
const configured = process.env.NEXT_PUBLIC_SITE_URL?.trim();
const origin = forever || (configured ? getSiteOrigin() : await getRequestOrigin());
const origin = configured ? getSiteOrigin() : await getRequestOrigin();
return {
rules: [
{
userAgent: "*",
allow: "/",
disallow: [
"/profile",
"/history",
"/favorites",
"/likes",
"/settings",
"/feedback",
// 前缀匹配:/search、/search/短剧/... 均不抓
"/search",
],
+2 -2
View File
@@ -27,8 +27,8 @@ export async function generateMetadata({ params }: { params: Params }): Promise<
return resolvePageMetadata({
seoInfo: {
title: `搜索「${parsed.query}」`,
description: `在海角社区搜索「${parsed.query}」的${parsed.type}结果。`,
keyworld: ["海角社区", "搜索", parsed.query, parsed.type],
description: `在黄果黑料搜索「${parsed.query}」的${parsed.type}结果。`,
keyworld: ["黄果黑料", "搜索", parsed.query, parsed.type],
},
page: parsed.page,
canonical: searchListHref(parsed.query, parsed.type, parsed.page),
+2 -2
View File
@@ -17,8 +17,8 @@ function first(value: string | string[] | undefined) {
export async function generateMetadata(): Promise<Metadata> {
return resolvePageMetadata({
fallbackTitle: "搜索",
fallbackDescription: "搜索海角社区短剧与视频。",
fallbackKeywords: ["海角社区", "搜索"],
fallbackDescription: "搜索黄果黑料短剧与视频。",
fallbackKeywords: ["黄果黑料", "搜索"],
canonical: "/search",
// 搜索入口不收录;robots.txt 同步 Disallow /search
robots: { index: false, follow: false },
+41 -21
View File
@@ -13,12 +13,12 @@ import {
playHref,
RECOMMEND_DISPLAY_COUNT,
resolvePlayURL,
totalEpisodeOf,
type DramaDetail,
type DramaPlayView,
type RelatedDrama,
} from "@/components/drama-detail/data";
import { DramaDetailPage } from "@/components/drama-detail/drama-detail-page";
import { RelatedAside } from "@/components/drama-detail/related-aside";
import { DramaPlayPage } from "@/components/drama-play/drama-play-page";
import { JsonLd } from "@/components/seo/json-ld";
import {
@@ -39,6 +39,7 @@ import {
import { ShortDramaPage } from "@/components/short-drama/short-drama-page";
import {
getPlayletChapterInfoCached,
getPlayletGuessListCached,
getPlayletInfoCached,
getPlayletListCached,
getPlayletRecommendListCached,
@@ -52,6 +53,7 @@ import {
buildPlayletDetailJSONLD,
buildPlayletPlayJSONLD,
buildWebPageJSONLD,
clipText,
getHomeBaseSEOCached,
mergeSEO,
resolveContentMetadata,
@@ -80,7 +82,10 @@ function playletLookup(idOrSlug: string): { id?: string; slug?: string } {
return { slug: idOrSlug };
}
/** 更多好剧:GET /api/app/playlet/recommend/list(sortList=1 推荐) */
/**
* 同类短剧推荐:先取 GET /api/app/playlet/recommend/list(sortList=1 推荐);
* 后台未配置推荐时为空,回退「猜你喜欢」(按当前短剧取相似)。
*/
async function loadPlayletRecommend(excludeId?: number | string | null): Promise<RelatedDrama[]> {
try {
const data = await getPlayletRecommendListCached({
@@ -88,9 +93,20 @@ async function loadPlayletRecommend(excludeId?: number | string | null): Promise
pageSize: RECOMMEND_PAGE_SIZE,
sortList: 1,
});
return mapPlayletRecommendList(data, { excludeId, limit: RECOMMEND_DISPLAY_COUNT });
const list = mapPlayletRecommendList(data, { excludeId, limit: RECOMMEND_DISPLAY_COUNT });
if (list.length) return list;
} catch (error) {
console.error("[short-drama/recommend]", error);
}
try {
const guess = await getPlayletGuessListCached({
id: excludeId ?? undefined,
pageNum: 1,
pageSize: RECOMMEND_PAGE_SIZE,
});
return mapPlayletRecommendList(guess, { excludeId, limit: RECOMMEND_DISPLAY_COUNT });
} catch (error) {
console.error("[short-drama/guess]", error);
return [];
}
}
@@ -140,9 +156,9 @@ export async function generateMetadata({ params }: { params: Params }): Promise<
seoInfo: theme?.seoInfo,
fallbackTitle: themeName ? `短剧·${themeName}·${sortText}` : `短剧·${sortText}`,
fallbackDescription: themeName
? `浏览海角社区短剧「${themeName}」题材,按${sortText}发现精彩作品。`
: `浏览海角社区短剧列表,按${sortText}发现精彩作品。`,
fallbackKeywords: ["海角社区", "短剧", themeName, sortText].filter(Boolean),
? `浏览黄果黑料短剧「${themeName}」题材,按${sortText}发现精彩作品。`
: `浏览黄果黑料短剧列表,按${sortText}发现精彩作品。`,
fallbackKeywords: ["黄果黑料", "短剧", themeName, sortText].filter(Boolean),
page: pageNum,
canonical: shortDramaListHref(sortKey, pageNum, themeSlug),
});
@@ -151,20 +167,23 @@ export async function generateMetadata({ params }: { params: Params }): Promise<
// 播放页 title:短剧标题 + 第几集 + 网页标题(站点名由 withSiteSuffix 追加)
if (page?.length === 1) {
const play = await loadPlayletPlay(decodeSegment(sort), page[0]);
if (!play) return resolveContentMetadata({ fallbackTitle: "海角社区" });
if (!play) return resolveContentMetadata({ fallbackTitle: "黄果黑料" });
const dramaTitle = play.detail.title?.trim() || "短剧";
const playTitle = `${dramaTitle} 第${play.episode}集`;
const { title: _seoTitle, ...playSeoRest } = play.detail.seoInfo ?? {};
// 播放页不用后台 title / description:每集都要带集数,避免同剧所有分集 TDK 完全相同
const { title: _seoTitle, description: seoDescription, ...playSeoRest } = play.detail.seoInfo ?? {};
void _seoTitle;
return resolveContentMetadata({
seoInfo: play.detail.seoInfo ? playSeoRest : null,
fallbackTitle: playTitle,
fallbackDescription:
play.detail.desc || `在线观看《${dramaTitle}》第${play.episode}集`,
fallbackDescription: [
`《${dramaTitle}》第${play.episode}集(共${totalEpisodeOf(play.detail)}集)`,
clipText(seoDescription || play.detail.desc, 70) || "按集收看,分集目录与同类短剧推荐见本页。",
].join(":"),
fallbackKeywords: [
"海角社区",
"黄果黑料",
"短剧",
"在线播放",
"分集",
dramaTitle,
playTitle,
...(play.detail.tagNames ?? []),
@@ -173,16 +192,19 @@ export async function generateMetadata({ params }: { params: Params }): Promise<
canonical: playHref(play.detail.id, play.episode, play.detail.slug),
});
}
if (page?.length) return resolvePageMetadata({ fallbackTitle: "海角社区" });
if (page?.length) return resolvePageMetadata({ fallbackTitle: "黄果黑料" });
// 详情页:TDK 用短剧详情(playlet/info)
const detail = await loadPlayletDetail(decodeSegment(sort));
if (!detail) return resolveContentMetadata({ fallbackTitle: "海角社区" });
if (!detail) return resolveContentMetadata({ fallbackTitle: "黄果黑料" });
return resolveContentMetadata({
seoInfo: detail.seoInfo,
fallbackTitle: detail.title,
fallbackDescription: detail.desc || `在线观看《${detail.title}》,查看选集、剧情信息与相关推荐。`,
fallbackKeywords: ["海角社区", "短剧", detail.title || "", ...(detail.tagNames ?? [])].filter(
fallbackTitle: detail.title ? `${detail.title}(全${totalEpisodeOf(detail)}集)` : undefined,
fallbackDescription: [
`《${detail.title || "短剧"}》全${totalEpisodeOf(detail)}集${detail.themeNames?.[0] ? `·${detail.themeNames[0]}` : ""}`,
clipText(detail.desc, 90) || "剧情介绍、分集目录与同类短剧推荐。",
].join("|"),
fallbackKeywords: ["黄果黑料", "短剧", detail.title || "", ...(detail.tagNames ?? [])].filter(
Boolean,
),
canonical: dramaDetailHref(detail),
@@ -317,8 +339,8 @@ async function ShortDramaRouteContent({ params }: { params: Params }) {
const listDescription =
listSeo.description ||
(themeName
? `浏览海角社区短剧「${themeName}」题材,按${sortText}发现精彩作品。`
: `浏览海角社区短剧列表,按${sortText}发现精彩作品。`);
? `浏览黄果黑料短剧「${themeName}」题材,按${sortText}发现精彩作品。`
: `浏览黄果黑料短剧列表,按${sortText}发现精彩作品。`);
return (
<>
<JsonLd
@@ -365,7 +387,6 @@ async function ShortDramaRouteContent({ params }: { params: Params }) {
<DramaDetailPage
detail={mapped.detail}
related={mapped.related}
relatedAside={<RelatedAside items={mapped.related} />}
adIcons={<AdIconsSlot variant="pc" />}
promo={<AdBannersSlot variant="pc" />}
adGrid={<AdIconsSlot variant="h5" />}
@@ -407,7 +428,6 @@ async function ShortDramaRouteContent({ params }: { params: Params }) {
episode={play.episode}
src={play.src}
related={play.related}
relatedAside={<RelatedAside items={play.related} />}
adIcons={<AdIconsSlot variant="pc" />}
promo={<AdBannersSlot variant="pc" />}
/>
+5 -5
View File
@@ -87,7 +87,7 @@ export async function generateMetadata({
const query = await searchParams;
const parsed = parseTagSlug((await params).slug, query.kind, query.page);
if (!parsed) {
return resolvePageMetadata({ seoInfo: { title: "标签", keyworld: ["海角社区", "标签"] } });
return resolvePageMetadata({ seoInfo: { title: "标签", keyworld: ["黄果黑料", "标签"] } });
}
const { key, page, kind: preferredKind } = parsed;
@@ -98,15 +98,15 @@ export async function generateMetadata({
return resolvePageMetadata({
seoInfo: {
title: `${data.name}标签`,
description: `浏览海角社区「${data.name}」标签相关${unit}。`,
keyworld: ["海角社区", "标签", data.name, unit],
description: `浏览黄果黑料「${data.name}」标签相关${unit}。`,
keyworld: ["黄果黑料", "标签", data.name, unit],
},
page,
canonical: tagListHref(pathKey, page, data.kind),
});
} catch {
return resolvePageMetadata({
seoInfo: { title: key, keyworld: ["海角社区", "标签", key] },
seoInfo: { title: key, keyworld: ["黄果黑料", "标签", key] },
page,
canonical: tagListHref(key, page, preferredKind ?? "playlet"),
});
@@ -163,7 +163,7 @@ export default async function Page({
data={buildWebPageJSONLD({
path: tagListHref(pathKey, page, data.kind),
title: `${data.name}标签`,
description: `浏览海角社区「${data.name}」标签相关${unit}。`,
description: `浏览黄果黑料「${data.name}」标签相关${unit}。`,
type: "CollectionPage",
breadcrumbs: [
["首页", "/"],
+2 -2
View File
@@ -8,7 +8,7 @@ export async function generateMetadata(): Promise<Metadata> {
return resolvePageMetadata({
seoInfo: {
title: "用户协议",
description: "查看海角社区用户服务条款及平台使用规则。",
description: "查看黄果黑料用户服务条款及平台使用规则。",
},
canonical: "/terms",
});
@@ -22,7 +22,7 @@ export default async function Page() {
data={buildWebPageJSONLD({
path: "/terms",
title: "用户协议",
description: "查看海角社区用户服务条款及平台使用规则。",
description: "查看黄果黑料用户服务条款及平台使用规则。",
type: "WebPage",
breadcrumbs: [
["首页", "/"],
+3 -3
View File
@@ -166,8 +166,8 @@ export async function generateMetadata({
return resolvePageMetadata({
seoInfo: data.seoInfo ?? {
title: name,
description: (data.desc || "").trim() || `浏览海角社区专题「${name}」。`,
keyworld: ["海角社区", name],
description: (data.desc || "").trim() || `浏览黄果黑料专题「${name}」。`,
keyworld: ["黄果黑料", name],
},
page: parsed.page,
canonical,
@@ -229,7 +229,7 @@ export default async function Page({
data={buildWebPageJSONLD({
path: topicListHref(pathKey, { dataType: resolvedDataType, page, sortType }),
title: `${name} · ${topicSortLabel(sortType)}`,
description: desc || `浏览海角社区专题「${name}」。`,
description: desc || `浏览黄果黑料专题「${name}」。`,
type: "CollectionPage",
breadcrumbs: [
["首页", "/"],
+37 -12
View File
@@ -7,6 +7,7 @@ import { PageLoading } from "@/components/common/page-loading";
import { sortLabels } from "@/components/short-drama/data";
import {
buildVideoList,
mapVideoListItem,
VIDEO_PAGE_SIZE,
videoSorts,
videoSortType,
@@ -25,7 +26,6 @@ import {
VIDEO_RECOMMEND_DISPLAY_COUNT,
type VideoPlayView,
} from "@/components/video-play/data";
import { VideoRelatedAside } from "@/components/video-play/related-aside";
import { VideoPlayPage } from "@/components/video-play/video-play-page";
import { JsonLd } from "@/components/seo/json-ld";
import {
@@ -41,6 +41,7 @@ import { primeRequestForwardHeaders } from "@/lib/api/request-context";
import {
buildVideoPlayJSONLD,
buildWebPageJSONLD,
clipText,
getHomeBaseSEOCached,
mergeSEO,
resolveContentMetadata,
@@ -98,7 +99,28 @@ async function loadVideoDetailWithRecommend(idOrSlug: string): Promise<VideoPlay
const mapped = await loadVideoDetail(idOrSlug);
if (!mapped) return null;
const related = await loadVideoRecommend(mapped.detail.id);
return { ...mapped, related: related.length ? related : mapped.related };
if (related.length) return { ...mapped, related };
if (mapped.related.length) return mapped;
return { ...mapped, related: await loadHotVideoFallback(mapped.detail.id) };
}
/** 推荐与 hotVideoList 都为空时:取视频库热度列表兜底,排除当前视频 */
async function loadHotVideoFallback(excludeId: number | string): Promise<ShortDramaItem[]> {
try {
const data = await getVideoListCached({
pageNum: 1,
pageSize: VIDEO_RECOMMEND_DISPLAY_COUNT + 4,
sortType: videoSortType("hot"),
});
return (data.list ?? [])
.filter((item) => item.type !== 2 && item.id && Number(item.id) !== Number(excludeId))
.map(mapVideoListItem)
.filter((item): item is ShortDramaItem => Boolean(item))
.slice(0, VIDEO_RECOMMEND_DISPLAY_COUNT);
} catch (error) {
console.error("[video/hot-fallback]", error);
return [];
}
}
export async function generateMetadata({ params }: { params: Params }): Promise<Metadata> {
@@ -119,23 +141,27 @@ export async function generateMetadata({ params }: { params: Params }): Promise<
seoInfo: category?.seoInfo,
fallbackTitle: categoryName ? `视频·${categoryName}·${sortText}` : `视频·${sortText}`,
fallbackDescription: categoryName
? `浏览海角社区视频「${categoryName}」分类,按${sortText}发现精彩内容。`
: `浏览海角社区视频列表,按${sortText}发现精彩内容。`,
fallbackKeywords: ["海角社区", "视频", categoryName, sortText].filter(Boolean),
? `浏览黄果黑料视频「${categoryName}」分类,按${sortText}发现精彩内容。`
: `浏览黄果黑料视频列表,按${sortText}发现精彩内容。`,
fallbackKeywords: ["黄果黑料", "视频", categoryName, sortText].filter(Boolean),
page: pageNum,
canonical: videoListHref(sortKey, pageNum, categorySlug),
});
}
if (page?.length) return resolvePageMetadata({ fallbackTitle: "海角社区" });
if (page?.length) return resolvePageMetadata({ fallbackTitle: "黄果黑料" });
// 视频播放页:TDK 用视频详情(video/info)
const mapped = await loadVideoDetail(decodeSegment(sort));
const detail = mapped?.detail;
if (!detail) return resolveContentMetadata({ fallbackTitle: "海角社区" });
if (!detail) return resolveContentMetadata({ fallbackTitle: "黄果黑料" });
const tagNames = (detail.tags ?? []).map((tag) => tag.name?.trim() || "").filter(Boolean);
return resolveContentMetadata({
fallbackTitle: detail.title,
fallbackDescription: detail.desc || (detail.title ? `在线观看《${detail.title}》` : undefined),
fallbackKeywords: ["海角社区", "视频", detail.title || "", ...tagNames].filter(Boolean),
// 描述带发布日期与标签,避免与其它站点同文案
fallbackDescription: [
`视频《${detail.title || "未命名"}》${/^\d{4}-\d{2}-\d{2}/.test(detail.addedTime || "") ? `·${(detail.addedTime || "").slice(0, 10)}发布` : ""}${tagNames.length ? `·${tagNames.slice(0, 3).join("/")}` : ""}`,
clipText(detail.desc, 90) || "视频介绍与相关视频推荐。",
].join("|"),
fallbackKeywords: ["黄果黑料", "视频库", detail.title || "", ...tagNames].filter(Boolean),
ogType: "video.episode",
canonical: videoHref(String(detail.id), detail.slug),
});
@@ -231,8 +257,8 @@ async function VideoRouteContent({ params }: { params: Params }) {
const listDescription =
listSeo.description ||
(categoryName
? `浏览海角社区视频「${categoryName}」分类,按${sortText}发现精彩内容。`
: `浏览海角社区视频列表,按${sortText}发现精彩内容。`);
? `浏览黄果黑料视频「${categoryName}」分类,按${sortText}发现精彩内容。`
: `浏览黄果黑料视频列表,按${sortText}发现精彩内容。`);
return (
<>
<JsonLd
@@ -281,7 +307,6 @@ async function VideoRouteContent({ params }: { params: Params }) {
detail={mapped.detail}
src={mapped.src}
related={mapped.related}
relatedAside={<VideoRelatedAside items={mapped.related} />}
adIcons={<AdIconsSlot variant="pc" labels={false} />}
promo={<AdBannersSlot variant="pc" />}
adGrid={<AdIconsSlot variant="h5" />}
+8 -12
View File
@@ -4,16 +4,13 @@ import { cache } from "react";
import { JsonLd } from "@/components/seo/json-ld";
import { WayHomePage } from "@/components/way-home/way-home-page";
import { getHomeBaseCached } from "@/lib/api/home";
import type { AnnouncementInfo, ConfigInfo } from "@/lib/api/types";
import { buildWebPageJSONLD, resolvePageMetadata } from "@/lib/seo";
import type { AnnouncementInfo } from "@/lib/api/types";
import { buildWebPageJSONLD, resolvePageMetadata, SITE_NAME } from "@/lib/seo";
const loadWayHomeBase = cache(async (): Promise<{
announcement?: AnnouncementInfo;
config?: ConfigInfo;
}> => {
const loadWayHomeBase = cache(async (): Promise<{ announcement?: AnnouncementInfo }> => {
try {
const base = await getHomeBaseCached();
return { announcement: base.announcement, config: base.config };
return { announcement: base.announcement };
} catch (error) {
console.warn("[way-home] home/base failed", error);
return {};
@@ -21,8 +18,7 @@ const loadWayHomeBase = cache(async (): Promise<{
});
export async function generateMetadata(): Promise<Metadata> {
const { config } = await loadWayHomeBase();
const siteName = config?.webSiteName?.trim() || config?.dhSiteName?.trim() || "海角社区";
const siteName = SITE_NAME;
return resolvePageMetadata({
siteName,
seoInfo: {
@@ -35,8 +31,8 @@ export async function generateMetadata(): Promise<Metadata> {
}
export default async function Page() {
const { announcement, config } = await loadWayHomeBase();
const siteName = config?.webSiteName?.trim() || config?.dhSiteName?.trim() || "海角社区";
const { announcement } = await loadWayHomeBase();
const siteName = SITE_NAME;
return (
<>
<JsonLd
@@ -52,7 +48,7 @@ export default async function Page() {
siteName,
})}
/>
<WayHomePage announcement={announcement} config={config} />
<WayHomePage announcement={announcement} />
</>
);
}
+3 -3
View File
@@ -12,8 +12,8 @@ export async function generateMetadata(): Promise<Metadata> {
return resolvePageMetadata({
seoInfo: {
title: "福利应用",
description: "海角社区福利应用与精选推荐。",
keyworld: ["海角社区", "福利应用"],
description: "黄果黑料福利应用与精选推荐。",
keyworld: ["黄果黑料", "福利应用"],
},
canonical: "/welfare",
});
@@ -35,7 +35,7 @@ async function WelfareContent() {
data={buildWebPageJSONLD({
path: "/welfare",
title: "福利应用",
description: "海角社区福利应用与精选推荐。",
description: "黄果黑料福利应用与精选推荐。",
type: "CollectionPage",
breadcrumbs: [
["首页", "/"],