Files
kr-phim/app/pages/phim/index.vue
T

195 lines
7.6 KiB
Vue

<script setup lang="ts">
const route = useRoute()
const router = useRouter()
const page = ref(Math.max(Number(route.query.page || 1), 1))
const keyword = ref(typeof route.query.q === 'string' ? route.query.q : '')
const debouncedKeyword = ref(keyword.value.trim())
const type = ref(typeof route.query.type === 'string' ? route.query.type : 'all')
let searchTimer: ReturnType<typeof setTimeout> | undefined
watch(keyword, (value) => {
if (searchTimer) clearTimeout(searchTimer)
searchTimer = setTimeout(() => {
debouncedKeyword.value = value.trim()
page.value = 1
}, 420)
})
watch(type, () => {
page.value = 1
})
watch([page, debouncedKeyword, type], () => {
router.replace({
query: {
page: page.value > 1 ? page.value : undefined,
q: debouncedKeyword.value || undefined,
type: type.value !== 'all' ? type.value : undefined,
},
})
})
const { data, pending, error } = await useFetch('/api/movies', {
query: computed(() => ({
page: page.value,
keyword: debouncedKeyword.value || undefined,
})),
})
const movies = computed(() => data.value?.items ?? [])
const filteredMovies = computed(() => {
if (type.value === 'series') {
return movies.value.filter((movie: any) => movie.type !== 'single')
}
if (type.value === 'single') {
return movies.value.filter((movie: any) => movie.type === 'single' || movie.episode === 'Full')
}
return movies.value
})
const totalPages = computed(() => Number(data.value?.pagination?.totalPages || data.value?.pagination?.total_pages || 0))
const sourceStatus = computed(() => data.value?.sources ?? [])
function movieLink(movie: any) {
return {
path: `/phim/${movie.slug}`,
}
}
function nextPage() {
page.value += 1
window.scrollTo({ top: 0, behavior: 'smooth' })
}
function previousPage() {
page.value = Math.max(1, page.value - 1)
window.scrollTo({ top: 0, behavior: 'smooth' })
}
onBeforeUnmount(() => {
if (searchTimer) clearTimeout(searchTimer)
})
useHead({
title: 'Kho phim Hàn Quốc - CineK',
meta: [
{
name: 'description',
content: 'Duyệt kho phim Hàn Quốc online trên CineK: tìm phim bộ, phim lẻ, phim mới cập nhật, Vietsub, thuyết minh và lồng tiếng.',
},
{
property: 'og:title',
content: 'Kho phim Hàn Quốc - CineK',
},
{
property: 'og:description',
content: 'Tìm kiếm và lọc phim Hàn Quốc online theo thể loại, tập mới và nội dung mới cập nhật.',
},
],
})
</script>
<template>
<main class="min-h-screen bg-slate-950">
<AppHeader />
<section class="mx-auto max-w-390 px-4 pb-16 pt-36 sm:px-6 lg:px-8 lg:pt-28 xl:px-10">
<div class="mb-8 flex flex-col gap-5 lg:flex-row lg:items-end lg:justify-between">
<div>
<p class="text-sm font-semibold text-yellow-200">Kho phim Hàn Quốc</p>
<h1 class="mt-2 text-3xl font-black text-white sm:text-5xl">
Duyệt phim
</h1>
</div>
<div class="flex w-full flex-col gap-3 sm:flex-row lg:max-w-3xl">
<div
class="flex min-w-0 flex-1 items-center rounded-full border border-white/10 bg-white/8 px-4 py-3 shadow-2xl shadow-yellow-950/20">
<AppIcon name="search" class="mr-3 size-4 shrink-0 text-yellow-200" />
<input v-model="keyword" type="search" placeholder="Tìm phim..."
class="w-full bg-transparent text-sm text-white outline-none placeholder:text-slate-400">
</div>
<div
class="grid grid-cols-3 rounded-full border border-white/10 bg-white/8 p-1 text-sm font-bold text-slate-200">
<button type="button" class="rounded-full px-4 py-2 transition"
:class="type === 'all' ? 'bg-yellow-300 text-slate-950' : 'hover:text-white'" @click="type = 'all'">
Tất cả
</button>
<button type="button" class="rounded-full px-4 py-2 transition"
:class="type === 'series' ? 'bg-yellow-300 text-slate-950' : 'hover:text-white'" @click="type = 'series'">
Phim bộ
</button>
<button type="button" class="rounded-full px-4 py-2 transition"
:class="type === 'single' ? 'bg-yellow-300 text-slate-950' : 'hover:text-white'" @click="type = 'single'">
Phim lẻ
</button>
</div>
</div>
</div>
<div v-if="sourceStatus.length" class="hidden">
<span v-for="source in sourceStatus" :key="source.name"
class="rounded-full border border-white/10 bg-white/8 px-3 py-1">
{{ source.name }}: {{ source.ok ? 'sẵn sàng' : 'đang bị chặn' }}
</span>
</div>
<p v-if="error" class="rounded-md border border-red-300/30 bg-red-500/12 p-4 text-red-100">
Không tải được dữ liệu phim. Vui lòng thử lại sau.
</p>
<div v-if="pending" class="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6">
<div v-for="item in 18" :key="item" class="aspect-2/3 animate-pulse rounded-md bg-white/10" />
</div>
<div v-else class="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6">
<NuxtLink v-for="movie in filteredMovies" :key="`${movie.source}-${movie.slug}`"
: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"
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">{{ getEpisodeDisplay(movie.episode, movie.episodeTotal) || 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">
{{ movie.sources.length }} server
</span>
<span v-if="getEpisodeDisplay(movie.episode, movie.episodeTotal)"
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.episode, movie.episodeTotal) }}
</span>
</div>
</NuxtLink>
</div>
<div v-if="!pending && !filteredMovies.length"
class="rounded-md border border-white/10 bg-white/8 p-8 text-center text-slate-300">
Không phim phù hợp.
</div>
<div v-if="movies.length" class="flex items-center justify-center gap-3 pt-10">
<button type="button" :disabled="page === 1"
class="grid size-11 place-items-center rounded-full border border-white/15 bg-white/8 text-white disabled:cursor-not-allowed disabled:opacity-40"
aria-label="Trang trước" @click="previousPage">
<AppIcon name="chevron-left" class="size-5" />
</button>
<span class="text-sm text-slate-300">Trang {{ page }}<template v-if="totalPages"> / {{ totalPages
}}</template></span>
<button type="button"
class="grid size-11 place-items-center rounded-full bg-yellow-300 text-slate-950 hover:bg-white"
aria-label="Trang sau" @click="nextPage">
<AppIcon name="chevron-right" class="size-5" />
</button>
</div>
</section>
</main>
</template>