播放页引导下载添加

This commit is contained in:
谢宇宁
2026-10-27 21:45:47 +07:00
parent 93a64beaa0
commit 3c9ab2630f
6 changed files with 31 additions and 12 deletions
+9
View File
@@ -3142,3 +3142,12 @@ html body .hg-footer-original .hg-footer-original-title .footer-brand-text{displ
.hg-detail-actions .play-detail-actions{display:flex;flex-direction:row;flex-wrap:wrap;gap:8px}
@media(max-width:760px){.hg-detail-actions .play-detail-actions{width:100%}.hg-detail-actions .play-detail-actions>button{flex:1 1 0;min-width:0}.hg-detail-actions .detail-play,.hg-detail-actions .series-download:not([hidden]){flex:1 1 auto;justify-content:center}}
@media(max-width:760px){#drama-play-page .hg-detail-actions .play-detail-actions{flex-wrap:nowrap}#drama-play-page .hg-detail-actions .play-detail-actions>button{flex:1 1 0;width:auto;min-width:0;max-width:none;padding:9px 6px;justify-content:center}}
.play-screen .hg-app-gate:not([hidden]){position:absolute;inset:0;z-index:6;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:24px;background:radial-gradient(circle at 50% 40%,#1d1e1ee6,#050505f2);text-align:center}
.hg-app-gate-copy{display:flex;flex-direction:column;gap:6px}
.hg-app-gate-copy strong{color:#fff;font-size:clamp(17px,2vw,22px);font-weight:700}
.hg-app-gate-copy p,.hg-app-gate-fallback{margin:0;color:#c9c9c9;font-size:14px;line-height:1.7}
.hg-app-gate-btn:not([hidden]){display:inline-flex;align-items:center;gap:8px;height:46px;padding:0 26px;border-radius:999px;font-size:15px;font-weight:700;text-decoration:none}
.hg-app-gate-btn svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8}
.hg-app-gate:has(.hg-app-gate-btn:not([hidden])) .hg-app-gate-fallback{display:none}
.hg-app-gate:has(.hg-app-gate-btn[hidden]) .hg-app-gate-copy{display:none}
.play-screen:has(.hg-app-gate:not([hidden]))>:not(.hg-app-gate):not(video){visibility:hidden}
+7 -2
View File
@@ -13,6 +13,11 @@ export function orderedChapters(value:unknown):Chapter[] {
return recordList<Chapter>(value).filter(item=>Number.isSafeInteger(item.id)&&item.id>0).sort((a,b)=>(a.currentEpisode||0)-(b.currentEpisode||0));
}
/** Shown in place of the player when this episode has no playable source: invites the visitor to the App. */
export function appGate(heading:string,visible=false):string {
return `<div class="hg-app-gate"${visible?'':' hidden'}><div class="hg-app-gate-copy"><strong>本集请在 黄果吃瓜 App 观看</strong><p>下载 App 即可继续观看《${escape(heading)}》及全部剧集</p></div><p class="hg-app-gate-fallback">本集视频暂未开放,请稍后再试</p><a class="primary hg-app-gate-btn" data-app-download data-app-download-entry target="_blank" rel="sponsored nofollow noopener noreferrer" hidden><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="6" y="2" width="12" height="20" rx="3"/><path d="M10 5h4M11 19h2"/></svg><span>下载 App 继续观看</span></a></div>`;
}
/** Recommended titles replace comments: crawlable internal links with full context for each title. */
export function recommendationsMarkup(raw:DramaDetail):string {
const related=recordList<Drama>(raw.relatedRecommendData).filter(item=>item.id!==raw.id).slice(0,12);
@@ -25,11 +30,11 @@ export function playerMarkup(raw:DramaDetail,ordered:Chapter[],options:PlayerVie
const displayTitle=title(raw),episodes=raw.totalEpisode||ordered.length,actionState=chapter||raw;
const heading=chapter?`${displayTitle} 第${chapter.currentEpisode||1}集`:displayTitle;
const access=isVideo?{allowed:!!playbackUrl(raw.video?.url),gate:playbackUrl(raw.video?.url)?'':'unavailable',source:playbackUrl(raw.video?.url)} as const:chapterAccess(chapter);
const previewSource=isVideo?'':playbackUrl(raw.preview),mode=initialPlayMode(access,raw.preview);
const previewSource=isVideo?'':playbackUrl(raw.preview),mode=initialPlayMode(access,raw.preview),blocked=!!chapter&&mode==='main'&&!access.allowed;
const modeTabs=previewSource?`<div class="channel-tabs play-mode-tabs" role="group" aria-label="播放内容"><button type="button" data-play-mode="main" aria-pressed="${mode==='main'}">正片</button><button type="button" data-play-mode="preview" aria-pressed="${mode==='preview'}">免费预告</button></div>`:'';
const initialEpisodePage=chapter?Math.floor(Math.max(0,ordered.findIndex(item=>item.id===chapter.id))/45):0;
const actions=()=>`<div class="play-detail-actions"><button type="button" data-engage="like" aria-pressed="${actionState.isLike===1}">${detailActionIcon('like')}<span>${actionState.isLike===1?'已点赞':'点赞'}</span></button><button type="button" data-engage="collect" aria-pressed="${actionState.isCollect===1}">${detailActionIcon('save')}<span>${actionState.isCollect===1?'已收藏':'收藏'}</span></button><button type="button" data-share>${detailActionIcon('share')}<span>分享</span></button></div>`;
const screen=()=>`${modeTabs}<div class="play-screen" data-play-mode="${mode}"><video ${INLINE_VIDEO_ATTRIBUTES} preload="metadata" ${imageAttributes(raw.cover||raw.coverImg,undefined,'poster')} aria-label="${escape(heading)}"></video><div class="play-screen-overlay"><button type="button" aria-label="播放">▶</button><p class="play-source-status" role="status">正在准备视频…</p></div><div class="play-screen-controls"><button data-play-action="next" ${!chapter||ordered.at(-1)?.id===chapter.id?'disabled':''}>下一集</button></div></div>`;
const screen=()=>`${modeTabs}<div class="play-screen" data-play-mode="${mode}"><video ${INLINE_VIDEO_ATTRIBUTES} preload="metadata" ${imageAttributes(raw.cover||raw.coverImg,undefined,'poster')} aria-label="${escape(heading)}"></video><div class="play-screen-overlay"><button type="button" aria-label="播放"${blocked?' hidden':''}>▶</button><p class="play-source-status" role="status"${blocked?' hidden':''}>正在准备视频…</p></div>${appGate(heading,blocked)}<div class="play-screen-controls"><button data-play-action="next" ${!chapter||ordered.at(-1)?.id===chapter.id?'disabled':''}>下一集</button></div></div>`;
const applicationAds=promotionRow(raw.advitise?.nineGridList||home.advitise?.nineGridList);
const bannerAds=ads(raw.advitise?.detailbannerList,'play-banner-ad');
const watchInfo=()=>`<section class="hg-watch-info">${chapter?`<span class="play-episode-caption">正在观看 第 ${chapter.currentEpisode||1} 集 / 共 ${episodes} 集</span>`:''}<h1 id="play-detail-title">${escape(heading)}</h1>${detailFacts(raw,episodes,actionState)}${actions()}</section>${synopsisMarkup(raw,`《${displayTitle}》剧情简介`)}`;
+5 -4
View File
@@ -97,11 +97,12 @@ export async function renderPlayer(app:SiteContext,id:number,chapterId?:number,i
page.querySelectorAll<HTMLButtonElement>('[data-play-mode]').forEach(button=>{if(button.tagName==='BUTTON')button.setAttribute('aria-pressed',String(button.dataset.playMode===mode));});
const blocked=mode==='main'&&!access.allowed;
box.querySelector('.ui-empty-state')?.remove();
const center=box.querySelector<HTMLButtonElement>('[data-media="toggle"]');if(center)center.hidden=false;
status.hidden=false;
const center=box.querySelector<HTMLButtonElement>('[data-media="toggle"]');if(center)center.hidden=blocked;
const gate=box.querySelector<HTMLElement>('.hg-app-gate');if(gate)gate.hidden=!blocked;
status.hidden=blocked;
void media!.setSource(mode==='preview'?previewSource:access.allowed?access.source:'');
if(blocked)status.textContent='本集视频暂未开放,请稍后再试';
else if(!(mode==='preview'?previewSource:access.source))status.textContent='该剧集暂未提供视频源';
// A blocked main episode shows the App gate instead of a status line.
if(!blocked&&!(mode==='preview'?previewSource:access.source))status.textContent='该剧集暂未提供视频源';
else if(mode==='preview')status.textContent='免费预告 · 点击播放';
}
page.querySelectorAll<HTMLButtonElement>('button[data-play-mode]').forEach(button=>button.addEventListener('click',()=>{setMode(button.dataset.playMode as PlayMode);if(mode==='preview')void media!.toggle();},{signal}));