mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-07-02 04:51:30 +08:00
feat:发现内容聚合
This commit is contained in:
409
src/api/types.ts
409
src/api/types.ts
File diff suppressed because it is too large
Load Diff
@@ -1,90 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import personIcon from '@images/misc/person-icon.png'
|
||||
import type { BangumiPerson } from '@/api/types'
|
||||
|
||||
const personProps = defineProps({
|
||||
person: Object as PropType<BangumiPerson>,
|
||||
width: String,
|
||||
height: String,
|
||||
})
|
||||
|
||||
// 当前人物
|
||||
const personInfo = ref(personProps.person)
|
||||
|
||||
// 人物图片是否加载
|
||||
const isImageLoaded = ref(false)
|
||||
|
||||
// 人物图片地址
|
||||
function getPersonImage() {
|
||||
if (!personInfo.value?.images) return personIcon
|
||||
return personInfo.value?.images?.medium
|
||||
}
|
||||
|
||||
// 使用、拼装人物角色
|
||||
function getPersonCharacter() {
|
||||
if (!personInfo.value?.career) return ''
|
||||
return personInfo.value?.career.join('、')
|
||||
}
|
||||
|
||||
// 打开人物详情
|
||||
function goPersonDetail() {
|
||||
if (!personInfo.value?.id) return
|
||||
window.open(`https://bangumi.tv/person/${personInfo.value?.id}`, '_blank')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VHover>
|
||||
<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 transition duration-150 ease-in-out scale-100 ring-gray-700"
|
||||
>
|
||||
<div style="padding-block-end: 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"
|
||||
>
|
||||
{{ getPersonCharacter() }}
|
||||
</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>
|
||||
@@ -1,84 +0,0 @@
|
||||
<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>
|
||||
<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 transition duration-150 ease-in-out scale-100 ring-gray-700"
|
||||
>
|
||||
<div style="padding-block-end: 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>
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import personIcon from '@images/misc/person-icon.png'
|
||||
import type { TmdbPerson } from '@/api/types'
|
||||
import type { Person } from '@/api/types'
|
||||
import router from '@/router'
|
||||
|
||||
const personProps = defineProps({
|
||||
person: Object as PropType<TmdbPerson>,
|
||||
person: Object as PropType<Person>,
|
||||
width: String,
|
||||
height: String,
|
||||
})
|
||||
@@ -17,8 +17,37 @@ const isImageLoaded = ref(false)
|
||||
|
||||
// 人物图片地址
|
||||
function getPersonImage() {
|
||||
if (!personInfo.value?.profile_path) return personIcon
|
||||
return `https://image.tmdb.org/t/p/w600_and_h900_bestv2${personInfo.value?.profile_path}`
|
||||
if (personProps.person?.source === 'themoviedb') {
|
||||
if (!personInfo.value?.profile_path) return personIcon
|
||||
return `https://image.tmdb.org/t/p/w600_and_h900_bestv2${personInfo.value?.profile_path}`
|
||||
} else if (personProps.person?.source === 'douban') {
|
||||
if (!personInfo.value?.avatar) return personIcon
|
||||
if (typeof personInfo.value?.avatar === 'object') {
|
||||
return personInfo.value?.avatar?.normal
|
||||
} else {
|
||||
return personInfo.value?.avatar
|
||||
}
|
||||
} else if (personProps.person?.source === 'bangumi') {
|
||||
if (!personInfo.value?.images) return personIcon
|
||||
return personInfo.value?.images?.medium
|
||||
} else {
|
||||
return personIcon
|
||||
}
|
||||
}
|
||||
|
||||
// 人物姓名
|
||||
function getPersonName() {
|
||||
return personInfo.value?.name
|
||||
}
|
||||
|
||||
// 人物角色
|
||||
function getPersonCharacter() {
|
||||
if (personProps.person?.source === 'bangumi') {
|
||||
if (!personInfo.value?.career) return ''
|
||||
return personInfo.value?.career.join('、')
|
||||
} else {
|
||||
return personInfo.value?.character
|
||||
}
|
||||
}
|
||||
|
||||
// 人物详情
|
||||
@@ -28,6 +57,7 @@ function goPersonDetail() {
|
||||
path: '/person',
|
||||
query: {
|
||||
personid: personInfo.value?.id,
|
||||
source: personInfo.value?.source,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -62,13 +92,13 @@ function goPersonDetail() {
|
||||
</VAvatar>
|
||||
</div>
|
||||
<div class="w-full truncate text-center font-bold">
|
||||
{{ personInfo?.name }}
|
||||
{{ getPersonName() }}
|
||||
</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 }}
|
||||
{{ getPersonCharacter() }}
|
||||
</div>
|
||||
<div class="absolute bottom-0 left-0 right-0 h-12 rounded-b" />
|
||||
</div>
|
||||
@@ -19,35 +19,11 @@ let title = route.query?.title?.toString()
|
||||
const type = route.query?.type?.toString()
|
||||
if (type === 'person') title = '演员:' + title
|
||||
|
||||
// 识别类型
|
||||
const RecognizeType = ref('themoviedb')
|
||||
|
||||
// 计算人物类型
|
||||
const PersonType = computed(() => {
|
||||
if (RecognizeType.value === 'douban') return 'douban'
|
||||
else if (RecognizeType.value === 'bangumi') return 'bangumi'
|
||||
else return 'tmdb'
|
||||
})
|
||||
|
||||
// 计算API路径
|
||||
function getApiPath(paths: string[] | string) {
|
||||
if (Array.isArray(paths)) return paths.join('/')
|
||||
else return paths
|
||||
}
|
||||
|
||||
// 加载系统设计
|
||||
async function loadSystemSettings() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/env')
|
||||
if (result.success) RecognizeType.value = result.data?.RECOGNIZE_SOURCE
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (type === 'person') await loadSystemSettings()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -62,12 +38,7 @@ onMounted(async () => {
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<PersonCardListView
|
||||
v-if="type === 'person'"
|
||||
:apipath="getApiPath(props.paths || '')"
|
||||
:type="PersonType"
|
||||
:params="route.query"
|
||||
/>
|
||||
<PersonCardListView v-if="type === 'person'" :apipath="getApiPath(props.paths || '')" :params="route.query" />
|
||||
<MediaCardListView v-else :apipath="getApiPath(props.paths || '')" :params="route.query" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -7,15 +7,15 @@ const route = useRoute()
|
||||
// Person Id
|
||||
const personid = route.query?.personid?.toString()
|
||||
|
||||
// 来源
|
||||
const source = route.query?.source?.toString()
|
||||
|
||||
// 类型
|
||||
const type = route.query?.type?.toString()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<PersonDetailView
|
||||
:personid="personid"
|
||||
:type="type"
|
||||
/>
|
||||
<PersonDetailView :personid="personid" :type="type" :source="source" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import api from '@/api'
|
||||
import DoubanPersonCard from '@/components/cards/DoubanPersonCard.vue'
|
||||
import TmdbPersonCard from '@/components/cards/TmdbPersonCard.vue'
|
||||
import BangumiPersonCard from '@/components/cards/BangumiPersonCard.vue'
|
||||
import PersonCard from '@/components/cards/PersonCard.vue'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
|
||||
// 输入参数
|
||||
@@ -115,14 +113,8 @@ async function fetchData({ done }: { done: any }) {
|
||||
<VInfiniteScroll mode="intersect" side="end" :items="dataList" class="overflow-hidden" @load="fetchData">
|
||||
<template #loading />
|
||||
<template #empty />
|
||||
<div v-if="dataList.length > 0 && props.type === 'tmdb'" class="grid gap-4 grid-media-card mx-3" tabindex="0">
|
||||
<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" />
|
||||
</div>
|
||||
<div v-if="dataList.length > 0 && props.type === 'bangumi'" class="grid gap-4 grid-media-card mx-3" tabindex="0">
|
||||
<BangumiPersonCard v-for="data in dataList" :key="data.id" :person="data" />
|
||||
<div v-if="dataList.length > 0" class="grid gap-4 grid-media-card mx-3" tabindex="0">
|
||||
<PersonCard v-for="data in dataList" :key="data.id" :person="data" />
|
||||
</div>
|
||||
<NoDataFound
|
||||
v-if="dataList.length === 0 && isRefreshed"
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import TmdbPersonCard from '@/components/cards/TmdbPersonCard.vue'
|
||||
import PersonCard from '@/components/cards/PersonCard.vue'
|
||||
import api from '@/api'
|
||||
import SlideView from '@/components/slide/SlideView.vue'
|
||||
import DoubanPersonCard from '@/components/cards/DoubanPersonCard.vue'
|
||||
import BangumiPersonCard from '@/components/cards/BangumiPersonCard.vue'
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
@@ -13,7 +11,7 @@ const props = defineProps({
|
||||
type: String,
|
||||
})
|
||||
|
||||
provide('rankingPropsKey', reactive({...props}))
|
||||
provide('rankingPropsKey', reactive({ ...props }))
|
||||
|
||||
// 组件加载完成
|
||||
const componentLoaded = ref(false)
|
||||
@@ -24,14 +22,11 @@ const dataList = ref<any>([])
|
||||
// 获取订阅列表数据
|
||||
async function fetchData() {
|
||||
try {
|
||||
if (!props.apipath)
|
||||
return
|
||||
if (!props.apipath) return
|
||||
|
||||
dataList.value = await api.get(props.apipath)
|
||||
if (dataList.value.length > 0)
|
||||
componentLoaded.value = true
|
||||
}
|
||||
catch (error) {
|
||||
if (dataList.value.length > 0) componentLoaded.value = true
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
@@ -41,32 +36,10 @@ onMounted(fetchData)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SlideView
|
||||
v-if="componentLoaded"
|
||||
>
|
||||
<SlideView v-if="componentLoaded">
|
||||
<template #content>
|
||||
<template
|
||||
v-for="data in dataList"
|
||||
:key="data.id"
|
||||
>
|
||||
<TmdbPersonCard
|
||||
v-if="props.type === 'tmdb'"
|
||||
:person="data"
|
||||
height="15rem"
|
||||
width="10rem"
|
||||
/>
|
||||
<DoubanPersonCard
|
||||
v-if="props.type === 'douban'"
|
||||
:person="data"
|
||||
height="15rem"
|
||||
width="10rem"
|
||||
/>
|
||||
<BangumiPersonCard
|
||||
v-if="props.type === 'bangumi'"
|
||||
:person="data"
|
||||
height="15rem"
|
||||
width="10rem"
|
||||
/>
|
||||
<template v-for="data in dataList" :key="data.id">
|
||||
<PersonCard :person="data" height="15rem" width="10rem" />
|
||||
</template>
|
||||
</template>
|
||||
</SlideView>
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
import MediaCardListView from './MediaCardListView.vue'
|
||||
import api from '@/api'
|
||||
import personIcon from '@images/misc/person.png'
|
||||
import type { TmdbPerson } from '@/api/types'
|
||||
import type { Person } from '@/api/types'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
|
||||
// 输入参数
|
||||
const personProps = defineProps({
|
||||
personid: String,
|
||||
type: String,
|
||||
source: String,
|
||||
})
|
||||
|
||||
// 媒体详情
|
||||
const personDetail = ref<TmdbPerson>({} as TmdbPerson)
|
||||
const personDetail = ref<Person>({} as Person)
|
||||
|
||||
// 是否已加载完成
|
||||
const isRefreshed = ref(false)
|
||||
@@ -23,21 +24,67 @@ const isImageLoaded = ref(false)
|
||||
// 调用API查询详情
|
||||
async function getPersonDetail() {
|
||||
if (personProps.personid) {
|
||||
personDetail.value = await api.get(`tmdb/person/${personProps.personid}`)
|
||||
if (personProps.source === 'themoviedb') {
|
||||
personDetail.value = await api.get(`tmdb/person/${personProps.personid}`)
|
||||
} else if (personProps.source === 'douban') {
|
||||
personDetail.value = await api.get(`douban/person/${personProps.personid}`)
|
||||
} else if (personProps.source === 'bangumi') {
|
||||
personDetail.value = await api.get(`bangumi/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}`
|
||||
if (personProps.source === 'themoviedb') {
|
||||
if (!personDetail.value?.profile_path) return personIcon
|
||||
return `https://image.tmdb.org/t/p/w600_and_h900_bestv2${personDetail.value?.profile_path}`
|
||||
} else if (personProps.source === 'douban') {
|
||||
if (!personDetail.value?.avatar) return personIcon
|
||||
if (typeof personDetail.value?.avatar === 'object') {
|
||||
return personDetail.value?.avatar?.normal
|
||||
} else {
|
||||
return personDetail.value?.avatar
|
||||
}
|
||||
} else if (personProps.source === 'bangumi') {
|
||||
if (!personDetail.value?.images) return personIcon
|
||||
return personDetail.value?.images?.medium
|
||||
} else {
|
||||
return personIcon
|
||||
}
|
||||
}
|
||||
|
||||
// 将别名数组拆分为、分隔的字符串
|
||||
function getAlsoKnownAs() {
|
||||
if (!personDetail.value?.also_known_as) return ''
|
||||
return personDetail.value.also_known_as.join('、')
|
||||
if (personProps.source === 'themoviedb') {
|
||||
return '别名:' + personDetail.value.also_known_as.join('、')
|
||||
} else {
|
||||
return personDetail.value.also_known_as.join(',')
|
||||
}
|
||||
}
|
||||
|
||||
// 参演作品路由地址
|
||||
function getPersonCreditsPath() {
|
||||
let apipath = 'tmdb'
|
||||
if (personProps.source === 'douban') {
|
||||
apipath = 'douban'
|
||||
} else if (personProps.source === 'bangumi') {
|
||||
apipath = 'bangumi'
|
||||
}
|
||||
return `/browse/${apipath}/person/credits/${personDetail.value.id}?title=参演作品`
|
||||
}
|
||||
|
||||
// 参演作品API路径
|
||||
function getPersonCreditsApiPath() {
|
||||
let apipath = 'tmdb'
|
||||
if (personProps.source === 'douban') {
|
||||
apipath = 'douban'
|
||||
} else if (personProps.source === 'bangumi') {
|
||||
apipath = 'bangumi'
|
||||
}
|
||||
return `${apipath}/person/credits/${personDetail.value.id}`
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
@@ -67,7 +114,7 @@ onBeforeMount(() => {
|
||||
<span v-if="personDetail.place_of_birth"> | </span>
|
||||
<span v-if="personDetail.place_of_birth">{{ personDetail.place_of_birth }}</span>
|
||||
</div>
|
||||
<div v-if="personDetail.also_known_as">别名:{{ getAlsoKnownAs() }}</div>
|
||||
<div v-if="personDetail.also_known_as">{{ getAlsoKnownAs() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -80,12 +127,12 @@ onBeforeMount(() => {
|
||||
</div>
|
||||
<div>
|
||||
<div class="slider-header">
|
||||
<RouterLink :to="`/browse/tmdb/person/credits/${personDetail.id}?title=参演作品`" class="slider-title">
|
||||
<RouterLink :to="getPersonCreditsPath()" class="slider-title">
|
||||
<span>参演作品</span>
|
||||
<VIcon icon="mdi-arrow-right-circle-outline" class="ms-1" />
|
||||
</RouterLink>
|
||||
</div>
|
||||
<MediaCardListView :apipath="`tmdb/person/credits/${personDetail.id}`" />
|
||||
<MediaCardListView :apipath="getPersonCreditsApiPath()" />
|
||||
</div>
|
||||
</div>
|
||||
<NoDataFound
|
||||
|
||||
Reference in New Issue
Block a user