feat:人物搜索

This commit is contained in:
jxxghp
2024-04-26 20:35:40 +08:00
parent 54415377ee
commit db0d5133e8
5 changed files with 82 additions and 124 deletions
+8 -11
View File
@@ -17,15 +17,13 @@ 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() { function goPersonDetail() {
if (!personInfo.value?.id) if (!personInfo.value?.id) return
return
router.push({ router.push({
path: '/person', path: '/person',
query: { query: {
@@ -51,7 +49,7 @@ function goPersonDetail() {
<div <div
class="person-card relative transform-gpu cursor-pointer rounded shadow ring-1 transition duration-150 ease-in-out scale-100 ring-gray-700" 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 style="padding-block-end: 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 <VAvatar
@@ -60,17 +58,16 @@ function goPersonDetail() {
'ring-1 ring-gray-700': isImageLoaded, 'ring-1 ring-gray-700': isImageLoaded,
}" }"
> >
<VImg <VImg :src="getPersonImage()" cover @load="isImageLoaded = true" />
:src="getPersonImage()"
cover
@load="isImageLoaded = true"
/>
</VAvatar> </VAvatar>
</div> </div>
<div class="w-full truncate text-center font-bold"> <div class="w-full truncate text-center font-bold">
{{ personInfo?.name }} {{ personInfo?.name }}
</div> </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;"> <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 }} {{ personInfo?.character }}
</div> </div>
<div class="absolute bottom-0 left-0 right-0 h-12 rounded-b" /> <div class="absolute bottom-0 left-0 right-0 h-12 rounded-b" />
+21 -24
View File
@@ -11,20 +11,28 @@ const searchDialog = ref(false)
// ref // ref
const searchWordInput = ref<HTMLElement | null>(null) const searchWordInput = ref<HTMLElement | null>(null)
// 当前的搜索类型 media/person
const searchType = ref('media')
// Search // Search
function search() { function search() {
if (!searchWord.value) if (!searchWord.value) return
return
searchDialog.value = false searchDialog.value = false
router.push({ router.push({
path: '/browse/media/search', path: '/browse/media/search',
query: { query: {
title: searchWord.value, title: searchWord.value,
type: searchType.value,
}, },
}) })
} }
// 切换搜索类型
function switchSearchType() {
searchType.value = searchType.value === 'media' ? 'person' : 'media'
}
// 打开搜索弹窗 // 打开搜索弹窗
function openSearchDialog() { function openSearchDialog() {
searchDialog.value = true searchDialog.value = true
@@ -36,15 +44,8 @@ function openSearchDialog() {
<template> <template>
<!-- 👉 Search Button --> <!-- 👉 Search Button -->
<div <div class="d-flex align-center cursor-pointer" style="user-select: none">
class="d-flex align-center cursor-pointer" <VDialog v-model="searchDialog" max-width="50rem" transition="dialog-top-transition">
style="user-select: none;"
>
<VDialog
v-model="searchDialog"
max-width="50rem"
transition="dialog-top-transition"
>
<!-- Dialog Content --> <!-- Dialog Content -->
<VCard title="搜索"> <VCard title="搜索">
<VCardText> <VCardText>
@@ -53,8 +54,10 @@ function openSearchDialog() {
<VTextField <VTextField
ref="searchWordInput" ref="searchWordInput"
v-model="searchWord" v-model="searchWord"
label="电影、电视剧名称" :prepend-inner-icon="searchType == 'person' ? 'mdi-account' : 'mdi-movie-roll'"
:label="searchType == 'person' ? '演员名称' : '电影、电视剧名称'"
@keydown.enter="search" @keydown.enter="search"
@click:prepend-inner="switchSearchType"
/> />
</VCol> </VCol>
</VRow> </VRow>
@@ -62,21 +65,13 @@ function openSearchDialog() {
<VCardActions> <VCardActions>
<VSpacer /> <VSpacer />
<VBtn <VBtn variant="tonal" @click="search"> 搜索 </VBtn>
variant="tonal"
@click="search"
>
搜索
</VBtn>
</VCardActions> </VCardActions>
</VCard> </VCard>
</VDialog> </VDialog>
</div> </div>
<!-- 👉 Search Icon --> <!-- 👉 Search Icon -->
<IconBtn <IconBtn class="d-lg-none" @click="openSearchDialog">
class="d-lg-none"
@click="openSearchDialog"
>
<VIcon icon="mdi-magnify" /> <VIcon icon="mdi-magnify" />
</IconBtn> </IconBtn>
<!-- 👉 Search Textfield --> <!-- 👉 Search Textfield -->
@@ -87,20 +82,22 @@ function openSearchDialog() {
class="d-none d-lg-block text-disabled search-box" class="d-none d-lg-block text-disabled search-box"
density="compact" density="compact"
variant="solo" variant="solo"
label="搜索电影、电视剧" :prepend-inner-icon="searchType == 'person' ? 'mdi-account' : 'mdi-movie-roll'"
:label="searchType == 'person' ? '演员名称' : '电影、电视剧名称'"
append-inner-icon="mdi-magnify" append-inner-icon="mdi-magnify"
single-line single-line
hide-details hide-details
flat flat
rounded rounded
@click:append-inner="search" @click:append-inner="search"
@click:prepend-inner="switchSearchType"
@keydown.enter="search" @keydown.enter="search"
/> />
</span> </span>
</template> </template>
<style lang="scss"> <style lang="scss">
.search-box div.v-input__control div[role="textbox"] { .search-box div.v-input__control div[role='textbox'] {
border: 1px solid rgb(var(--v-theme-background)); border: 1px solid rgb(var(--v-theme-background));
} }
</style> </style>
+17 -7
View File
@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import MediaCardListView from '@/views/discover/MediaCardListView.vue' import MediaCardListView from '@/views/discover/MediaCardListView.vue'
import PersonCardListView from '@/views/discover/PersonCardListView.vue'
// 输入参数 // 输入参数
const props = defineProps({ const props = defineProps({
@@ -11,14 +12,17 @@ const props = defineProps({
const route = useRoute() const route = useRoute()
// 标题 // 标题
const title = route.query?.title?.toString() let title = route.query?.title?.toString()
// 类型
const type = route.query?.type?.toString()
if (type === 'person') title = '演员:' + title
// 计算API路径 // 计算API路径
function getApiPath(paths: string[] | string) { function getApiPath(paths: string[] | string) {
if (Array.isArray(paths)) if (Array.isArray(paths)) return paths.join('/')
return paths.join('/') else return paths
else
return paths
} }
</script> </script>
@@ -26,14 +30,20 @@ function getApiPath(paths: string[] | string) {
<div> <div>
<div v-if="title" class="mt-3 md:flex md:items-center md:justify-between"> <div v-if="title" class="mt-3 md:flex md:items-center md:justify-between">
<div class="min-w-0 flex-1 mx-0"> <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"> <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> <span class="text-moviepilot">{{ title }}</span>
</h2> </h2>
</div> </div>
</div> </div>
<MediaCardListView <PersonCardListView
v-if="type === 'person'"
:apipath="getApiPath(props.paths || '')" :apipath="getApiPath(props.paths || '')"
type="tmdb"
:params="route.query" :params="route.query"
/> />
<MediaCardListView v-else :apipath="getApiPath(props.paths || '')" :params="route.query" />
</div> </div>
</template> </template>
+9 -34
View File
@@ -12,11 +12,7 @@ const props = defineProps({
// 判断是否有滚动条 // 判断是否有滚动条
function hasScroll() { function hasScroll() {
return ( return document.body.scrollHeight - (window.innerHeight || document.documentElement.clientHeight) > 2
document.body.scrollHeight
- (window.innerHeight || document.documentElement.clientHeight)
> 2
)
} }
// 当前页码 // 当前页码
@@ -37,8 +33,7 @@ function getParams() {
let params = { let params = {
page: page.value, page: page.value,
} }
if (props.params) if (props.params) params = { ...params, ...props.params }
params = { ...params, ...props.params }
return params return params
} }
@@ -46,8 +41,7 @@ function getParams() {
// 获取列表数据 // 获取列表数据
async function fetchData({ done }: { done: any }) { async function fetchData({ done }: { done: any }) {
try { try {
if (!props.apipath) if (!props.apipath) return
return
// 如果正在加载中,直接返回 // 如果正在加载中,直接返回
if (loading.value) { if (loading.value) {
@@ -81,8 +75,7 @@ async function fetchData({ done }: { done: any }) {
// 返回加载成功 // 返回加载成功
done('ok') done('ok')
} }
} } else {
else {
// 加载一次 // 加载一次
// 设置加载中 // 设置加载中
loading.value = true loading.value = true
@@ -106,8 +99,7 @@ async function fetchData({ done }: { done: any }) {
} }
// 取消加载中 // 取消加载中
loading.value = false loading.value = false
} } catch (error) {
catch (error) {
console.error(error) console.error(error)
// 返回加载失败 // 返回加载失败
done('error') done('error')
@@ -116,29 +108,12 @@ async function fetchData({ done }: { done: any }) {
</script> </script>
<template> <template>
<LoadingBanner <LoadingBanner v-if="!isRefreshed" class="mt-12" />
v-if="!isRefreshed" <VInfiniteScroll mode="intersect" side="end" :items="dataList" class="overflow-hidden" @load="fetchData">
class="mt-12"
/>
<VInfiniteScroll
mode="intersect"
side="end"
:items="dataList"
class="overflow-hidden"
@load="fetchData"
>
<template #loading /> <template #loading />
<template #empty /> <template #empty />
<div <div v-if="dataList.length > 0" class="grid gap-4 grid-media-card mx-3" tabindex="0">
v-if="dataList.length > 0" <MediaCard v-for="data in dataList" :key="data.tmdb_id || data.douban_id" :media="data" />
class="grid gap-4 grid-media-card mx-3"
tabindex="0"
>
<MediaCard
v-for="data in dataList"
:key="data.tmdb_id || data.douban_id"
:media="data"
/>
</div> </div>
<NoDataFound <NoDataFound
v-if="dataList.length === 0 && isRefreshed" v-if="dataList.length === 0 && isRefreshed"
+27 -48
View File
@@ -2,6 +2,7 @@
import api from '@/api' import api from '@/api'
import DoubanPersonCard from '@/components/cards/DoubanPersonCard.vue' import DoubanPersonCard from '@/components/cards/DoubanPersonCard.vue'
import TmdbPersonCard from '@/components/cards/TmdbPersonCard.vue' import TmdbPersonCard from '@/components/cards/TmdbPersonCard.vue'
import BangumiPersonCard from '@/components/cards/BangumiPersonCard.vue'
import NoDataFound from '@/components/NoDataFound.vue' import NoDataFound from '@/components/NoDataFound.vue'
// 输入参数 // 输入参数
@@ -13,11 +14,7 @@ const props = defineProps({
// 判断是否有滚动条 // 判断是否有滚动条
function hasScroll() { function hasScroll() {
return ( return document.body.scrollHeight - (window.innerHeight || document.documentElement.clientHeight) > 2
document.body.scrollHeight
- (window.innerHeight || document.documentElement.clientHeight)
> 2
)
} }
// 当前页码 // 当前页码
@@ -33,11 +30,20 @@ const isRefreshed = ref(false)
const dataList = ref<any>([]) const dataList = ref<any>([])
const currData = ref<any>([]) const currData = ref<any>([])
// 拼装参数
function getParams() {
let params = {
page: page.value,
}
if (props.params) params = { ...params, ...props.params }
return params
}
// 获取列表数据 // 获取列表数据
async function fetchData({ done }: { done: any }) { async function fetchData({ done }: { done: any }) {
try { try {
if (!props.apipath) if (!props.apipath) return
return
// 如果正在加载中,直接返回 // 如果正在加载中,直接返回
if (loading.value) { if (loading.value) {
@@ -53,9 +59,7 @@ async function fetchData({ done }: { done: any }) {
loading.value = true loading.value = true
// 请求API // 请求API
currData.value = await api.get(props.apipath, { currData.value = await api.get(props.apipath, {
params: { params: getParams(),
page: page.value,
},
}) })
// 取消加载中 // 取消加载中
loading.value = false loading.value = false
@@ -64,6 +68,7 @@ async function fetchData({ done }: { done: any }) {
if (currData.value.length === 0) { if (currData.value.length === 0) {
// 如果没有数据,跳出 // 如果没有数据,跳出
done('empty') done('empty')
return
} else { } else {
// 合并数据 // 合并数据
dataList.value = [...dataList.value, ...currData.value] dataList.value = [...dataList.value, ...currData.value]
@@ -73,16 +78,13 @@ async function fetchData({ done }: { done: any }) {
done('ok') done('ok')
} }
} }
} } else {
else {
// 加载一次 // 加载一次
// 设置加载中 // 设置加载中
loading.value = true loading.value = true
// 请求API // 请求API
currData.value = await api.get(props.apipath, { currData.value = await api.get(props.apipath, {
params: { params: getParams(),
page: page.value,
},
}) })
// 标计为已请求完成 // 标计为已请求完成
isRefreshed.value = true isRefreshed.value = true
@@ -100,8 +102,7 @@ async function fetchData({ done }: { done: any }) {
// 取消加载中 // 取消加载中
loading.value = false loading.value = false
} }
} } catch (error) {
catch (error) {
console.error(error) console.error(error)
// 返回加载失败 // 返回加载失败
done('error') done('error')
@@ -110,40 +111,18 @@ async function fetchData({ done }: { done: any }) {
</script> </script>
<template> <template>
<LoadingBanner <LoadingBanner v-if="!isRefreshed" class="mt-12" />
v-if="!isRefreshed" <VInfiniteScroll mode="intersect" side="end" :items="dataList" class="overflow-hidden" @load="fetchData">
class="mt-12"
/>
<VInfiniteScroll
mode="intersect"
side="end"
:items="dataList"
class="overflow-hidden"
@load="fetchData"
>
<template #loading /> <template #loading />
<template #empty /> <template #empty />
<div <div v-if="dataList.length > 0 && props.type === 'tmdb'" class="grid gap-4 grid-media-card mx-3" tabindex="0">
v-if="dataList.length > 0 && props.type === 'tmdb'" <TmdbPersonCard v-for="data in dataList" :key="data.id" :person="data" />
class="grid gap-4 grid-media-card mx-3"
tabindex="0"
>
<TmdbPersonCard
v-for="data in dataList"
:key="data.id"
:person="data"
/>
</div> </div>
<div <div v-if="dataList.length > 0 && props.type === 'douban'" class="grid gap-4 grid-media-card mx-3" tabindex="0">
v-if="dataList.length > 0 && props.type === 'douban'" <DoubanPersonCard v-for="data in dataList" :key="data.id" :person="data" />
class="grid gap-4 grid-media-card mx-3" </div>
tabindex="0" <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" />
<DoubanPersonCard
v-for="data in dataList"
:key="data.id"
:person="data"
/>
</div> </div>
<NoDataFound <NoDataFound
v-if="dataList.length === 0 && isRefreshed" v-if="dataList.length === 0 && isRefreshed"