mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-04 15:10:56 +08:00
feat: 音乐搜索与识别前端联动统一
- 音乐搜索从 music/search 切换为 media/search?type=music - MediaInfoCard 支持音乐卡片(方形封面、时长、音乐实体类型、详情外链) - 订阅与资源选择器适配音乐元数据 - 同步中英繁语言包
This commit is contained in:
@@ -11,6 +11,11 @@ const props = defineProps({
|
||||
// 音乐元数据使用 title,影视元数据使用 name,识别结果卡片统一兼容两种字段。
|
||||
const recognizedName = computed(() => props.context?.meta_info?.name || props.context?.meta_info?.title)
|
||||
|
||||
// 是否为音乐识别结果
|
||||
const isMusic = computed(
|
||||
() => props.context?.media_info?.type === '音乐' || props.context?.meta_info?.type === '音乐',
|
||||
)
|
||||
|
||||
// TMDB图片转换为w500大小
|
||||
function getW500Image(url = '') {
|
||||
if (!url) return ''
|
||||
@@ -24,6 +29,27 @@ function openTmdbPage(type: string, tmdbId: number) {
|
||||
const url = `https://www.themoviedb.org/${type === '电影' ? 'movie' : 'tv'}/${tmdbId}`
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
// 秒数格式化为 m:ss 时长文本
|
||||
function formatDuration(seconds: number | undefined) {
|
||||
if (!seconds || seconds <= 0) return ''
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const rest = seconds % 60
|
||||
return `${minutes}:${rest.toString().padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// 音乐封面优先使用方形封面,仅 W500 图片处理对TMDB类 URL 生效
|
||||
const musicCover = computed(() => getW500Image(
|
||||
props.context?.media_info?.cover_url || props.context?.media_info?.poster_path || '',
|
||||
))
|
||||
|
||||
// 音乐详情外链
|
||||
const musicLink = computed(() => props.context?.media_info?.detail_link || '')
|
||||
|
||||
// 打开音乐详情外链
|
||||
function openMusicDetail() {
|
||||
if (musicLink.value) window.open(musicLink.value, '_blank')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -33,7 +59,22 @@ function openTmdbPage(type: string, tmdbId: number) {
|
||||
v-if="recognizedName"
|
||||
class="d-flex justify-space-between flex-wrap flex-md-nowrap flex-column flex-md-row"
|
||||
>
|
||||
<div v-if="context?.media_info?.poster_path" class="ma-auto">
|
||||
<div v-if="isMusic && musicCover" class="ma-auto">
|
||||
<VImg
|
||||
width="10rem"
|
||||
aspect-ratio="1"
|
||||
class="object-cover rounded-lg ring-1 ring-gray-500"
|
||||
:src="musicCover"
|
||||
cover
|
||||
>
|
||||
<template #placeholder>
|
||||
<div class="w-full h-full">
|
||||
<VSkeletonLoader class="object-cover" />
|
||||
</div>
|
||||
</template>
|
||||
</VImg>
|
||||
</div>
|
||||
<div v-else-if="context?.media_info?.poster_path" class="ma-auto">
|
||||
<VImg
|
||||
width="10rem"
|
||||
aspect-ratio="2/3"
|
||||
@@ -77,46 +118,133 @@ function openTmdbPage(type: string, tmdbId: number) {
|
||||
>
|
||||
{{ context?.media_info?.type || context?.meta_info?.type }}
|
||||
</VChip>
|
||||
<!-- 音乐实体类型 -->
|
||||
<VChip
|
||||
v-if="isMusic && context?.media_info?.music_type"
|
||||
variant="elevated"
|
||||
class="me-1 mb-1 text-white bg-blue-500"
|
||||
>
|
||||
{{ context?.media_info?.music_type }}
|
||||
</VChip>
|
||||
<!-- 艺术家 -->
|
||||
<VChip v-if="context?.media_info?.artist" variant="elevated" class="me-1 mb-1 text-white bg-purple-500">
|
||||
{{ context?.media_info?.artist }}
|
||||
</VChip>
|
||||
<!-- 专辑 -->
|
||||
<VChip v-if="context?.media_info?.album" variant="elevated" class="me-1 mb-1 text-white bg-purple-500">
|
||||
{{ context?.media_info?.album }}
|
||||
</VChip>
|
||||
<!-- 专辑艺术家 -->
|
||||
<VChip
|
||||
v-if="context?.media_info?.album_artist"
|
||||
variant="elevated"
|
||||
class="me-1 mb-1 text-white bg-purple-500"
|
||||
>
|
||||
{{ context?.media_info?.album_artist }}
|
||||
</VChip>
|
||||
<!-- 发行日期 -->
|
||||
<VChip
|
||||
v-if="context?.media_info?.release_date"
|
||||
variant="elevated"
|
||||
class="me-1 mb-1 text-white bg-purple-500"
|
||||
>
|
||||
{{ context?.media_info?.release_date }}
|
||||
</VChip>
|
||||
<!-- 风格 -->
|
||||
<VChip
|
||||
v-for="genre in context?.media_info?.genres"
|
||||
:key="genre"
|
||||
variant="elevated"
|
||||
class="me-1 mb-1 text-white bg-purple-500"
|
||||
>
|
||||
{{ genre }}
|
||||
</VChip>
|
||||
<!-- 音频技术参数 -->
|
||||
<VChip
|
||||
v-if="context?.meta_info?.audio_format"
|
||||
variant="elevated"
|
||||
class="me-1 mb-1 text-white bg-orange-500"
|
||||
>
|
||||
{{ context?.meta_info?.audio_format }}
|
||||
</VChip>
|
||||
<VChip
|
||||
v-if="context?.meta_info?.bit_depth || context?.meta_info?.sample_rate"
|
||||
variant="elevated"
|
||||
class="me-1 mb-1 text-white bg-orange-500"
|
||||
>
|
||||
{{ [context?.meta_info?.bit_depth, context?.meta_info?.sample_rate].filter(Boolean).join(' kHz ') }}
|
||||
</VChip>
|
||||
<!-- 时长 -->
|
||||
<VChip
|
||||
v-if="formatDuration(context?.media_info?.duration)"
|
||||
variant="elevated"
|
||||
class="me-1 mb-1 text-white bg-orange-500"
|
||||
>
|
||||
{{ formatDuration(context?.media_info?.duration) }}
|
||||
</VChip>
|
||||
<!-- 曲目信息 -->
|
||||
<VChip
|
||||
v-if="context?.media_info?.track_number"
|
||||
variant="elevated"
|
||||
class="me-1 mb-1 text-white bg-red-500"
|
||||
>
|
||||
{{ `曲目 ${context?.media_info?.track_number}${context?.media_info?.total_tracks ? ` / ${context?.media_info?.total_tracks}` : ''}` }}
|
||||
</VChip>
|
||||
<!-- ISRC -->
|
||||
<VChip v-if="context?.media_info?.isrc" variant="elevated" class="me-1 mb-1 text-white bg-red-500">
|
||||
{{ context?.media_info?.isrc }}
|
||||
</VChip>
|
||||
<!-- MusicBrainz 外链 -->
|
||||
<VChip
|
||||
v-if="musicLink"
|
||||
variant="elevated"
|
||||
class="me-1 mb-1 text-white bg-green-500"
|
||||
@click="openMusicDetail"
|
||||
>
|
||||
详情
|
||||
</VChip>
|
||||
<!-- 二级分类 -->
|
||||
<VChip v-if="context?.media_info?.category" variant="elevated" class="me-1 mb-1 text-white bg-blue-500">
|
||||
<VChip v-if="!isMusic && context?.media_info?.category" variant="elevated" class="me-1 mb-1 text-white bg-blue-500">
|
||||
{{ context?.media_info?.category }}
|
||||
</VChip>
|
||||
<!-- TMDBID -->
|
||||
<VChip
|
||||
v-if="context?.media_info?.tmdb_id"
|
||||
v-if="!isMusic && context?.media_info?.tmdb_id"
|
||||
variant="elevated"
|
||||
class="me-1 mb-1 text-white bg-green-500"
|
||||
@click="openTmdbPage(context?.media_info?.type || '', context?.media_info?.tmdb_id)"
|
||||
>
|
||||
{{ context?.media_info?.tmdb_id }}
|
||||
</VChip>
|
||||
<!-- meta_info -->
|
||||
<VChip v-if="context?.meta_info?.web_source" variant="elevated" class="me-1 mb-1 text-white bg-purple-500">
|
||||
{{ context?.meta_info?.web_source }}
|
||||
</VChip>
|
||||
<VChip v-if="context?.meta_info?.edition" variant="elevated" class="me-1 mb-1 text-white bg-red-500">
|
||||
{{ context?.meta_info?.edition }}
|
||||
</VChip>
|
||||
<VChip v-if="context?.meta_info?.resource_pix" variant="elevated" class="me-1 mb-1 text-white bg-red-500">
|
||||
{{ context?.meta_info?.resource_pix }}
|
||||
</VChip>
|
||||
<VChip
|
||||
v-if="context?.meta_info?.video_encode"
|
||||
variant="elevated"
|
||||
class="me-1 mb-1 text-white bg-orange-500"
|
||||
>
|
||||
{{ context?.meta_info?.video_encode }}
|
||||
</VChip>
|
||||
<VChip
|
||||
v-if="context?.meta_info?.audio_encode"
|
||||
variant="elevated"
|
||||
class="me-1 mb-1 text-white bg-orange-500"
|
||||
>
|
||||
{{ context?.meta_info?.audio_encode }}
|
||||
</VChip>
|
||||
<VChip v-if="context?.meta_info?.resource_team" variant="elevated" class="me-1 mb-1 text-white bg-cyan-500">
|
||||
{{ context?.meta_info?.resource_team }}
|
||||
</VChip>
|
||||
<!-- meta_info(音乐不显示影视资源信息) -->
|
||||
<template v-if="!isMusic">
|
||||
<VChip v-if="context?.meta_info?.web_source" variant="elevated" class="me-1 mb-1 text-white bg-purple-500">
|
||||
{{ context?.meta_info?.web_source }}
|
||||
</VChip>
|
||||
<VChip v-if="context?.meta_info?.edition" variant="elevated" class="me-1 mb-1 text-white bg-red-500">
|
||||
{{ context?.meta_info?.edition }}
|
||||
</VChip>
|
||||
<VChip v-if="context?.meta_info?.resource_pix" variant="elevated" class="me-1 mb-1 text-white bg-red-500">
|
||||
{{ context?.meta_info?.resource_pix }}
|
||||
</VChip>
|
||||
<VChip
|
||||
v-if="context?.meta_info?.video_encode"
|
||||
variant="elevated"
|
||||
class="me-1 mb-1 text-white bg-orange-500"
|
||||
>
|
||||
{{ context?.meta_info?.video_encode }}
|
||||
</VChip>
|
||||
<VChip
|
||||
v-if="context?.meta_info?.audio_encode"
|
||||
variant="elevated"
|
||||
class="me-1 mb-1 text-white bg-orange-500"
|
||||
>
|
||||
{{ context?.meta_info?.audio_encode }}
|
||||
</VChip>
|
||||
<VChip v-if="context?.meta_info?.resource_team" variant="elevated" class="me-1 mb-1 text-white bg-cyan-500">
|
||||
{{ context?.meta_info?.resource_team }}
|
||||
</VChip>
|
||||
</template>
|
||||
</VCardItem>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,4 +271,4 @@ function openTmdbPage(type: string, tmdbId: number) {
|
||||
</VExpansionPanel>
|
||||
</VExpansionPanels>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
@@ -27,9 +27,6 @@ const keyword = ref<string>()
|
||||
// 选择分类
|
||||
const selectCategory = ref<number[]>([])
|
||||
|
||||
// 选择主媒体类型,用于按站点定义的电影、电视剧、音乐分类浏览。
|
||||
const selectMediaType = ref<string>()
|
||||
|
||||
// 全部分类
|
||||
const siteCategoryList = ref<SiteCategory[]>()
|
||||
|
||||
@@ -69,11 +66,8 @@ const categoryOptions = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const mediaTypeOptions = computed(() => [
|
||||
{ title: t('mediaType.movie'), value: '电影' },
|
||||
{ title: t('mediaType.tv'), value: '电视剧' },
|
||||
{ title: t('mediaType.music'), value: '音乐' },
|
||||
])
|
||||
// 站点是否配置了资源分类
|
||||
const hasSiteCategory = computed(() => (siteCategoryList.value?.length ?? 0) > 0)
|
||||
|
||||
// 总条数
|
||||
const resourceTotalItems = computed(() => resourceDataList.value.length)
|
||||
@@ -169,7 +163,6 @@ async function getResourceList() {
|
||||
params: {
|
||||
keyword: keyword.value,
|
||||
cat: selectCategory.value?.join(','),
|
||||
mtype: selectMediaType.value,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -288,20 +281,9 @@ onMounted(() => {
|
||||
clearable
|
||||
prepend-inner-icon="mdi-folder"
|
||||
hide-details
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="2">
|
||||
<VSelect
|
||||
v-model="selectMediaType"
|
||||
:items="mediaTypeOptions"
|
||||
class="site-resource-filter-input"
|
||||
density="compact"
|
||||
variant="solo-filled"
|
||||
flat
|
||||
clearable
|
||||
:label="t('common.type')"
|
||||
prepend-inner-icon="mdi-shape-outline"
|
||||
hide-details
|
||||
:disabled="!hasSiteCategory"
|
||||
:hint="hasSiteCategory ? '' : t('dialog.siteResource.noCategory')"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3" class="d-flex align-center">
|
||||
@@ -369,20 +351,6 @@ onMounted(() => {
|
||||
@keyup.enter="getResourceList"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VSelect
|
||||
v-model="selectMediaType"
|
||||
:items="mediaTypeOptions"
|
||||
class="site-resource-filter-input"
|
||||
density="compact"
|
||||
variant="solo-filled"
|
||||
flat
|
||||
clearable
|
||||
:label="t('common.type')"
|
||||
prepend-inner-icon="mdi-shape-outline"
|
||||
hide-details
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VSelect
|
||||
v-model="selectCategory"
|
||||
@@ -398,6 +366,9 @@ onMounted(() => {
|
||||
clearable
|
||||
prepend-inner-icon="mdi-folder"
|
||||
hide-details
|
||||
:disabled="!hasSiteCategory"
|
||||
:hint="hasSiteCategory ? '' : t('dialog.siteResource.noCategory')"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" class="d-flex gap-2">
|
||||
|
||||
@@ -58,19 +58,15 @@ async function searchMedias() {
|
||||
// 调用API搜索词条
|
||||
try {
|
||||
loading.value = true
|
||||
const result: MediaInfo[] =
|
||||
props.type === 'musicbrainz'
|
||||
? await api.get('music/search', {
|
||||
params: { query: searchKeyword, count: 20 },
|
||||
})
|
||||
: await api.get('media/search', {
|
||||
params: {
|
||||
title: searchKeyword,
|
||||
page: 1,
|
||||
count: 20,
|
||||
source: props.type,
|
||||
},
|
||||
})
|
||||
const result: MediaInfo[] = await api.get('media/search', {
|
||||
params: {
|
||||
title: searchKeyword,
|
||||
type: props.type === 'musicbrainz' ? 'music' : 'media',
|
||||
page: 1,
|
||||
count: 20,
|
||||
source: props.type,
|
||||
},
|
||||
})
|
||||
|
||||
// 清空
|
||||
items.value = []
|
||||
|
||||
Reference in New Issue
Block a user