feat:重构推荐页面,添加推荐数据源接口并更新路由和视图

This commit is contained in:
jxxghp
2025-02-08 21:47:57 +08:00
parent 52843dcf97
commit 5def9d5f81
10 changed files with 210 additions and 87 deletions

View File

@@ -1244,3 +1244,11 @@ export interface DiscoverSource {
// 过滤参数UI配置
filter_ui: RenderProps[]
}
// 推荐的数据源
export interface RecommendSource {
// 数据源名称
name: string
// 媒体数据源API地址
api_path: string
}

View File

@@ -10,7 +10,7 @@ const route = useRoute()
const activeState = computed(() => {
return {
home: route.path === '/dashboard',
ranking: route.path === '/ranking',
recommend: route.path === '/recommend',
movie: route.path === '/subscribe/movie',
tv: route.path === '/subscribe/tv',
apps: route.path === '/apps',
@@ -31,8 +31,8 @@ const activeState = computed(() => {
<VIcon v-if="activeState.home" size="28">mdi-home</VIcon>
<VIcon v-else size="28">mdi-home-outline</VIcon>
</VBtn>
<VBtn to="/ranking" :ripple="false">
<VIcon v-if="activeState.ranking" size="28">mdi-star</VIcon>
<VBtn to="/recommend" :ripple="false">
<VIcon v-if="activeState.recommend" size="28">mdi-star</VIcon>
<VIcon v-else size="28">mdi-star-outline</VIcon>
</VBtn>
<VBtn to="/subscribe/movie" :ripple="false">

View File

@@ -1,77 +0,0 @@
<script setup lang="ts">
import MediaCardSlideView from '@/views/discover/MediaCardSlideView.vue'
const viewList = reactive<{ apipath: string; linkurl: string; title: string }[]>([
{
apipath: 'tmdb/trending',
linkurl: '/browse/tmdb/trending?title=流行趋势',
title: '流行趋势',
},
{
apipath: 'douban/showing',
linkurl: '/browse/douban/showing?title=正在热映',
title: '正在热映',
},
{
apipath: 'bangumi/calendar',
linkurl: '/browse/bangumi/calendar?title=Bangumi每日放送',
title: 'Bangumi每日放送',
},
{
apipath: 'tmdb/movies',
linkurl: '/browse/tmdb/movies?title=TMDB热门电影',
title: 'TMDB热门电影',
},
{
apipath: 'tmdb/tvs?with_original_language=zh|en|ja|ko',
linkurl: '/browse/tmdb/tvs??with_original_language=zh|en|ja|ko&title=TMDB热门电视剧',
title: 'TMDB热门电视剧',
},
{
apipath: 'douban/movie_hot',
linkurl: '/browse/douban/movie_hot?title=豆瓣热门电影',
title: '豆瓣热门电影',
},
{
apipath: 'douban/tv_hot',
linkurl: '/browse/douban/tv_hot?title=豆瓣热门电视剧',
title: '豆瓣热门电视剧',
},
{
apipath: 'douban/tv_animation',
linkurl: '/browse/douban/tv_animation?title=豆瓣热门动漫',
title: '豆瓣热门动漫',
},
{
apipath: 'douban/movies',
linkurl: '/browse/douban/movies?title=豆瓣最新电影',
title: '豆瓣最新电影',
},
{
apipath: 'douban/tvs',
linkurl: '/browse/douban/tvs?title=豆瓣最新电视剧',
title: '豆瓣最新电视剧',
},
{
apipath: 'douban/movie_top250',
linkurl: '/browse/douban/movie_top250?title=电影TOP250',
title: '豆瓣电影TOP250',
},
{
apipath: 'douban/tv_weekly_chinese',
linkurl: '/browse/douban/tv_weekly_chinese?title=豆瓣国产剧集榜',
title: '豆瓣国产剧集榜',
},
{
apipath: 'douban/tv_weekly_global',
linkurl: '/browse/douban/tv_weekly_global?title=豆瓣全球剧集榜',
title: '豆瓣全球剧集榜',
},
])
</script>
<template>
<div>
<MediaCardSlideView v-for="(item, index) in viewList" :key="index" v-bind="item" />
</div>
</template>

192
src/pages/recommend.vue Normal file
View File

@@ -0,0 +1,192 @@
<script setup lang="ts">
import api from '@/api'
import { RecommendSource } from '@/api/types'
import MediaCardSlideView from '@/views/discover/MediaCardSlideView.vue'
import { useDisplay } from 'vuetify'
// APP
const display = useDisplay()
const appMode = inject('pwaMode') && display.mdAndDown.value
const viewList = reactive<{ apipath: string; linkurl: string; title: string }[]>([
{
apipath: 'recommend/tmdb_trending',
linkurl: '/browse/recommend/tmdb_trending?title=流行趋势',
title: '流行趋势',
},
{
apipath: 'recommend/douban_showing',
linkurl: '/browse/recommend/douban_showing?title=正在热映',
title: '正在热映',
},
{
apipath: 'bangumi/calendar',
linkurl: '/browse/bangumi/calendar?title=Bangumi每日放送',
title: 'Bangumi每日放送',
},
{
apipath: 'recommend/tmdb_movies',
linkurl: '/browse/recommend/tmdb_movies?title=TMDB热门电影',
title: 'TMDB热门电影',
},
{
apipath: 'recommend/tmdb_tvs?with_original_language=zh|en|ja|ko',
linkurl: '/browse/recommend/tmdb_tvs??with_original_language=zh|en|ja|ko&title=TMDB热门电视剧',
title: 'TMDB热门电视剧',
},
{
apipath: 'recommend/douban_movie_hot',
linkurl: '/browse/recommend/douban_movie_hot?title=豆瓣热门电影',
title: '豆瓣热门电影',
},
{
apipath: 'recommend/douban_tv_hot',
linkurl: '/browse/recommend/douban_tv_hot?title=豆瓣热门电视剧',
title: '豆瓣热门电视剧',
},
{
apipath: 'recommend/douban_tv_animation',
linkurl: '/browse/recommend/douban_tv_animation?title=豆瓣热门动漫',
title: '豆瓣热门动漫',
},
{
apipath: 'recommend/douban_movies',
linkurl: '/browse/recommend/douban_movies?title=豆瓣最新电影',
title: '豆瓣最新电影',
},
{
apipath: 'recommend/douban_tvs',
linkurl: '/browse/recommend/douban_tvs?title=豆瓣最新电视剧',
title: '豆瓣最新电视剧',
},
{
apipath: 'recommend/douban_movie_top250',
linkurl: '/browse/recommend/douban_movie_top250?title=电影TOP250',
title: '豆瓣电影TOP250',
},
{
apipath: 'recommend/douban_tv_weekly_chinese',
linkurl: '/browse/recommend/douban_tv_weekly_chinese?title=豆瓣国产剧集榜',
title: '豆瓣国产剧集榜',
},
{
apipath: 'recommend/douban_tv_weekly_global',
linkurl: '/browse/recommend/douban_tv_weekly_global?title=豆瓣全球剧集榜',
title: '豆瓣全球剧集榜',
},
])
// 计算启用的视图
const enabledViews = computed(() => viewList.filter(item => enableConfig.value[item.title]))
// 榜单启用配置, 以title为key
const enableConfig = ref<{ [key: string]: boolean }>({
...Object.fromEntries(viewList.map(item => [item.title, true])),
})
// 弹窗
const dialog = ref(false)
// 额外的数据源
const extraRecommendSources = ref<RecommendSource[]>([])
// 加载额外的发现数据源
async function loadExtraRecommendSources() {
try {
extraRecommendSources.value = await api.get('recommend/source')
if (extraRecommendSources.value.length > 0) {
viewList.push(
...extraRecommendSources.value.map(source => ({
apipath: source.api_path,
linkurl: `/browse/recommend/${source.api_path}?title=${source.name}`,
title: source.name,
})),
)
}
} catch (error) {
console.log(error)
}
}
// 加载面板配置
async function loadConfig() {
// 显示配置
const local_enable = localStorage.getItem('MP_RECOMMEND')
if (local_enable) {
enableConfig.value = JSON.parse(local_enable)
} else {
const response = await api.get('/user/config/Recommend')
if (response && response.data && response.data.value) {
enableConfig.value = response.data.value
localStorage.setItem('MP_RECOMMEND', JSON.stringify(response.data.value))
}
}
}
// 设置项目
async function saveConfig() {
// 启用配置
const enableString = JSON.stringify(enableConfig.value)
localStorage.setItem('MP_RECOMMEND', enableString)
// 保存到服务端
try {
await api.post('/user/config/Recommend', enableConfig.value)
} catch (error) {
console.error(error)
}
dialog.value = false
}
onBeforeMount(async () => {
await loadConfig()
})
onMounted(async () => {
await loadExtraRecommendSources()
})
</script>
<template>
<div>
<MediaCardSlideView v-for="(item, index) in enabledViews" :key="index" v-bind="item" />
<!-- 弹窗根据配置生成选项 -->
<VDialog v-if="dialog" v-model="dialog" max-width="35rem" scrollable>
<VCard>
<VCardItem>
<VCardTitle>设置推荐榜单</VCardTitle>
</VCardItem>
<VDivider />
<VCardText>
<VRow>
<VCol v-for="item in viewList" :key="item.title" cols="6" md="4" sm="4">
<VCheckbox v-model="enableConfig[item.title]" :label="item.title" />
</VCol>
</VRow>
</VCardText>
<VDivider />
<VCardText class="pt-5 text-end">
<VSpacer />
<VBtn variant="outlined" color="secondary" class="me-4" @click="dialog = false"> 关闭 </VBtn>
<VBtn @click="saveConfig">
<template #prepend>
<VIcon icon="mdi-content-save" />
</template>
保存
</VBtn>
</VCardText>
</VCard>
</VDialog>
</div>
<!-- 底部操作按钮 -->
<VFab
icon="mdi-text-box-edit"
location="bottom"
size="x-large"
fixed
app
appear
@click="dialog = true"
:class="{ 'mb-12': appMode }"
/>
</template>

View File

@@ -28,8 +28,8 @@ const router = createRouter({
},
},
{
path: '/ranking',
component: () => import('../pages/ranking.vue'),
path: '/recommend',
component: () => import('../pages/recommend.vue'),
meta: {
keepAlive: true,
requiresAuth: true,

View File

@@ -18,7 +18,7 @@ export const SystemNavMenus = [
{
title: '推荐',
icon: 'mdi-star-outline',
to: '/ranking',
to: '/recommend',
header: '发现',
admin: false,
footer: true,

View File

@@ -102,6 +102,6 @@ watch([filterParams], () => {
</div>
</div>
<div>
<MediaCardListView :key="currentKey" apipath="bangumi/subjects" :params="filterParams" />
<MediaCardListView :key="currentKey" apipath="discover/bangumi" :params="filterParams" />
</div>
</template>

View File

@@ -197,6 +197,6 @@ watch([type, filterParams], () => {
</div>
</div>
<div>
<MediaCardListView :key="currentKey" :apipath="`douban/${type}`" :params="filterParams" />
<MediaCardListView :key="currentKey" :apipath="`discover/douban_${type}`" :params="filterParams" />
</div>
</template>

View File

@@ -173,6 +173,6 @@ watch([type, filterParams], () => {
</div>
<div>
<MediaCardListView :key="currentKey" :apipath="`tmdb/${type}`" :params="filterParams" />
<MediaCardListView :key="currentKey" :apipath="`discover/tmdb_${type}`" :params="filterParams" />
</div>
</template>

View File

@@ -101,7 +101,7 @@ export default defineConfig({
'shortcuts': [
{
'name': '推荐',
'url': './ranking',
'url': './recommend',
'icons': [
{
'src': './sparkles-icon-192x192.png',