From c6981e9955a0ff6e6479cee95768a5a0e452617d Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 24 May 2026 23:33:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=89=A7=E9=9B=86?= =?UTF-8?q?=E7=BB=84=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=9F=A5=E8=AF=A2=E5=92=8C=E6=89=8B=E5=8A=A8=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E5=89=A7=E9=9B=86=E7=BB=84=E7=BC=96=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/dialog/ReorganizeDialog.vue | 96 +++++++++++++++++++++- src/locales/en-US.ts | 14 ++-- src/locales/zh-CN.ts | 14 ++-- src/locales/zh-TW.ts | 14 ++-- 4 files changed, 121 insertions(+), 17 deletions(-) diff --git a/src/components/dialog/ReorganizeDialog.vue b/src/components/dialog/ReorganizeDialog.vue index 3b688a68..7bd527ab 100644 --- a/src/components/dialog/ReorganizeDialog.vue +++ b/src/components/dialog/ReorganizeDialog.vue @@ -150,6 +150,15 @@ let previewFileBodyResizeObserver: ResizeObserver | undefined // 所有存储 const storages = ref([]) +// 所有剧集组 +const episodeGroups = ref<{ [key: string]: any }[]>([]) + +// 剧集组加载状态 +const episodeGroupLoading = ref(false) + +// 剧集组查询防抖句柄 +let episodeGroupQueryTimer: ReturnType | undefined + // 查询存储 async function loadStorages() { 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(() => { 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(() => { return previewData.value?.items ?? [] @@ -528,6 +613,7 @@ function createTransferPayload(options: { item?: FileItem; items?: FileItem[]; l ...transferForm, fileitem: sourceItem, logid: options.logid ?? 0, + episode_group: transferForm.episode_group?.trim() || undefined, } if (options.items?.length) { @@ -1014,6 +1100,7 @@ onMounted(() => { onUnmounted(() => { stopLoadingProgress() + if (episodeGroupQueryTimer) clearTimeout(episodeGroupQueryTimer) previewFileBodyResizeObserver?.disconnect() }) @@ -1123,9 +1210,14 @@ onUnmounted(() => { - - + Replacement\n' + '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', identifierSaveFailed: 'Failed to save custom identifiers!', @@ -2544,9 +2544,13 @@ export default { doubanId: 'Douban ID', mediaIdHint: 'Query media ID by name, leave empty for auto recognition', mediaIdPlaceholder: 'Leave empty for auto recognition', - episodeGroup: 'Episode Group ID', - episodeGroupHint: 'Specify episode group', - episodeGroupPlaceholder: 'Manually query episode group', + episodeGroup: 'Episode Group', + episodeGroupHint: 'After entering a TMDB ID, episode groups are queried automatically; group IDs can still be entered manually', + 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', seasonHint: 'Which season', episodeDetail: 'Episode', @@ -2660,7 +2664,7 @@ export default { customWords: 'Custom Recognition Words', customWordsHint: 'Recognition words only used for this subscription', 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', save: 'Save', cancelSubscribeConfirm: 'Are you sure you want to cancel the subscription?', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index a7cac3f8..b17cae81 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -1819,7 +1819,7 @@ export default { '被替换词 => 替换词\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: '自定义识别词保存成功', identifierSaveFailed: '自定义识别词保存失败!', @@ -2498,9 +2498,13 @@ export default { doubanId: '豆瓣编号', mediaIdHint: '按名称查询媒体编号,留空自动识别', mediaIdPlaceholder: '留空自动识别', - episodeGroup: '剧集组编号', - episodeGroupHint: '指定剧集组', - episodeGroupPlaceholder: '手动查询剧集组', + episodeGroup: '剧集组', + episodeGroupHint: '输入 TMDB 编号后自动查询剧集组,也可手动填写剧集组编号', + episodeGroupPlaceholder: '先输入 TMDB 编号', + defaultEpisodeGroup: '不指定剧集组', + defaultEpisodeGroupHint: '使用 TMDB 默认季集排序', + seasonCount: '{count} 季', + episodeCount: '{count} 集', season: '季', seasonHint: '第几季', episodeDetail: '集', @@ -2613,7 +2617,7 @@ export default { customWords: '自定义识别词', customWordsHint: '只对该订阅使用的识别词', 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: '取消订阅', save: '保存', cancelSubscribeConfirm: '是否确认取消订阅?', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 095d1cc5..716952c1 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -1820,7 +1820,7 @@ export default { '被替換詞 => 替換詞\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: '自定義識別詞保存成功', identifierSaveFailed: '自定義識別詞保存失敗!', @@ -2499,9 +2499,13 @@ export default { doubanId: '豆瓣編號', mediaIdHint: '按名稱查詢媒體編號,留空自動識別', mediaIdPlaceholder: '留空自動識別', - episodeGroup: '劇集組編號', - episodeGroupHint: '指定劇集組', - episodeGroupPlaceholder: '手動查詢劇集組', + episodeGroup: '劇集組', + episodeGroupHint: '輸入 TMDB 編號後自動查詢劇集組,也可手動填寫劇集組編號', + episodeGroupPlaceholder: '先輸入 TMDB 編號', + defaultEpisodeGroup: '不指定劇集組', + defaultEpisodeGroupHint: '使用 TMDB 預設季集排序', + seasonCount: '{count} 季', + episodeCount: '{count} 集', season: '季', seasonHint: '第幾季', episodeDetail: '集', @@ -2614,7 +2618,7 @@ export default { customWords: '自定義識別詞', customWordsHint: '只對該訂閱使用的識別詞', 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: '取消訂閱', save: '儲存', cancelSubscribeConfirm: '是否確認取消訂閱?',