优化剧集组管理,添加剧集组选项和查询功能,调整订阅逻辑

This commit is contained in:
jxxghp
2025-04-04 12:17:29 +08:00
parent 357191afcf
commit 2e05c8079b
4 changed files with 145 additions and 73 deletions
-2
View File
@@ -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
} }
+6 -1
View File
@@ -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
+26 -15
View File
@@ -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)
} }
+113 -55
View File
@@ -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">