mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 07:28:37 +08:00
优化剧集组管理,添加剧集组选项和查询功能,调整订阅逻辑
This commit is contained in:
@@ -78,8 +78,6 @@ export interface Subscribe {
|
|||||||
filter_groups?: string[]
|
filter_groups?: string[]
|
||||||
// 下载器
|
// 下载器
|
||||||
downloader?: string
|
downloader?: string
|
||||||
// 可选剧集组
|
|
||||||
episode_groups?: Record<string, any>[]
|
|
||||||
// 自定义剧集组
|
// 自定义剧集组
|
||||||
episode_group?: string
|
episode_group?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ const searchMenuShow = ref(false)
|
|||||||
// 选择站点对话框
|
// 选择站点对话框
|
||||||
const chooseSiteDialog = ref(false)
|
const chooseSiteDialog = ref(false)
|
||||||
|
|
||||||
|
// 选择的剧集组
|
||||||
|
const episodeGroup = ref('')
|
||||||
|
|
||||||
// 查询所有站点
|
// 查询所有站点
|
||||||
async function querySites() {
|
async function querySites() {
|
||||||
try {
|
try {
|
||||||
@@ -146,6 +149,7 @@ async function addSubscribe(season: number = 0, best_version: number = 0) {
|
|||||||
mediaid: props.media?.media_id ? `${props.media?.mediaid_prefix}:${props.media?.media_id}` : '',
|
mediaid: props.media?.media_id ? `${props.media?.mediaid_prefix}:${props.media?.media_id}` : '',
|
||||||
season,
|
season,
|
||||||
best_version,
|
best_version,
|
||||||
|
episode_group: episodeGroup.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 订阅状态
|
// 订阅状态
|
||||||
@@ -288,8 +292,9 @@ function handleSubscribe() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 订阅多季
|
// 订阅多季
|
||||||
function subscribeSeasons(seasons: MediaSeason[], seasonNoExists: { [key: number]: number }) {
|
function subscribeSeasons(seasons: MediaSeason[], seasonNoExists: { [key: number]: number }, groudId: string) {
|
||||||
subscribeSeasonDialog.value = false
|
subscribeSeasonDialog.value = false
|
||||||
|
episodeGroup.value = groudId
|
||||||
seasonsSelected.value = seasons || []
|
seasonsSelected.value = seasons || []
|
||||||
seasonsSelected.value.forEach(season => {
|
seasonsSelected.value.forEach(season => {
|
||||||
let best_version = 0
|
let best_version = 0
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import api from '@/api'
|
|||||||
import type { DownloaderConf, FilterRuleGroup, Site, Subscribe, TransferDirectoryConf } from '@/api/types'
|
import type { DownloaderConf, FilterRuleGroup, Site, Subscribe, TransferDirectoryConf } from '@/api/types'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
import { useConfirm } from 'vuetify-use-dialog'
|
import { useConfirm } from 'vuetify-use-dialog'
|
||||||
import { VTextarea, VTextField } from 'vuetify/lib/components/index.mjs'
|
|
||||||
|
|
||||||
// 显示器宽度
|
// 显示器宽度
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
@@ -53,6 +52,7 @@ const subscribeForm = ref<Subscribe>({
|
|||||||
downloader: '',
|
downloader: '',
|
||||||
date: '',
|
date: '',
|
||||||
show_edit_dialog: false,
|
show_edit_dialog: false,
|
||||||
|
episode_group: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
// 提示框
|
// 提示框
|
||||||
@@ -61,8 +61,21 @@ const $toast = useToast()
|
|||||||
// 下载器选项
|
// 下载器选项
|
||||||
const downloaderOptions = ref<{ title: string; value: string }[]>([])
|
const downloaderOptions = ref<{ title: string; value: string }[]>([])
|
||||||
|
|
||||||
|
// 所有剧集组
|
||||||
|
const episodeGroups = ref<{ [key: string]: any }[]>([])
|
||||||
|
|
||||||
// 剧集组选项
|
// 剧集组选项
|
||||||
const episodeGroupOptions = ref<{ title: string; subtitle: string; value: number }[]>([])
|
const episodeGroupOptions = computed(() => {
|
||||||
|
return (episodeGroups.value as { id: number; name: string; group_count: number; episode_count: number }[]).map(
|
||||||
|
item => {
|
||||||
|
return {
|
||||||
|
title: item.name,
|
||||||
|
subtitle: `${item.group_count} 季 • ${item.episode_count} 集`,
|
||||||
|
value: item.id,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
// 生成1到100季的下拉框选项
|
// 生成1到100季的下拉框选项
|
||||||
const seasonItems = ref(
|
const seasonItems = ref(
|
||||||
@@ -80,6 +93,15 @@ function episodeGroupItemProps(item: { title: string; subtitle: string }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询所有剧集组
|
||||||
|
async function getEpisodeGroups() {
|
||||||
|
try {
|
||||||
|
episodeGroups.value = await api.get(`media/groups/${subscribeForm.value.tmdbid}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadDownloaderSetting() {
|
async function loadDownloaderSetting() {
|
||||||
try {
|
try {
|
||||||
const downloaders: DownloaderConf[] = await api.get('download/clients')
|
const downloaders: DownloaderConf[] = await api.get('download/clients')
|
||||||
@@ -197,19 +219,8 @@ async function getSubscribeInfo() {
|
|||||||
subscribeForm.value = result
|
subscribeForm.value = result
|
||||||
subscribeForm.value.best_version = subscribeForm.value.best_version === 1
|
subscribeForm.value.best_version = subscribeForm.value.best_version === 1
|
||||||
subscribeForm.value.search_imdbid = subscribeForm.value.search_imdbid === 1
|
subscribeForm.value.search_imdbid = subscribeForm.value.search_imdbid === 1
|
||||||
|
// 加载剧集组
|
||||||
// 从result.episode_groups中获取剧集组
|
if (subscribeForm.value.type == '电视剧') getEpisodeGroups()
|
||||||
if (result?.episode_groups) {
|
|
||||||
episodeGroupOptions.value = (result?.episode_groups as { id: number; name: string; episode_count: number }[]).map(
|
|
||||||
item => {
|
|
||||||
return {
|
|
||||||
title: item.name,
|
|
||||||
subtitle: `${item.episode_count} 集`,
|
|
||||||
value: item.id,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { doneNProgress, startNProgress } from '@/api/nprogress'
|
|
||||||
import { MediaInfo, MediaSeason, NotExistMediaInfo } from '@/api/types'
|
import { MediaInfo, MediaSeason, NotExistMediaInfo } from '@/api/types'
|
||||||
import { PropType } from 'vue'
|
import { PropType } from 'vue'
|
||||||
import NoDataFound from '@/components/NoDataFound.vue'
|
import NoDataFound from '@/components/NoDataFound.vue'
|
||||||
@@ -32,6 +31,40 @@ const seasonsNotExisted = ref<{ [key: number]: number }>({})
|
|||||||
// 是否刷新过
|
// 是否刷新过
|
||||||
const isRefreshed = ref(false)
|
const isRefreshed = ref(false)
|
||||||
|
|
||||||
|
// 所有剧集组
|
||||||
|
const episodeGroups = ref<{ [key: string]: any }[]>([])
|
||||||
|
|
||||||
|
// 当前选择剧集组
|
||||||
|
const episodeGroup = ref(0)
|
||||||
|
|
||||||
|
// 剧集组选项属性
|
||||||
|
function episodeGroupItemProps(item: { title: string; subtitle: string }) {
|
||||||
|
return {
|
||||||
|
title: item.title,
|
||||||
|
subtitle: item.subtitle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 剧集组选项
|
||||||
|
const episodeGroupOptions = computed(() => {
|
||||||
|
let options = (episodeGroups.value as { id: number; name: string; group_count: number; episode_count: number }[]).map(
|
||||||
|
item => {
|
||||||
|
return {
|
||||||
|
title: item.name,
|
||||||
|
subtitle: `${item.group_count} 季 • ${item.episode_count} 集`,
|
||||||
|
value: item.id,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// 添加不使用选项
|
||||||
|
options.unshift({
|
||||||
|
title: '默认',
|
||||||
|
subtitle: `${seasonInfos.value.length} 季`,
|
||||||
|
value: 0,
|
||||||
|
})
|
||||||
|
return options
|
||||||
|
})
|
||||||
|
|
||||||
// 获得mediaid
|
// 获得mediaid
|
||||||
function getMediaId() {
|
function getMediaId() {
|
||||||
if (props.media?.tmdb_id) return `tmdb:${props.media?.tmdb_id}`
|
if (props.media?.tmdb_id) return `tmdb:${props.media?.tmdb_id}`
|
||||||
@@ -40,9 +73,17 @@ function getMediaId() {
|
|||||||
else return `${props.media?.mediaid_prefix}:${props.media?.media_id}`
|
else return `${props.media?.mediaid_prefix}:${props.media?.media_id}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询所有剧集组
|
||||||
|
async function getEpisodeGroups() {
|
||||||
|
try {
|
||||||
|
episodeGroups.value = await api.get(`media/groups/${props.media?.tmdb_id}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 查询TMDB的所有季信息
|
// 查询TMDB的所有季信息
|
||||||
async function getMediaSeasons() {
|
async function getMediaSeasons() {
|
||||||
startNProgress()
|
|
||||||
try {
|
try {
|
||||||
seasonInfos.value = await api.get('media/seasons', {
|
seasonInfos.value = await api.get('media/seasons', {
|
||||||
params: {
|
params: {
|
||||||
@@ -56,13 +97,23 @@ async function getMediaSeasons() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
doneNProgress()
|
}
|
||||||
|
|
||||||
|
// 查询剧集组的剧集
|
||||||
|
async function getGroupSeasons() {
|
||||||
|
if (!episodeGroup.value) return
|
||||||
|
isRefreshed.value = false
|
||||||
|
try {
|
||||||
|
seasonInfos.value = await api.get(`media/group/seasons/${episodeGroup.value}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
isRefreshed.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查所有季的缺失状态(数据库)
|
// 检查所有季的缺失状态(数据库)
|
||||||
async function checkSeasonsNotExists() {
|
async function checkSeasonsNotExists() {
|
||||||
// 开始处理
|
// 开始处理
|
||||||
startNProgress()
|
|
||||||
try {
|
try {
|
||||||
const result: NotExistMediaInfo[] = await api.post('mediaserver/notexists', props.media)
|
const result: NotExistMediaInfo[] = await api.post('mediaserver/notexists', props.media)
|
||||||
if (result) {
|
if (result) {
|
||||||
@@ -76,9 +127,6 @@ async function checkSeasonsNotExists() {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
} finally {
|
|
||||||
// 处理完成
|
|
||||||
doneNProgress()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +152,7 @@ function getExistText(season: number) {
|
|||||||
|
|
||||||
// 拼装季图片地址
|
// 拼装季图片地址
|
||||||
function getSeasonPoster(posterPath: string) {
|
function getSeasonPoster(posterPath: string) {
|
||||||
if (!posterPath) return ''
|
if (!posterPath) return props.media?.poster_path
|
||||||
return `https://${globalSettings.TMDB_IMAGE_DOMAIN}/t/p/w500${posterPath}`
|
return `https://${globalSettings.TMDB_IMAGE_DOMAIN}/t/p/w500${posterPath}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,11 +171,17 @@ function getYear(airDate: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function subscribeSeasons() {
|
function subscribeSeasons() {
|
||||||
emit('subscribe', seasonsSelected.value, seasonsNotExisted.value)
|
emit('subscribe', seasonsSelected.value, seasonsNotExisted.value, episodeGroup.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (episodeGroup.value) getGroupSeasons()
|
||||||
|
else getMediaSeasons()
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
getMediaSeasons()
|
getMediaSeasons()
|
||||||
|
getEpisodeGroups()
|
||||||
checkSeasonsNotExists()
|
checkSeasonsNotExists()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -141,53 +195,57 @@ onMounted(async () => {
|
|||||||
</VCardItem>
|
</VCardItem>
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
|
<VSelect
|
||||||
|
v-model="episodeGroup"
|
||||||
|
:items="episodeGroupOptions"
|
||||||
|
:item-props="episodeGroupItemProps"
|
||||||
|
label="选择剧集组"
|
||||||
|
persistent-hint
|
||||||
|
/>
|
||||||
<LoadingBanner v-if="!isRefreshed" class="mt-5" />
|
<LoadingBanner v-if="!isRefreshed" class="mt-5" />
|
||||||
<VList
|
<div v-else-if="seasonInfos.length > 0">
|
||||||
v-else-if="seasonInfos.length > 0"
|
<VList v-model:selected="seasonsSelected" lines="three" select-strategy="classic">
|
||||||
v-model:selected="seasonsSelected"
|
<VListItem v-for="(item, i) in seasonInfos" :key="i" :value="item">
|
||||||
lines="three"
|
<template #prepend>
|
||||||
select-strategy="classic"
|
<VImg
|
||||||
>
|
height="90"
|
||||||
<VListItem v-for="(item, i) in seasonInfos" :key="i" :value="item">
|
width="60"
|
||||||
<template #prepend>
|
:src="getSeasonPoster(item.poster_path || '')"
|
||||||
<VImg
|
aspect-ratio="2/3"
|
||||||
height="90"
|
class="object-cover rounded shadow ring-gray-500 me-3"
|
||||||
width="60"
|
cover
|
||||||
:src="getSeasonPoster(item.poster_path || '')"
|
>
|
||||||
aspect-ratio="2/3"
|
<template #placeholder>
|
||||||
class="object-cover rounded shadow ring-gray-500 me-3"
|
<div class="w-full h-full">
|
||||||
cover
|
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
|
||||||
>
|
</div>
|
||||||
<template #placeholder>
|
</template>
|
||||||
<div class="w-full h-full">
|
</VImg>
|
||||||
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
|
</template>
|
||||||
</div>
|
<VListItemTitle> 第 {{ item.season_number }} 季 </VListItemTitle>
|
||||||
</template>
|
<VListItemSubtitle class="mt-1 me-2">
|
||||||
</VImg>
|
<VChip v-if="item.vote_average" color="primary" size="small" class="mb-1">
|
||||||
</template>
|
<VIcon icon="mdi-star" /> {{ item.vote_average }}
|
||||||
<VListItemTitle> 第 {{ item.season_number }} 季 </VListItemTitle>
|
</VChip>
|
||||||
<VListItemSubtitle class="mt-1 me-2">
|
{{ getYear(item.air_date || '') }} • {{ item.episode_count }} 集
|
||||||
<VChip v-if="item.vote_average" color="primary" size="small" class="mb-1">
|
</VListItemSubtitle>
|
||||||
<VIcon icon="mdi-star" /> {{ item.vote_average }}
|
<VListItemSubtitle>
|
||||||
</VChip>
|
《{{ media?.title }}》第 {{ item.season_number }} 季于 {{ formatAirDate(item.air_date || '') }} 首播。
|
||||||
{{ getYear(item.air_date || '') }} • {{ item.episode_count }} 集
|
</VListItemSubtitle>
|
||||||
</VListItemSubtitle>
|
<VListItemSubtitle>
|
||||||
<VListItemSubtitle>
|
<VChip v-if="noexists" class="mt-2" size="small" :color="getExistColor(item.season_number || 0)">
|
||||||
《{{ media?.title }}》第 {{ item.season_number }} 季于 {{ formatAirDate(item.air_date || '') }} 首播。
|
{{ getExistText(item.season_number || 0) }}
|
||||||
</VListItemSubtitle>
|
</VChip>
|
||||||
<VListItemSubtitle>
|
</VListItemSubtitle>
|
||||||
<VChip v-if="noexists" class="mt-2" size="small" :color="getExistColor(item.season_number || 0)">
|
<template #append="{ isSelected }">
|
||||||
{{ getExistText(item.season_number || 0) }}
|
<VListItemAction start>
|
||||||
</VChip>
|
<VSwitch :model-value="isSelected" />
|
||||||
</VListItemSubtitle>
|
</VListItemAction>
|
||||||
<template #append="{ isSelected }">
|
</template>
|
||||||
<VListItemAction start>
|
</VListItem>
|
||||||
<VSwitch :model-value="isSelected" />
|
</VList>
|
||||||
</VListItemAction>
|
</div>
|
||||||
</template>
|
<NoDataFound v-else errorTitle="出错啦!" :errorDescription="`${props.media?.title} 未查询到季集信息`" />
|
||||||
</VListItem>
|
|
||||||
</VList>
|
|
||||||
<NoDataFound v-else errorTitle="出错啦!" :errorDescription="`${props.media?.title} 无法识别TMDB媒体信息!`" />
|
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<div class="my-2 text-center">
|
<div class="my-2 text-center">
|
||||||
<VBtn :disabled="seasonsSelected.length === 0" width="30%" @click="subscribeSeasons">
|
<VBtn :disabled="seasonsSelected.length === 0" width="30%" @click="subscribeSeasons">
|
||||||
|
|||||||
Reference in New Issue
Block a user