mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-27 11:10:12 +08:00
feat person detail page
This commit is contained in:
@@ -324,7 +324,7 @@ function getExistText(season: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 打开详情页
|
// 打开详情页
|
||||||
function openDetailWindow() {
|
function goMediaDetail() {
|
||||||
router.push({
|
router.push({
|
||||||
path: '/media',
|
path: '/media',
|
||||||
query: {
|
query: {
|
||||||
@@ -433,7 +433,7 @@ const getImgUrl: Ref<string> = computed(() => {
|
|||||||
<VCardText
|
<VCardText
|
||||||
v-show="hover.isHovering || imageLoadError"
|
v-show="hover.isHovering || imageLoadError"
|
||||||
class="w-full flex flex-col flex-wrap justify-end align-left text-white absolute bottom-0 cursor-pointer pa-2"
|
class="w-full flex flex-col flex-wrap justify-end align-left text-white absolute bottom-0 cursor-pointer pa-2"
|
||||||
@click.stop="openDetailWindow"
|
@click.stop="goMediaDetail"
|
||||||
>
|
>
|
||||||
<span class="font-bold">{{ props.media?.year }}</span>
|
<span class="font-bold">{{ props.media?.year }}</span>
|
||||||
<h1 class="mb-1 text-white font-extrabold text-xl line-clamp-2 overflow-hidden text-ellipsis ...">
|
<h1 class="mb-1 text-white font-extrabold text-xl line-clamp-2 overflow-hidden text-ellipsis ...">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import personIcon from '@images/misc/person.png'
|
import personIcon from '@images/misc/person.png'
|
||||||
import type { TmdbPerson } from '@/api/types'
|
import type { TmdbPerson } from '@/api/types'
|
||||||
|
import router from '@/router'
|
||||||
|
|
||||||
const personProps = defineProps({
|
const personProps = defineProps({
|
||||||
person: Object as PropType<TmdbPerson>,
|
person: Object as PropType<TmdbPerson>,
|
||||||
@@ -11,12 +12,27 @@ const personProps = defineProps({
|
|||||||
// 当前人物
|
// 当前人物
|
||||||
const personInfo = ref(personProps.person)
|
const personInfo = ref(personProps.person)
|
||||||
|
|
||||||
|
// 人物图片是否加载
|
||||||
|
const isImageLoaded = ref(false)
|
||||||
|
|
||||||
// 人物图片地址
|
// 人物图片地址
|
||||||
function getPersonImage() {
|
function getPersonImage() {
|
||||||
if (!personInfo.value?.profile_path)
|
if (!personInfo.value?.profile_path)
|
||||||
return personIcon
|
return personIcon
|
||||||
return `https://image.tmdb.org/t/p/w600_and_h900_bestv2${personInfo.value?.profile_path}`
|
return `https://image.tmdb.org/t/p/w600_and_h900_bestv2${personInfo.value?.profile_path}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 人物详情
|
||||||
|
function goPersonDetail() {
|
||||||
|
if (!personInfo.value?.id)
|
||||||
|
return
|
||||||
|
router.push({
|
||||||
|
path: '/person',
|
||||||
|
query: {
|
||||||
|
personid: personInfo.value?.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -29,6 +45,7 @@ function getPersonImage() {
|
|||||||
:class="{
|
:class="{
|
||||||
'transition transform-cpu duration-300 scale-105': hover.isHovering,
|
'transition transform-cpu duration-300 scale-105': hover.isHovering,
|
||||||
}"
|
}"
|
||||||
|
@click.stop="goPersonDetail"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="person-card relative transform-gpu cursor-pointer rounded text-white shadow ring-1 transition duration-150 ease-in-out scale-100 ring-gray-700"
|
class="person-card relative transform-gpu cursor-pointer rounded text-white shadow ring-1 transition duration-150 ease-in-out scale-100 ring-gray-700"
|
||||||
@@ -36,11 +53,17 @@ function getPersonImage() {
|
|||||||
<div style="padding-bottom: 150%;">
|
<div style="padding-bottom: 150%;">
|
||||||
<div class="absolute inset-0 flex h-full w-full flex-col items-center p-2">
|
<div class="absolute inset-0 flex h-full w-full flex-col items-center p-2">
|
||||||
<div class="relative mt-2 mb-4 flex h-1/2 w-full justify-center">
|
<div class="relative mt-2 mb-4 flex h-1/2 w-full justify-center">
|
||||||
<VAvatar size="120" class="ring-1 ring-gray-700">
|
<VAvatar
|
||||||
|
size="120"
|
||||||
|
:class="{
|
||||||
|
'ring-1 ring-gray-700': isImageLoaded,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<VImg
|
<VImg
|
||||||
v-img
|
v-img
|
||||||
:src="getPersonImage()"
|
:src="getPersonImage()"
|
||||||
cover
|
cover
|
||||||
|
@load="isImageLoaded = true"
|
||||||
/>
|
/>
|
||||||
</VAvatar>
|
</VAvatar>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
39
src/pages/credits.vue
Normal file
39
src/pages/credits.vue
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import PersonCardListView from '@/views/discover/PersonCardListView.vue'
|
||||||
|
|
||||||
|
// 输入参数
|
||||||
|
const props = defineProps({
|
||||||
|
// API路径
|
||||||
|
paths: Array as PropType<string[]> | PropType<string>,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 路由参数
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
const title = route.query?.title?.toString()
|
||||||
|
|
||||||
|
// 计算API路径
|
||||||
|
function getApiPath(paths: string[] | string) {
|
||||||
|
if (Array.isArray(paths))
|
||||||
|
return paths.join('/')
|
||||||
|
else
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="title" class="mt-8 md:flex md:items-center md:justify-between">
|
||||||
|
<div class="min-w-0 flex-1 mx-0">
|
||||||
|
<h2 class="mb-4 truncate text-2xl font-bold leading-7 text-gray-100 sm:overflow-visible sm:text-4xl sm:leading-9 md:mb-0" data-testid="page-header">
|
||||||
|
<span class="text-moviepilot">{{ title }}</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PersonCardListView
|
||||||
|
:apipath="getApiPath(props.paths || '')"
|
||||||
|
:params="route.query"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,39 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import PersonCardListView from '@/views/discover/PersonCardListView.vue'
|
import PersonDetailView from '@/views/discover/PersonDetailView.vue'
|
||||||
|
|
||||||
// 输入参数
|
|
||||||
const props = defineProps({
|
|
||||||
// API路径
|
|
||||||
paths: Array as PropType<string[]> | PropType<string>,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 路由参数
|
// 路由参数
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
// 标题
|
// Person Id
|
||||||
const title = route.query?.title?.toString()
|
const personid = route.query?.personid?.toString()
|
||||||
|
|
||||||
// 计算API路径
|
// 类型
|
||||||
function getApiPath(paths: string[] | string) {
|
const type = route.query?.type?.toString()
|
||||||
if (Array.isArray(paths))
|
|
||||||
return paths.join('/')
|
|
||||||
else
|
|
||||||
return paths
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="title" class="mt-8 md:flex md:items-center md:justify-between">
|
<PersonDetailView
|
||||||
<div class="min-w-0 flex-1 mx-0">
|
:personid="personid"
|
||||||
<h2 class="mb-4 truncate text-2xl font-bold leading-7 text-gray-100 sm:overflow-visible sm:text-4xl sm:leading-9 md:mb-0" data-testid="page-header">
|
:type="type"
|
||||||
<span class="text-moviepilot">{{ title }}</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<PersonCardListView
|
|
||||||
:apipath="getApiPath(props.paths || '')"
|
|
||||||
:params="route.query"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -99,7 +99,15 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/person/:paths+',
|
path: '/credits/:paths+',
|
||||||
|
component: () => import('../pages/credits.vue'),
|
||||||
|
props: true,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/person',
|
||||||
component: () => import('../pages/person.vue'),
|
component: () => import('../pages/person.vue'),
|
||||||
props: true,
|
props: true,
|
||||||
meta: {
|
meta: {
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ onBeforeMount(() => {
|
|||||||
<PersonCardSlideView :apipath="`tmdb/credits/${mediaDetail.tmdb_id}/${mediaProps.type}`">
|
<PersonCardSlideView :apipath="`tmdb/credits/${mediaDetail.tmdb_id}/${mediaProps.type}`">
|
||||||
<template #title="{ loaded }">
|
<template #title="{ loaded }">
|
||||||
<div v-if="loaded" class="slider-header">
|
<div v-if="loaded" class="slider-header">
|
||||||
<RouterLink :to="`/person/tmdb/credits/${mediaDetail.tmdb_id}/${mediaProps.type}?title=演员阵容`" class="slider-title">
|
<RouterLink :to="`/credits/tmdb/credits/${mediaDetail.tmdb_id}/${mediaProps.type}?title=演员阵容`" class="slider-title">
|
||||||
<span>演员阵容</span>
|
<span>演员阵容</span>
|
||||||
<VIcon icon="mdi-arrow-right-circle-outline" class="ms-1" />
|
<VIcon icon="mdi-arrow-right-circle-outline" class="ms-1" />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|||||||
100
src/views/discover/PersonDetailView.vue
Normal file
100
src/views/discover/PersonDetailView.vue
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import MediaCardListView from './MediaCardListView.vue'
|
||||||
|
import api from '@/api'
|
||||||
|
import personIcon from '@images/misc/person.png'
|
||||||
|
import type { TmdbPerson } from '@/api/types'
|
||||||
|
import NoDataFound from '@/components/NoDataFound.vue'
|
||||||
|
|
||||||
|
// 输入参数
|
||||||
|
const personProps = defineProps({
|
||||||
|
personid: String,
|
||||||
|
type: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 媒体详情
|
||||||
|
const personDetail = ref<TmdbPerson>({} as TmdbPerson)
|
||||||
|
|
||||||
|
// 是否已加载完成
|
||||||
|
const isRefreshed = ref(false)
|
||||||
|
|
||||||
|
// 人物图片是否加载
|
||||||
|
const isImageLoaded = ref(false)
|
||||||
|
|
||||||
|
// 调用API查询详情
|
||||||
|
async function getPersonDetail() {
|
||||||
|
if (personProps.personid) {
|
||||||
|
personDetail.value = await api.get(`tmdb/person/${personProps.personid}`)
|
||||||
|
isRefreshed.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 人物图片地址
|
||||||
|
function getPersonImage() {
|
||||||
|
if (!personDetail.value?.profile_path)
|
||||||
|
return personIcon
|
||||||
|
return `https://image.tmdb.org/t/p/w600_and_h900_bestv2${personDetail.value?.profile_path}`
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
getPersonDetail()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="!isRefreshed"
|
||||||
|
class="mt-12 w-full text-center text-gray-500 text-sm flex flex-col items-center"
|
||||||
|
>
|
||||||
|
<VProgressCircular
|
||||||
|
size="48"
|
||||||
|
indeterminate
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="personDetail.id" class="max-w-8xl mx-auto px-4">
|
||||||
|
<div class="relative z-10 mt-4 mb-8 flex flex-col items-center lg:flex-row ">
|
||||||
|
<VAvatar
|
||||||
|
size="200"
|
||||||
|
:class="{
|
||||||
|
'ring-1 ring-gray-700': isImageLoaded,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<VImg
|
||||||
|
v-img
|
||||||
|
:src="getPersonImage()"
|
||||||
|
cover
|
||||||
|
@load="isImageLoaded = true"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
<div class="text-start ms-3 md:text-center">
|
||||||
|
<h1 class="text-3xl lg:text-4xl">
|
||||||
|
{{ personDetail.name }}
|
||||||
|
</h1>
|
||||||
|
<div class="mt-1 mb-2 space-y-1 text-xs text-white sm:text-sm lg:text-base">
|
||||||
|
<div>{{ personDetail.birthday }} | {{ personDetail.place_of_birth }}</div>
|
||||||
|
<div v-if="personDetail.also_known_as">
|
||||||
|
别名:{{ personDetail.also_known_as }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="slider-header">
|
||||||
|
<RouterLink :to="`/browse/tmdb/person/credits/${personDetail.id}?title=参演作品`" class="slider-title">
|
||||||
|
<span>参演作品</span>
|
||||||
|
<VIcon icon="mdi-arrow-right-circle-outline" class="ms-1" />
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
<MediaCardListView :apipath="`tmdb/person/credits/${personDetail.id}`" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NoDataFound
|
||||||
|
v-if="!personDetail.id && isRefreshed"
|
||||||
|
error-code="500"
|
||||||
|
error-title="出错啦!"
|
||||||
|
error-description="无法获取到媒体信息,请检查网络连接。"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user