From 849a43bed7e5f3fa4139f5ff5ae3b2dba2b42095 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 3 Sep 2026 06:47:22 +0800 Subject: [PATCH] fix(history): refine status filter layout --- src/views/reorganize/TransferHistoryView.vue | 218 ++++++++++++------ .../__tests__/TransferHistoryView.spec.ts | 28 +++ 2 files changed, 178 insertions(+), 68 deletions(-) diff --git a/src/views/reorganize/TransferHistoryView.vue b/src/views/reorganize/TransferHistoryView.vue index 42ac4c8f..8e3df8eb 100644 --- a/src/views/reorganize/TransferHistoryView.vue +++ b/src/views/reorganize/TransferHistoryView.vue @@ -92,15 +92,46 @@ const completedDeleteSteps = new Map(getRouteStatusFilter(route.query.status)) +const mobileStatusFilterMenu = ref(false) -const statusFilterItems = computed(() => [ - { title: t('transferHistory.statusFilter.all'), value: 'all' }, - { title: t('transferHistory.status.success'), value: 'success' }, - { title: t('transferHistory.status.failed'), value: 'failed' }, +const statusFilterItems = computed(() => [ + { title: t('transferHistory.statusFilter.all'), value: 'all', icon: 'mdi-filter-multiple-outline' }, + { title: t('transferHistory.status.success'), value: 'success', icon: 'mdi-check-circle', color: 'success' }, + { title: t('transferHistory.status.failed'), value: 'failed', icon: 'mdi-alert-circle', color: 'error' }, ]) +const currentStatusFilter = computed(() => { + return ( + statusFilterItems.value.find(item => item.value === statusFilter.value) ?? { + title: t('transferHistory.statusFilter.all'), + value: 'all', + icon: 'mdi-filter-multiple-outline', + } + ) +}) + +const statusFilterButtonColor = computed(() => + statusFilter.value === 'all' ? 'gray' : currentStatusFilter.value.color, +) + +// 移动端状态筛选沿用订阅页的外部激活器菜单,确保按钮固定在标题栏右侧。 +const mobileStatusFilterActivator = computed(() => '[data-menu-activator="history-status-filter-btn"]') + +// 选择移动端状态后立即关闭菜单,状态监听器负责刷新列表并同步地址栏。 +function selectStatusFilter(value: TransferHistoryStatusFilter) { + statusFilter.value = value + mobileStatusFilterMenu.value = false +} + // 移动端批量选择模式 const mobileBatchMode = ref(false) @@ -1601,40 +1632,43 @@ onUnmounted(() => { - - - - - + +
+ + +
@@ -1904,20 +1938,32 @@ onUnmounted(() => { class="transfer-history-mobile-title my-0" style="margin-block: 0" /> - +
+ + +
{ clearable /> - + + + + + {{ t('transferHistory.statusFilter.label') }} + + + {{ option.title }} + + + + + + { border-radius: 0; } +.transfer-history-desktop-filter-group { + display: flex; + inline-size: min(100%, 36rem); +} + +.transfer-history-desktop-search { + flex: 1 1 auto; + min-inline-size: 12rem; +} + +.transfer-history-desktop-search :deep(.v-field) { + border-start-end-radius: 0; + border-end-end-radius: 0; +} + +.transfer-history-desktop-status { + flex: 0 0 10rem; + margin-inline-start: -1px; +} + +.transfer-history-desktop-status :deep(.v-field) { + border-start-start-radius: 0; + border-end-start-radius: 0; +} + .transfer-history-desktop-media-cell { display: flex; align-items: center; @@ -2236,8 +2320,10 @@ onUnmounted(() => { min-inline-size: 0; } -.transfer-history-mobile-titlebar__batch { +.transfer-history-mobile-titlebar__actions { + display: flex; flex: 0 0 auto; + align-items: center; } .transfer-history-mobile-title :deep(h2) { @@ -2249,10 +2335,6 @@ onUnmounted(() => { min-inline-size: 0; } -.transfer-history-mobile-status { - margin-block: 0.75rem; -} - .transfer-history-mobile-search :deep(.v-field) { background: var(--transfer-history-mobile-search-bg); min-block-size: 3.75rem; diff --git a/src/views/reorganize/__tests__/TransferHistoryView.spec.ts b/src/views/reorganize/__tests__/TransferHistoryView.spec.ts index 692edf98..af8957f0 100644 --- a/src/views/reorganize/__tests__/TransferHistoryView.spec.ts +++ b/src/views/reorganize/__tests__/TransferHistoryView.spec.ts @@ -438,6 +438,34 @@ describe('TransferHistoryView', () => { await waitFor(() => expect(requests).toEqual([{ count: 50, page: 1, status: false, title: '失败' }])) }) + it('joins the desktop status filter to search and moves the mobile filter into the titlebar menu', () => { + expect(transferHistorySource).toContain('class="transfer-history-desktop-filter-group"') + expect(transferHistorySource).toContain('class="text-disabled transfer-history-desktop-search"') + expect(transferHistorySource).toContain('class="transfer-history-desktop-status"') + expect(transferHistorySource).toContain('border-start-end-radius: 0;') + expect(transferHistorySource).toContain('border-start-start-radius: 0;') + expect(transferHistorySource).toContain('data-menu-activator="history-status-filter-btn"') + expect(transferHistorySource).not.toContain('class="transfer-history-mobile-status"') + }) + + it('selects a mobile status from the titlebar dropdown and refreshes with the explicit status query', async () => { + mocks.desktop = false + const requests: Array> = [] + mocks.apiGet.mockImplementation((path: string, config?: { params?: Record }) => { + if (path === 'system/setting/public/Storages') return Promise.resolve(storageResponse()) + requests.push(config?.params ?? {}) + return Promise.resolve(historyResponse([])) + }) + + const { router } = await renderHistory() + await fireEvent.click(screen.getByRole('button', { name: '状态筛选' })) + await fireEvent.click(screen.getByRole('button', { name: '失败' })) + + await waitFor(() => expect(router.currentRoute.value.query.status).toBe('failed')) + await fireEvent.click(screen.getByRole('button', { name: '加载下一页' })) + await waitFor(() => expect(requests).toContainEqual({ count: 25, page: 1, status: false, title: '' })) + }) + it('prevents an older desktop request from replacing a newer route search', async () => { const oldRequest = createDeferred>() const newRequest = createDeferred>()