mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 23:56:42 +08:00
Limit dashboard recommendations to TMDB sources
This commit is contained in:
@@ -1,15 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { MediaInfo, RecommendSource } from '@/api/types'
|
import type { MediaInfo } from '@/api/types'
|
||||||
import { getMediaSubscribeId } from '@/composables/useMediaSubscribe'
|
import { getMediaSubscribeId } from '@/composables/useMediaSubscribe'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { useGlobalSettingsStore } from '@/stores'
|
import { useGlobalSettingsStore } from '@/stores'
|
||||||
import { getDisplayImageUrl } from '@/utils/imageUtils'
|
import { getDisplayImageUrl } from '@/utils/imageUtils'
|
||||||
import {
|
import { createBuiltInRecommendSources, type RecommendViewSource } from '@/utils/recommendSources'
|
||||||
createBuiltInRecommendSources,
|
|
||||||
mergeExtraRecommendSources,
|
|
||||||
type RecommendViewSource,
|
|
||||||
} from '@/utils/recommendSources'
|
|
||||||
import noImage from '@images/no-image.jpeg'
|
import noImage from '@images/no-image.jpeg'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
@@ -19,8 +15,15 @@ const RECOMMEND_SOURCE_STORAGE_KEY = 'MP_DASHBOARD_RECOMMEND_SOURCE'
|
|||||||
const RECOMMEND_SLIDE_COUNT = 5
|
const RECOMMEND_SLIDE_COUNT = 5
|
||||||
const RECOMMEND_AUTOPLAY_INTERVAL = 8000
|
const RECOMMEND_AUTOPLAY_INTERVAL = 8000
|
||||||
|
|
||||||
const sources = ref<RecommendViewSource[]>(createBuiltInRecommendSources(t))
|
const sources = ref<RecommendViewSource[]>(
|
||||||
const selectedSourcePath = ref(localStorage.getItem(RECOMMEND_SOURCE_STORAGE_KEY) || sources.value[0].apipath)
|
createBuiltInRecommendSources(t).filter(source => source.apipath.startsWith('recommend/tmdb_')),
|
||||||
|
)
|
||||||
|
const storedSourcePath = localStorage.getItem(RECOMMEND_SOURCE_STORAGE_KEY)
|
||||||
|
const selectedSourcePath = ref(
|
||||||
|
storedSourcePath && sources.value.some(source => source.apipath === storedSourcePath)
|
||||||
|
? storedSourcePath
|
||||||
|
: sources.value[0].apipath,
|
||||||
|
)
|
||||||
const mediaItems = shallowRef<MediaInfo[]>([])
|
const mediaItems = shallowRef<MediaInfo[]>([])
|
||||||
const mediaCache = new Map<string, MediaInfo[]>()
|
const mediaCache = new Map<string, MediaInfo[]>()
|
||||||
const activeIndex = ref(0)
|
const activeIndex = ref(0)
|
||||||
@@ -37,14 +40,6 @@ const selectedSource = computed(
|
|||||||
|
|
||||||
const activeMedia = computed(() => mediaItems.value[activeIndex.value])
|
const activeMedia = computed(() => mediaItems.value[activeIndex.value])
|
||||||
|
|
||||||
/** 返回与媒体来源匹配的 Material Design 图标。 */
|
|
||||||
function getSourceIcon(source: RecommendViewSource) {
|
|
||||||
if (source.apipath.includes('bangumi')) return 'mdi-television-play'
|
|
||||||
if (source.apipath.includes('douban')) return 'mdi-star-circle-outline'
|
|
||||||
if (source.apipath.includes('tmdb')) return 'mdi-movie-open-star-outline'
|
|
||||||
return 'mdi-compass-outline'
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 将不同接口包装格式归一化为媒体数组。 */
|
/** 将不同接口包装格式归一化为媒体数组。 */
|
||||||
function normalizeMediaResponse(response: unknown): MediaInfo[] {
|
function normalizeMediaResponse(response: unknown): MediaInfo[] {
|
||||||
if (Array.isArray(response)) return response
|
if (Array.isArray(response)) return response
|
||||||
@@ -60,17 +55,11 @@ function normalizeMediaResponse(response: unknown): MediaInfo[] {
|
|||||||
|
|
||||||
/** 判断媒体是否具备可展示图片和可进入详情页的标识。 */
|
/** 判断媒体是否具备可展示图片和可进入详情页的标识。 */
|
||||||
function isUsableMedia(item: MediaInfo) {
|
function isUsableMedia(item: MediaInfo) {
|
||||||
const hasMediaId = Boolean(
|
const hasMediaId = Boolean(item.tmdb_id || item.collection_id)
|
||||||
item.tmdb_id ||
|
|
||||||
item.douban_id ||
|
|
||||||
item.bangumi_id ||
|
|
||||||
item.collection_id ||
|
|
||||||
(item.mediaid_prefix && item.media_id),
|
|
||||||
)
|
|
||||||
return Boolean(item.title && (item.backdrop_path || item.poster_path) && hasMediaId)
|
return Boolean(item.title && (item.backdrop_path || item.poster_path) && hasMediaId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 构造轮播项稳定键,兼容合集与扩展媒体来源。 */
|
/** 构造轮播项稳定键,兼容 TMDB 媒体与合集。 */
|
||||||
function getMediaKey(item: MediaInfo) {
|
function getMediaKey(item: MediaInfo) {
|
||||||
if (item.collection_id) return `collection:${item.collection_id}`
|
if (item.collection_id) return `collection:${item.collection_id}`
|
||||||
return getMediaSubscribeId(item)
|
return getMediaSubscribeId(item)
|
||||||
@@ -108,16 +97,6 @@ async function loadMedia(sourcePath = selectedSourcePath.value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 加载后端扩展的推荐来源。 */
|
|
||||||
async function loadExtraSources() {
|
|
||||||
try {
|
|
||||||
const extraSources: RecommendSource[] = (await api.get('recommend/source')) ?? []
|
|
||||||
mergeExtraRecommendSources(sources.value, extraSources)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 切换当前推荐来源并持久化用户选择。 */
|
/** 切换当前推荐来源并持久化用户选择。 */
|
||||||
function selectSource(source: RecommendViewSource) {
|
function selectSource(source: RecommendViewSource) {
|
||||||
if (selectedSourcePath.value === source.apipath) return
|
if (selectedSourcePath.value === source.apipath) return
|
||||||
@@ -129,7 +108,7 @@ function selectSource(source: RecommendViewSource) {
|
|||||||
|
|
||||||
/** 返回经过全局图片缓存与代理设置处理的背景图地址。 */
|
/** 返回经过全局图片缓存与代理设置处理的背景图地址。 */
|
||||||
function getBackdropUrl(item: MediaInfo) {
|
function getBackdropUrl(item: MediaInfo) {
|
||||||
const sourceUrl = (item.backdrop_path || item.poster_path || noImage).replace('original', 'w1280')
|
const sourceUrl = item.backdrop_path || item.poster_path || noImage
|
||||||
return getDisplayImageUrl(sourceUrl, globalSettingsStore.globalSettings.GLOBAL_IMAGE_CACHE)
|
return getDisplayImageUrl(sourceUrl, globalSettingsStore.globalSettings.GLOBAL_IMAGE_CACHE)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,10 +189,8 @@ function stopAutoplay() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([loadExtraSources(), loadMedia()])
|
localStorage.setItem(RECOMMEND_SOURCE_STORAGE_KEY, selectedSourcePath.value)
|
||||||
if (!sources.value.some(source => source.apipath === selectedSourcePath.value)) {
|
await loadMedia()
|
||||||
selectSource(sources.value[0])
|
|
||||||
}
|
|
||||||
startAutoplay()
|
startAutoplay()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -269,7 +246,7 @@ onBeforeUnmount(stopAutoplay)
|
|||||||
rounded="pill"
|
rounded="pill"
|
||||||
append-icon="mdi-chevron-down"
|
append-icon="mdi-chevron-down"
|
||||||
>
|
>
|
||||||
<VIcon :icon="getSourceIcon(selectedSource)" color="primary" start />
|
<VIcon icon="mdi-movie-open-star-outline" color="primary" start />
|
||||||
<span>{{ selectedSource.title }}</span>
|
<span>{{ selectedSource.title }}</span>
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</template>
|
</template>
|
||||||
@@ -278,7 +255,7 @@ onBeforeUnmount(stopAutoplay)
|
|||||||
v-for="source in sources"
|
v-for="source in sources"
|
||||||
:key="source.apipath"
|
:key="source.apipath"
|
||||||
:active="source.apipath === selectedSourcePath"
|
:active="source.apipath === selectedSourcePath"
|
||||||
:prepend-icon="getSourceIcon(source)"
|
prepend-icon="mdi-movie-open-star-outline"
|
||||||
:title="source.title"
|
:title="source.title"
|
||||||
@click="selectSource(source)"
|
@click="selectSource(source)"
|
||||||
/>
|
/>
|
||||||
@@ -352,6 +329,8 @@ onBeforeUnmount(stopAutoplay)
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* stylelint-disable selector-pseudo-class-no-unknown */
|
||||||
|
|
||||||
.dashboard-recommend {
|
.dashboard-recommend {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
Reference in New Issue
Block a user