feat 支持更多豆瓣详情展示

This commit is contained in:
jxxghp
2023-11-18 21:52:35 +08:00
parent 7e221cfd46
commit 9af200f89e
7 changed files with 206 additions and 19 deletions

View File

@@ -337,7 +337,7 @@ export interface TmdbEpisode {
guest_stars: Object[]
}
// TMDB人信息
// TMDB人信息
export interface TmdbPerson {
// ID
id?: number
@@ -388,6 +388,34 @@ export interface TmdbPerson {
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 {

View File

@@ -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>

View File

@@ -13,6 +13,9 @@ const route = useRoute()
// 标题
const title = route.query?.title?.toString()
// 类型
const type = route.query?.type?.toString()
// 计算API路径
function getApiPath(paths: string[] | string) {
if (Array.isArray(paths))
@@ -34,6 +37,7 @@ function getApiPath(paths: string[] | string) {
<PersonCardListView
:apipath="getApiPath(props.paths || '')"
:params="route.query"
:type="type"
/>
</div>
</template>

View File

@@ -109,7 +109,7 @@ async function checkMovieExists() {
// 查询当前媒体是否已订阅
async function checkSubscribe(season = 0) {
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}`, {
params: {
@@ -420,9 +420,9 @@ onBeforeMount(() => {
/>
</div>
<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">
<VImg class="h-96" :src="mediaDetail.backdrop_path" cover />
<VImg class="h-96" :src="mediaDetail.backdrop_path || mediaDetail.poster_path" cover />
</div>
<div class="vue-media-back absolute left-0 top-0 w-full h-96" />
</template>
@@ -512,10 +512,6 @@ onBeforeMount(() => {
<span>{{ joinArray(director.roles) }}</span>
<a class="crew-name" :href="`${director.url}`" target="_blank">{{ director.name }}</a>
</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>
<div class="mt-6">
<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 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 v-if="mediaDetail.tmdb_id">
<PersonCardSlideView
: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="演员阵容"
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 v-if="mediaDetail.tmdb_id">
@@ -682,6 +722,13 @@ onBeforeMount(() => {
title="推荐"
/>
</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">
<MediaCardSlideView
:apipath="`tmdb/similar/${mediaDetail.tmdb_id}/${mediaProps.type}`"

View File

@@ -1,13 +1,14 @@
<script lang="ts" setup>
import api from '@/api'
import type { TmdbPerson } from '@/api/types'
import PersonCard from '@/components/cards/PersonCard.vue'
import DoubanPersonCard from '@/components/cards/DoubanPersonCard.vue'
import TmdbPersonCard from '@/components/cards/TmdbPersonCard.vue'
import NoDataFound from '@/components/NoDataFound.vue'
// 输入参数
const props = defineProps({
apipath: String,
params: Object as PropType<{ [key: string]: any }>,
type: String,
})
// 判断是否有滚动条
@@ -29,8 +30,8 @@ const loading = ref(false)
const isRefreshed = ref(false)
// 数据列表
const dataList = ref<TmdbPerson[]>([])
const currData = ref<TmdbPerson[]>([])
const dataList = ref<any>([])
const currData = ref<any>([])
// 获取列表数据
async function fetchData({ done }: { done: any }) {
@@ -135,11 +136,22 @@ async function fetchData({ done }: { done: any }) {
>
<template #loading />
<div
v-if="dataList.length > 0"
v-if="dataList.length > 0 && props.type === 'tmdb'"
class="grid gap-4 grid-media-card mx-3"
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"
:key="data.id"
:person="data"

View File

@@ -1,21 +1,22 @@
<script lang="ts" setup>
import PersionCard from '@/components/cards/PersonCard.vue'
import TmdbPersonCard from '@/components/cards/TmdbPersonCard.vue'
import api from '@/api'
import type { TmdbPerson } from '@/api/types'
import SlideView from '@/components/slide/SlideView.vue'
import DoubanPersonCard from '@/components/cards/DoubanPersonCard.vue'
// 输入参数
const props = defineProps({
apipath: String,
linkurl: String,
title: String,
type: String,
})
// 组件加载完成
const componentLoaded = ref(false)
// 数据列表
const dataList = ref<TmdbPerson[]>([])
const dataList = ref<any>([])
// 获取订阅列表数据
async function fetchData() {
@@ -46,7 +47,14 @@ onMounted(fetchData)
v-for="data in dataList"
:key="data.id"
>
<PersionCard
<TmdbPersonCard
v-if="props.type === 'tmdb'"
:person="data"
height="15rem"
width="10rem"
/>
<DoubanPersonCard
v-if="props.type === 'douban'"
:person="data"
height="15rem"
width="10rem"