From 6a0217d18cc4adff3456553eb2077207855826dd Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 13 Aug 2026 15:22:03 +0800 Subject: [PATCH] fix(reorganize): refine transfer dialog layouts --- src/api/types.ts | 2 +- src/components/dialog/ReorganizeDialog.vue | 21 +++++++---- src/components/dialog/TransferQueueDialog.vue | 22 +---------- .../dialog/__tests__/ReorganizeDialog.spec.ts | 9 ++++- .../__tests__/TransferQueueDialog.spec.ts | 18 +++++++++ src/views/reorganize/TransferHistoryView.vue | 37 +++++++++++-------- .../__tests__/TransferHistoryView.spec.ts | 12 +++--- 7 files changed, 71 insertions(+), 50 deletions(-) diff --git a/src/api/types.ts b/src/api/types.ts index a34c4b8e..d10243e4 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -1871,7 +1871,7 @@ export interface TransferForm { // 目标路径 target_path: string | null // 媒体数据源 - media_source?: MediaDataSource + media_source?: MediaDataSource | null // 数据源原生ID media_id?: string | null // 音乐实体类型 diff --git a/src/components/dialog/ReorganizeDialog.vue b/src/components/dialog/ReorganizeDialog.vue index 80f5a737..bf37732a 100644 --- a/src/components/dialog/ReorganizeDialog.vue +++ b/src/components/dialog/ReorganizeDialog.vue @@ -44,7 +44,8 @@ const props = defineProps({ const globalSettingsStore = useGlobalSettingsStore() const globalSettings = globalSettingsStore.globalSettings -const mediaSourceItems = computed<{ title: string; value: MediaDataSource }[]>(() => [ +const mediaSourceItems = computed<{ title: string; value: MediaDataSource | null }[]>(() => [ + { title: t('dialog.reorganize.auto'), value: null }, { title: t('setting.cache.recognitionSource.themoviedb'), value: 'themoviedb' }, { title: t('setting.cache.recognitionSource.douban'), value: 'douban' }, { title: t('setting.cache.recognitionSource.bangumi'), value: 'bangumi' }, @@ -54,7 +55,7 @@ const mediaSourceItems = computed<{ title: string; value: MediaDataSource }[]>(( { title: t('setting.cache.recognitionSource.doubanmusic'), value: 'doubanmusic' }, ]) -// 获取后台设置中的默认识别数据源,未知值兼容回退到TheMovieDb。 +/** 获取后台设置中的默认识别数据源,未知值兼容回退到 TheMovieDb。 */ function getDefaultMediaSource(): MediaDataSource { const configuredSource = globalSettings.RECOGNIZE_SOURCE as MediaDataSource return mediaSourceItems.value.some(item => item.value === configuredSource) ? configuredSource : 'themoviedb' @@ -329,7 +330,8 @@ const transferForm = reactive({ logid: 0, target_storage: initialTargetPath ? (props.target_storage ?? 'local') : null, target_path: initialTargetPath, - media_source: getDefaultMediaSource(), + type_name: '', + media_source: null, media_id: null, music_type: null, transfer_type: null, @@ -345,8 +347,11 @@ const transferForm = reactive({ // 历史记录入口和文件浏览器命中的成功历史都属于重新整理。 const isReorganize = computed(() => Boolean(props.logids?.length || transferForm.reorganize)) -// 当前手动识别与刮削数据源。 -const mediaSource = computed(() => transferForm.media_source ?? 'themoviedb') +// 当前手动识别与刮削数据源;自动模式按媒体类型解析实际来源。 +const mediaSource = computed(() => { + if (transferForm.media_source) return transferForm.media_source + return transferForm.type_name === '音乐' ? 'musicbrainz' : getDefaultMediaSource() +}) // 当前数据源对应的原生ID标签。 const mediaIdLabel = computed(() => { @@ -481,7 +486,7 @@ watch([() => transferForm.type_name, () => mediaSource.value], ([typeName, sourc watch( () => transferForm.type_name, typeName => { - if (typeName === '音乐' && !isMusicMediaSource(transferForm.media_source)) { + if (typeName === '音乐' && !isMusicMediaSource(transferForm.media_source ?? undefined)) { transferForm.media_source = 'musicbrainz' } transferForm.music_type = typeName === '音乐' ? (transferForm.music_type ?? 'recording') : null @@ -492,11 +497,11 @@ watch( watch( () => transferForm.media_source, (source, previousSource) => { - if (previousSource && source !== previousSource) { + if (source !== previousSource) { transferForm.media_id = null mediaSelectorDialog.value = false } - if (isMusicMediaSource(source) && transferForm.type_name !== '音乐') { + if (isMusicMediaSource(source ?? undefined) && transferForm.type_name !== '音乐') { transferForm.type_name = '音乐' } }, diff --git a/src/components/dialog/TransferQueueDialog.vue b/src/components/dialog/TransferQueueDialog.vue index 33e5c9ee..ef56b5d8 100644 --- a/src/components/dialog/TransferQueueDialog.vue +++ b/src/components/dialog/TransferQueueDialog.vue @@ -442,6 +442,7 @@ onUnmounted(() => { :src="getPosterUrl(group.media)" :alt="getMediaTitle(group.media, group.season)" cover + rounded="md" />
{{ getMediaTitle(group.media, group.season) }}
@@ -467,6 +468,7 @@ onUnmounted(() => { :src="getPosterUrl(activeMediaGroup?.media)" :alt="getMediaTitle(activeMediaGroup?.media, activeMediaGroup?.season)" cover + rounded="md" />

@@ -633,20 +635,6 @@ onUnmounted(() => { border-block-start: 0; } -.media-selector__item::before { - position: absolute; - border-radius: var(--app-vuetify-rounded-pill); - background: rgb(var(--v-theme-primary)); - block-size: 0; - content: ''; - inline-size: 3px; - inset-block-start: 50%; - inset-inline-start: 0.25rem; - transition: - block-size 0.18s ease, - inset-block-start 0.18s ease; -} - .media-selector__item:hover { background: rgba(var(--v-theme-on-surface), var(--v-hover-opacity)); } @@ -655,14 +643,8 @@ onUnmounted(() => { background: rgba(var(--v-theme-primary), 0.08); } -.media-selector__item--active::before { - block-size: calc(100% - 1rem); - inset-block-start: 0.5rem; -} - .media-selector__poster, .active-media__poster { - border-radius: var(--app-control-radius); background: rgba(var(--v-theme-on-surface), 0.06); } diff --git a/src/components/dialog/__tests__/ReorganizeDialog.spec.ts b/src/components/dialog/__tests__/ReorganizeDialog.spec.ts index 9293e3ff..c89e9bdd 100644 --- a/src/components/dialog/__tests__/ReorganizeDialog.spec.ts +++ b/src/components/dialog/__tests__/ReorganizeDialog.spec.ts @@ -470,6 +470,13 @@ describe('ReorganizeDialog payloads and lifecycle', () => { vi.spyOn(console, 'warn').mockImplementation(() => {}) }) + it('shows automatic defaults when media type and source are unset', async () => { + await renderDialog() + + expect(screen.getByLabelText('类型')).toHaveDisplayValue('自动') + expect(screen.getByLabelText('数据源')).toHaveDisplayValue('自动') + }) + it('deduplicates selected files and submits nullable automatic target fields in one background request', async () => { const bodies: unknown[] = [] const backgrounds: string[] = [] @@ -730,7 +737,7 @@ describe('ReorganizeDialog payloads and lifecycle', () => { }), ) - await selectOption('数据源', 1) + await selectOption('数据源', 2) await user.click(screen.getByRole('button', { name: '加入整理队列' })) await waitFor(() => expect(bodies).toHaveLength(2)) expect(bodies[1]).toEqual( diff --git a/src/components/dialog/__tests__/TransferQueueDialog.spec.ts b/src/components/dialog/__tests__/TransferQueueDialog.spec.ts index 4a389419..bb0ac664 100644 --- a/src/components/dialog/__tests__/TransferQueueDialog.spec.ts +++ b/src/components/dialog/__tests__/TransferQueueDialog.spec.ts @@ -4,8 +4,13 @@ import { screen, waitFor, within } from '@testing-library/vue' import { renderWithProviders } from '@tests/support/render' import { flushPromises } from '@vue/test-utils' import userEvent from '@testing-library/user-event' +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { cwd } from 'node:process' import { beforeEach, describe, expect, it, vi } from 'vitest' +const transferQueueSource = readFileSync(resolve(cwd(), 'src/components/dialog/TransferQueueDialog.vue'), 'utf8') + const mocks = vi.hoisted(() => ({ apiDelete: vi.fn(), apiGet: vi.fn(), @@ -197,6 +202,19 @@ describe('TransferQueueDialog', () => { expect(screen.queryByText('来源 A.mkv')).not.toBeInTheDocument() }) + it('uses fixed medium poster rounding without an active-card accent strip', async () => { + mocks.apiGet.mockResolvedValue(createQueue('圆角媒体', '/downloads/rounded.mkv')) + + const { container } = await renderDialog() + + await screen.findByRole('navigation', { name: '媒体队列' }) + expect(container.querySelector('.media-selector__poster')).toHaveClass('rounded-md') + expect(container.querySelector('.active-media__poster')).toHaveClass('rounded-md') + expect(transferQueueSource).not.toContain('.media-selector__item::before') + expect(transferQueueSource).not.toContain('.media-selector__item--active::before') + expect(transferQueueSource).not.toContain('border-radius: var(--app-control-radius)') + }) + it('uses canonical built-in identities before falling back to the title', async () => { const builtIn = createQueueItem({ id: 7301, diff --git a/src/views/reorganize/TransferHistoryView.vue b/src/views/reorganize/TransferHistoryView.vue index fae39a2e..4e869e21 100644 --- a/src/views/reorganize/TransferHistoryView.vue +++ b/src/views/reorganize/TransferHistoryView.vue @@ -1489,7 +1489,7 @@ onUnmounted(() => {