mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-04 23:18:44 +08:00
重构发现页面,添加豆瓣和TheMovieDb过滤选项,优化媒体卡组件
This commit is contained in:
@@ -535,7 +535,7 @@ function onRemoveSubscribe() {
|
|||||||
density="compact"
|
density="compact"
|
||||||
class="absolute bottom-1 right-1"
|
class="absolute bottom-1 right-1"
|
||||||
tile
|
tile
|
||||||
v-if="!hover.isHovering && isImageLoaded && props.media?.source"
|
v-if="!hover.isHovering && isImageLoaded && props.media?.source && !imageLoadError"
|
||||||
>
|
>
|
||||||
<VImg cover :src="sourceIconDict[props.media?.source]" class="shadow-lg" />
|
<VImg cover :src="sourceIconDict[props.media?.source]" class="shadow-lg" />
|
||||||
</VAvatar>
|
</VAvatar>
|
||||||
|
|||||||
@@ -1,20 +1,83 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import MediaCardListView from '@/views/discover/MediaCardListView.vue'
|
import MediaCardListView from '@/views/discover/MediaCardListView.vue'
|
||||||
|
|
||||||
// 电影或者电视剧 movies/tvs
|
|
||||||
const type = ref('movies')
|
|
||||||
|
|
||||||
// 过滤参数
|
// 过滤参数
|
||||||
const filterParams = ref({
|
const filterParams = reactive({
|
||||||
'type': 2,
|
'type': 2,
|
||||||
'cat': null,
|
'cat': null,
|
||||||
'sort': 'rank', // date/rank
|
'sort': 'rank', // date/rank
|
||||||
'year': null,
|
'year': null,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Bangumi cat字典
|
||||||
|
/**
|
||||||
|
* 0 为 其他
|
||||||
|
1 为 TV
|
||||||
|
2 为 OVA
|
||||||
|
3 为 Movie
|
||||||
|
5 为 WEB
|
||||||
|
*/
|
||||||
|
const bangumiCatDict = {
|
||||||
|
'0': '其他',
|
||||||
|
'1': 'TV',
|
||||||
|
'2': 'OVA',
|
||||||
|
'3': 'Movie',
|
||||||
|
'5': 'WEB',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bangumi排序字典
|
||||||
|
const bangumiSortDict = {
|
||||||
|
'rank': '排名',
|
||||||
|
'date': '日期',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前Key
|
||||||
|
const currentKey = ref(0)
|
||||||
|
|
||||||
|
// 类型和过滤参数变化后重新刷新列表
|
||||||
|
watch([filterParams], () => {
|
||||||
|
currentKey.value++
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div class="px-3">
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>类别</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup column v-model="filterParams.cat">
|
||||||
|
<VChip
|
||||||
|
:color="filterParams.cat == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in bangumiCatDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>排序</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup column v-model="filterParams.sort">
|
||||||
|
<VChip
|
||||||
|
:color="filterParams.sort == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in bangumiSortDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<MediaCardListView apipath="bangumi/subjects" :params="filterParams" />
|
<MediaCardListView :key="currentKey" apipath="bangumi/subjects" :params="filterParams" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,14 +5,189 @@ import MediaCardListView from '@/views/discover/MediaCardListView.vue'
|
|||||||
const type = ref('movies')
|
const type = ref('movies')
|
||||||
|
|
||||||
// 过滤参数
|
// 过滤参数
|
||||||
const filterParams = ref({
|
const filterParams = reactive({
|
||||||
'sort': 'R',
|
'sort': 'U',
|
||||||
'tags': '',
|
'tags': '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 豆瓣风格类型
|
||||||
|
const doubanCategory = ref('')
|
||||||
|
|
||||||
|
// 地区
|
||||||
|
const doubanZone = ref('')
|
||||||
|
|
||||||
|
// 年代
|
||||||
|
const doubanYear = ref('')
|
||||||
|
|
||||||
|
// 豆瓣风格字典
|
||||||
|
const categoryDict = {
|
||||||
|
'喜剧': '喜剧',
|
||||||
|
'爱情': '爱情',
|
||||||
|
'动作': '动作',
|
||||||
|
'科幻': '科幻',
|
||||||
|
'动画': '动画',
|
||||||
|
'悬疑': '悬疑',
|
||||||
|
'犯罪': '犯罪',
|
||||||
|
'惊悚': '惊悚',
|
||||||
|
'冒险': '冒险',
|
||||||
|
'音乐': '音乐',
|
||||||
|
'历史': '历史',
|
||||||
|
'奇幻': '奇幻',
|
||||||
|
'恐怖': '恐怖',
|
||||||
|
'战争': '战争',
|
||||||
|
'传记': '传记',
|
||||||
|
'歌舞': '歌舞',
|
||||||
|
'武侠': '武侠',
|
||||||
|
'情色': '情色',
|
||||||
|
'灾难': '灾难',
|
||||||
|
'西部': '西部',
|
||||||
|
'纪录片': '纪录片',
|
||||||
|
'短片': '短片',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 地区字典
|
||||||
|
const zoneDict = {
|
||||||
|
'华语': '华语',
|
||||||
|
'欧美': '欧美',
|
||||||
|
'韩国': '韩国',
|
||||||
|
'日本': '日本',
|
||||||
|
'中国大陆': '中国大陆',
|
||||||
|
'美国': '美国',
|
||||||
|
'中国香港': '中国香港',
|
||||||
|
'中国台湾': '中国台湾',
|
||||||
|
'英国': '英国',
|
||||||
|
'法国': '法国',
|
||||||
|
'德国': '德国',
|
||||||
|
'意大利': '意大利',
|
||||||
|
'西班牙': '西班牙',
|
||||||
|
'印度': '印度',
|
||||||
|
'泰国': '泰国',
|
||||||
|
'俄罗斯': '俄罗斯',
|
||||||
|
'加拿大': '加拿大',
|
||||||
|
'澳大利亚': '澳大利亚',
|
||||||
|
'爱尔兰': '爱尔兰',
|
||||||
|
'瑞典': '瑞典',
|
||||||
|
'巴西': '巴西',
|
||||||
|
'丹麦': '丹麦',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 年代字典
|
||||||
|
const yearDict = {
|
||||||
|
'2020年代': '2020年代',
|
||||||
|
'2010年代': '2010年代',
|
||||||
|
'2000年代': '2000年代',
|
||||||
|
'90年代': '90年代',
|
||||||
|
'80年代': '80年代',
|
||||||
|
'70年代': '70年代',
|
||||||
|
'60年代': '60年代',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 豆瓣过滤参数
|
||||||
|
const doubanSortDict = {
|
||||||
|
'U': '综合排序',
|
||||||
|
'R': '首播时间',
|
||||||
|
'T': '近期热度',
|
||||||
|
'S': '高分优先',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 风格、年代、地区变化时,以,分隔拼接到tags参数
|
||||||
|
watch([doubanCategory, doubanZone, doubanYear], () => {
|
||||||
|
filterParams.tags = [doubanCategory.value, doubanZone.value, doubanYear.value].filter(Boolean).join(',')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 当前Key
|
||||||
|
const currentKey = ref(0)
|
||||||
|
|
||||||
|
// 类型和过滤参数变化后重新刷新列表
|
||||||
|
watch([type, filterParams], () => {
|
||||||
|
if (!type.value) {
|
||||||
|
type.value = 'movies'
|
||||||
|
}
|
||||||
|
currentKey.value++
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div class="px-3">
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>类型</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup column v-model="type">
|
||||||
|
<VChip :color="type == 'movies' ? 'primary' : ''" filter tile value="movies">电影</VChip>
|
||||||
|
<VChip :color="type == 'tvs' ? 'primary' : ''" filter tile value="tvs">电视剧</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>排序</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup column v-model="filterParams.sort">
|
||||||
|
<VChip
|
||||||
|
:color="filterParams.sort == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in doubanSortDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>风格</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup column v-model="doubanCategory">
|
||||||
|
<VChip
|
||||||
|
:color="doubanCategory == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in categoryDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>地区</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup column v-model="doubanZone">
|
||||||
|
<VChip
|
||||||
|
:color="doubanZone == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in zoneDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>年代</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup column v-model="doubanYear">
|
||||||
|
<VChip
|
||||||
|
:color="doubanYear == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in yearDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<MediaCardListView :apipath="`douban/${type}`" :params="filterParams" />
|
<MediaCardListView :key="currentKey" :apipath="`douban/${type}`" :params="filterParams" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const isRefreshed = ref(false)
|
|||||||
// 数据列表
|
// 数据列表
|
||||||
const dataList = ref<MediaInfo[]>([])
|
const dataList = ref<MediaInfo[]>([])
|
||||||
const currData = ref<MediaInfo[]>([])
|
const currData = ref<MediaInfo[]>([])
|
||||||
|
|
||||||
// 拼装参数
|
// 拼装参数
|
||||||
function getParams() {
|
function getParams() {
|
||||||
let params = {
|
let params = {
|
||||||
@@ -77,7 +78,6 @@ async function fetchData({ done }: { done: any }) {
|
|||||||
} else {
|
} else {
|
||||||
// 加载一次
|
// 加载一次
|
||||||
// 设置加载中
|
// 设置加载中
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
// 请求API
|
// 请求API
|
||||||
currData.value = await api.get(props.apipath, {
|
currData.value = await api.get(props.apipath, {
|
||||||
@@ -115,7 +115,11 @@ async function fetchData({ done }: { done: any }) {
|
|||||||
<div v-if="dataList.length > 0" class="grid gap-4 grid-media-card mx-3" tabindex="0">
|
<div v-if="dataList.length > 0" 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" />
|
<MediaCard v-for="data in dataList" :key="data.tmdb_id || data.douban_id" :media="data" />
|
||||||
</div>
|
</div>
|
||||||
<NoDataFound v-if="dataList.length === 0 && isRefreshed" error-code="404" error-title="没有数据"
|
<NoDataFound
|
||||||
error-description="无法获取到媒体信息。" />
|
v-if="dataList.length === 0 && isRefreshed"
|
||||||
|
error-code="404"
|
||||||
|
error-title="没有数据"
|
||||||
|
error-description="无法获取到媒体信息。"
|
||||||
|
/>
|
||||||
</VInfiniteScroll>
|
</VInfiniteScroll>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,20 +5,147 @@ import MediaCardListView from '@/views/discover/MediaCardListView.vue'
|
|||||||
const type = ref('movies')
|
const type = ref('movies')
|
||||||
|
|
||||||
// 过滤参数
|
// 过滤参数
|
||||||
const filterParams = ref({
|
const filterParams = reactive({
|
||||||
'sort_by': 'popularity.desc',
|
sort_by: 'popularity.desc',
|
||||||
'with_genres': '',
|
with_genres: '',
|
||||||
'with_original_language': '',
|
with_original_language: '',
|
||||||
'with_keywords': '',
|
with_keywords: '',
|
||||||
'with_watch_providers': '',
|
with_watch_providers: '',
|
||||||
'vote_average.gte': 0,
|
vote_average: 0,
|
||||||
'vote_count.gte': 0,
|
vote_count: 0,
|
||||||
'release_date.gte': '',
|
release_date: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TMDB排序字典
|
||||||
|
const tmdbSortDict = {
|
||||||
|
'popularity.desc': '热度降序',
|
||||||
|
'popularity.asc': '热度升序',
|
||||||
|
'release_date.desc': '上映日期降序',
|
||||||
|
'release_date.asc': '上映日期升序',
|
||||||
|
'vote_average.desc': '评分降序',
|
||||||
|
'vote_average.asc': '评分升序',
|
||||||
|
}
|
||||||
|
|
||||||
|
// TMDB风格字典
|
||||||
|
const tmdbGenreDict = {
|
||||||
|
'28': '动作',
|
||||||
|
'12': '冒险',
|
||||||
|
'16': '动画',
|
||||||
|
'35': '喜剧',
|
||||||
|
'80': '犯罪',
|
||||||
|
'99': '纪录片',
|
||||||
|
'18': '剧情',
|
||||||
|
'10751': '家庭',
|
||||||
|
'14': '奇幻',
|
||||||
|
'36': '历史',
|
||||||
|
'27': '恐怖',
|
||||||
|
'10402': '音乐',
|
||||||
|
'9648': '悬疑',
|
||||||
|
'10749': '爱情',
|
||||||
|
'878': '科幻',
|
||||||
|
'10770': '电视电影',
|
||||||
|
'53': '惊悚',
|
||||||
|
'10752': '战争',
|
||||||
|
'37': '西部',
|
||||||
|
}
|
||||||
|
|
||||||
|
// TMDB原始语言字典(主要语言)
|
||||||
|
const tmdbLanguageDict = {
|
||||||
|
'zh': '中文',
|
||||||
|
'en': '英语',
|
||||||
|
'ja': '日语',
|
||||||
|
'ko': '韩语',
|
||||||
|
'fr': '法语',
|
||||||
|
'de': '德语',
|
||||||
|
'es': '西班牙语',
|
||||||
|
'it': '意大利语',
|
||||||
|
'ru': '俄语',
|
||||||
|
'pt': '葡萄牙语',
|
||||||
|
'ar': '阿拉伯语',
|
||||||
|
'hi': '印地语',
|
||||||
|
'th': '泰语',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前Key
|
||||||
|
const currentKey = ref(0)
|
||||||
|
|
||||||
|
// 类型和过滤参数变化后重新刷新列表
|
||||||
|
watch([type, filterParams], () => {
|
||||||
|
if (!type.value) {
|
||||||
|
type.value = 'movies'
|
||||||
|
}
|
||||||
|
if (!filterParams.sort_by) {
|
||||||
|
filterParams.sort_by = 'popularity.desc'
|
||||||
|
}
|
||||||
|
currentKey.value++
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div class="px-3">
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>类型</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup column v-model="type">
|
||||||
|
<VChip :color="type == 'movies' ? 'primary' : ''" filter tile value="movies">电影</VChip>
|
||||||
|
<VChip :color="type == 'tvs' ? 'primary' : ''" filter tile value="tvs">电视剧</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>排序</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup column v-model="filterParams.sort_by">
|
||||||
|
<VChip
|
||||||
|
:color="filterParams.sort_by == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in tmdbSortDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>风格</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup column v-model="filterParams.with_genres">
|
||||||
|
<VChip
|
||||||
|
:color="filterParams.with_genres == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in tmdbGenreDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-start align-center">
|
||||||
|
<div class="mr-5">
|
||||||
|
<VLabel>语言</VLabel>
|
||||||
|
</div>
|
||||||
|
<VChipGroup column v-model="filterParams.with_original_language">
|
||||||
|
<VChip
|
||||||
|
:color="filterParams.with_original_language == key ? 'primary' : ''"
|
||||||
|
filter
|
||||||
|
tile
|
||||||
|
:value="key"
|
||||||
|
v-for="(value, key) in tmdbLanguageDict"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
{{ value }}
|
||||||
|
</VChip>
|
||||||
|
</VChipGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<MediaCardListView :apipath="`tmdb/${type}`" :params="filterParams" />
|
<MediaCardListView :key="currentKey" :apipath="`tmdb/${type}`" :params="filterParams" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user