feat: add episode total handling and display across movie pages

- Implemented `getEpisodeDisplay` function to format episode information for movies.
- Updated movie display components to show episode information using the new function.
- Added `episodeTotal` field to the movies schema and updated related API endpoints to handle this new field.
- Enhanced movie synchronization process to fetch and store episode totals from external sources.
- Created a new error page component for better user experience on 404 errors.
- Added migration scripts to update the database schema for episode totals.
This commit is contained in:
ngthanhvu
2026-07-23 02:56:24 -04:00
parent a35403a5b9
commit f42dff9b0a
19 changed files with 983 additions and 124 deletions
+1 -1
View File
@@ -236,7 +236,7 @@ useHead(() => ({
<div class="relative mx-auto max-w-390 px-3 pb-8 pt-40 sm:px-6 sm:pb-12 sm:pt-52 lg:px-8 lg:pt-56 xl:px-10">
<div class="lg:grid lg:grid-cols-[22rem_minmax(0,1fr)] lg:gap-6">
<aside class="mb-3 flex flex-col items-center text-center lg:mb-6 lg:items-start lg:text-left">
<img :src="movie.thumb || movie.poster" :alt="movie.name"
<img :src="movie.poster || movie.thumb" :alt="movie.name"
class="mx-auto aspect-2/3 w-29 rounded-md object-cover shadow-xl shadow-black/40 sm:w-52 lg:w-full">
<h1 class="mt-4 text-[1.35rem] font-black leading-tight sm:text-2xl lg:mt-5">{{ movie.name }}</h1>
+23 -3
View File
@@ -72,6 +72,21 @@ function movieLink(movie: any) {
}
}
function getEpisodeDisplay(movie: any) {
const ep = movie.episode
const total = movie.episodeTotal
if (!ep) return undefined
const totalNum = total ? total.replace(/[^0-9]/g, '') : ''
const epNum = ep.replace(/[^0-9]/g, '')
if (epNum && totalNum && epNum !== totalNum) {
return `Tập ${epNum}/${totalNum}`
}
return ep
}
function nextPage() {
page.value += 1
window.scrollTo({ top: 0, behavior: 'smooth' })
@@ -164,15 +179,20 @@ useHead({
:to="movieLink(movie)" class="group">
<div
class="relative aspect-2/3 overflow-hidden rounded-md bg-slate-900 shadow-xl shadow-black/25 ring-1 ring-white/10 transition duration-300 group-hover:-translate-y-1 group-hover:ring-yellow-300/60">
<img :src="movie.thumb || movie.poster" :alt="movie.name"
<img :src="movie.poster || movie.thumb" :alt="movie.name"
class="h-full w-full object-cover transition duration-500 group-hover:scale-105">
<div class="absolute inset-x-0 bottom-0 bg-linear-to-t from-slate-950 via-slate-950/70 to-transparent p-3">
<p class="line-clamp-2 text-sm font-bold leading-snug text-white">{{ movie.name }}</p>
<p class="mt-1 truncate text-xs text-yellow-100">{{ movie.episode || movie.year || movie.quality }}</p>
<p class="mt-1 truncate text-xs text-yellow-100">{{ getEpisodeDisplay(movie) || movie.year || movie.quality }}</p>
</div>
<span v-if="movie.sources?.length > 1" class="absolute left-2 top-2 rounded bg-yellow-400 px-2 py-1 text-xs font-black text-slate-950">
<span v-if="movie.sources?.length > 1"
class="absolute left-2 top-2 rounded bg-yellow-400 px-2 py-1 text-xs font-black text-slate-950">
{{ movie.sources.length }} server
</span>
<span v-if="getEpisodeDisplay(movie)"
class="absolute right-2 top-2 rounded bg-red-600/90 px-2 py-1 text-xs font-black text-white backdrop-blur-sm">
{{ getEpisodeDisplay(movie) }}
</span>
</div>
</NuxtLink>
</div>