mirror of
https://github.com/ngthanhvu/kr-phim.git
synced 2026-08-10 15:27:46 +00:00
feat: sửa poster dọc ngang ở home
This commit is contained in:
@@ -49,7 +49,7 @@ defineExpose({ goTo })
|
|||||||
<SwiperSlide v-for="(slide, index) in props.slides" :key="`${slide.source}-${slide.slug}-${index}`"
|
<SwiperSlide v-for="(slide, index) in props.slides" :key="`${slide.source}-${slide.slug}-${index}`"
|
||||||
class="h-full w-full">
|
class="h-full w-full">
|
||||||
<img :src="slide.poster || slide.thumb" :alt="slide.name"
|
<img :src="slide.poster || slide.thumb" :alt="slide.name"
|
||||||
class="absolute inset-[-1px] h-[calc(100%+2px)] w-[calc(100%+2px)] max-w-none object-cover object-top lg:object-[72%_center]">
|
class="absolute -inset-px h-[calc(100%+2px)] w-[calc(100%+2px)] max-w-none object-cover object-top lg:object-[72%_center]">
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
</Swiper>
|
</Swiper>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,39 +1,47 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
movie: {
|
movie: {
|
||||||
source: string
|
source: string;
|
||||||
slug: string
|
slug: string;
|
||||||
name: string
|
name: string;
|
||||||
originName?: string
|
originName?: string;
|
||||||
thumb?: string
|
thumb?: string;
|
||||||
poster?: string
|
poster?: string;
|
||||||
year?: number
|
year?: number;
|
||||||
time?: string
|
time?: string;
|
||||||
episode?: string
|
episode?: string;
|
||||||
episodeTotal?: string
|
episodeTotal?: string;
|
||||||
quality?: string
|
quality?: string;
|
||||||
lang?: string
|
lang?: string;
|
||||||
categories?: string[]
|
categories?: string[];
|
||||||
countries?: string[]
|
countries?: string[];
|
||||||
sources?: { source: string, slug: string }[]
|
sources?: { source: string; slug: string }[];
|
||||||
}
|
};
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const episodeDisplay = computed(() =>
|
const episodeDisplay = computed(() => {
|
||||||
getEpisodeDisplay(props.movie.episode, props.movie.episodeTotal),
|
const raw = getEpisodeDisplay(props.movie.episode, props.movie.episodeTotal, 'PĐ.')
|
||||||
)
|
if (!raw) return ''
|
||||||
|
const slashIdx = raw.indexOf('/')
|
||||||
|
if (slashIdx !== -1) return raw.slice(0, slashIdx).trim()
|
||||||
|
return raw
|
||||||
|
})
|
||||||
|
|
||||||
const isPreviewVisible = ref(false)
|
const isPreviewVisible = ref(false);
|
||||||
const isDescriptionExpanded = ref(false)
|
const isDescriptionExpanded = ref(false);
|
||||||
const previewStyle = ref<Record<string, string>>({})
|
const previewStyle = ref<Record<string, string>>({});
|
||||||
let hideTimer: ReturnType<typeof setTimeout> | undefined
|
let hideTimer: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
|
||||||
const movieSourcesQuery = computed(() =>
|
const movieSourcesQuery = computed(() =>
|
||||||
(props.movie.sources || [{ source: props.movie.source, slug: props.movie.slug }])
|
(
|
||||||
|
props.movie.sources || [
|
||||||
|
{ source: props.movie.source, slug: props.movie.slug },
|
||||||
|
]
|
||||||
|
)
|
||||||
.filter((source) => source.source && source.slug)
|
.filter((source) => source.source && source.slug)
|
||||||
.map((source) => `${source.source}:${source.slug}`)
|
.map((source) => `${source.source}:${source.slug}`)
|
||||||
.join(','),
|
.join(","),
|
||||||
)
|
);
|
||||||
|
|
||||||
const movieLink = computed(() => ({
|
const movieLink = computed(() => ({
|
||||||
path: `/phim/${props.movie.slug}`,
|
path: `/phim/${props.movie.slug}`,
|
||||||
@@ -41,67 +49,79 @@ const movieLink = computed(() => ({
|
|||||||
source: props.movie.source,
|
source: props.movie.source,
|
||||||
srcs: movieSourcesQuery.value || undefined,
|
srcs: movieSourcesQuery.value || undefined,
|
||||||
},
|
},
|
||||||
}))
|
}));
|
||||||
|
|
||||||
function showPreview(event: MouseEvent) {
|
function showPreview(event: MouseEvent) {
|
||||||
if (window.matchMedia('(max-width: 639px)').matches) return
|
if (window.matchMedia("(max-width: 639px)").matches) return;
|
||||||
if (hideTimer) clearTimeout(hideTimer)
|
if (hideTimer) clearTimeout(hideTimer);
|
||||||
|
|
||||||
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()
|
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
||||||
const width = 408
|
const width = 408;
|
||||||
const height = 344
|
const height = 344;
|
||||||
const gap = 14
|
const gap = 14;
|
||||||
const left = Math.min(Math.max(rect.left - 10, 16), window.innerWidth - width - 16)
|
const left = Math.min(
|
||||||
const top = Math.min(Math.max(rect.top - gap, 86), window.innerHeight - height - 16)
|
Math.max(rect.left - 10, 16),
|
||||||
|
window.innerWidth - width - 16,
|
||||||
|
);
|
||||||
|
const top = Math.min(
|
||||||
|
Math.max(rect.top - gap, 86),
|
||||||
|
window.innerHeight - height - 16,
|
||||||
|
);
|
||||||
|
|
||||||
previewStyle.value = {
|
previewStyle.value = {
|
||||||
left: `${left}px`,
|
left: `${left}px`,
|
||||||
top: `${top}px`,
|
top: `${top}px`,
|
||||||
width: `${width}px`,
|
width: `${width}px`,
|
||||||
}
|
};
|
||||||
isPreviewVisible.value = true
|
isPreviewVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function scheduleHide() {
|
function scheduleHide() {
|
||||||
if (hideTimer) clearTimeout(hideTimer)
|
if (hideTimer) clearTimeout(hideTimer);
|
||||||
hideTimer = setTimeout(() => {
|
hideTimer = setTimeout(() => {
|
||||||
isPreviewVisible.value = false
|
isPreviewVisible.value = false;
|
||||||
isDescriptionExpanded.value = false
|
isDescriptionExpanded.value = false;
|
||||||
}, 90)
|
}, 90);
|
||||||
}
|
}
|
||||||
|
|
||||||
function keepPreview() {
|
function keepPreview() {
|
||||||
if (hideTimer) clearTimeout(hideTimer)
|
if (hideTimer) clearTimeout(hideTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (hideTimer) clearTimeout(hideTimer)
|
if (hideTimer) clearTimeout(hideTimer);
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NuxtLink :to="movieLink"
|
<div class="w-44 shrink-0 snap-start sm:w-52 xl:w-56">
|
||||||
class="group relative block h-78 w-44 shrink-0 snap-start transition duration-300 hover:z-30 hover:-translate-y-1 sm:h-82 sm:w-52 xl:w-56"
|
<!-- Movie Card -->
|
||||||
@mouseenter="showPreview" @mouseleave="scheduleHide" @focus="showPreview" @blur="scheduleHide">
|
<NuxtLink :to="movieLink" class="group block" @mouseenter="showPreview" @mouseleave="scheduleHide"
|
||||||
<div
|
@focus="showPreview" @blur="scheduleHide">
|
||||||
class="absolute inset-0 overflow-hidden rounded-md bg-slate-900 shadow-xl shadow-black/25 ring-1 ring-white/10 transition duration-300 group-hover:ring-yellow-300/60">
|
<!-- Poster wrapper -->
|
||||||
|
<div class="relative aspect-2/3">
|
||||||
|
<div class="h-full w-full overflow-hidden rounded-t-[7px] bg-slate-900 shadow-xl shadow-black/25 ring-1 ring-white/10">
|
||||||
<img :src="movie.thumb || movie.poster" :alt="movie.name"
|
<img :src="movie.thumb || movie.poster" :alt="movie.name"
|
||||||
class="h-full w-full object-cover transition duration-500 group-hover:scale-105">
|
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">{{ episodeDisplay || movie.year || movie.quality }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<span v-if="movie.sources?.length && 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="episodeDisplay"
|
<span v-if="episodeDisplay"
|
||||||
class="absolute right-2 top-2 rounded bg-red-600/90 px-2 py-1 text-xs font-black text-white backdrop-blur-sm">
|
class="absolute bottom-0 left-1/2 z-10 -translate-x-1/2 whitespace-nowrap rounded-t-md bg-blue-600 px-2 py-1 text-[11px] font-semibold leading-none text-white">
|
||||||
{{ episodeDisplay }}
|
{{ episodeDisplay }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Movie name & originName -->
|
||||||
|
<div class="mt-2 px-0.5">
|
||||||
|
<p
|
||||||
|
class="truncate text-[14px] font-normal leading-5 text-white transition-colors duration-200 group-hover:text-yellow-300">
|
||||||
|
{{ movie.name }}
|
||||||
|
</p>
|
||||||
|
<p v-if="movie.originName" class="mt-0.5 truncate text-[13px] leading-5 text-slate-500">
|
||||||
|
{{ movie.originName }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
||||||
|
<!-- Desktop hover preview -->
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<Transition enter-active-class="transition duration-180 ease-out" enter-from-class="scale-95 opacity-0"
|
<Transition enter-active-class="transition duration-180 ease-out" enter-from-class="scale-95 opacity-0"
|
||||||
enter-to-class="scale-100 opacity-100" leave-active-class="transition duration-120 ease-in"
|
enter-to-class="scale-100 opacity-100" leave-active-class="transition duration-120 ease-in"
|
||||||
@@ -109,9 +129,12 @@ onBeforeUnmount(() => {
|
|||||||
<NuxtLink v-if="isPreviewVisible" :to="movieLink"
|
<NuxtLink v-if="isPreviewVisible" :to="movieLink"
|
||||||
class="fixed z-90 hidden origin-top-left overflow-hidden rounded-xl bg-[#07111d] text-white shadow-2xl shadow-black/60 ring-1 ring-yellow-300/45 sm:block"
|
class="fixed z-90 hidden origin-top-left overflow-hidden rounded-xl bg-[#07111d] text-white shadow-2xl shadow-black/60 ring-1 ring-yellow-300/45 sm:block"
|
||||||
:style="previewStyle" @mouseenter="keepPreview" @mouseleave="scheduleHide">
|
:style="previewStyle" @mouseenter="keepPreview" @mouseleave="scheduleHide">
|
||||||
|
<!-- Preview image -->
|
||||||
<div class="relative h-44 overflow-hidden">
|
<div class="relative h-44 overflow-hidden">
|
||||||
<img :src="movie.poster || movie.thumb" :alt="movie.name" class="h-full w-full object-cover object-top">
|
<img :src="movie.poster || movie.thumb" :alt="movie.name" class="h-full w-full object-cover object-top" />
|
||||||
|
<!-- Gradient -->
|
||||||
<div class="absolute inset-0 bg-linear-to-t from-[#07111d] via-[#07111d]/30 to-transparent" />
|
<div class="absolute inset-0 bg-linear-to-t from-[#07111d] via-[#07111d]/30 to-transparent" />
|
||||||
|
<!-- Movie title -->
|
||||||
<div class="absolute inset-x-0 bottom-0 p-4">
|
<div class="absolute inset-x-0 bottom-0 p-4">
|
||||||
<h3 class="line-clamp-2 text-xl font-black leading-tight text-white">
|
<h3 class="line-clamp-2 text-xl font-black leading-tight text-white">
|
||||||
{{ movie.name }}
|
{{ movie.name }}
|
||||||
@@ -122,46 +145,72 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Preview content -->
|
||||||
<div class="p-4 pt-3">
|
<div class="p-4 pt-3">
|
||||||
|
<!-- Actions -->
|
||||||
<div class="grid grid-cols-[1fr_6.25rem_6.25rem] gap-2">
|
<div class="grid grid-cols-[1fr_6.25rem_6.25rem] gap-2">
|
||||||
|
<!-- Watch -->
|
||||||
<span
|
<span
|
||||||
class="inline-flex h-10 items-center justify-center gap-2 rounded-md bg-yellow-400 px-4 text-sm font-black text-slate-950 transition hover:bg-white">
|
class="inline-flex h-10 items-center justify-center gap-2 rounded-md bg-yellow-400 px-4 text-sm font-black text-slate-950 transition hover:bg-white">
|
||||||
<AppIcon name="play" class="size-4 fill-current" />
|
<AppIcon name="play" class="size-4 fill-current" />
|
||||||
Xem ngay
|
Xem ngay
|
||||||
</span>
|
</span>
|
||||||
|
<!-- Favorite -->
|
||||||
<span class="grid h-10 place-items-center rounded-md border border-white/30 bg-white/8 text-white">
|
<span class="grid h-10 place-items-center rounded-md border border-white/30 bg-white/8 text-white">
|
||||||
<AppIcon name="heart" class="size-5 fill-current" />
|
<AppIcon name="heart" class="size-5 fill-current" />
|
||||||
</span>
|
</span>
|
||||||
|
<!-- Other action -->
|
||||||
<span class="grid h-10 place-items-center rounded-md border border-white/30 bg-white/8 text-white">
|
<span class="grid h-10 place-items-center rounded-md border border-white/30 bg-white/8 text-white">
|
||||||
<AppIcon name="circle" class="size-5 fill-current" />
|
<AppIcon name="circle" class="size-5 fill-current" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Movie meta -->
|
||||||
<div class="mt-3 flex flex-wrap gap-2 text-xs font-black">
|
<div class="mt-3 flex flex-wrap gap-2 text-xs font-black">
|
||||||
<span v-if="movie.quality" class="rounded bg-black/28 px-2.5 py-1.5 text-white ring-1 ring-white/10">{{
|
<!-- Quality -->
|
||||||
movie.quality }}</span>
|
<span v-if="movie.quality" class="rounded bg-black/28 px-2.5 py-1.5 text-white ring-1 ring-white/10">
|
||||||
<span v-if="movie.lang" class="rounded bg-yellow-300 px-2.5 py-1.5 text-slate-950">{{ movie.lang }}</span>
|
{{ movie.quality }}
|
||||||
<span v-if="movie.time" class="rounded bg-white px-2.5 py-1.5 text-slate-950">{{ movie.time }}</span>
|
</span>
|
||||||
<span v-if="episodeDisplay" class="rounded bg-black/28 px-2.5 py-1.5 text-white ring-1 ring-white/10">{{
|
<!-- Language -->
|
||||||
episodeDisplay }}</span>
|
<span v-if="movie.lang" class="rounded bg-yellow-300 px-2.5 py-1.5 text-slate-950">
|
||||||
|
{{ movie.lang }}
|
||||||
|
</span>
|
||||||
|
<!-- Time -->
|
||||||
|
<span v-if="movie.time" class="rounded bg-white px-2.5 py-1.5 text-slate-950">
|
||||||
|
{{ movie.time }}
|
||||||
|
</span>
|
||||||
|
<!-- Episode -->
|
||||||
|
<span v-if="episodeDisplay" class="rounded bg-black/28 px-2.5 py-1.5 text-white ring-1 ring-white/10">
|
||||||
|
{{ episodeDisplay }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Description -->
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<p class="text-xs font-bold leading-6 text-slate-200" :class="isDescriptionExpanded ? '' : 'line-clamp-1'">
|
<p class="text-xs font-bold leading-6 text-slate-200"
|
||||||
<template v-if="movie.countries?.length">{{ movie.countries.slice(0, 2).join(' • ') }}</template>
|
:class="isDescriptionExpanded ? '' : 'line-clamp-1'">
|
||||||
<template v-if="movie.countries?.length && movie.categories?.length"> • </template>
|
<template v-if="movie.countries?.length">
|
||||||
<template v-if="movie.categories?.length">{{ movie.categories.slice(0, 3).join(' • ') }}</template>
|
{{ movie.countries.slice(0, 2).join(" • ") }}
|
||||||
<template v-if="!movie.countries?.length && !movie.categories?.length">Phim Hàn Quốc Vietsub mới cập
|
</template>
|
||||||
nhật</template>
|
<template v-if="movie.countries?.length && movie.categories?.length">
|
||||||
|
•
|
||||||
|
</template>
|
||||||
|
<template v-if="movie.categories?.length">
|
||||||
|
{{ movie.categories.slice(0, 3).join(" • ") }}
|
||||||
|
</template>
|
||||||
|
<template v-if="!movie.countries?.length && !movie.categories?.length">
|
||||||
|
Phim Hàn Quốc Vietsub mới cập nhật
|
||||||
|
</template>
|
||||||
</p>
|
</p>
|
||||||
|
<!-- Expand description -->
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="mt-1 inline-flex items-center gap-0.5 text-[10px] font-semibold text-yellow-300 transition hover:text-yellow-200"
|
class="mt-1 inline-flex items-center gap-0.5 text-[10px] font-semibold text-yellow-300 transition hover:text-yellow-200"
|
||||||
@click.stop="isDescriptionExpanded = !isDescriptionExpanded">
|
@click.stop="isDescriptionExpanded = !isDescriptionExpanded">
|
||||||
<template v-if="isDescriptionExpanded">
|
<template v-if="isDescriptionExpanded">
|
||||||
Thu gọn <AppIcon name="chevron-up" class="size-3" />
|
Thu gọn
|
||||||
|
<AppIcon name="chevron-up" class="size-3" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
Xem thêm <AppIcon name="chevron-down" class="size-3" />
|
Xem thêm
|
||||||
|
<AppIcon name="chevron-down" class="size-3" />
|
||||||
</template>
|
</template>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -169,4 +218,5 @@ onBeforeUnmount(() => {
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+4
-4
@@ -470,7 +470,7 @@ useHead({
|
|||||||
:class="index === heroIndex ? 'border-white/90 opacity-100 scale-100 shadow-md' : 'border-transparent opacity-80 scale-95 hover:scale-100 hover:opacity-100'"
|
:class="index === heroIndex ? 'border-white/90 opacity-100 scale-100 shadow-md' : 'border-transparent opacity-80 scale-95 hover:scale-100 hover:opacity-100'"
|
||||||
style="-webkit-mask-image: -webkit-radial-gradient(white, black); mask-image: radial-gradient(white, black);"
|
style="-webkit-mask-image: -webkit-radial-gradient(white, black); mask-image: radial-gradient(white, black);"
|
||||||
:aria-label="`Chuyển đến phim ${index + 1}`" @click="goToSlide(index)">
|
:aria-label="`Chuyển đến phim ${index + 1}`" @click="goToSlide(index)">
|
||||||
<img :src="slide.thumb || slide.poster" :alt="slide.name"
|
<img :src="slide.poster || slide.thumb" :alt="slide.name"
|
||||||
class="absolute inset-0 h-full w-full object-cover rounded-full md:rounded-lg pointer-events-none">
|
class="absolute inset-0 h-full w-full object-cover rounded-full md:rounded-lg pointer-events-none">
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -580,7 +580,7 @@ useHead({
|
|||||||
<span class="block h-full bg-yellow-400" :style="{ width: `${watchProgressPercent(item)}%` }" />
|
<span class="block h-full bg-yellow-400" :style="{ width: `${watchProgressPercent(item)}%` }" />
|
||||||
</div>
|
</div>
|
||||||
<span class="absolute left-2 top-2 rounded bg-yellow-400 px-2 py-1 text-xs font-black text-slate-950">
|
<span class="absolute left-2 top-2 rounded bg-yellow-400 px-2 py-1 text-xs font-black text-slate-950">
|
||||||
{{ item.episodeName || `Tập ${(item.episodeIndex || 0) + 1}` }}
|
{{ item.episodeName || getEpisodeDisplay(String((item.episodeIndex || 0) + 1), '') }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="absolute bottom-3 right-3 rounded bg-black/70 px-2 py-1 text-[11px] font-black text-white opacity-0 transition group-hover:opacity-100">
|
class="absolute bottom-3 right-3 rounded bg-black/70 px-2 py-1 text-[11px] font-black text-white opacity-0 transition group-hover:opacity-100">
|
||||||
@@ -667,9 +667,9 @@ useHead({
|
|||||||
<!-- Movie Poster -->
|
<!-- Movie Poster -->
|
||||||
<div
|
<div
|
||||||
class="h-18 w-12 shrink-0 overflow-hidden rounded-lg shadow-md ring-1 ring-white/10 sm:h-19 sm:w-13">
|
class="h-18 w-12 shrink-0 overflow-hidden rounded-lg shadow-md ring-1 ring-white/10 sm:h-19 sm:w-13">
|
||||||
<img v-if="comment.movie?.poster" :src="comment.movie.poster" :alt="comment.movie?.name"
|
<img v-if="comment.movie?.thumb" :src="comment.movie.thumb" :alt="comment.movie?.name"
|
||||||
class="h-full w-full object-cover">
|
class="h-full w-full object-cover">
|
||||||
<img v-else-if="comment.movie?.thumb" :src="comment.movie.thumb" :alt="comment.movie?.name"
|
<img v-else-if="comment.movie?.poster" :src="comment.movie.poster" :alt="comment.movie?.name"
|
||||||
class="h-full w-full object-cover">
|
class="h-full w-full object-cover">
|
||||||
<div v-else class="h-full w-full bg-slate-700" />
|
<div v-else class="h-full w-full bg-slate-700" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+26
-2
@@ -11,15 +11,39 @@ function extractEpisodeNumber(value?: string): number {
|
|||||||
export function getEpisodeDisplay(
|
export function getEpisodeDisplay(
|
||||||
episode?: string,
|
episode?: string,
|
||||||
episodeTotal?: string,
|
episodeTotal?: string,
|
||||||
|
prefix = 'Tập',
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (!episode) return undefined
|
if (!episode) return undefined
|
||||||
|
|
||||||
|
const trimmed = episode.trim()
|
||||||
|
|
||||||
|
// "Hoàn tất" status
|
||||||
|
if (/hoan.t|hoàn.t/.test(trimmed)) {
|
||||||
|
return 'Hoàn tất'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract "X/Y" pattern anywhere in the string (with or without spaces)
|
||||||
|
const slashMatch = trimmed.match(/(\d+)\s*\/\s*(\d+)/)
|
||||||
|
if (slashMatch) {
|
||||||
|
const epNum = Number(slashMatch[1])
|
||||||
|
const totalNum = Number(slashMatch[2])
|
||||||
|
if (epNum > 0 && totalNum > 0 && epNum < totalNum) {
|
||||||
|
return `${prefix} ${epNum}/${totalNum}`
|
||||||
|
}
|
||||||
|
return `${prefix} ${totalNum}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single number
|
||||||
const totalNum = extractEpisodeNumber(episodeTotal)
|
const totalNum = extractEpisodeNumber(episodeTotal)
|
||||||
const epNum = extractEpisodeNumber(episode)
|
const epNum = extractEpisodeNumber(episode)
|
||||||
|
|
||||||
if (totalNum > 0 && epNum > 0 && epNum !== totalNum) {
|
if (totalNum > 0 && epNum > 0 && epNum !== totalNum) {
|
||||||
return `Tập ${epNum}/${totalNum}`
|
return `${prefix} ${epNum}/${totalNum}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return episode
|
if (epNum > 0) {
|
||||||
|
return `${prefix} ${epNum}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmed
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user