diff --git a/app/pages/phim/[slug].vue b/app/pages/phim/[slug].vue index a8994cf..c682aad 100644 --- a/app/pages/phim/[slug].vue +++ b/app/pages/phim/[slug].vue @@ -2,21 +2,31 @@ import { ChevronDown, Heart, Play, Plus, Share2, Star } from 'lucide-vue-next' const route = useRoute() +const requestedSource = computed(() => String(route.query.source || 'ophim')) const selectedServer = ref(0) const movieInfoOpen = ref(false) +const activeTab = ref<'episodes' | 'actors'>('episodes') +const sourceOptions = [ + { label: 'OPhim', value: 'ophim' }, + { label: 'NguonC', value: 'nguonc' }, +] -const { data: movie, pending, error } = await useFetch(`/api/movies/${route.params.slug}`, { - query: { - source: route.query.source, - }, +const { data: movie, pending, error } = await useFetch(() => `/api/movies/${route.params.slug}`, { + query: computed(() => ({ + source: requestedSource.value, + })), + watch: [requestedSource], }) const servers = computed(() => movie.value?.servers ?? []) const activeServer = computed(() => servers.value[selectedServer.value]) +const activeSource = computed(() => String(movie.value?.source || route.query.source || 'ophim')) +const actors = computed(() => movie.value?.actors ?? []) +const actorSummary = computed(() => actors.value.map((actor: any) => actor.name).filter(Boolean).slice(0, 6).join(', ')) const firstWatchLink = computed(() => ({ path: `/xem/${route.params.slug}`, query: { - source: route.query.source, + source: activeSource.value, server: selectedServer.value, ep: 1, }, @@ -27,13 +37,33 @@ function episodeLink(index: number) { return { path: `/xem/${route.params.slug}`, query: { - source: route.query.source, + source: activeSource.value, server: selectedServer.value, ep: index + 1, }, } } +function sourceLink(source: string) { + return { + path: `/phim/${route.params.slug}`, + query: { source }, + } +} + +function formatEpisodeName(name: string, index: number) { + const label = String(name || index + 1).trim() + return /^\d+$/.test(label) ? `Tập ${label}` : label +} + +function actorInitial(name: string) { + return name.trim().charAt(0).toUpperCase() +} + +watch(requestedSource, () => { + selectedServer.value = 0 +}) + useHead(() => ({ title: movie.value ? `${movie.value.name} - KR Phim` : 'Đang tải phim - KR Phim', meta: [ @@ -70,7 +100,7 @@ useHead(() => ({
-
+
@@ -129,12 +159,23 @@ useHead(() => ({ }}

Quốc gia: {{ movie.countries.join(', ') }}

-

Diễn viên: {{ - movie.actors.slice(0, 6).join(', ') }}

+

Diễn viên: {{ + actorSummary }}

+
+ +
+ class="flex flex-col gap-4 border-b border-white/10 py-4 sm:flex-row sm:items-center sm:justify-between sm:py-5">
@@ -143,7 +184,17 @@ useHead(() => ({
-
+
+
+ Nguồn API + + {{ source.label }} + +
+ +
+
- - + +
-
+

Danh sách tập

@@ -193,7 +249,7 @@ useHead(() => ({ - {{ episode.name }} + {{ formatEpisodeName(episode.name, index) }}
@@ -201,6 +257,36 @@ useHead(() => ({ Chưa có tập xem từ nguồn này.

+ +
+

Diễn viên

+ +
+
+ +
+ {{ actorInitial(actor.name) }} +
+ +
+

{{ actor.name }}

+

+ {{ actor.originalName || 'Tên quốc tế đang cập nhật' }} +

+

+ {{ actor.role || 'Vai diễn đang cập nhật' }} +

+
+
+
+ +

+ Nguồn này chưa có dữ liệu diễn viên. +

+
diff --git a/app/pages/xem/[slug].vue b/app/pages/xem/[slug].vue index 50af37f..2140018 100644 --- a/app/pages/xem/[slug].vue +++ b/app/pages/xem/[slug].vue @@ -17,6 +17,7 @@ const servers = computed(() => movie.value?.servers ?? []) const activeServer = computed(() => servers.value[selectedServer.value] ?? servers.value[0]) const activeEpisode = computed(() => activeServer.value?.episodes?.[selectedEpisode.value] ?? activeServer.value?.episodes?.[0]) const playerUrl = computed(() => activeEpisode.value?.linkEmbed || activeEpisode.value?.linkM3u8 || '') +const actorSummary = computed(() => (movie.value?.actors ?? []).map((actor: any) => actor.name).filter(Boolean).slice(0, 6).join(', ')) type WatchHistoryItem = { source: string @@ -217,8 +218,8 @@ useHead(() => ({

Quốc gia: {{ movie.countries.join(', ') }}

Năm: {{ movie.year }}

-

Diễn viên: {{ - movie.actors.slice(0, 6).join(', ') }}

+

Diễn viên: {{ + actorSummary }}

diff --git a/server/utils/movies.ts b/server/utils/movies.ts index 7231069..986b141 100644 --- a/server/utils/movies.ts +++ b/server/utils/movies.ts @@ -31,9 +31,16 @@ export interface NormalizedServer { episodes: NormalizedEpisode[] } +export interface NormalizedActor { + name: string + originalName?: string + role?: string + avatar?: string +} + export interface MovieDetail extends NormalizedMovie { content: string - actors: string[] + actors: NormalizedActor[] directors: string[] trailer?: string servers: NormalizedServer[] @@ -97,6 +104,23 @@ function stripHtml(value?: string) { .trim() } +function normalizeActor(actor: any): NormalizedActor | undefined { + if (typeof actor === 'string') { + const name = actor.trim() + return name ? { name } : undefined + } + + const name = text(actor?.name || actor?.actor_name || actor?.title).trim() + if (!name) return undefined + + return { + name, + originalName: text(actor?.original_name || actor?.origin_name || actor?.real_name) || undefined, + role: text(actor?.role || actor?.character || actor?.as || actor?.cast_name) || undefined, + avatar: joinImage('', actor?.avatar || actor?.image || actor?.thumb_url || actor?.poster_url) || undefined, + } +} + export function normalizeOphimMovie(movie: any, pathImage = OPHIM_IMAGE): NormalizedMovie { return { id: `ophim:${movie?._id ?? movie?.slug}`, @@ -239,7 +263,7 @@ export async function getOphimDetail(slug: string): Promise { return { ...normalized, content: stripHtml(movie?.content), - actors: toArray(movie?.actor).map((item) => text(item)).filter(Boolean), + actors: toArray(movie?.actor).map(normalizeActor).filter(Boolean) as NormalizedActor[], directors: toArray(movie?.director).map((item) => text(item)).filter(Boolean), trailer: text(movie?.trailer_url), servers: toArray(data?.episodes ?? json?.episodes ?? movie?.episodes).map((server: any) => ({ @@ -262,7 +286,7 @@ export async function getNguoncDetail(slug: string): Promise { return { ...normalized, content: stripHtml(movie?.content || movie?.description), - actors: toArray(movie?.actor || movie?.actors).map((item: any) => text(item?.name ?? item)).filter(Boolean), + actors: toArray(movie?.actor || movie?.actors).map(normalizeActor).filter(Boolean) as NormalizedActor[], directors: toArray(movie?.director || movie?.directors).map((item: any) => text(item?.name ?? item)).filter(Boolean), trailer: text(movie?.trailer_url || movie?.trailer), servers: toArray(movie?.episodes ?? json?.episodes ?? json?.data?.episodes).map((server: any) => ({