mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-07-07 07:21:29 +08:00
fix: adjust file manager selection toolbar
This commit is contained in:
@@ -113,10 +113,12 @@ const episodeFormatRecommendState = reactive<{
|
|||||||
|
|
||||||
const episodeFormatRuleConfigured = ref<boolean | undefined>(undefined)
|
const episodeFormatRuleConfigured = ref<boolean | undefined>(undefined)
|
||||||
|
|
||||||
|
// 生成文件项稳定键,用于去重和状态同步。
|
||||||
function getFileItemKey(item?: FileItem) {
|
function getFileItemKey(item?: FileItem) {
|
||||||
return [item?.storage ?? '', item?.type ?? '', item?.path ?? ''].join('|')
|
return [item?.storage ?? '', item?.type ?? '', item?.path ?? ''].join('|')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按存储、类型和路径去重文件项。
|
||||||
function dedupeFileItems(fileItems?: FileItem[]) {
|
function dedupeFileItems(fileItems?: FileItem[]) {
|
||||||
if (!fileItems?.length) return []
|
if (!fileItems?.length) return []
|
||||||
|
|
||||||
@@ -128,6 +130,7 @@ function dedupeFileItems(fileItems?: FileItem[]) {
|
|||||||
return Array.from(uniqueItems.values())
|
return Array.from(uniqueItems.values())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成预览项稳定键,避免合并多次预览结果时重复展示。
|
||||||
function getPreviewItemKey(item: ManualTransferPreviewItem) {
|
function getPreviewItemKey(item: ManualTransferPreviewItem) {
|
||||||
return [item.source ?? '', item.target ?? '', item.success === false ? 'failed' : 'success'].join('|')
|
return [item.source ?? '', item.target ?? '', item.success === false ? 'failed' : 'success'].join('|')
|
||||||
}
|
}
|
||||||
@@ -438,6 +441,7 @@ const previewToggleIcon = computed(() => {
|
|||||||
return previewVisible.value ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
|
return previewVisible.value ? 'mdi-eye-off-outline' : 'mdi-eye-outline'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 获取文件父目录键,用于判断多文件是否来自同一目录。
|
||||||
function getFileParentKey(item?: FileItem) {
|
function getFileParentKey(item?: FileItem) {
|
||||||
if (!item?.path) return ''
|
if (!item?.path) return ''
|
||||||
const storage = item.storage ?? 'local'
|
const storage = item.storage ?? 'local'
|
||||||
@@ -502,10 +506,12 @@ watch(
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 判断文件集合是否可以按批量文件请求提交。
|
||||||
function shouldUseBatchFileItems(items: FileItem[]) {
|
function shouldUseBatchFileItems(items: FileItem[]) {
|
||||||
return items.length > 0 && items.every(item => item.type === 'file')
|
return items.length > 0 && items.every(item => item.type === 'file')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成批量文件在提示和错误信息中的显示名称。
|
||||||
function getBatchItemsLabel(items: FileItem[]) {
|
function getBatchItemsLabel(items: FileItem[]) {
|
||||||
if (items.length === 1) return items[0].path || items[0].name
|
if (items.length === 1) return items[0].path || items[0].name
|
||||||
return t('dialog.reorganize.multipleItemsTitle', { count: items.length })
|
return t('dialog.reorganize.multipleItemsTitle', { count: items.length })
|
||||||
@@ -540,9 +546,10 @@ async function requestManualTransfer<T = any>(
|
|||||||
payload: ManualTransferPayload,
|
payload: ManualTransferPayload,
|
||||||
background: boolean = false,
|
background: boolean = false,
|
||||||
): Promise<ApiResponse<T>> {
|
): Promise<ApiResponse<T>> {
|
||||||
return await api.post(`transfer/manual?background=${background}`, payload)
|
return await api.post<ApiResponse<T>, ApiResponse<T>>(`transfer/manual?background=${background}`, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载剧集格式规则配置状态,用于决定是否允许自动推荐。
|
||||||
async function loadEpisodeFormatRuleConfiguration() {
|
async function loadEpisodeFormatRuleConfiguration() {
|
||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.get('system/setting/EpisodeFormatRuleTable')
|
const result: { [key: string]: any } = await api.get('system/setting/EpisodeFormatRuleTable')
|
||||||
@@ -553,6 +560,7 @@ async function loadEpisodeFormatRuleConfiguration() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据当前文件或同目录多文件请求推荐剧集格式。
|
||||||
async function handleRecommendEpisodeFormat() {
|
async function handleRecommendEpisodeFormat() {
|
||||||
const sourceItem = episodeFormatRecommendSourceItem.value
|
const sourceItem = episodeFormatRecommendSourceItem.value
|
||||||
const selectedFileItems = episodeFormatRecommendSelectedFileItems.value
|
const selectedFileItems = episodeFormatRecommendSelectedFileItems.value
|
||||||
@@ -575,7 +583,7 @@ async function handleRecommendEpisodeFormat() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const hasExistingEpisodeFormat = Boolean(transferForm.episode_format?.trim())
|
const hasExistingEpisodeFormat = Boolean(transferForm.episode_format?.trim())
|
||||||
const result = await api.post(
|
const result = await api.post<ApiResponse<EpisodeFormatRecommendData>, ApiResponse<EpisodeFormatRecommendData>>(
|
||||||
'transfer/episode-format/recommend',
|
'transfer/episode-format/recommend',
|
||||||
hasValidSelectedFiles
|
hasValidSelectedFiles
|
||||||
? {
|
? {
|
||||||
@@ -591,7 +599,7 @@ async function handleRecommendEpisodeFormat() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = (result.data ?? {}) as EpisodeFormatRecommendData
|
const data = result.data ?? {}
|
||||||
if (!data.episode_format) {
|
if (!data.episode_format) {
|
||||||
$toast.error(t('dialog.reorganize.episodeFormatRecommendFailed'))
|
$toast.error(t('dialog.reorganize.episodeFormatRecommendFailed'))
|
||||||
return
|
return
|
||||||
@@ -615,7 +623,7 @@ async function handleRecommendEpisodeFormat() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认预览数据
|
// 创建空预览数据,作为多次预览结果的合并目标。
|
||||||
function getDefaultPreviewData(): ManualTransferPreviewData {
|
function getDefaultPreviewData(): ManualTransferPreviewData {
|
||||||
return {
|
return {
|
||||||
summary: {
|
summary: {
|
||||||
@@ -628,18 +636,21 @@ function getDefaultPreviewData(): ManualTransferPreviewData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重置预览数据和分页状态。
|
||||||
function resetPreviewState() {
|
function resetPreviewState() {
|
||||||
previewData.value = undefined
|
previewData.value = undefined
|
||||||
previewLoaded.value = false
|
previewLoaded.value = false
|
||||||
previewPage.value = 1
|
previewPage.value = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 判断预览结果中是否存在失败项。
|
||||||
function previewHasFailures(data?: ManualTransferPreviewData) {
|
function previewHasFailures(data?: ManualTransferPreviewData) {
|
||||||
if (!data) return false
|
if (!data) return false
|
||||||
|
|
||||||
return (data.summary.failed ?? 0) > 0 || (data.items ?? []).some(item => item.success === false)
|
return (data.summary.failed ?? 0) > 0 || (data.items ?? []).some(item => item.success === false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成预览结果成功和失败数量摘要。
|
||||||
function getPreviewResultSummaryMessage(data?: ManualTransferPreviewData) {
|
function getPreviewResultSummaryMessage(data?: ManualTransferPreviewData) {
|
||||||
const success = data?.summary.success ?? 0
|
const success = data?.summary.success ?? 0
|
||||||
const failed = data?.summary.failed ?? 0
|
const failed = data?.summary.failed ?? 0
|
||||||
@@ -650,6 +661,7 @@ function getPreviewResultSummaryMessage(data?: ManualTransferPreviewData) {
|
|||||||
].join(',')
|
].join(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构造单条失败预览数据,便于把异常请求合并到预览列表。
|
||||||
function createFailedPreviewData(options: { source?: string; type?: string; title?: string; message?: string }) {
|
function createFailedPreviewData(options: { source?: string; type?: string; title?: string; message?: string }) {
|
||||||
const failedItem: ManualTransferPreviewItem = {
|
const failedItem: ManualTransferPreviewItem = {
|
||||||
source: options.source,
|
source: options.source,
|
||||||
@@ -824,6 +836,7 @@ async function previewTransfer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切换预览面板,首次展开时拉取最新预览结果。
|
||||||
async function togglePreview() {
|
async function togglePreview() {
|
||||||
if (previewLoading.value) return
|
if (previewLoading.value) return
|
||||||
|
|
||||||
@@ -886,6 +899,7 @@ async function handleTransfer(item: FileItem, background: boolean = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量整理文件并按后台模式决定是否提示入队成功。
|
||||||
async function handleTransferBatch(items: FileItem[], background: boolean = false) {
|
async function handleTransferBatch(items: FileItem[], background: boolean = false) {
|
||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await requestManualTransfer(createTransferPayload({ items }), background)
|
const result: { [key: string]: any } = await requestManualTransfer(createTransferPayload({ items }), background)
|
||||||
|
|||||||
@@ -772,19 +772,16 @@ onUnmounted(() => {
|
|||||||
rounded
|
rounded
|
||||||
/>
|
/>
|
||||||
<VSpacer v-if="isFile" />
|
<VSpacer v-if="isFile" />
|
||||||
<IconBtn v-if="!isFile" @click="ignoreCase = !ignoreCase">
|
<IconBtn v-if="!isFile && !selectMode" @click="ignoreCase = !ignoreCase">
|
||||||
<VIcon :color="ignoreCase ? 'primary' : 'error'" icon="mdi-format-letter-case" />
|
<VIcon :color="ignoreCase ? 'primary' : 'error'" icon="mdi-format-letter-case" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
<IconBtn v-if="!isFile" @click="changeSelectMode">
|
|
||||||
<VIcon color="primary" :icon="selectMode ? 'mdi-selection-remove' : 'mdi-select'" />
|
|
||||||
</IconBtn>
|
|
||||||
<IconBtn v-if="isFile" @click="recognize(inProps.item.path || '')">
|
<IconBtn v-if="isFile" @click="recognize(inProps.item.path || '')">
|
||||||
<VIcon color="primary"> mdi-text-recognition </VIcon>
|
<VIcon color="primary"> mdi-text-recognition </VIcon>
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
<IconBtn v-if="isFile && items.length > 0" @click="download(items[0])">
|
<IconBtn v-if="isFile && items.length > 0" @click="download(items[0])">
|
||||||
<VIcon color="primary"> mdi-download </VIcon>
|
<VIcon color="primary"> mdi-download </VIcon>
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
<IconBtn v-if="!isFile" @click="list_files">
|
<IconBtn v-if="!isFile && !selectMode" @click="list_files">
|
||||||
<VIcon color="primary"> mdi-refresh </VIcon>
|
<VIcon color="primary"> mdi-refresh </VIcon>
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
<!-- 批量操作按钮 -->
|
<!-- 批量操作按钮 -->
|
||||||
@@ -799,6 +796,9 @@ onUnmounted(() => {
|
|||||||
<VIcon icon="mdi-delete-outline" color="error" />
|
<VIcon icon="mdi-delete-outline" color="error" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
</span>
|
</span>
|
||||||
|
<IconBtn v-if="!isFile" @click="changeSelectMode">
|
||||||
|
<VIcon color="primary" :icon="selectMode ? 'mdi-selection-remove' : 'mdi-select'" />
|
||||||
|
</IconBtn>
|
||||||
</div>
|
</div>
|
||||||
<LoadingBanner v-if="loading" />
|
<LoadingBanner v-if="loading" />
|
||||||
<!-- 文件详情 -->
|
<!-- 文件详情 -->
|
||||||
|
|||||||
Reference in New Issue
Block a user