feat: 文件管理呈目录形式展示 (#60)

This commit is contained in:
luoqiz
2025-04-16 13:41:50 +08:00
committed by GitHub
parent 471f30e1e7
commit 70e2de3250
7 changed files with 78 additions and 9 deletions

View File

@@ -24,3 +24,13 @@ export function deleteFile(id: string) {
export function getFileStatistics() {
return http.get<T.FileStatisticsResp>(`${BASE_URL}/statistics`)
}
/** @desc 根据sha256检测文件是否已经在服务器存在 */
export function checkFile(sha256: string) {
return http.get<T.FileItem>(`${BASE_URL}/check`, { fileHash: sha256 })
}
/** @desc 创建文件夹 */
export function createDir(path: string, name: string) {
return http.post<T.FileItem>(`${BASE_URL}/createDir`, { parentPath: path, name })
}

View File

@@ -207,7 +207,7 @@ export interface FileItem {
parentPath: string
absPath: string
metadata: string
md5: string
sha256: string
contentType: string
thumbnailSize: number
thumbnailUrl: string

View File

@@ -46,3 +46,5 @@ export const OfficeTypes = ['ppt', 'pptx', 'doc', 'docx', 'xls', 'xlsx', 'pdf']
export const WordTypes = ['doc', 'docx']
export const ExcelTypes = ['xls', 'xlsx']
export const DirTypes = ['dir']

View File

@@ -14,7 +14,7 @@
</a-descriptions-item>
<a-descriptions-item label="大小">{{ formatFileSize(data.size) }}</a-descriptions-item>
<a-descriptions-item label="路径">{{ data.absPath + getFileName(data) }}</a-descriptions-item>
<a-descriptions-item label="MD5">{{ data.md5 }}</a-descriptions-item>
<a-descriptions-item label="SHA256">{{ data.sha256 }}</a-descriptions-item>
<a-descriptions-item label="上传时间">{{ data.createTime }}</a-descriptions-item>
<a-descriptions-item label="修改时间">{{ data.updateTime }}</a-descriptions-item>
<a-descriptions-item label="存储名称">{{ data.storageName }}</a-descriptions-item>

View File

@@ -13,7 +13,7 @@
scroll-to-close
>
<a-grid-item>
<div class="file-grid-item" @click.stop="handleClickFile(item)">
<div class="file-grid-item" @click.stop="handleClickFile(item)" @dblclick="handleDblclickFile(item)">
<section class="file-grid-item__wrapper">
<div class="file-icon">
<FileImage :data="item" :title="item.name"></FileImage>
@@ -51,6 +51,7 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<{
(e: 'click', record: FileItem): void
(e: 'dblclick', record: FileItem): void
(e: 'select', record: FileItem): void
(e: 'right-menu-click', mode: string, item: FileItem): void
}>()
@@ -73,6 +74,11 @@ const handleClickFile = (item: FileItem) => {
emit('click', item)
}
// 双击事件
const handleDblclickFile = (item: FileItem) => {
emit('dblclick', item)
}
// 选中事件
const handleCheckFile = (item: FileItem) => {
emit('select', item)

View File

@@ -34,7 +34,7 @@
</a-typography-paragraph>
</section>
<template #content>
<FileRightMenu :data="record" @click="handleRightMenuClick($event, record)"></FileRightMenu>
<FileRightMenu :data="record" @click="handleRightMenuClick($event, record)" @dblclick="handleDblclickFile(record)"></FileRightMenu>
</template>
</a-trigger>
</template>
@@ -77,6 +77,7 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<{
(e: 'click', record: FileItem): void
(e: 'dblclick', record: FileItem): void
(e: 'select', record: FileItem): void
(e: 'right-menu-click', mode: string, item: FileItem): void
}>()
@@ -109,6 +110,11 @@ const handleRowClick: TableInstance['onRowClick'] = (record) => {
emit('click', record as unknown as FileItem)
}
// 双击事件
const handleDblclickFile = (item: FileItem) => {
emit('dblclick', item)
}
// 右键菜单点击事件
const handleRightMenuClick = (mode: string, item: FileItem) => {
emit('right-menu-click', mode, item)

View File

@@ -38,6 +38,12 @@
<icon-delete />
</template>
</a-button>
<a-button type="primary" :disabled="!queryForm.absPath" @click="createDirModalVisible = !createDirModalVisible">
<template #icon>
<icon-folder />
</template>
<template #default>新建文件夹</template>
</a-button>
<a-button type="primary" @click="isBatchMode = !isBatchMode">
<template #icon>
<icon-select-all />
@@ -62,14 +68,14 @@
<FileGrid
v-show="fileList.length && mode === 'grid'" :data="fileList" :is-batch-mode="isBatchMode"
:selected-file-ids="selectedFileIds" @click="handleClickFile" @select="handleSelectFile"
@right-menu-click="handleRightMenuClick"
@right-menu-click="handleRightMenuClick" @dblclick="handleDblclickFile"
></FileGrid>
<!-- 文件列表-列表模式 -->
<FileList
v-show="fileList.length && mode === 'list'" :data="fileList" :is-batch-mode="isBatchMode"
:selected-file-ids="selectedFileIds" @click="handleClickFile" @select="handleSelectFile"
@right-menu-click="handleRightMenuClick"
@right-menu-click="handleRightMenuClick" @dblclick="handleDblclickFile"
></FileList>
<a-empty v-if="!fileList.length" />
@@ -78,6 +84,11 @@
<div class="pagination">
<a-pagination v-bind="pagination" />
</div>
<!-- 弹出新建窗口 -->
<a-modal v-model:visible="createDirModalVisible" title="新建文件夹" @ok="handleCreateDir" @cancel="handleCancel">
<a-input v-model="newDirName" placeholder="请输入文件夹名称" size="large" allow-clear />
</a-modal>
</div>
</template>
@@ -93,8 +104,8 @@ import {
import FileGrid from './FileGrid.vue'
import useFileManage from './useFileManage'
import { useTable } from '@/hooks'
import { type FileItem, type FileQuery, deleteFile, listFile, uploadFile } from '@/apis'
import { ImageTypes, OfficeTypes } from '@/constant/file'
import { type FileItem, type FileQuery, checkFile, createDir, deleteFile, listFile, uploadFile } from '@/apis'
import { DirTypes, ImageTypes, OfficeTypes } from '@/constant/file'
import 'viewerjs/dist/viewer.css'
import { downloadByUrl } from '@/utils/downloadFile'
import mittBus from '@/utils/mitt'
@@ -114,9 +125,15 @@ const queryTypeOption = [{
value: 'absPath',
}]
const queryType = ref<string>('name')
// 新建文件夹弹窗显示
const createDirModalVisible = ref<boolean>(false)
// 新文件名称
const newDirName = ref()
const queryForm = reactive<FileQuery>({
name: undefined,
absPath: undefined,
absPath: '/',
type: route.query.type?.toString() !== '0' ? route.query.type?.toString() : undefined,
sort: ['updateTime,desc'],
})
@@ -180,6 +197,19 @@ const handleClickFile = (item: FileItem) => {
previewFileAudioModal(item)
}
}
// 双击文件
const handleDblclickFile = (item: FileItem) => {
if (DirTypes.includes(item.extension)) {
if (item.absPath.endsWith('/')) {
queryForm.absPath = item.absPath + item.name
} else {
queryForm.absPath = `${item.absPath}/${item.name}`
}
search()
}
}
// 下载文件
const onDownload = async (fileInfo: FileItem) => {
const res = await downloadByUrl({
@@ -242,6 +272,7 @@ const handleUpload = (options: RequestOption) => {
const { onProgress, onError, onSuccess, fileItem, name = 'file' } = options
onProgress(20)
const formData = new FormData()
formData.append('path', queryForm.absPath ?? '/')
formData.append(name as string, fileItem.file as Blob)
try {
const res = await uploadFile(formData)
@@ -272,6 +303,20 @@ onBeforeRouteUpdate((to) => {
search()
})
// 新建文件夹弹窗窗口取消事件
const handleCancel = () => {
newDirName.value = undefined
createDirModalVisible.value = false
}
// 新建文件夹弹窗窗口确认事件
const handleCreateDir = async () => {
const res = await createDir(queryForm.absPath ?? '/', newDirName.value)
newDirName.value = undefined
createDirModalVisible.value = false
search()
}
onMounted(() => {
search()
})