feat:文件管理批量选择

This commit is contained in:
jxxghp
2024-06-20 15:32:17 +08:00
parent 5dd071adf4
commit 4f67bb0250
3 changed files with 164 additions and 69 deletions

View File

@@ -13,7 +13,7 @@ const display = useDisplay()
// 输入参数
const props = defineProps({
path: String,
paths: Array<string>,
target: String,
logids: Array<number>,
})
@@ -50,7 +50,18 @@ const progressText = ref('请稍候 ...')
// 整理进度
const progressValue = ref(0)
// 文件转移表单
// 标题
const dialogTitle = computed(() => {
if (props.paths) {
if (props.paths.length > 1) return `整理 - 共 ${props.paths.length}`
return `整理 - ${props.paths[0]}`
} else if (props.logids) {
return `整理 - 共 ${props.logids.length}`
}
return '手动整理'
})
// 表单
const transferForm = reactive({
logid: 0,
path: '',
@@ -77,12 +88,6 @@ const targetDirectories = computed(() => {
return [...new Set(directories)]
})
// 监听输入变化
watchEffect(() => {
transferForm.path = props.path ?? ''
transferForm.target = props.target ?? null
})
// 监听目的路径变化,自动查询目录的刮削配置
watch(transferForm, async () => {
if (transferForm.target) {
@@ -117,47 +122,25 @@ function stopLoadingProgress() {
}
// 整理文件
// eslint-disable-next-line sonarjs/cognitive-complexity
async function transfer() {
if (!props.logids && !props.path) return
if (!props.logids && !props.paths) return
// 显示进度条
progressDialog.value = true
// 开始监听进度
startLoadingProgress()
if (props.path) {
// 文件整理
try {
const result: { [key: string]: any } = await api.post(
'transfer/manual',
{},
{
params: transferForm,
},
)
// 显示结果
if (result.success) $toast.success(`${props.path} 整理完成!`)
else $toast.error(`${props.path} 整理失败:${result.message}`)
} catch (e) {
console.log(e)
// 文件整理
if (props.paths) {
for (const path of props.paths) {
await handleTransfer(path)
}
} else if (props.logids) {
// 日志整理
}
// 日志整理
if (props.logids) {
for (const logid of props.logids) {
transferForm.logid = logid
try {
const result: { [key: string]: any } = await api.post(
'transfer/manual',
{},
{
params: transferForm,
},
)
if (!result.success) $toast.error(`历史记录 ${logid} 重新整理失败:${result.message}`)
} catch (e) {
console.log(e)
}
await handleTransferLog(logid)
}
}
@@ -169,6 +152,28 @@ async function transfer() {
emit('done')
}
// 整理文件
async function handleTransfer(path: string) {
transferForm.path = path
try {
const result: { [key: string]: any } = await api.post('transfer/manual', {}, { params: transferForm })
if (!result.success) $toast.error(`文件 ${path} 整理失败:${result.message}`)
} catch (e) {
console.log(e)
}
}
// 整理日志
async function handleTransferLog(logid: number) {
transferForm.logid = logid
try {
const result: { [key: string]: any } = await api.post('transfer/manual', {}, { params: transferForm })
if (!result.success) $toast.error(`历史记录 ${logid} 重新整理失败:${result.message}`)
} catch (e) {
console.log(e)
}
}
// 调用API加载当前系统环境设置
async function loadSystemSettings() {
try {
@@ -199,10 +204,7 @@ onMounted(() => {
<template>
<VDialog scrollable max-width="50rem" :fullscreen="!display.mdAndUp.value">
<VCard
:title="`${props.path ? `整理 - ${props.path}` : `整理 - 共 ${props.logids?.length} 条记录`}`"
class="rounded-t"
>
<VCard :title="dialogTitle" class="rounded-t">
<DialogCloseBtn @click="emit('close')" />
<VDivider />
<VCardText>

View File

@@ -46,6 +46,9 @@ const createConfirm = useConfirm()
// 提示框
const $toast = useToast()
// 是否选择模式
const selectMode = ref(false)
// 是否正在加载
const loading = ref(true)
@@ -82,6 +85,9 @@ const renameAll = ref(false)
// 当前操作项
const currentItem = ref<FileItem>()
// 选中的项目
const selected = ref<FileItem[]>([])
// 识别结果
const nameTestResult = ref<Context>()
@@ -105,6 +111,16 @@ const isDir = computed(() => inProps.item.path?.endsWith('/'))
// 是否文件
const isFile = computed(() => !isDir.value)
// 需要整理的path
const transferPaths = ref<string[]>([])
// 大小控制
const scrollStyle = computed(() => {
return appMode
? 'height: calc(100vh - 15.5rem - env(safe-area-inset-bottom) - 3.5rem)'
: 'height: calc(100vh - 14.5rem - env(safe-area-inset-bottom)'
})
// 是否为图片文件
const isImage = computed(() => {
const ext = inProps.item.path?.split('.').pop()?.toLowerCase()
@@ -158,12 +174,61 @@ async function deleteItem(item: FileItem) {
}
}
// 批量删除
async function batchDelete() {
const confirmed = await createConfirm({
title: '确认',
content: `是否确认删除选中的 ${selected.value.length} 个项目?`,
})
if (confirmed) {
emit('loading', true)
// 显示进度条
progressDialog.value = true
progressValue.value = 0
// 删除选中的项目
selected.value.every(async item => {
progressText.value = `正在删除 ${item.name} ...`
const url = inProps.endpoints?.delete.url.replace(/{storage}/g, inProps.storage)
const config: AxiosRequestConfig<FileItem> = {
url,
method: inProps.endpoints?.delete.method || 'post',
data: item,
}
await inProps.axios.request(config)
})
// 关闭进度条
progressDialog.value = false
emit('filedeleted')
emit('loading', false)
// 重新加载
list_files()
}
}
// 切换路径
function changePath(item: FileItem) {
item.path = inProps.item.path + item.name + (item.type === 'dir' ? '/' : '')
emit('pathchanged', item)
}
// 点击列表项
function listItemClick(item: FileItem) {
if (selectMode.value) {
if (selected.value.includes(item)) {
selected.value = selected.value.filter(i => i !== item)
} else {
selected.value.push(item)
}
return false
}
changePath(item)
}
// 新窗口中下载文件
async function download(item: FileItem) {
const url = inProps.endpoints?.download.url.replace(/{storage}/g, inProps.storage)
@@ -264,10 +329,22 @@ async function rename() {
// 显示整理对话框
function showTransfer(item: FileItem) {
currentItem.value = item
transferPaths.value = [item.path || '']
transferPopper.value = true
}
// 显示批量整理对话框
function showBatchTransfer() {
transferPaths.value = selected.value.map(item => item.path || '')
transferPopper.value = true
}
// 整理完成
function transferDone() {
transferPopper.value = false
list_files()
}
// 将文件修改时间timestape转换为本地时间
function formatTime(timestape: number) {
return new Date(timestape * 1000).toLocaleString()
@@ -392,6 +469,13 @@ async function scrape(path: string) {
}
}
// 批量刮削
async function batchScrape() {
selected.value.map(item => {
scrape(item.path || '')
})
}
// 使用SSE监听加载进度
function startLoadingProgress() {
progressText.value = '请稍候 ...'
@@ -436,6 +520,10 @@ onMounted(() => {
rounded="0"
/>
<VSpacer v-if="isFile" />
<IconBtn v-if="!isFile" @click="selectMode = !selectMode">
<VIcon color="primary" v-if="selectMode"> mdi-selection-remove </VIcon>
<VIcon color="primary" v-else>mdi-select</VIcon>
</IconBtn>
<IconBtn v-if="isFile" @click="recognize(inProps.item.path || '')">
<VIcon color="primary"> mdi-text-recognition </VIcon>
</IconBtn>
@@ -445,6 +533,18 @@ onMounted(() => {
<IconBtn v-if="!isFile" @click="list_files">
<VIcon color="primary"> mdi-refresh </VIcon>
</IconBtn>
<!-- 批量操作按钮 -->
<span v-if="selected.length > 0">
<IconBtn @click.stop="batchScrape">
<VIcon color="primary" icon="mdi-auto-fix" />
</IconBtn>
<IconBtn @click.stop="showBatchTransfer">
<VIcon color="primary" icon="mdi-folder-arrow-right" />
</IconBtn>
<IconBtn @click.stop="batchDelete">
<VIcon icon="mdi-delete-outline" color="error" />
</IconBtn>
</span>
</VToolbar>
<VCardText v-if="loading" class="text-center flex flex-col items-center">
<VProgressCircular size="48" indeterminate color="primary" />
@@ -471,31 +571,29 @@ onMounted(() => {
<!-- 目录和文件列表 -->
<VCardText v-else-if="dirs.length || files.length" class="p-0">
<VList subheader>
<VVirtualScroll
:items="[...dirs, ...files]"
:style="
appMode
? 'height: calc(100vh - 15.5rem - env(safe-area-inset-bottom) - 3.5rem)'
: 'height: calc(100vh - 14.5rem - env(safe-area-inset-bottom)'
"
>
<VVirtualScroll :items="[...dirs, ...files]" :style="scrollStyle">
<template #default="{ item }">
<VHover>
<template #default="hover">
<VListItem v-bind="hover.props" class="px-3 pe-1" @click="changePath(item)">
<VListItem v-bind="hover.props" class="px-3 pe-1" @click="listItemClick(item)">
<template #prepend>
<VIcon
v-if="inProps.icons && item.extension"
:icon="inProps.icons[item.extension.toLowerCase()] || inProps.icons?.other"
/>
<VIcon v-else icon="mdi-folder-outline" />
<VListItemAction v-if="selectMode">
<VCheckbox v-model="selected" :value="item" />
</VListItemAction>
<template v-else>
<VIcon
v-if="inProps.icons && item.extension"
:icon="inProps.icons[item.extension.toLowerCase()] || inProps.icons?.other"
/>
<VIcon v-else icon="mdi-folder-outline" />
</template>
</template>
<VListItemTitle v-text="item.name" />
<VListItemSubtitle v-if="item.size">
{{ formatBytes(item.size) }}
</VListItemSubtitle>
<template #append>
<IconBtn v-if="display.smAndDown.value">
<IconBtn v-if="display.smAndDown.value && !selectMode">
<VIcon icon="mdi-dots-vertical" />
<VMenu activator="parent" close-on-content-click>
<VList>
@@ -515,7 +613,7 @@ onMounted(() => {
</VList>
</VMenu>
</IconBtn>
<span v-if="hover.isHovering && display.mdAndUp" class="flex">
<span v-if="hover.isHovering && display.mdAndUp.value && !selectMode" class="flex">
<VTooltip text="识别">
<template #activator="{ props }">
<IconBtn v-bind="props" @click.stop="recognize(item.path)">
@@ -594,13 +692,8 @@ onMounted(() => {
<ReorganizeDialog
v-if="transferPopper"
v-model="transferPopper"
:path="currentItem?.path"
@done="
() => {
transferPopper = false
list_files()
}
"
:paths="transferPaths"
@done="transferDone"
@close="transferPopper = false"
/>
<!-- 进度框 -->

View File

@@ -347,7 +347,7 @@ function reloadPage() {
function ensureNumber(value: any, defaultValue: number = 0) {
value = Number(value)
// 如果不是数字
if (value !== value) {
if (Number.isNaN(value)) {
value = defaultValue
}
return value