mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-07-04 22:11:29 +08:00
Refine episode group subscriptions and mobile carousel
This commit is contained in:
@@ -32,6 +32,7 @@ const emit = defineEmits(['subscribe', 'close'])
|
||||
const props = defineProps({
|
||||
media: Object as PropType<MediaInfo>,
|
||||
selectedSeason: Number,
|
||||
initialEpisodeGroup: String,
|
||||
subscribedSeasons: Array as PropType<number[]>,
|
||||
subscribedSeasonModes: Object as PropType<SeasonSubscribeModes>,
|
||||
defaultSubscribeMode: String as PropType<SubscribeMode>,
|
||||
@@ -64,7 +65,7 @@ const isRefreshed = ref(false)
|
||||
const episodeGroups = ref<{ [key: string]: any }[]>([])
|
||||
|
||||
// 当前选择剧集组
|
||||
const episodeGroup = ref('')
|
||||
const episodeGroup = ref(props.initialEpisodeGroup ?? '')
|
||||
|
||||
const subscribeModeOptions = computed<SubscribeModeOption[]>(() => [
|
||||
{
|
||||
@@ -84,12 +85,14 @@ const subscribeModeOptions = computed<SubscribeModeOption[]>(() => [
|
||||
},
|
||||
])
|
||||
|
||||
// 获取订阅模式的主题色。
|
||||
function getSubscribeModeColor(mode: SubscribeMode) {
|
||||
if (mode === 'normal') return 'primary'
|
||||
if (mode === 'best_version') return 'warning'
|
||||
return 'success'
|
||||
}
|
||||
|
||||
// 校验弹窗输入是否为支持的订阅模式。
|
||||
function isSubscribeMode(value: unknown): value is SubscribeMode {
|
||||
return value === 'normal' || value === 'best_version' || value === 'best_version_full'
|
||||
}
|
||||
@@ -265,6 +268,7 @@ function getYear(airDate: string) {
|
||||
return date.getFullYear()
|
||||
}
|
||||
|
||||
// 切换当前剧集组并清空上一组的派生数据。
|
||||
function setEpisodeGroup(value: string) {
|
||||
if (episodeGroup.value === value) return
|
||||
|
||||
@@ -273,6 +277,7 @@ function setEpisodeGroup(value: string) {
|
||||
episodeGroup.value = value
|
||||
}
|
||||
|
||||
// 提交当前剧集组下选中的季及其订阅模式。
|
||||
function subscribeSeasons() {
|
||||
const selectedSeasons = seasonInfos.value.filter(item => {
|
||||
const seasonNumber = item.season_number ?? null
|
||||
@@ -289,6 +294,7 @@ function subscribeSeasons() {
|
||||
)
|
||||
}
|
||||
|
||||
// 写入指定季的订阅模式。
|
||||
function setSeasonMode(season: number, mode: SubscribeMode) {
|
||||
seasonModes.value = {
|
||||
...seasonModes.value,
|
||||
@@ -296,22 +302,26 @@ function setSeasonMode(season: number, mode: SubscribeMode) {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理用户手动切换指定季的订阅模式。
|
||||
function updateSeasonMode(season: number, mode: unknown) {
|
||||
if (!isSubscribeMode(mode)) return
|
||||
manuallySelectedModeSeasons.value.add(season)
|
||||
setSeasonMode(season, mode)
|
||||
}
|
||||
|
||||
// 根据入库状态和系统配置计算指定季的默认订阅模式。
|
||||
function getDefaultSeasonMode(season: number) {
|
||||
if (!seasonsNotExisted.value[season]) return 'best_version_full'
|
||||
|
||||
return props.defaultSubscribeMode ?? 'normal'
|
||||
}
|
||||
|
||||
// 确保指定季已初始化订阅模式。
|
||||
function ensureSeasonMode(season: number) {
|
||||
if (!seasonModes.value[season]) setSeasonMode(season, props.subscribedSeasonModes?.[season] ?? getDefaultSeasonMode(season))
|
||||
}
|
||||
|
||||
// 在入库状态刷新后同步尚未手动修改的默认模式。
|
||||
function syncDefaultSeasonModes() {
|
||||
seasonsSelected.value.forEach(season => {
|
||||
if (subscribedSeasonSet.value.has(season) || manuallySelectedModeSeasons.value.has(season)) return
|
||||
@@ -319,14 +329,17 @@ function syncDefaultSeasonModes() {
|
||||
})
|
||||
}
|
||||
|
||||
// 判断指定季是否已有订阅。
|
||||
function isSeasonSubscribed(season: number) {
|
||||
return subscribedSeasonSet.value.has(season)
|
||||
}
|
||||
|
||||
// 判断指定季是否在本次提交选择中。
|
||||
function isSeasonSelected(season: number) {
|
||||
return selectedSeasonSet.value.has(season)
|
||||
}
|
||||
|
||||
// 设置指定季的选择状态并同步其订阅模式。
|
||||
function setSeasonSelected(season: number, selected: boolean | null) {
|
||||
const nextSeasons = new Set(seasonsSelected.value)
|
||||
if (selected) {
|
||||
@@ -338,10 +351,12 @@ function setSeasonSelected(season: number, selected: boolean | null) {
|
||||
seasonsSelected.value = [...nextSeasons].sort((a, b) => a - b)
|
||||
}
|
||||
|
||||
// 切换指定季的选择状态。
|
||||
function toggleSeasonSelected(season: number) {
|
||||
setSeasonSelected(season, !isSeasonSelected(season))
|
||||
}
|
||||
|
||||
// 将入口预选季和已有订阅同步到当前剧集组的可见季列表。
|
||||
function syncSelectedSeason() {
|
||||
if (!seasonInfos.value.length) return
|
||||
|
||||
@@ -381,7 +396,8 @@ watch(() => props.subscribedSeasons, syncSelectedSeason)
|
||||
watch(() => props.subscribedSeasonModes, syncSelectedSeason)
|
||||
|
||||
onMounted(async () => {
|
||||
getMediaSeasons()
|
||||
// 自定义剧集组由 watchEffect 首次加载,避免默认季数据异步覆盖它。
|
||||
if (!episodeGroup.value) getMediaSeasons()
|
||||
getEpisodeGroups()
|
||||
checkSeasonsNotExists()
|
||||
})
|
||||
|
||||
@@ -49,6 +49,7 @@ const SubscribeSeasonDialog = defineAsyncComponent(() => import('@/components/di
|
||||
|
||||
export type SeasonSubscribeModes = Record<number, SubscribeMode>
|
||||
|
||||
// 生成跨媒体源稳定的订阅媒体标识。
|
||||
export function getMediaSubscribeId(media?: MediaInfo) {
|
||||
if (media?.tmdb_id) return `tmdb:${media.tmdb_id}`
|
||||
if (media?.douban_id) return `douban:${media.douban_id}`
|
||||
@@ -56,6 +57,7 @@ export function getMediaSubscribeId(media?: MediaInfo) {
|
||||
return `${media?.mediaid_prefix}:${media?.media_id}`
|
||||
}
|
||||
|
||||
// 将订阅模式转换为后端订阅字段。
|
||||
function getSubscribePayload(mode: SubscribeMode): SubscribePayload {
|
||||
return {
|
||||
best_version: mode === 'normal' ? 0 : 1,
|
||||
@@ -63,16 +65,19 @@ function getSubscribePayload(mode: SubscribeMode): SubscribePayload {
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容布尔值和数字、字符串形式的开关值。
|
||||
function isEnabledFlag(value: unknown) {
|
||||
return value === true || value === 1 || value === '1'
|
||||
}
|
||||
|
||||
// 从订阅字段解析统一的订阅模式。
|
||||
export function getSubscribeMode(subscribe: { best_version?: unknown; best_version_full?: unknown }): SubscribeMode {
|
||||
if (!isEnabledFlag(subscribe.best_version)) return 'normal'
|
||||
|
||||
return isEnabledFlag(subscribe.best_version_full) ? 'best_version_full' : 'best_version'
|
||||
}
|
||||
|
||||
// 从默认订阅配置解析订阅模式。
|
||||
function getSubscribeConfigMode(config?: SubscribeConfig): SubscribeMode {
|
||||
return getSubscribeMode({
|
||||
best_version: config?.best_version,
|
||||
@@ -80,30 +85,36 @@ function getSubscribeConfigMode(config?: SubscribeConfig): SubscribeMode {
|
||||
})
|
||||
}
|
||||
|
||||
// 获取订阅模式的本地化名称。
|
||||
function getModeName(t: ReturnType<typeof useI18n>['t'], mode: SubscribeMode) {
|
||||
if (mode === 'normal') return t('dialog.subscribeMode.normal')
|
||||
if (mode === 'best_version') return t('dialog.subscribeMode.bestVersionEpisode')
|
||||
return t('dialog.subscribeMode.bestVersionFull')
|
||||
}
|
||||
|
||||
// 封装媒体卡片与详情页共用的订阅交互。
|
||||
export function useMediaSubscribe(options: UseMediaSubscribeOptions) {
|
||||
const { t } = useI18n()
|
||||
const $toast = useToast()
|
||||
const createConfirm = useConfirm()
|
||||
const episodeGroup = ref('')
|
||||
|
||||
// 获取调用方当前媒体,避免在异步流程中持有旧对象。
|
||||
function currentMedia() {
|
||||
return options.media()
|
||||
}
|
||||
|
||||
// 获取当前媒体的统一订阅标识。
|
||||
function getMediaId() {
|
||||
return getMediaSubscribeId(currentMedia())
|
||||
}
|
||||
|
||||
// 获取主订阅入口默认对应的季号。
|
||||
function getPrimarySeason() {
|
||||
return options.primarySeason?.() ?? currentMedia()?.season ?? null
|
||||
}
|
||||
|
||||
// 同步调用方状态和订阅状态缓存。
|
||||
function updateSubscribeStatus(season: number | null, subscribed: boolean, mode: SubscribeMode = 'normal') {
|
||||
const media = currentMedia()
|
||||
|
||||
@@ -143,6 +154,7 @@ export function useMediaSubscribe(options: UseMediaSubscribeOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
// 打开已创建订阅的编辑弹窗。
|
||||
function openSubscribeEditDialog(subid: number) {
|
||||
openSharedDialog(
|
||||
SubscribeEditDialog,
|
||||
@@ -160,6 +172,7 @@ export function useMediaSubscribe(options: UseMediaSubscribeOptions) {
|
||||
)
|
||||
}
|
||||
|
||||
// 打开订阅模式选择弹窗并转换选择结果。
|
||||
function openSubscribeModeDialog(
|
||||
modes: SubscribeMode[],
|
||||
choose: (payload: SubscribePayload, mode: SubscribeMode) => void,
|
||||
@@ -174,7 +187,8 @@ export function useMediaSubscribe(options: UseMediaSubscribeOptions) {
|
||||
)
|
||||
}
|
||||
|
||||
async function openSubscribeSeasonDialog(selectedSeason?: number | null) {
|
||||
// 打开季订阅弹窗,并保留发起入口当前使用的剧集组。
|
||||
async function openSubscribeSeasonDialog(selectedSeason?: number | null, initialEpisodeGroup = '') {
|
||||
const media = currentMedia()
|
||||
if (!media) return
|
||||
const defaultSubscribeConfig = await queryDefaultSubscribeConfig()
|
||||
@@ -184,6 +198,7 @@ export function useMediaSubscribe(options: UseMediaSubscribeOptions) {
|
||||
{
|
||||
media,
|
||||
selectedSeason,
|
||||
initialEpisodeGroup,
|
||||
subscribedSeasons: options.subscribedSeasons?.value ?? [],
|
||||
subscribedSeasonModes: options.subscribedSeasonModes?.value ?? {},
|
||||
defaultSubscribeMode: getSubscribeConfigMode(defaultSubscribeConfig),
|
||||
@@ -195,6 +210,7 @@ export function useMediaSubscribe(options: UseMediaSubscribeOptions) {
|
||||
)
|
||||
}
|
||||
|
||||
// 查询系统默认订阅配置。
|
||||
async function queryDefaultSubscribeConfig(): Promise<SubscribeConfig | undefined> {
|
||||
if (!options.canSubscribe()) return undefined
|
||||
|
||||
@@ -214,6 +230,7 @@ export function useMediaSubscribe(options: UseMediaSubscribeOptions) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// 展示订阅新增结果通知。
|
||||
function showSubscribeAddToast(
|
||||
result: boolean,
|
||||
title: string,
|
||||
@@ -229,6 +246,7 @@ export function useMediaSubscribe(options: UseMediaSubscribeOptions) {
|
||||
else $toast.error(`${title} ${t('subscribe.addFailed', { name: subname, message })}`)
|
||||
}
|
||||
|
||||
// 创建指定季和模式的订阅。
|
||||
async function addSubscribe(
|
||||
season: number | null = null,
|
||||
payload: SubscribePayload = {},
|
||||
@@ -267,6 +285,7 @@ export function useMediaSubscribe(options: UseMediaSubscribeOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
// 删除指定季的订阅。
|
||||
async function removeSubscribe(season: number | null = null, removeOptions: RemoveSubscribeOptions = {}) {
|
||||
if (removeOptions.confirm ?? true) {
|
||||
const confirmed = await createConfirm({
|
||||
@@ -302,6 +321,7 @@ export function useMediaSubscribe(options: UseMediaSubscribeOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查当前媒体指定季是否已订阅。
|
||||
async function checkSubscribe(season: number | null = null) {
|
||||
try {
|
||||
const result: Subscribe = await api.get(`subscribe/media/${getMediaId()}`, {
|
||||
@@ -319,6 +339,7 @@ export function useMediaSubscribe(options: UseMediaSubscribeOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
// 查询当前媒体指定季的订阅记录。
|
||||
async function querySubscribe(season: number | null = null) {
|
||||
try {
|
||||
const result: Subscribe = await api.get(`subscribe/media/${getMediaId()}`, {
|
||||
@@ -336,6 +357,7 @@ export function useMediaSubscribe(options: UseMediaSubscribeOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新已有单季订阅的模式。
|
||||
async function updateSubscribeMode(season: number, mode: SubscribeMode) {
|
||||
const media = currentMedia()
|
||||
if (!media) return
|
||||
@@ -368,15 +390,17 @@ export function useMediaSubscribe(options: UseMediaSubscribeOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleSeasonSubscribe(season: number) {
|
||||
// 处理单季订阅入口,未订阅时将当前剧集组带入季选择弹窗。
|
||||
function handleSeasonSubscribe(season: number, initialEpisodeGroup = '') {
|
||||
if (options.seasonsSubscribed?.value[season]) {
|
||||
removeSubscribe(season)
|
||||
return
|
||||
}
|
||||
|
||||
openSubscribeSeasonDialog(season)
|
||||
openSubscribeSeasonDialog(season, initialEpisodeGroup)
|
||||
}
|
||||
|
||||
// 处理媒体主订阅入口,电视剧统一进入季选择弹窗。
|
||||
function handlePrimarySubscribe() {
|
||||
const media = currentMedia()
|
||||
if (!media) return
|
||||
@@ -401,15 +425,17 @@ export function useMediaSubscribe(options: UseMediaSubscribeOptions) {
|
||||
addSubscribe(null)
|
||||
}
|
||||
|
||||
function handleSubscribe(season?: number | null) {
|
||||
// 根据是否指定季号分发主订阅或单季订阅操作。
|
||||
function handleSubscribe(season?: number | null, initialEpisodeGroup = '') {
|
||||
if (season !== undefined && season !== null) {
|
||||
handleSeasonSubscribe(season)
|
||||
handleSeasonSubscribe(season, initialEpisodeGroup)
|
||||
return
|
||||
}
|
||||
|
||||
handlePrimarySubscribe()
|
||||
}
|
||||
|
||||
// 批量对齐弹窗中选择的季、订阅模式和当前订阅状态。
|
||||
function subscribeSeasons(
|
||||
seasons: MediaSeason[] = [],
|
||||
seasonExistsStates: { [key: number]: number } = {},
|
||||
|
||||
@@ -550,6 +550,10 @@ onBeforeUnmount(stopAutoplay)
|
||||
|
||||
@media (max-width: 740px) {
|
||||
.dashboard-recommend {
|
||||
aspect-ratio: auto;
|
||||
inline-size: 100%;
|
||||
max-inline-size: 100%;
|
||||
min-inline-size: 0;
|
||||
min-block-size: 460px;
|
||||
}
|
||||
|
||||
|
||||
@@ -484,9 +484,9 @@ const isAllSeasonsSubscribed = computed(
|
||||
subscribedSeasonNumbers.value.length >= subscribeSeasonTotal.value,
|
||||
)
|
||||
|
||||
// 订阅按钮响应
|
||||
function handleSubscribe(season: number | null = null) {
|
||||
subscribeActions.handleSubscribe(season)
|
||||
// 订阅按钮响应;单季入口同时传递详情页当前选择的剧集组。
|
||||
function handleSubscribe(season: number | null = null, episodeGroup = '') {
|
||||
subscribeActions.handleSubscribe(season, episodeGroup)
|
||||
}
|
||||
|
||||
// 从genres中获取name,使用、分隔
|
||||
@@ -1017,7 +1017,7 @@ onUnmounted(() => {
|
||||
class="ms-1"
|
||||
:color="seasonsSubscribed[season.season_number || 0] ? 'error' : 'warning'"
|
||||
variant="text"
|
||||
@click.stop="handleSubscribe(season.season_number ?? null)"
|
||||
@click.stop="handleSubscribe(season.season_number ?? null, selectedEpisodeGroup)"
|
||||
>
|
||||
<VIcon
|
||||
:icon="seasonsSubscribed[season.season_number || 0] ? 'mdi-heart' : 'mdi-heart-outline'"
|
||||
|
||||
Reference in New Issue
Block a user