diff --git a/package.json b/package.json index 138dfdff..a352c0d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moviepilot", - "version": "2.15.0", + "version": "2.15.1", "private": true, "type": "module", "bin": "dist/service.js", diff --git a/src/components/dialog/SubscribeSeasonDialog.vue b/src/components/dialog/SubscribeSeasonDialog.vue index 52604e04..aaa9d188 100644 --- a/src/components/dialog/SubscribeSeasonDialog.vue +++ b/src/components/dialog/SubscribeSeasonDialog.vue @@ -2,10 +2,12 @@ import api from '@/api' import { MediaInfo, MediaSeason, NotExistMediaInfo } from '@/api/types' import { PropType } from 'vue' +import noImage from '@images/no-image.jpeg' import NoDataFound from '@/components/states/NoDataFound.vue' import { useI18n } from 'vue-i18n' import { useGlobalSettingsStore } from '@/stores' import type { SeasonSubscribeModes, SubscribeMode } from '@/composables/useMediaSubscribe' +import { getDisplayImageUrl } from '@/utils/imageUtils' import { useDisplay } from 'vuetify' type SubscribeModeOption = { @@ -22,7 +24,7 @@ type EpisodeGroupOption = { } // 国际化 -const { t } = useI18n() +const { locale, t } = useI18n() const { mdAndUp } = useDisplay() // 定义事件 @@ -264,24 +266,60 @@ function getExistText(season: number) { else return t('dialog.subscribeSeason.status.exists') } -// 拼装季图片地址 -function getSeasonPoster(posterPath: string) { - if (!posterPath) return props.media?.poster_path - return `https://${globalSettings.TMDB_IMAGE_DOMAIN}/t/p/w500${posterPath}` +// 获取季海报地址,数据源未提供季海报时使用主海报。 +function getSeasonPoster(posterPath?: string) { + const resolvedPosterPath = posterPath?.trim() || props.media?.poster_path?.trim() + if (!resolvedPosterPath) return noImage + + const posterUrl = resolvedPosterPath.startsWith('/') + ? `https://${globalSettings.TMDB_IMAGE_DOMAIN}/t/p/w500${resolvedPosterPath}` + : resolvedPosterPath.replace('/t/p/original/', '/t/p/w500/') + + return getDisplayImageUrl(posterUrl, globalSettings.GLOBAL_IMAGE_CACHE) } -// 将yyyy-mm-dd转换为yyyy年mm月dd日 +// 按当前界面语言格式化数据源返回的首播日期。 function formatAirDate(airDate: string) { if (!airDate) return '' - const date = new Date(airDate.replaceAll(/-/g, '/')) - return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日` + const dateParts = /^(\d{4})-(\d{1,2})-(\d{1,2})/.exec(airDate) + if (!dateParts) return airDate + + const date = new Date(Number(dateParts[1]), Number(dateParts[2]) - 1, Number(dateParts[3])) + if (Number.isNaN(date.getTime())) return airDate + + return new Intl.DateTimeFormat(locale.value, { + day: 'numeric', + month: 'long', + year: 'numeric', + }).format(date) } -// 从yyyy-mm-dd中提取年份 -function getYear(airDate: string) { - if (!airDate) return '' - const date = new Date(airDate.replaceAll(/-/g, '/')) - return date.getFullYear() +// 获取本地化季号标题,确保数据源未返回名称时仍有稳定文本。 +function getSeasonTitle(item: MediaSeason) { + return t('dialog.subscribeSeason.seasonNumber', { number: item.season_number ?? 0 }) +} + +// 获取数据源提供的非重复季名称,保留特别篇等来源语义。 +function getSeasonName(item: MediaSeason) { + const name = item.name?.trim() + if (!name || item.season_number === undefined) return '' + + const number = item.season_number + const normalizedName = name.toLocaleLowerCase().replaceAll(/\s/g, '') + const genericNames = new Set([ + `第${number}季`, + `season${number}`, + `season${String(number).padStart(2, '0')}`, + `s${number}`, + `s${String(number).padStart(2, '0')}`, + ]) + + return genericNames.has(normalizedName) ? '' : name +} + +// 获取同时包含季号和来源季名称的海报替代文本。 +function getSeasonPosterAlt(item: MediaSeason) { + return [getSeasonTitle(item), getSeasonName(item)].filter(Boolean).join(' - ') } // 切换当前剧集组并清空上一组的派生数据。 @@ -518,19 +556,20 @@ onBeforeUnmount(() => {