import type { Drama, HomeBlock, HomeData, PageData, Topic } from '../api/types'; import type { ApiClient } from './api'; import { recordList } from '../api/protocol'; import { contentPath, contentQuery, contentSegment, getRankPage, matchesContentKey, paginationPath, RANK_PAGES, type SiteRoute } from '../routing'; import { card, empty, escape, pagination, topicCard } from './render'; const labels:Record={shorts:'短剧',topics:'专题推荐',theme:'题材短剧',tag:'标签短剧',config:'更多好剧',rank:'热播榜'}; const shortsSortNames=['最新更新','当前热播','独家原创','随机推荐']; function channelSort(route:SiteRoute,search:string):number { const value=Number(new URLSearchParams(search).get('sort')||1); return (route.key==='shorts'?[1,2,3,4]:[1,2,3]).includes(value)?value:1; } export function rankNavigation(slug='hot'):string { return ``; } export function channelRequest(route:SiteRoute,search=''):{endpoint:string;query:Record} { const sort=channelSort(route,search); const query:Record={pageNum:route.page||1,pageSize:24}; let endpoint=''; if(route.key==='shorts'){endpoint='playlet/all/list';query.sortType=sort;} if(route.key==='rank'){endpoint='playlet/hot/rank/list';query.rankType=getRankPage(route.value)!.rankType;} if(route.key==='topics'){endpoint=route.value?'playlet/topic/detail':'playlet/topic/list';} // The service exposes the same theme IDs through its paginated topic detail endpoint. if(route.key==='theme')endpoint='playlet/topic/detail'; if(endpoint==='playlet/topic/detail'){ const identity=contentQuery(route.value!); if(!identity.id)throw new Error('专题 slug 需要先解析为 ID'); query.id=identity.id; } if(route.key==='tag'){endpoint='playlet/tag/list';Object.assign(query,contentQuery(route.value!,'tagId','tagSlug'));query.sortType=sort;} if(route.key==='config'){endpoint='playlet/home';return {endpoint,query:{}};} if(!endpoint)throw new Error('该页面不存在'); return {endpoint,query}; } /** Keep server rendering and browser navigation on the same documented contracts. */ export async function loadChannel(api:Pick,route:SiteRoute,search='',signal?:AbortSignal):Promise> { let topic:Topic|undefined; if((route.key==='theme'||route.key==='topics'&&route.value)&&contentQuery(route.value!).slug){ // topic/detail accepts only an ID. Resolve its slug through the complete topic directory. const seen=new Set(); for(let page=1;!topic;page++){ const data=await api.get>('playlet/topic/list',{pageNum:page,pageSize:50},signal); if(!Array.isArray(data.list))throw new Error('专题目录暂不可用'); const list=recordList(data.list); topic=list.find(item=>Number.isSafeInteger(item.id)&&item.id>0&&matchesContentKey(item,route.value!)); if(topic)break; const before=seen.size;for(const item of list)if(Number.isSafeInteger(item.id)&&item.id>0)seen.add(item.id); if(!list.length||Number.isSafeInteger(data.totalCount)&&data.totalCount!>=0&&seen.size>=data.totalCount!)return {missing:true}; if(seen.size===before)throw new Error('专题目录分页异常'); } } const {endpoint,query}=channelRequest(topic?{...route,value:String(topic.id)}:route,search); if(route.key!=='config'){ const data=await api.get>(endpoint,query,signal); if(route.key==='theme'||route.key==='topics'&&route.value){ const expected=topic?.id??Number(route.value); if(data.id!==undefined&&data.id!==expected||data.topicInfo?.id!==undefined&&data.topicInfo.id!==expected)return {missing:true}; return {...data,id:expected,slug:data.topicInfo?.slug||data.slug||topic?.slug}; } return data; } // No paginated home-config endpoint exists in this API. Render only this block's real items. const home=await api.get(endpoint,query,signal); const block=recordList(home?.homeConfigData).find(item=>matchesContentKey(item,route.value!)&&item.status&&[1,3].includes(item.type)); if(!block)return {list:[],totalCount:0,missing:true}; const count=recordList(block.type===3?block.themeList:block.list).length; return {...block,list:(route.page||1)>1?[]:recordList(block.list),themeList:(route.page||1)>1?[]:recordList(block.themeList),totalCount:count,pageNum:route.page||1,pageSize:24}; } /** Shared by the initial server response and client-side page navigation. */ export function channelView(route:SiteRoute,data:PageData,path:string) { const ranking=route.key==='rank'?getRankPage(route.value):undefined; const sort=channelSort(route,new URL(path,'https://site.invalid').search); const title=ranking?.title||(typeof data.name==='string'?data.name:'')||(typeof data.topicInfo?.name==='string'?data.topicInfo.name:'')||labels[route.key]||'内容列表'; const subtitle=typeof data.desc==='string'?data.desc:typeof data.topicInfo?.desc==='string'?data.topicInfo.desc:''; const total=typeof data.totalCount==='number'&&Number.isFinite(data.totalCount)?Math.max(0,data.totalCount):0; const identity={id:Number(data.topicInfo?.id??data.id??route.value),slug:data.topicInfo?.slug||data.slug}; const pagePath=route.value&&['topics','theme','tag','config'].includes(route.key)&&contentSegment(identity)?contentPath(route.key==='theme'?'topics':route.key as 'topics'|'tag'|'config',identity)+new URL(path,'https://site.invalid').search:path; const page=route.page||1,href=(page:number)=>paginationPath(pagePath,page); let tabs=ranking?rankNavigation(ranking.slug):''; if(!ranking&&route.key==='shorts'){ tabs=`
${shortsSortNames.map((name,index)=>``).join('')}
`; } let content:string; if(route.key==='topics'&&!route.value){ content=`
${recordList(data.list).map(item=>topicCard(item,false,2)).join('')||empty('暂无专题')}
`; }else if(route.key==='config'&&data.type===3){ content=`
${recordList(data.themeList).map(item=>topicCard(item,true,2)).join('')||empty()}
`; }else{ const summary=ranking?`
${ranking.title}共 ${total} 部作品
`:''; content=tabs+summary+`
${recordList(data.list).map((item,index)=>card(item,false,{headingLevel:2,...(ranking?{rank:(page-1)*24+index+1}:{})})).join('')||empty(typeof data.message==='string'?data.message:undefined)}
`; } return {title,subtitle,html:content+pagination(total,page,href,route.key==='config'?Math.max(24,total):24)}; }