fix(reorganize): refine transfer dialog layouts

This commit is contained in:
jxxghp
2026-08-13 15:22:03 +08:00
parent 4aaeac360e
commit 6a0217d18c
7 changed files with 71 additions and 50 deletions
+13 -8
View File
@@ -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<TransferForm>({
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<TransferForm>({
// 历史记录入口和文件浏览器命中的成功历史都属于重新整理。
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 = '音乐'
}
},
+2 -20
View File
@@ -442,6 +442,7 @@ onUnmounted(() => {
:src="getPosterUrl(group.media)"
:alt="getMediaTitle(group.media, group.season)"
cover
rounded="md"
/>
<div class="media-selector__info">
<div class="media-selector__title">{{ getMediaTitle(group.media, group.season) }}</div>
@@ -467,6 +468,7 @@ onUnmounted(() => {
:src="getPosterUrl(activeMediaGroup?.media)"
:alt="getMediaTitle(activeMediaGroup?.media, activeMediaGroup?.season)"
cover
rounded="md"
/>
<div class="active-media__info">
<h3 class="active-media__title">
@@ -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);
}
@@ -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<HTMLSelectElement>('类型')).toHaveDisplayValue('自动')
expect(screen.getByLabelText<HTMLSelectElement>('数据源')).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(
@@ -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,