mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 23:56:42 +08:00
feat: 添加剧集组功能,支持自动查询和手动输入剧集组编号
This commit is contained in:
@@ -150,6 +150,15 @@ let previewFileBodyResizeObserver: ResizeObserver | undefined
|
|||||||
// 所有存储
|
// 所有存储
|
||||||
const storages = ref<StorageConf[]>([])
|
const storages = ref<StorageConf[]>([])
|
||||||
|
|
||||||
|
// 所有剧集组
|
||||||
|
const episodeGroups = ref<{ [key: string]: any }[]>([])
|
||||||
|
|
||||||
|
// 剧集组加载状态
|
||||||
|
const episodeGroupLoading = ref(false)
|
||||||
|
|
||||||
|
// 剧集组查询防抖句柄
|
||||||
|
let episodeGroupQueryTimer: ReturnType<typeof setTimeout> | undefined
|
||||||
|
|
||||||
// 查询存储
|
// 查询存储
|
||||||
async function loadStorages() {
|
async function loadStorages() {
|
||||||
try {
|
try {
|
||||||
@@ -169,6 +178,57 @@ const storageOptions = computed(() => {
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 剧集组选项属性
|
||||||
|
function episodeGroupItemProps(item: { title: string; subtitle?: string }) {
|
||||||
|
return {
|
||||||
|
title: item.title,
|
||||||
|
subtitle: item.subtitle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 剧集组选项,保留空值作为不指定剧集组。
|
||||||
|
const episodeGroupOptions = computed(() => {
|
||||||
|
const options = (
|
||||||
|
episodeGroups.value as { id: string; name: string; group_count: number; episode_count: number }[]
|
||||||
|
).map(item => {
|
||||||
|
return {
|
||||||
|
title: item.name,
|
||||||
|
subtitle: `${t('dialog.reorganize.seasonCount', { count: item.group_count })} • ${t(
|
||||||
|
'dialog.reorganize.episodeCount',
|
||||||
|
{ count: item.episode_count },
|
||||||
|
)}`,
|
||||||
|
value: item.id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
options.unshift({
|
||||||
|
title: t('dialog.reorganize.defaultEpisodeGroup'),
|
||||||
|
subtitle: t('dialog.reorganize.defaultEpisodeGroupHint'),
|
||||||
|
value: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
return options
|
||||||
|
})
|
||||||
|
|
||||||
|
// 查询指定 TMDB 剧集的所有剧集组。
|
||||||
|
async function getEpisodeGroups(tmdbid?: number | string) {
|
||||||
|
const normalizedTmdbId = Number(tmdbid)
|
||||||
|
if (!Number.isInteger(normalizedTmdbId) || normalizedTmdbId <= 0) {
|
||||||
|
episodeGroups.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
episodeGroupLoading.value = true
|
||||||
|
try {
|
||||||
|
episodeGroups.value = await api.get(`media/groups/${normalizedTmdbId}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
episodeGroups.value = []
|
||||||
|
} finally {
|
||||||
|
episodeGroupLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 标题
|
// 标题
|
||||||
const dialogTitle = computed(() => {
|
const dialogTitle = computed(() => {
|
||||||
return t('dialog.reorganize.manualTitle')
|
return t('dialog.reorganize.manualTitle')
|
||||||
@@ -252,6 +312,31 @@ watch(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 监听 TMDB 编号变化,自动加载可用剧集组并清空旧选择。
|
||||||
|
watch(
|
||||||
|
() => transferForm.tmdbid,
|
||||||
|
tmdbid => {
|
||||||
|
transferForm.episode_group = ''
|
||||||
|
episodeGroups.value = []
|
||||||
|
if (episodeGroupQueryTimer) clearTimeout(episodeGroupQueryTimer)
|
||||||
|
if (transferForm.type_name !== '电视剧' || mediaSource.value !== 'themoviedb') return
|
||||||
|
episodeGroupQueryTimer = setTimeout(() => getEpisodeGroups(tmdbid), 400)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// 切换媒体类型或识别源时,非 TMDB 电视剧不保留剧集组选择。
|
||||||
|
watch(
|
||||||
|
[() => transferForm.type_name, () => mediaSource.value],
|
||||||
|
([typeName, source]) => {
|
||||||
|
if (typeName === '电视剧' && source === 'themoviedb' && transferForm.tmdbid) {
|
||||||
|
getEpisodeGroups(transferForm.tmdbid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
transferForm.episode_group = ''
|
||||||
|
episodeGroups.value = []
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// 过滤后的预览数据
|
// 过滤后的预览数据
|
||||||
const filteredPreviewItems = computed(() => {
|
const filteredPreviewItems = computed(() => {
|
||||||
return previewData.value?.items ?? []
|
return previewData.value?.items ?? []
|
||||||
@@ -528,6 +613,7 @@ function createTransferPayload(options: { item?: FileItem; items?: FileItem[]; l
|
|||||||
...transferForm,
|
...transferForm,
|
||||||
fileitem: sourceItem,
|
fileitem: sourceItem,
|
||||||
logid: options.logid ?? 0,
|
logid: options.logid ?? 0,
|
||||||
|
episode_group: transferForm.episode_group?.trim() || undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.items?.length) {
|
if (options.items?.length) {
|
||||||
@@ -1014,6 +1100,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
stopLoadingProgress()
|
stopLoadingProgress()
|
||||||
|
if (episodeGroupQueryTimer) clearTimeout(episodeGroupQueryTimer)
|
||||||
previewFileBodyResizeObserver?.disconnect()
|
previewFileBodyResizeObserver?.disconnect()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -1123,9 +1210,14 @@ onUnmounted(() => {
|
|||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
<VRow v-show="transferForm.type_name === '电视剧'">
|
<VRow v-show="transferForm.type_name === '电视剧'">
|
||||||
<VCol cols="12" md="6">
|
<VCol v-if="mediaSource === 'themoviedb'" cols="12" md="6">
|
||||||
<VTextField
|
<VCombobox
|
||||||
v-model="transferForm.episode_group"
|
v-model="transferForm.episode_group"
|
||||||
|
:items="episodeGroupOptions"
|
||||||
|
:item-props="episodeGroupItemProps"
|
||||||
|
:loading="episodeGroupLoading"
|
||||||
|
:disabled="!transferForm.tmdbid"
|
||||||
|
clearable
|
||||||
:label="t('dialog.reorganize.episodeGroup')"
|
:label="t('dialog.reorganize.episodeGroup')"
|
||||||
:placeholder="t('dialog.reorganize.episodeGroupPlaceholder')"
|
:placeholder="t('dialog.reorganize.episodeGroupPlaceholder')"
|
||||||
:hint="t('dialog.reorganize.episodeGroupHint')"
|
:hint="t('dialog.reorganize.episodeGroupHint')"
|
||||||
|
|||||||
@@ -1850,7 +1850,7 @@ export default {
|
|||||||
'Word to replace => Replacement\n' +
|
'Word to replace => Replacement\n' +
|
||||||
'Front word <> Back word >> Episode offset (EP)\n' +
|
'Front word <> Back word >> Episode offset (EP)\n' +
|
||||||
'Word to replace => Replacement && Front word <> Back word >> Episode offset (EP)\n' +
|
'Word to replace => Replacement && Front word <> Back word >> Episode offset (EP)\n' +
|
||||||
'Replacement format supports: {[tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx]} to directly specify TMDBID/Douban ID, where s and e are season and episode numbers (optional)',
|
'Replacement format supports: {[tmdbid/doubanid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} to directly specify TMDBID/Douban ID, where g is the episode group ID and s/e are season and episode numbers (all optional)',
|
||||||
identifierSaveSuccess: 'Custom identifiers saved successfully',
|
identifierSaveSuccess: 'Custom identifiers saved successfully',
|
||||||
identifierSaveFailed: 'Failed to save custom identifiers!',
|
identifierSaveFailed: 'Failed to save custom identifiers!',
|
||||||
|
|
||||||
@@ -2544,9 +2544,13 @@ export default {
|
|||||||
doubanId: 'Douban ID',
|
doubanId: 'Douban ID',
|
||||||
mediaIdHint: 'Query media ID by name, leave empty for auto recognition',
|
mediaIdHint: 'Query media ID by name, leave empty for auto recognition',
|
||||||
mediaIdPlaceholder: 'Leave empty for auto recognition',
|
mediaIdPlaceholder: 'Leave empty for auto recognition',
|
||||||
episodeGroup: 'Episode Group ID',
|
episodeGroup: 'Episode Group',
|
||||||
episodeGroupHint: 'Specify episode group',
|
episodeGroupHint: 'After entering a TMDB ID, episode groups are queried automatically; group IDs can still be entered manually',
|
||||||
episodeGroupPlaceholder: 'Manually query episode group',
|
episodeGroupPlaceholder: 'Enter TMDB ID first',
|
||||||
|
defaultEpisodeGroup: 'No episode group',
|
||||||
|
defaultEpisodeGroupHint: 'Use TMDB default season and episode order',
|
||||||
|
seasonCount: '{count} seasons',
|
||||||
|
episodeCount: '{count} episodes',
|
||||||
season: 'Season',
|
season: 'Season',
|
||||||
seasonHint: 'Which season',
|
seasonHint: 'Which season',
|
||||||
episodeDetail: 'Episode',
|
episodeDetail: 'Episode',
|
||||||
@@ -2660,7 +2664,7 @@ export default {
|
|||||||
customWords: 'Custom Recognition Words',
|
customWords: 'Custom Recognition Words',
|
||||||
customWordsHint: 'Recognition words only used for this subscription',
|
customWordsHint: 'Recognition words only used for this subscription',
|
||||||
customWordsPlaceholder:
|
customWordsPlaceholder:
|
||||||
'Block word\nReplaced word => Replacement word\nPrefix <> Suffix >> Episode offset (EP)\nReplaced word => Replacement word && Prefix <> Suffix >> Episode offset (EP)\nReplacement word supports format: { tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx } to directly specify TMDBID/Douban ID recognition, where s, e are season and episode numbers (optional)',
|
'Block word\nReplaced word => Replacement word\nPrefix <> Suffix >> Episode offset (EP)\nReplaced word => Replacement word && Prefix <> Suffix >> Episode offset (EP)\nReplacement word supports format: {[tmdbid/doubanid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} to directly specify TMDBID/Douban ID recognition, where g is the episode group ID and s/e are season and episode numbers (all optional)',
|
||||||
cancelSubscribe: 'Cancel Subscription',
|
cancelSubscribe: 'Cancel Subscription',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
cancelSubscribeConfirm: 'Are you sure you want to cancel the subscription?',
|
cancelSubscribeConfirm: 'Are you sure you want to cancel the subscription?',
|
||||||
|
|||||||
@@ -1819,7 +1819,7 @@ export default {
|
|||||||
'被替换词 => 替换词\n' +
|
'被替换词 => 替换词\n' +
|
||||||
'前定位词 <> 后定位词 >> 集偏移量(EP)\n' +
|
'前定位词 <> 后定位词 >> 集偏移量(EP)\n' +
|
||||||
'被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)\n' +
|
'被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)\n' +
|
||||||
'其中替换词支持格式:{[tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID识别,其中s、e为季数和集数(可选)',
|
'其中替换词支持格式:{[tmdbid/doubanid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID识别,其中g为剧集组编号,s、e为季数和集数(均可选)',
|
||||||
identifierSaveSuccess: '自定义识别词保存成功',
|
identifierSaveSuccess: '自定义识别词保存成功',
|
||||||
identifierSaveFailed: '自定义识别词保存失败!',
|
identifierSaveFailed: '自定义识别词保存失败!',
|
||||||
|
|
||||||
@@ -2498,9 +2498,13 @@ export default {
|
|||||||
doubanId: '豆瓣编号',
|
doubanId: '豆瓣编号',
|
||||||
mediaIdHint: '按名称查询媒体编号,留空自动识别',
|
mediaIdHint: '按名称查询媒体编号,留空自动识别',
|
||||||
mediaIdPlaceholder: '留空自动识别',
|
mediaIdPlaceholder: '留空自动识别',
|
||||||
episodeGroup: '剧集组编号',
|
episodeGroup: '剧集组',
|
||||||
episodeGroupHint: '指定剧集组',
|
episodeGroupHint: '输入 TMDB 编号后自动查询剧集组,也可手动填写剧集组编号',
|
||||||
episodeGroupPlaceholder: '手动查询剧集组',
|
episodeGroupPlaceholder: '先输入 TMDB 编号',
|
||||||
|
defaultEpisodeGroup: '不指定剧集组',
|
||||||
|
defaultEpisodeGroupHint: '使用 TMDB 默认季集排序',
|
||||||
|
seasonCount: '{count} 季',
|
||||||
|
episodeCount: '{count} 集',
|
||||||
season: '季',
|
season: '季',
|
||||||
seasonHint: '第几季',
|
seasonHint: '第几季',
|
||||||
episodeDetail: '集',
|
episodeDetail: '集',
|
||||||
@@ -2613,7 +2617,7 @@ export default {
|
|||||||
customWords: '自定义识别词',
|
customWords: '自定义识别词',
|
||||||
customWordsHint: '只对该订阅使用的识别词',
|
customWordsHint: '只对该订阅使用的识别词',
|
||||||
customWordsPlaceholder:
|
customWordsPlaceholder:
|
||||||
'屏蔽词\n被替换词 => 替换词\n前定位词 <> 后定位词 >> 集偏移量(EP)\n被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)\n其中替换词支持格式:{ tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx } 直接指定TMDBID/豆瓣ID识别,其中s、e为季数和集数(可选)',
|
'屏蔽词\n被替换词 => 替换词\n前定位词 <> 后定位词 >> 集偏移量(EP)\n被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)\n其中替换词支持格式:{[tmdbid/doubanid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID识别,其中g为剧集组编号,s、e为季数和集数(均可选)',
|
||||||
cancelSubscribe: '取消订阅',
|
cancelSubscribe: '取消订阅',
|
||||||
save: '保存',
|
save: '保存',
|
||||||
cancelSubscribeConfirm: '是否确认取消订阅?',
|
cancelSubscribeConfirm: '是否确认取消订阅?',
|
||||||
|
|||||||
@@ -1820,7 +1820,7 @@ export default {
|
|||||||
'被替換詞 => 替換詞\n' +
|
'被替換詞 => 替換詞\n' +
|
||||||
'前定位詞 <> 後定位詞 >> 集偏移量(EP)\n' +
|
'前定位詞 <> 後定位詞 >> 集偏移量(EP)\n' +
|
||||||
'被替換詞 => 替換詞 && 前定位詞 <> 後定位詞 >> 集偏移量(EP)\n' +
|
'被替換詞 => 替換詞 && 前定位詞 <> 後定位詞 >> 集偏移量(EP)\n' +
|
||||||
'其中替換詞支持格式:{[tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID識別,其中s、e為季數和集數(可選)',
|
'其中替換詞支持格式:{[tmdbid/doubanid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID識別,其中g為劇集組編號,s、e為季數和集數(均可選)',
|
||||||
identifierSaveSuccess: '自定義識別詞保存成功',
|
identifierSaveSuccess: '自定義識別詞保存成功',
|
||||||
identifierSaveFailed: '自定義識別詞保存失敗!',
|
identifierSaveFailed: '自定義識別詞保存失敗!',
|
||||||
|
|
||||||
@@ -2499,9 +2499,13 @@ export default {
|
|||||||
doubanId: '豆瓣編號',
|
doubanId: '豆瓣編號',
|
||||||
mediaIdHint: '按名稱查詢媒體編號,留空自動識別',
|
mediaIdHint: '按名稱查詢媒體編號,留空自動識別',
|
||||||
mediaIdPlaceholder: '留空自動識別',
|
mediaIdPlaceholder: '留空自動識別',
|
||||||
episodeGroup: '劇集組編號',
|
episodeGroup: '劇集組',
|
||||||
episodeGroupHint: '指定劇集組',
|
episodeGroupHint: '輸入 TMDB 編號後自動查詢劇集組,也可手動填寫劇集組編號',
|
||||||
episodeGroupPlaceholder: '手動查詢劇集組',
|
episodeGroupPlaceholder: '先輸入 TMDB 編號',
|
||||||
|
defaultEpisodeGroup: '不指定劇集組',
|
||||||
|
defaultEpisodeGroupHint: '使用 TMDB 預設季集排序',
|
||||||
|
seasonCount: '{count} 季',
|
||||||
|
episodeCount: '{count} 集',
|
||||||
season: '季',
|
season: '季',
|
||||||
seasonHint: '第幾季',
|
seasonHint: '第幾季',
|
||||||
episodeDetail: '集',
|
episodeDetail: '集',
|
||||||
@@ -2614,7 +2618,7 @@ export default {
|
|||||||
customWords: '自定義識別詞',
|
customWords: '自定義識別詞',
|
||||||
customWordsHint: '只對該訂閱使用的識別詞',
|
customWordsHint: '只對該訂閱使用的識別詞',
|
||||||
customWordsPlaceholder:
|
customWordsPlaceholder:
|
||||||
'屏蔽詞\n被替換詞 => 替換詞\n前定位詞 <> 後定位詞 >> 集偏移量(EP)\n被替換詞 => 替換詞 && 前定位詞 <> 後定位詞 >> 集偏移量(EP)\n其中替換詞支援格式:{ tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx } 直接指定TMDBID/豆瓣ID識別,其中s、e為季數和集數(可選)',
|
'屏蔽詞\n被替換詞 => 替換詞\n前定位詞 <> 後定位詞 >> 集偏移量(EP)\n被替換詞 => 替換詞 && 前定位詞 <> 後定位詞 >> 集偏移量(EP)\n其中替換詞支援格式:{[tmdbid/doubanid=xxx;type=movie/tv;g=xxx;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID識別,其中g為劇集組編號,s、e為季數和集數(均可選)',
|
||||||
cancelSubscribe: '取消訂閱',
|
cancelSubscribe: '取消訂閱',
|
||||||
save: '儲存',
|
save: '儲存',
|
||||||
cancelSubscribeConfirm: '是否確認取消訂閱?',
|
cancelSubscribeConfirm: '是否確認取消訂閱?',
|
||||||
|
|||||||
Reference in New Issue
Block a user