mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-08 17:26:41 +08:00
fix(history): scope batch actions to visible records
This commit is contained in:
@@ -509,6 +509,13 @@ async function fetchData(page = currentPage.value, count = itemsPerPage.value, o
|
|||||||
|
|
||||||
isRefreshed.value = true
|
isRefreshed.value = true
|
||||||
dataList.value = list
|
dataList.value = list
|
||||||
|
if (isDesktop.value && selected.value.length > 0) {
|
||||||
|
const refreshedItems = new Map(list.map(item => [item.id, item]))
|
||||||
|
selected.value = selected.value.flatMap(item => {
|
||||||
|
const refreshed = refreshedItems.get(item.id)
|
||||||
|
return refreshed ? [refreshed] : []
|
||||||
|
})
|
||||||
|
}
|
||||||
totalItems.value = ensureNumber(result.total, 0)
|
totalItems.value = ensureNumber(result.total, 0)
|
||||||
updateSearchHintList(list)
|
updateSearchHintList(list)
|
||||||
|
|
||||||
@@ -871,8 +878,9 @@ async function removeBatch(deleteSrc: boolean, deleteDest: boolean) {
|
|||||||
if (hasRunningAiRedo.value) return
|
if (hasRunningAiRedo.value) return
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
closeDeleteConfirmDialog()
|
closeDeleteConfirmDialog()
|
||||||
|
const batchItems = scopeSelectionToVisibleHistory()
|
||||||
// 总条数
|
// 总条数
|
||||||
const total = selected.value.length
|
const total = batchItems.length
|
||||||
if (total === 0) return
|
if (total === 0) return
|
||||||
|
|
||||||
// 已处理条数
|
// 已处理条数
|
||||||
@@ -882,7 +890,7 @@ async function removeBatch(deleteSrc: boolean, deleteDest: boolean) {
|
|||||||
// 显示进度条
|
// 显示进度条
|
||||||
openProgressDialog()
|
openProgressDialog()
|
||||||
// 循环调用removeHistory
|
// 循环调用removeHistory
|
||||||
for (const item of selected.value) {
|
for (const item of batchItems) {
|
||||||
// 开始删除
|
// 开始删除
|
||||||
const seasonEpisode = `${item.seasons || ''}${item.episodes || ''}`
|
const seasonEpisode = `${item.seasons || ''}${item.episodes || ''}`
|
||||||
const name = [item.title, seasonEpisode].filter(Boolean).join(' ')
|
const name = [item.title, seasonEpisode].filter(Boolean).join(' ')
|
||||||
@@ -930,12 +938,13 @@ async function deleteConfirmHandler(deleteSrc: boolean, deleteDest: boolean) {
|
|||||||
// 批量删除历史记录
|
// 批量删除历史记录
|
||||||
async function removeHistoryBatch() {
|
async function removeHistoryBatch() {
|
||||||
if (hasRunningAiRedo.value) return
|
if (hasRunningAiRedo.value) return
|
||||||
if (selected.value.length === 0) return
|
const batchItems = scopeSelectionToVisibleHistory()
|
||||||
|
if (batchItems.length === 0) return
|
||||||
|
|
||||||
// 清空当前操作记录
|
// 清空当前操作记录
|
||||||
currentHistory.value = undefined
|
currentHistory.value = undefined
|
||||||
confirmTitle.value = t('transferHistory.deleteConfirmBatch', {
|
confirmTitle.value = t('transferHistory.deleteConfirmBatch', {
|
||||||
count: selected.value.length,
|
count: batchItems.length,
|
||||||
})
|
})
|
||||||
// 打开确认弹窗
|
// 打开确认弹窗
|
||||||
openDeleteConfirmDialog()
|
openDeleteConfirmDialog()
|
||||||
@@ -943,12 +952,13 @@ async function removeHistoryBatch() {
|
|||||||
// 批量重新整理
|
// 批量重新整理
|
||||||
async function retransferBatch() {
|
async function retransferBatch() {
|
||||||
if (hasRunningAiRedo.value) return
|
if (hasRunningAiRedo.value) return
|
||||||
if (selected.value.length === 0) return
|
const batchItems = scopeSelectionToVisibleHistory()
|
||||||
|
if (batchItems.length === 0) return
|
||||||
|
|
||||||
// 清空当前操作记录
|
// 清空当前操作记录
|
||||||
currentHistory.value = undefined
|
currentHistory.value = undefined
|
||||||
// 重新整理IDS
|
// 重新整理IDS
|
||||||
redoIds.value = selected.value.map(item => item.id)
|
redoIds.value = batchItems.map(item => item.id)
|
||||||
// 打开识别弹窗
|
// 打开识别弹窗
|
||||||
openRedoDialog()
|
openRedoDialog()
|
||||||
}
|
}
|
||||||
@@ -1091,7 +1101,7 @@ async function triggerBatchAiRedo() {
|
|||||||
}
|
}
|
||||||
if (hasRunningAiRedo.value) return
|
if (hasRunningAiRedo.value) return
|
||||||
|
|
||||||
const historyIds = [...new Set(selected.value.map(item => item.id))]
|
const historyIds = [...new Set(scopeSelectionToVisibleHistory().map(item => item.id))]
|
||||||
if (historyIds.length === 0) return
|
if (historyIds.length === 0) return
|
||||||
|
|
||||||
aiRedoIds.value = [...new Set([...aiRedoIds.value, ...historyIds])]
|
aiRedoIds.value = [...new Set([...aiRedoIds.value, ...historyIds])]
|
||||||
@@ -1226,6 +1236,14 @@ function ensurePageSize(value: any, defaultValue: number = 50) {
|
|||||||
// 已选历史记录 ID 集合,供移动端卡片和分组选择状态复用。
|
// 已选历史记录 ID 集合,供移动端卡片和分组选择状态复用。
|
||||||
const selectedIdSet = computed(() => new Set(selected.value.map(item => item.id)))
|
const selectedIdSet = computed(() => new Set(selected.value.map(item => item.id)))
|
||||||
|
|
||||||
|
// 将批量操作限制在当前可见数据源,避免搜索或分页后遗留的隐藏选择项被处理。
|
||||||
|
function scopeSelectionToVisibleHistory() {
|
||||||
|
const visibleItems = isMobile.value ? mobileDataList.value : dataList.value
|
||||||
|
const visibleSelected = visibleItems.filter(item => selectedIdSet.value.has(item.id))
|
||||||
|
selected.value = visibleSelected
|
||||||
|
return visibleSelected
|
||||||
|
}
|
||||||
|
|
||||||
// 移动端当前已加载记录数量,用于批量菜单展示选择进度。
|
// 移动端当前已加载记录数量,用于批量菜单展示选择进度。
|
||||||
const mobileBatchTotalCount = computed(() => mobileDataList.value.length)
|
const mobileBatchTotalCount = computed(() => mobileDataList.value.length)
|
||||||
|
|
||||||
|
|||||||
@@ -458,6 +458,35 @@ describe('TransferHistoryView', () => {
|
|||||||
expect(screen.queryByText('旧结果')).not.toBeInTheDocument()
|
expect(screen.queryByText('旧结果')).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('drops hidden desktop selections before a filtered batch delete', async () => {
|
||||||
|
const hidden = createHistory(1, '筛选前记录')
|
||||||
|
const visible = createHistory(2, '筛选后记录')
|
||||||
|
mocks.apiGet.mockImplementation((path: string, config?: { params?: { title?: string } }) => {
|
||||||
|
if (path === 'system/setting/public/Storages') return Promise.resolve(storageResponse())
|
||||||
|
return Promise.resolve(historyResponse(config?.params?.title === 'new' ? [visible] : [hidden]))
|
||||||
|
})
|
||||||
|
|
||||||
|
const { router } = await renderHistory('/history')
|
||||||
|
expect(await screen.findByText('筛选前记录')).toBeInTheDocument()
|
||||||
|
await fireEvent.click(screen.getByRole('button', { name: '选择当前页' }))
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
await router.push('/history?search=new')
|
||||||
|
expect(await screen.findByText('筛选后记录')).toBeInTheDocument()
|
||||||
|
expect(getDynamicMenuItems()).toBeUndefined()
|
||||||
|
|
||||||
|
await fireEvent.click(screen.getByRole('button', { name: '选择当前页' }))
|
||||||
|
await nextTick()
|
||||||
|
runDynamicAction('transferHistory.actions.batchDelete')
|
||||||
|
await getDialogCall().events.delete(false, true)
|
||||||
|
|
||||||
|
expect(mocks.apiDelete).toHaveBeenCalledOnce()
|
||||||
|
expect(mocks.apiDelete).toHaveBeenCalledWith(
|
||||||
|
'history/transfer?deletesrc=false&deletedest=true',
|
||||||
|
expect.objectContaining({ data: expect.objectContaining({ id: 2 }) }),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it('constrains desktop Poster images to a fixed 2:3 cover frame', async () => {
|
it('constrains desktop Poster images to a fixed 2:3 cover frame', async () => {
|
||||||
const item = createHistory(1, '桌面海报', {
|
const item = createHistory(1, '桌面海报', {
|
||||||
image: '/poster.jpg',
|
image: '/poster.jpg',
|
||||||
|
|||||||
Reference in New Issue
Block a user