mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-28 03:27:54 +08:00
feat 支持更多豆瓣详情展示
This commit is contained in:
+29
-1
@@ -337,7 +337,7 @@ export interface TmdbEpisode {
|
|||||||
guest_stars: Object[]
|
guest_stars: Object[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// TMDB人特信息
|
// TMDB人物信息
|
||||||
export interface TmdbPerson {
|
export interface TmdbPerson {
|
||||||
// ID
|
// ID
|
||||||
id?: number
|
id?: number
|
||||||
@@ -388,6 +388,34 @@ export interface TmdbPerson {
|
|||||||
biography?: string
|
biography?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 豆瓣人物信息
|
||||||
|
export interface DoubanPerson {
|
||||||
|
// ID
|
||||||
|
id?: string
|
||||||
|
|
||||||
|
// 名称
|
||||||
|
name?: string
|
||||||
|
|
||||||
|
// 角色
|
||||||
|
roles?: string[]
|
||||||
|
|
||||||
|
// 简介
|
||||||
|
title?: string
|
||||||
|
|
||||||
|
// 详情页面
|
||||||
|
url?: string
|
||||||
|
|
||||||
|
// 饰演
|
||||||
|
character?: string
|
||||||
|
|
||||||
|
// 图片 large/normal
|
||||||
|
avatar?: { [key: string]: string }
|
||||||
|
|
||||||
|
// 别名
|
||||||
|
latin_name?: string
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// 站点
|
// 站点
|
||||||
export interface Site {
|
export interface Site {
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import personIcon from '@images/misc/person-icon.png'
|
||||||
|
import type { DoubanPerson } from '@/api/types'
|
||||||
|
|
||||||
|
const personProps = defineProps({
|
||||||
|
person: Object as PropType<DoubanPerson>,
|
||||||
|
width: String,
|
||||||
|
height: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 当前人物
|
||||||
|
const personInfo = ref(personProps.person)
|
||||||
|
|
||||||
|
// 人物图片是否加载
|
||||||
|
const isImageLoaded = ref(false)
|
||||||
|
|
||||||
|
// 人物图片地址
|
||||||
|
function getPersonImage() {
|
||||||
|
if (!personInfo.value?.avatar)
|
||||||
|
return personIcon
|
||||||
|
return personInfo.value?.avatar?.large
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开人物详情
|
||||||
|
function goPersonDetail() {
|
||||||
|
if (!personInfo.value?.id)
|
||||||
|
return
|
||||||
|
window.open(`https://movie.douban.com/celebrity/${personInfo.value?.id}/`, '_blank')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VHover v-bind="personProps">
|
||||||
|
<template #default="hover">
|
||||||
|
<VCard
|
||||||
|
v-bind="hover.props"
|
||||||
|
:height="personProps.height"
|
||||||
|
:width="personProps.width"
|
||||||
|
class="rounded-lg"
|
||||||
|
:class="{
|
||||||
|
'transition transform-cpu duration-300 scale-105': hover.isHovering,
|
||||||
|
}"
|
||||||
|
@click.stop="goPersonDetail"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="person-card relative transform-gpu cursor-pointer rounded shadow ring-1 transition duration-150 ease-in-out scale-100 ring-gray-700"
|
||||||
|
>
|
||||||
|
<div style="padding-bottom: 150%;">
|
||||||
|
<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">
|
||||||
|
<VAvatar
|
||||||
|
size="120"
|
||||||
|
:class="{
|
||||||
|
'ring-1 ring-gray-700': isImageLoaded,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<VImg
|
||||||
|
v-img
|
||||||
|
:src="getPersonImage()"
|
||||||
|
cover
|
||||||
|
@load="isImageLoaded = true"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
</div>
|
||||||
|
<div class="w-full truncate text-center font-bold">
|
||||||
|
{{ personInfo?.name }}
|
||||||
|
</div>
|
||||||
|
<div class="overflow-hidden whitespace-normal text-center text-sm" style=" display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical;-webkit-line-clamp: 2;">
|
||||||
|
{{ personInfo?.character }}
|
||||||
|
</div>
|
||||||
|
<div class="absolute bottom-0 left-0 right-0 h-12 rounded-b" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VCard>
|
||||||
|
</template>
|
||||||
|
</VHover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.person-card {
|
||||||
|
background-image: linear-gradient(45deg, rgb(var(--v-theme-background)), rgb(var(--v-theme-surface)) 60%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.person-card:hover {
|
||||||
|
background-image: linear-gradient(45deg, rgb(var(--v-theme-background)), rgb(var(--v-custom-background)) 60%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -13,6 +13,9 @@ const route = useRoute()
|
|||||||
// 标题
|
// 标题
|
||||||
const title = route.query?.title?.toString()
|
const title = route.query?.title?.toString()
|
||||||
|
|
||||||
|
// 类型
|
||||||
|
const type = route.query?.type?.toString()
|
||||||
|
|
||||||
// 计算API路径
|
// 计算API路径
|
||||||
function getApiPath(paths: string[] | string) {
|
function getApiPath(paths: string[] | string) {
|
||||||
if (Array.isArray(paths))
|
if (Array.isArray(paths))
|
||||||
@@ -34,6 +37,7 @@ function getApiPath(paths: string[] | string) {
|
|||||||
<PersonCardListView
|
<PersonCardListView
|
||||||
:apipath="getApiPath(props.paths || '')"
|
:apipath="getApiPath(props.paths || '')"
|
||||||
:params="route.query"
|
:params="route.query"
|
||||||
|
:type="type"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ async function checkMovieExists() {
|
|||||||
// 查询当前媒体是否已订阅
|
// 查询当前媒体是否已订阅
|
||||||
async function checkSubscribe(season = 0) {
|
async function checkSubscribe(season = 0) {
|
||||||
try {
|
try {
|
||||||
const mediaid = `tmdb:${mediaDetail.value.tmdb_id}`
|
const mediaid = mediaDetail.value.tmdb_id ? `tmdb:${mediaDetail.value.tmdb_id}` : `douban:${mediaDetail.value.douban_id}`
|
||||||
|
|
||||||
const result: Subscribe = await api.get(`subscribe/media/${mediaid}`, {
|
const result: Subscribe = await api.get(`subscribe/media/${mediaid}`, {
|
||||||
params: {
|
params: {
|
||||||
@@ -420,9 +420,9 @@ onBeforeMount(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="mediaDetail.tmdb_id || mediaDetail.douban_id" class="max-w-8xl mx-auto px-4">
|
<div v-if="mediaDetail.tmdb_id || mediaDetail.douban_id" class="max-w-8xl mx-auto px-4">
|
||||||
<template v-if="mediaDetail.backdrop_path">
|
<template v-if="mediaDetail.backdrop_path || mediaDetail.poster_path">
|
||||||
<div class="vue-media-back absolute left-0 top-0 w-full h-96">
|
<div class="vue-media-back absolute left-0 top-0 w-full h-96">
|
||||||
<VImg class="h-96" :src="mediaDetail.backdrop_path" cover />
|
<VImg class="h-96" :src="mediaDetail.backdrop_path || mediaDetail.poster_path" cover />
|
||||||
</div>
|
</div>
|
||||||
<div class="vue-media-back absolute left-0 top-0 w-full h-96" />
|
<div class="vue-media-back absolute left-0 top-0 w-full h-96" />
|
||||||
</template>
|
</template>
|
||||||
@@ -512,10 +512,6 @@ onBeforeMount(() => {
|
|||||||
<span>{{ joinArray(director.roles) }}</span>
|
<span>{{ joinArray(director.roles) }}</span>
|
||||||
<a class="crew-name" :href="`${director.url}`" target="_blank">{{ director.name }}</a>
|
<a class="crew-name" :href="`${director.url}`" target="_blank">{{ director.name }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-for="director in mediaDetail.actors" :key="director.id">
|
|
||||||
<span>{{ joinArray(director.roles) }}</span>
|
|
||||||
<a class="crew-name" :href="`${director.url}`" target="_blank">{{ director.name }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<a v-if="mediaDetail.tmdb_id" class="mb-2 mr-2 inline-flex last:mr-0" :href="getTheMovieDbLink()" target="_blank">
|
<a v-if="mediaDetail.tmdb_id" class="mb-2 mr-2 inline-flex last:mr-0" :href="getTheMovieDbLink()" target="_blank">
|
||||||
@@ -667,12 +663,56 @@ onBeforeMount(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="mediaDetail.douban_id" class="media-overview-right">
|
||||||
|
<div class="media-facts">
|
||||||
|
<div v-if="mediaDetail.vote_average" class="media-ratings">
|
||||||
|
<VRating
|
||||||
|
v-model="mediaDetail.vote_average"
|
||||||
|
density="compact"
|
||||||
|
length="10"
|
||||||
|
class="ma-2"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="mediaDetail.douban_id" class="media-fact">
|
||||||
|
<span>豆瓣ID</span>
|
||||||
|
<span class="media-fact-value">{{ mediaDetail.douban_id }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="mediaDetail.original_title" class="media-fact">
|
||||||
|
<span>原始标题</span>
|
||||||
|
<span class="media-fact-value">{{ mediaDetail.original_title }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="mediaDetail.release_date" class="media-fact">
|
||||||
|
<span>上映日期</span>
|
||||||
|
<span class="media-fact-value">
|
||||||
|
{{ mediaDetail.release_date }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="mediaDetail.production_countries" class="media-fact border-b-0">
|
||||||
|
<span>出品国家</span>
|
||||||
|
<span class="media-fact-value">
|
||||||
|
<span v-for="country in getProductionCountries" :key="country" class="flex items-center justify-end text-end">
|
||||||
|
{{ country }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="mediaDetail.tmdb_id">
|
<div v-if="mediaDetail.tmdb_id">
|
||||||
<PersonCardSlideView
|
<PersonCardSlideView
|
||||||
:apipath="`tmdb/credits/${mediaDetail.tmdb_id}/${mediaProps.type}`"
|
:apipath="`tmdb/credits/${mediaDetail.tmdb_id}/${mediaProps.type}`"
|
||||||
:linkurl="`/credits/tmdb/credits/${mediaDetail.tmdb_id}/${mediaProps.type}?title=演员阵容`"
|
:linkurl="`/credits/tmdb/credits/${mediaDetail.tmdb_id}/${mediaProps.type}?title=演员阵容&type=tmdb`"
|
||||||
title="演员阵容"
|
title="演员阵容"
|
||||||
|
type="tmdb"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="mediaDetail.douban_id">
|
||||||
|
<PersonCardSlideView
|
||||||
|
:apipath="`douban/credits/${mediaDetail.douban_id}/${mediaProps.type}`"
|
||||||
|
:linkurl="`/credits/douban/credits/${mediaDetail.douban_id}/${mediaProps.type}?title=演员阵容&type=douban`"
|
||||||
|
title="演员阵容"
|
||||||
|
type="douban"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="mediaDetail.tmdb_id">
|
<div v-if="mediaDetail.tmdb_id">
|
||||||
@@ -682,6 +722,13 @@ onBeforeMount(() => {
|
|||||||
title="推荐"
|
title="推荐"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="mediaDetail.douban_id">
|
||||||
|
<MediaCardSlideView
|
||||||
|
:apipath="`douban/recommend/${mediaDetail.douban_id}/${mediaProps.type}`"
|
||||||
|
:linkurl="`/browse/douban/recommend/${mediaDetail.douban_id}/${mediaProps.type}?title=推荐`"
|
||||||
|
title="推荐"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div v-if="mediaDetail.tmdb_id">
|
<div v-if="mediaDetail.tmdb_id">
|
||||||
<MediaCardSlideView
|
<MediaCardSlideView
|
||||||
:apipath="`tmdb/similar/${mediaDetail.tmdb_id}/${mediaProps.type}`"
|
:apipath="`tmdb/similar/${mediaDetail.tmdb_id}/${mediaProps.type}`"
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { TmdbPerson } from '@/api/types'
|
import DoubanPersonCard from '@/components/cards/DoubanPersonCard.vue'
|
||||||
import PersonCard from '@/components/cards/PersonCard.vue'
|
import TmdbPersonCard from '@/components/cards/TmdbPersonCard.vue'
|
||||||
import NoDataFound from '@/components/NoDataFound.vue'
|
import NoDataFound from '@/components/NoDataFound.vue'
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
apipath: String,
|
apipath: String,
|
||||||
params: Object as PropType<{ [key: string]: any }>,
|
params: Object as PropType<{ [key: string]: any }>,
|
||||||
|
type: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 判断是否有滚动条
|
// 判断是否有滚动条
|
||||||
@@ -29,8 +30,8 @@ const loading = ref(false)
|
|||||||
const isRefreshed = ref(false)
|
const isRefreshed = ref(false)
|
||||||
|
|
||||||
// 数据列表
|
// 数据列表
|
||||||
const dataList = ref<TmdbPerson[]>([])
|
const dataList = ref<any>([])
|
||||||
const currData = ref<TmdbPerson[]>([])
|
const currData = ref<any>([])
|
||||||
|
|
||||||
// 获取列表数据
|
// 获取列表数据
|
||||||
async function fetchData({ done }: { done: any }) {
|
async function fetchData({ done }: { done: any }) {
|
||||||
@@ -135,11 +136,22 @@ async function fetchData({ done }: { done: any }) {
|
|||||||
>
|
>
|
||||||
<template #loading />
|
<template #loading />
|
||||||
<div
|
<div
|
||||||
v-if="dataList.length > 0"
|
v-if="dataList.length > 0 && props.type === 'tmdb'"
|
||||||
class="grid gap-4 grid-media-card mx-3"
|
class="grid gap-4 grid-media-card mx-3"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<PersonCard
|
<TmdbPersonCard
|
||||||
|
v-for="data in dataList"
|
||||||
|
:key="data.id"
|
||||||
|
:person="data"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="dataList.length > 0 && props.type === 'douban'"
|
||||||
|
class="grid gap-4 grid-media-card mx-3"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<DoubanPersonCard
|
||||||
v-for="data in dataList"
|
v-for="data in dataList"
|
||||||
:key="data.id"
|
:key="data.id"
|
||||||
:person="data"
|
:person="data"
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import PersionCard from '@/components/cards/PersonCard.vue'
|
import TmdbPersonCard from '@/components/cards/TmdbPersonCard.vue'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { TmdbPerson } from '@/api/types'
|
|
||||||
import SlideView from '@/components/slide/SlideView.vue'
|
import SlideView from '@/components/slide/SlideView.vue'
|
||||||
|
import DoubanPersonCard from '@/components/cards/DoubanPersonCard.vue'
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
apipath: String,
|
apipath: String,
|
||||||
linkurl: String,
|
linkurl: String,
|
||||||
title: String,
|
title: String,
|
||||||
|
type: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 组件加载完成
|
// 组件加载完成
|
||||||
const componentLoaded = ref(false)
|
const componentLoaded = ref(false)
|
||||||
|
|
||||||
// 数据列表
|
// 数据列表
|
||||||
const dataList = ref<TmdbPerson[]>([])
|
const dataList = ref<any>([])
|
||||||
|
|
||||||
// 获取订阅列表数据
|
// 获取订阅列表数据
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
@@ -46,7 +47,14 @@ onMounted(fetchData)
|
|||||||
v-for="data in dataList"
|
v-for="data in dataList"
|
||||||
:key="data.id"
|
:key="data.id"
|
||||||
>
|
>
|
||||||
<PersionCard
|
<TmdbPersonCard
|
||||||
|
v-if="props.type === 'tmdb'"
|
||||||
|
:person="data"
|
||||||
|
height="15rem"
|
||||||
|
width="10rem"
|
||||||
|
/>
|
||||||
|
<DoubanPersonCard
|
||||||
|
v-if="props.type === 'douban'"
|
||||||
:person="data"
|
:person="data"
|
||||||
height="15rem"
|
height="15rem"
|
||||||
width="10rem"
|
width="10rem"
|
||||||
|
|||||||
Reference in New Issue
Block a user