; className: string }) {
+function DramaMeta({ item, className }: { item: ShortDramaItem; className: string }) {
return (
{item.episodes ? 共{item.episodes} : null}
@@ -31,27 +31,7 @@ function DramaMeta({ item, className }: { item: Extract
-
-
-
- 推广
-
-
- {item.title}
-
- );
-}
-
export function ShortDramaCard({ item, rank }: { item: ShortDramaItem; rank?: number }) {
- if (item.kind === "ad") return ;
const href = item.href || dramaHref(item.id, item.slug);
return (
@@ -84,7 +64,6 @@ export function H5ShortDramaCard({
/** @deprecated 新卡片不在封面显示集数,保留参数兼容调用方 */
showEpisodes?: boolean;
}) {
- if (item.kind === "ad") return ;
const href = item.href || dramaHref(item.id, item.slug);
return (
diff --git a/src/components/short-drama/short-drama-page.tsx b/src/components/short-drama/short-drama-page.tsx
index 54f3f9f..455a09e 100644
--- a/src/components/short-drama/short-drama-page.tsx
+++ b/src/components/short-drama/short-drama-page.tsx
@@ -1,4 +1,3 @@
-import { AdBannersSlot, AdIconsSlot, FriendLinksSlot, H5FriendLinksSlot } from "@/components/common/ads/slots";
import { ChannelHeader, H5ChannelHeader } from "@/components/common/channel-header";
import { EmptyState } from "@/components/common/empty-state";
import { FilterChipLink, FilterOptionLink } from "@/components/common/filter-link";
@@ -25,7 +24,6 @@ export function ShortDramaPage({ list }: { list: ShortDramaListResult }) {
-
-
-
-
) : null}
diff --git a/src/components/tag/tag-page.tsx b/src/components/tag/tag-page.tsx
index 7d43d6d..249d1b3 100644
--- a/src/components/tag/tag-page.tsx
+++ b/src/components/tag/tag-page.tsx
@@ -1,6 +1,5 @@
import Link from "next/link";
-import { AdBannersSlot, AdIconsSlot } from "@/components/common/ads/slots";
import { EmptyState } from "@/components/common/empty-state";
import { Pagination } from "@/components/common/pagination";
import { buildPageHrefMap } from "@/components/common/pagination-map";
@@ -101,14 +100,6 @@ export function TagPage({ data }: { data: TagPageData }) {
) : null}
-
-
diff --git a/src/components/topic/topic-page.tsx b/src/components/topic/topic-page.tsx
index b4a2f24..3d81ed3 100644
--- a/src/components/topic/topic-page.tsx
+++ b/src/components/topic/topic-page.tsx
@@ -1,5 +1,4 @@
-import { AdBannersSlot, AdIconsSlot, FriendLinksSlot, H5FriendLinksSlot } from "@/components/common/ads/slots";
import { ChannelHeader, H5ChannelHeader } from "@/components/common/channel-header";
import { FilterChipLink, FilterOptionLink } from "@/components/common/filter-link";
import { Pagination } from "@/components/common/pagination";
@@ -165,7 +164,6 @@ export function TopicPage({ data }: { data: TopicPageData }) {
className="flex w-full min-w-0 flex-col gap-[18px] overflow-x-clip px-4 py-3 lg:hidden"
>
-
@@ -181,14 +179,11 @@ export function TopicPage({ data }: { data: TopicPageData }) {
gridClassName={layout.morePcClassName}
/>
-
-
-
@@ -204,9 +199,7 @@ export function TopicPage({ data }: { data: TopicPageData }) {
diff --git a/src/components/video-play/h5-video-play.tsx b/src/components/video-play/h5-video-play.tsx
index 9757a8d..206a24d 100644
--- a/src/components/video-play/h5-video-play.tsx
+++ b/src/components/video-play/h5-video-play.tsx
@@ -2,7 +2,7 @@
import type { ReactNode } from "react";
-import { AdBanners, AdIcons } from "@/components/common/ads";
+import { AdBanners } from "@/components/common/ads";
import { BackButton } from "@/components/common/back-button";
import { HlAction, InfoList, SectionTitle, TagLinks, VideoRecommend, type InfoRow, type TagLinkItem } from "@/components/media-page/parts";
import { XgPlayer } from "@/components/player/xg-player";
@@ -30,8 +30,8 @@ export function H5VideoPlay({
infoRows,
tags,
onPlaySuccess,
- adGrid,
bannerAds,
+ squareAds,
liked,
likeNum,
collected,
@@ -46,8 +46,9 @@ export function H5VideoPlay({
infoRows: InfoRow[];
tags: TagLinkItem[];
onPlaySuccess?: () => void;
- adGrid?: ReactNode;
bannerAds?: ReactNode;
+ /** 播放页小图标广告(AList) */
+ squareAds?: ReactNode;
}) {
const title = detail.title || "视频";
const desc = detail.desc?.trim() || "";
@@ -91,7 +92,7 @@ export function H5VideoPlay({
) : null}
- {adGrid ??
}
+ {squareAds}
diff --git a/src/components/video-play/video-play-page.tsx b/src/components/video-play/video-play-page.tsx
index 1c40c05..1957579 100644
--- a/src/components/video-play/video-play-page.tsx
+++ b/src/components/video-play/video-play-page.tsx
@@ -4,7 +4,7 @@ import { toast } from "@heroui/react";
import { useEffect, useState, type ReactNode } from "react";
import { openLoginDialog } from "@/components/auth/login-dialog";
-import { AdBanners, AdIcons } from "@/components/common/ads";
+import { AdBanners } from "@/components/common/ads";
import { formatDay, formatDuration } from "@/components/common/media-meta";
import { PcPlayer } from "@/components/drama-play/pc-player";
import { FloatingActions } from "@/components/home/floating-actions";
@@ -56,16 +56,18 @@ export function VideoPlayPage({
src,
related = [],
promo,
- adIcons,
- adGrid,
+ topAds,
+ h5Ads,
bannerAds,
}: {
detail: VideoDetail;
src: string;
related?: ShortDramaItem[];
promo?: ReactNode;
- adIcons?: ReactNode;
- adGrid?: ReactNode;
+ /** 服务端渲染的播放页小图标(AList):PC 顶部 */
+ topAds?: ReactNode;
+ /** 服务端渲染的播放页小图标(AList):H5 正文下方 */
+ h5Ads?: ReactNode;
bannerAds?: ReactNode;
}) {
const [liked, setLiked] = useState(Boolean(detail.isLike));
@@ -226,6 +228,7 @@ export function VideoPlayPage({
className="relative mx-auto hidden w-[1700px] flex-col gap-8 pt-6 pb-14 [zoom:var(--page-scale,1)] lg:flex"
>
+ {topAds}
- {adIcons ?? }
@@ -283,8 +285,8 @@ export function VideoPlayPage({
onLike={liking ? () => undefined : toggleLike}
onCollect={collecting ? () => undefined : toggleCollect}
onShare={share}
- adGrid={adGrid}
bannerAds={bannerAds}
+ squareAds={h5Ads}
/>
diff --git a/src/components/video/data.ts b/src/components/video/data.ts
index 373f36d..05c4fa4 100644
--- a/src/components/video/data.ts
+++ b/src/components/video/data.ts
@@ -1,5 +1,5 @@
import { pickVideoCardTags } from "@/components/common/card-tags";
-import { genres, regions, sorts, type Genre, type Region, type ShortDramaItem, type Sort } from "@/components/short-drama/data";
+import { sorts, type ShortDramaItem, type Sort } from "@/components/short-drama/data";
import { formatHeat } from "@/components/ranking/data";
import type { VideoCategoryListItem, VideoListItem, VideoListResp } from "@/lib/api/types";
@@ -13,16 +13,8 @@ export function videoSortType(sort: VideoSort): 1 | 2 {
}
export function mapVideoListItem(item: VideoListItem): ShortDramaItem | null {
- if (item.type === 2) {
- return {
- kind: "ad",
- id: `ad-${item.advertiseId || item.id || item.link || "unknown"}`,
- title: (item.title || "").trim() || "广告",
- subtitle: "",
- cover: (item.coverImg || "").trim(),
- href: (item.link || "").trim() || undefined,
- };
- }
+ // 信息流广告(type=2)不展示:网页只保留后台 5 个站点广告位
+ if (item.type === 2) return null;
if (!item.id) return null;
const slug = (item.slug || "").trim();
@@ -77,29 +69,3 @@ export function buildVideoList(
};
}
-/** @deprecated 本地 mock,供「更多」等未接接口页面临时使用 */
-const adSlots = new Set([5, 11, 17, 24, 25]);
-export function getVideos(genre: Genre, sort: Sort, page: number, region: Region = "全部"): ShortDramaItem[] {
- const seed = `${genres.indexOf(genre)}-${sorts.indexOf(sort)}-${regions.indexOf(region)}-${page}`;
-
- return Array.from({ length: VIDEO_PAGE_SIZE }, (_, index): ShortDramaItem => {
- const id = `video-${seed}-${index}`;
- const cover = "";
-
- if (adSlots.has(index)) {
- return { kind: "ad", id, title: "广告文案", subtitle: "我是副标题", cover };
- }
-
- return {
- kind: "drama",
- id,
- title: "有恩必报",
- subtitle: "权谋交锋爱意藏在局中",
- tags: [{ name: genre === "全部" ? "标签" : genre }],
- heat: "25.8万 热度",
- episodes: "30集",
- badge: "original",
- cover,
- };
- });
-}
diff --git a/src/components/video/video-card.tsx b/src/components/video/video-card.tsx
index bc7d9a4..3cfbd1b 100644
--- a/src/components/video/video-card.tsx
+++ b/src/components/video/video-card.tsx
@@ -26,7 +26,7 @@ function RankLabel({ rank, className = "" }: { rank?: number; className?: string
}
/** 视频卡元信息:观看数 + 发布日期 */
-function VideoMeta({ item, className }: { item: Extract
; className: string }) {
+function VideoMeta({ item, className }: { item: ShortDramaItem; className: string }) {
const day = formatDay(item.date);
return (
@@ -42,27 +42,7 @@ function VideoMeta({ item, className }: { item: Extract
-
-
-
- 推广
-
-
- {item.title}
-
- );
-}
-
export function VideoCard({ item, rank }: { item: ShortDramaItem; rank?: number }) {
- if (item.kind === "ad") return ;
const href = item.href || videoHref(item.id, item.slug);
return (
@@ -92,7 +72,6 @@ export function H5VideoCard({
/** @deprecated 新卡片不在封面显示集数,保留参数兼容调用方 */
showEpisodes?: boolean;
}) {
- if (item.kind === "ad") return ;
const href = item.href || videoHref(item.id, item.slug);
return (
diff --git a/src/components/video/video-page.tsx b/src/components/video/video-page.tsx
index 3c3902c..3d17b40 100644
--- a/src/components/video/video-page.tsx
+++ b/src/components/video/video-page.tsx
@@ -1,4 +1,3 @@
-import { AdBannersSlot, AdIconsSlot, FriendLinksSlot, H5FriendLinksSlot } from "@/components/common/ads/slots";
import { ChannelHeader, H5ChannelHeader } from "@/components/common/channel-header";
import { EmptyState } from "@/components/common/empty-state";
import { FilterChipLink, FilterOptionLink } from "@/components/common/filter-link";
@@ -27,7 +26,6 @@ export function VideoPage({ list }: { list: VideoListResult }) {
-
-
-
-
) : null}
diff --git a/src/lib/api/types.ts b/src/lib/api/types.ts
index c3abeb5..ce6c707 100644
--- a/src/lib/api/types.ts
+++ b/src/lib/api/types.ts
@@ -625,21 +625,21 @@ export type AnnouncementItem = {
};
/** GET /api/app/home/base → AdvitiseData(广告位列表) */
+/**
+ * home/base → advitise:网页只使用后台「站点」的 5 个广告位
+ * (接口还会返回 App 用的其它广告位,网页不读取)。
+ */
export type AdvitiseData = {
- homeList?: AdvertiseInfo[];
- detailTopList?: AdvertiseInfo[];
- nineGridList?: AdvertiseInfo[];
- detailbannerList?: AdvertiseInfo[];
- launchScreenList?: AdvertiseInfo[];
- floatingList?: AdvertiseInfo[];
- AList?: AdvertiseInfo[];
- BList?: AdvertiseInfo[];
+ /** 位置 1「弹窗」,素材 600×800 */
dialogList?: AdvertiseInfo[];
- postList?: AdvertiseInfo[];
- mustAppList?: AdvertiseInfo[];
- titleAppList?: AdvertiseInfo[];
- friendList?: AdvertiseInfo[];
- videoList?: AdvertiseInfo[];
+ /** 位置 2「9宫格」,小方块 */
+ nineGridList?: AdvertiseInfo[];
+ /** 位置 3「详情横幅」,素材 720×150 */
+ detailbannerList?: AdvertiseInfo[];
+ /** 位置 12「播放页小图标」,小方块 */
+ AList?: AdvertiseInfo[];
+ /** 位置 13「首页小图标」,小方块 */
+ BList?: AdvertiseInfo[];
};
/** GET /api/app/home/base → AnnouncementInfo(回家的路 / 公告地址) */
diff --git a/src/lib/browser/scroll-lock.ts b/src/lib/browser/scroll-lock.ts
new file mode 100644
index 0000000..23f4ac5
--- /dev/null
+++ b/src/lib/browser/scroll-lock.ts
@@ -0,0 +1,24 @@
+/**
+ * 全站弹窗共用的页面滚动锁(引用计数)。
+ * 各弹窗不再各自记录 / 恢复 body.overflow:多个弹窗同时打开、以任意顺序关闭时,
+ * 只有最后一个关闭才恢复滚动,避免「后关的弹窗把 hidden 当成原值恢复」导致全站无法滚动。
+ */
+let lockCount = 0;
+let previousOverflow = "";
+
+export function lockBodyScroll(): () => void {
+ if (typeof document === "undefined") return () => undefined;
+ if (lockCount === 0) {
+ previousOverflow = document.body.style.overflow === "hidden" ? "" : document.body.style.overflow;
+ document.body.style.overflow = "hidden";
+ }
+ lockCount += 1;
+
+ let released = false;
+ return () => {
+ if (released) return;
+ released = true;
+ lockCount = Math.max(0, lockCount - 1);
+ if (lockCount === 0) document.body.style.overflow = previousOverflow;
+ };
+}