mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2025-09-09 20:57:17 +08:00
refactor(system/file): 重构文件管理相关代码
This commit is contained in:
@@ -32,5 +32,5 @@ export function checkFile(sha256: string) {
|
|||||||
|
|
||||||
/** @desc 创建文件夹 */
|
/** @desc 创建文件夹 */
|
||||||
export function createDir(path: string, name: string) {
|
export function createDir(path: string, name: string) {
|
||||||
return http.post<T.FileItem>(`${BASE_URL}/createDir`, { parentPath: path, name })
|
return http.post<T.FileItem>(`${BASE_URL}/dir`, { path, originalName: name })
|
||||||
}
|
}
|
||||||
|
@@ -202,24 +202,25 @@ export interface NoticePageQuery extends NoticeQuery, PageQuery {
|
|||||||
export interface FileItem {
|
export interface FileItem {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
originalName: string
|
||||||
size: number
|
size: number
|
||||||
url: string
|
url: string
|
||||||
parentPath: string
|
path: string
|
||||||
absPath: string
|
|
||||||
metadata: string
|
|
||||||
sha256: string
|
sha256: string
|
||||||
contentType: string
|
contentType: string
|
||||||
|
metadata: string
|
||||||
thumbnailSize: number
|
thumbnailSize: number
|
||||||
thumbnailUrl: string
|
thumbnailName: string
|
||||||
thumbnailMetadata: string
|
thumbnailMetadata: string
|
||||||
|
thumbnailUrl: string
|
||||||
extension: string
|
extension: string
|
||||||
type: number
|
type: number
|
||||||
storageId: string
|
storageId: string
|
||||||
storageName: string
|
storageName: string
|
||||||
createUserString: string
|
createUserString: string
|
||||||
createTime: string
|
createTime: string
|
||||||
updateUserString: string
|
updateUserString?: string
|
||||||
updateTime: string
|
updateTime?: string
|
||||||
}
|
}
|
||||||
/** 文件资源统计信息 */
|
/** 文件资源统计信息 */
|
||||||
export interface FileStatisticsResp {
|
export interface FileStatisticsResp {
|
||||||
@@ -230,9 +231,9 @@ export interface FileStatisticsResp {
|
|||||||
data: Array<FileStatisticsResp>
|
data: Array<FileStatisticsResp>
|
||||||
}
|
}
|
||||||
export interface FileQuery {
|
export interface FileQuery {
|
||||||
name?: string
|
originalName?: string
|
||||||
type?: string
|
type?: string
|
||||||
absPath?: string
|
path?: string
|
||||||
sort: Array<string>
|
sort: Array<string>
|
||||||
}
|
}
|
||||||
export interface FilePageQuery extends FileQuery, PageQuery {
|
export interface FilePageQuery extends FileQuery, PageQuery {
|
||||||
|
@@ -101,7 +101,7 @@ const columns: ColumnItem[] = reactive([
|
|||||||
field: 'domain',
|
field: 'domain',
|
||||||
type: 'input',
|
type: 'input',
|
||||||
span: 24,
|
span: 24,
|
||||||
required: true,
|
required: false,
|
||||||
show: () => form.type === 2,
|
show: () => form.type === 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
<div ref="audioHeadRef" class="audio-box__header">
|
<div ref="audioHeadRef" class="audio-box__header">
|
||||||
<div class="audio-name">
|
<div class="audio-name">
|
||||||
<icon-music :size="16" spin />
|
<icon-music :size="16" spin />
|
||||||
<span>{{ props.data?.name }}.{{ props.data?.extension }}</span>
|
<span>{{ props.data?.originalName }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="close-icon" @click="close">
|
<div class="close-icon" @click="close">
|
||||||
<icon-close :size="12" />
|
<icon-close :size="12" />
|
||||||
|
@@ -9,14 +9,19 @@
|
|||||||
<a-descriptions-item label="名称">
|
<a-descriptions-item label="名称">
|
||||||
<a-typography-paragraph copyable :copy-text="data.url">
|
<a-typography-paragraph copyable :copy-text="data.url">
|
||||||
<template #copy-tooltip>复制链接</template>
|
<template #copy-tooltip>复制链接</template>
|
||||||
{{ getFileName(data) }}
|
{{ data.originalName }}
|
||||||
</a-typography-paragraph>
|
</a-typography-paragraph>
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="大小">{{ formatFileSize(data.size) }}</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="路径">{{ `${data.path === '/' ? '' : data.path}/${data.name}` }}</a-descriptions-item>
|
||||||
<a-descriptions-item label="SHA256">{{ data.sha256 }}</a-descriptions-item>
|
<a-descriptions-item v-if="data.sha256" label="SHA256">
|
||||||
|
<a-typography-paragraph copyable :copy-text="data.sha256">
|
||||||
|
<template #copy-tooltip>复制</template>
|
||||||
|
{{ data.sha256 }}
|
||||||
|
</a-typography-paragraph>
|
||||||
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="上传时间">{{ data.createTime }}</a-descriptions-item>
|
<a-descriptions-item label="上传时间">{{ data.createTime }}</a-descriptions-item>
|
||||||
<a-descriptions-item label="修改时间">{{ data.updateTime }}</a-descriptions-item>
|
<a-descriptions-item v-if="data?.updateTime" label="修改时间">{{ data?.updateTime }}</a-descriptions-item>
|
||||||
<a-descriptions-item label="存储名称">{{ data.storageName }}</a-descriptions-item>
|
<a-descriptions-item label="存储名称">{{ data.storageName }}</a-descriptions-item>
|
||||||
</a-descriptions>
|
</a-descriptions>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -32,11 +37,6 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {})
|
withDefaults(defineProps<Props>(), {})
|
||||||
|
|
||||||
// 文件名称带后缀
|
|
||||||
const getFileName = (item: FileItem) => {
|
|
||||||
return `${item.name}${item.extension ? `.${item.extension}` : ''}`
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@@ -3,11 +3,11 @@
|
|||||||
<a-form ref="formRef" :model="form" auto-label-width class="w-full">
|
<a-form ref="formRef" :model="form" auto-label-width class="w-full">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="文件名称"
|
label="文件名称"
|
||||||
field="name"
|
field="originalName"
|
||||||
:rules="[{ required: true, message: '请输入文件名称' }]"
|
:rules="[{ required: true, message: '请输入文件名称' }]"
|
||||||
style="margin-bottom: 0"
|
style="margin-bottom: 0"
|
||||||
>
|
>
|
||||||
<a-input v-model="form.name" placeholder="请输入文件名称" allow-clear />
|
<a-input v-model="form.originalName" placeholder="请输入文件名称" allow-clear />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-row>
|
</a-row>
|
||||||
@@ -24,7 +24,7 @@ const props = withDefaults(defineProps<Props>(), {})
|
|||||||
|
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
name: props.data?.name || '',
|
originalName: props.data?.originalName || '',
|
||||||
})
|
})
|
||||||
|
|
||||||
defineExpose({ formRef })
|
defineExpose({ formRef })
|
||||||
|
@@ -21,7 +21,7 @@ export function openFileRenameModal(data: FileItem, callback?: () => void) {
|
|||||||
const isInvalid = await ModalContentRef.value?.formRef?.validate()
|
const isInvalid = await ModalContentRef.value?.formRef?.validate()
|
||||||
const modelParams = ModalContentRef.value?.formRef?.model
|
const modelParams = ModalContentRef.value?.formRef?.model
|
||||||
if (isInvalid) return false
|
if (isInvalid) return false
|
||||||
await updateFile({ name: modelParams?.name }, data.id)
|
await updateFile({ originalName: modelParams?.originalName }, data.id)
|
||||||
Message.success('重命名成功')
|
Message.success('重命名成功')
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback()
|
callback()
|
||||||
|
@@ -16,9 +16,9 @@
|
|||||||
<div class="file-grid-item" @click.stop="handleClickFile(item)" @dblclick="handleDblclickFile(item)">
|
<div class="file-grid-item" @click.stop="handleClickFile(item)" @dblclick="handleDblclickFile(item)">
|
||||||
<section class="file-grid-item__wrapper">
|
<section class="file-grid-item__wrapper">
|
||||||
<div class="file-icon">
|
<div class="file-icon">
|
||||||
<FileImage :data="item" :title="item.name"></FileImage>
|
<FileImage :data="item" :title="item.originalName"></FileImage>
|
||||||
</div>
|
</div>
|
||||||
<p class="gi_line_1 file-name">{{ getFileName(item) }}</p>
|
<p class="gi_line_1 file-name">{{ item.originalName }}</p>
|
||||||
</section>
|
</section>
|
||||||
<!-- 勾选模式 -->
|
<!-- 勾选模式 -->
|
||||||
<section
|
<section
|
||||||
@@ -64,11 +64,6 @@ interface Props {
|
|||||||
isBatchMode?: boolean
|
isBatchMode?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件名称带后缀
|
|
||||||
const getFileName = (item: FileItem) => {
|
|
||||||
return `${item.name}${item.extension ? `.${item.extension}` : ''}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 点击事件
|
// 点击事件
|
||||||
const handleClickFile = (item: FileItem) => {
|
const handleClickFile = (item: FileItem) => {
|
||||||
emit('click', item)
|
emit('click', item)
|
||||||
|
@@ -15,13 +15,17 @@ const props = withDefaults(defineProps<Props>(), {})
|
|||||||
|
|
||||||
// 是否是图片类型文件
|
// 是否是图片类型文件
|
||||||
const isImage = computed(() => {
|
const isImage = computed(() => {
|
||||||
const extension = props.data.extension.toLowerCase()
|
const extension = props.data.extension?.toLowerCase()
|
||||||
return ImageTypes.includes(extension)
|
return ImageTypes.includes(extension)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取文件图标,如果是图片就显示图片
|
// 获取文件图标,如果是图片就显示图片
|
||||||
const getFileImg = computed<string>(() => {
|
const getFileImg = computed<string>(() => {
|
||||||
const extension = props.data.extension.toLowerCase()
|
// 文件夹
|
||||||
|
if (props.data.type === 0) {
|
||||||
|
return FileIcon.dir
|
||||||
|
}
|
||||||
|
const extension = props.data.extension?.toLowerCase()
|
||||||
if (ImageTypes.includes(extension)) {
|
if (ImageTypes.includes(extension)) {
|
||||||
return props.data.url || ''
|
return props.data.url || ''
|
||||||
}
|
}
|
||||||
|
@@ -30,7 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<a-typography-paragraph copyable :copy-text="record.url">
|
<a-typography-paragraph copyable :copy-text="record.url">
|
||||||
<template #copy-tooltip>复制链接</template>
|
<template #copy-tooltip>复制链接</template>
|
||||||
{{ getFileName(record) }}
|
{{ record.originalName }}
|
||||||
</a-typography-paragraph>
|
</a-typography-paragraph>
|
||||||
</section>
|
</section>
|
||||||
<template #content>
|
<template #content>
|
||||||
@@ -90,11 +90,6 @@ interface Props {
|
|||||||
isBatchMode?: boolean
|
isBatchMode?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件名称带后缀
|
|
||||||
const getFileName = (item: FileItem) => {
|
|
||||||
return `${item.name}${item.extension ? `.${item.extension}` : ''}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const rowSelection: TableRowSelection = reactive({
|
const rowSelection: TableRowSelection = reactive({
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
showCheckedAll: true,
|
showCheckedAll: true,
|
||||||
|
@@ -38,7 +38,7 @@
|
|||||||
<icon-delete />
|
<icon-delete />
|
||||||
</template>
|
</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" :disabled="!queryForm.absPath" @click="createDirModalVisible = !createDirModalVisible">
|
<a-button type="primary" :disabled="!queryForm.path" @click="createDirModalVisible = !createDirModalVisible">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-folder />
|
<icon-folder />
|
||||||
</template>
|
</template>
|
||||||
@@ -105,7 +105,7 @@ import FileGrid from './FileGrid.vue'
|
|||||||
import useFileManage from './useFileManage'
|
import useFileManage from './useFileManage'
|
||||||
import { useTable } from '@/hooks'
|
import { useTable } from '@/hooks'
|
||||||
import { type FileItem, type FileQuery, createDir, deleteFile, listFile, uploadFile } from '@/apis'
|
import { type FileItem, type FileQuery, createDir, deleteFile, listFile, uploadFile } from '@/apis'
|
||||||
import { DirTypes, ImageTypes, OfficeTypes } from '@/constant/file'
|
import { ImageTypes, OfficeTypes } from '@/constant/file'
|
||||||
import 'viewerjs/dist/viewer.css'
|
import 'viewerjs/dist/viewer.css'
|
||||||
import { downloadByUrl } from '@/utils/downloadFile'
|
import { downloadByUrl } from '@/utils/downloadFile'
|
||||||
import mittBus from '@/utils/mitt'
|
import mittBus from '@/utils/mitt'
|
||||||
@@ -119,12 +119,12 @@ const { mode, selectedFileIds, toggleMode, addSelectedFileItem } = useFileManage
|
|||||||
|
|
||||||
const queryTypeOption = [{
|
const queryTypeOption = [{
|
||||||
label: '文件名',
|
label: '文件名',
|
||||||
value: 'name',
|
value: 'originalName',
|
||||||
}, {
|
}, {
|
||||||
label: '路径',
|
label: '路径',
|
||||||
value: 'absPath',
|
value: 'path',
|
||||||
}]
|
}]
|
||||||
const queryType = ref<string>('name')
|
const queryType = ref<string>('originalName')
|
||||||
|
|
||||||
// 新建文件夹弹窗显示
|
// 新建文件夹弹窗显示
|
||||||
const createDirModalVisible = ref<boolean>(false)
|
const createDirModalVisible = ref<boolean>(false)
|
||||||
@@ -132,15 +132,15 @@ const createDirModalVisible = ref<boolean>(false)
|
|||||||
const newDirName = ref()
|
const newDirName = ref()
|
||||||
|
|
||||||
const queryForm = reactive<FileQuery>({
|
const queryForm = reactive<FileQuery>({
|
||||||
name: undefined,
|
originalName: undefined,
|
||||||
absPath: '/',
|
path: (!route.query.type || route.query.type?.toString() === '0') ? '/' : undefined,
|
||||||
type: route.query.type?.toString() !== '0' ? route.query.type?.toString() : undefined,
|
type: route.query.type?.toString() && route.query.type?.toString() !== '0' ? route.query.type?.toString() : undefined,
|
||||||
sort: ['updateTime,desc'],
|
sort: ['type,asc', 'updateTime,desc'],
|
||||||
})
|
})
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
queryForm.name = undefined
|
queryForm.originalName = undefined
|
||||||
queryForm.absPath = undefined
|
queryForm.path = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const paginationOption = reactive({
|
const paginationOption = reactive({
|
||||||
@@ -186,7 +186,7 @@ const handleClickFile = (item: FileItem) => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
filePreviewRef.value.onPreview({
|
filePreviewRef.value.onPreview({
|
||||||
fileInfo: { data: item.url, fileName: item.name, fileType: item.extension },
|
fileInfo: { data: item.url, fileName: item.originalName, fileType: item.extension },
|
||||||
excelConfig,
|
excelConfig,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -200,12 +200,8 @@ const handleClickFile = (item: FileItem) => {
|
|||||||
|
|
||||||
// 双击文件
|
// 双击文件
|
||||||
const handleDblclickFile = (item: FileItem) => {
|
const handleDblclickFile = (item: FileItem) => {
|
||||||
if (DirTypes.includes(item.extension)) {
|
if (item.type === 0) {
|
||||||
if (item.absPath.endsWith('/')) {
|
queryForm.path = `${item.path === '/' ? '' : item.path}/${item.name}`
|
||||||
queryForm.absPath = item.absPath + item.name
|
|
||||||
} else {
|
|
||||||
queryForm.absPath = `${item.absPath}/${item.name}`
|
|
||||||
}
|
|
||||||
search()
|
search()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,7 +211,7 @@ const onDownload = async (fileInfo: FileItem) => {
|
|||||||
const res = await downloadByUrl({
|
const res = await downloadByUrl({
|
||||||
url: fileInfo.url,
|
url: fileInfo.url,
|
||||||
target: '_self',
|
target: '_self',
|
||||||
fileName: `${fileInfo.name}.${fileInfo.extension}`,
|
fileName: fileInfo.originalName,
|
||||||
})
|
})
|
||||||
res ? Message.success('下载成功') : Message.error('下载失败')
|
res ? Message.success('下载成功') : Message.error('下载失败')
|
||||||
search()
|
search()
|
||||||
@@ -226,7 +222,7 @@ const handleRightMenuClick = async (mode: string, fileInfo: FileItem) => {
|
|||||||
if (mode === 'delete') {
|
if (mode === 'delete') {
|
||||||
Modal.warning({
|
Modal.warning({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: `是否确定删除文件「${fileInfo.name}」?`,
|
content: `是否确定删除${fileInfo.type === 0 ? '文件夹' : '文件'}「${fileInfo.originalName}」?`,
|
||||||
hideCancel: false,
|
hideCancel: false,
|
||||||
okButtonProps: { status: 'danger' },
|
okButtonProps: { status: 'danger' },
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
@@ -273,7 +269,7 @@ const handleUpload = (options: RequestOption) => {
|
|||||||
const { onProgress, onError, onSuccess, fileItem, name = 'file' } = options
|
const { onProgress, onError, onSuccess, fileItem, name = 'file' } = options
|
||||||
onProgress(20)
|
onProgress(20)
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('path', queryForm.absPath ?? '/')
|
formData.append('path', queryForm.path ?? '/')
|
||||||
formData.append(name as string, fileItem.file as Blob)
|
formData.append(name as string, fileItem.file as Blob)
|
||||||
try {
|
try {
|
||||||
const res = await uploadFile(formData)
|
const res = await uploadFile(formData)
|
||||||
@@ -295,10 +291,12 @@ const handleUpload = (options: RequestOption) => {
|
|||||||
|
|
||||||
onBeforeRouteUpdate((to) => {
|
onBeforeRouteUpdate((to) => {
|
||||||
if (!to.query.type) return
|
if (!to.query.type) return
|
||||||
if (to.query.type === '0') {
|
if (to.query.type === '0' || !to.query.type) {
|
||||||
queryForm.type = undefined
|
queryForm.type = undefined
|
||||||
|
queryForm.path = '/'
|
||||||
} else {
|
} else {
|
||||||
queryForm.type = to.query.type?.toString()
|
queryForm.type = to.query.type?.toString()
|
||||||
|
queryForm.path = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
search()
|
search()
|
||||||
@@ -312,7 +310,7 @@ const handleCancel = () => {
|
|||||||
|
|
||||||
// 新建文件夹弹窗窗口确认事件
|
// 新建文件夹弹窗窗口确认事件
|
||||||
const handleCreateDir = async () => {
|
const handleCreateDir = async () => {
|
||||||
await createDir(queryForm.absPath ?? '/', newDirName.value)
|
await createDir(queryForm.path ?? '/', newDirName.value)
|
||||||
newDirName.value = undefined
|
newDirName.value = undefined
|
||||||
createDirModalVisible.value = false
|
createDirModalVisible.value = false
|
||||||
search()
|
search()
|
||||||
|
Reference in New Issue
Block a user