From 5c6b29abb21e6c7d232ad7974d242b4e1ed8dd77 Mon Sep 17 00:00:00 2001 From: Yann <1319542051@qq.com> Date: Wed, 5 Nov 2025 17:07:54 +0800 Subject: [PATCH] =?UTF-8?q?add=20=E5=8F=B3=E4=B8=8B=E8=A7=92=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E6=8C=89=E9=92=AE|=E4=B8=8A=E4=BC=A0=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/file.ts | 8 +- src/store/index.ts | 3 +- src/store/modules/upload/index.ts | 127 ++++++++++++ src/views/files/components/index.ts | 2 + .../files/components/upload-modal-v2.vue | 196 ++++++++++++++++++ src/views/files/components/upload-panel.vue | 185 +++++++++++++++++ src/views/files/index.vue | 33 ++- 7 files changed, 551 insertions(+), 3 deletions(-) create mode 100644 src/store/modules/upload/index.ts create mode 100644 src/views/files/components/upload-modal-v2.vue create mode 100644 src/views/files/components/upload-panel.vue diff --git a/src/api/file.ts b/src/api/file.ts index 2b2f43c..f27d439 100644 --- a/src/api/file.ts +++ b/src/api/file.ts @@ -4,6 +4,7 @@ import type { FileItem, FileRecycleItem, } from '@/types/modules/file'; +import { AxiosProgressEvent } from 'axios'; /** * 查询文件列表 @@ -41,8 +42,12 @@ export function getFolderPath(folderId: string) { /** * 上传文件 + * + * @param file 文件 + * @param parentId pid + * @param onProgress progress */ -export function uploadFile(file: File, parentId?: string) { +export function uploadFile(file: File, parentId?: string, onProgress?: (progressEvent: AxiosProgressEvent) => void) { const formData = new FormData(); formData.append('file', file); if (parentId) { @@ -52,6 +57,7 @@ export function uploadFile(file: File, parentId?: string) { headers: { 'Content-Type': 'multipart/form-data', }, + onUploadProgress: onProgress, }); } diff --git a/src/store/index.ts b/src/store/index.ts index 18aa19e..3918c8f 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -2,8 +2,9 @@ import { createPinia } from 'pinia'; import useAppStore from './modules/app'; import useUserStore from './modules/user'; import useStorageStore from './modules/storage'; +import useUploadTaskStore from './modules/upload'; const pinia = createPinia(); -export { useAppStore, useUserStore, useStorageStore }; +export { useAppStore, useUserStore, useStorageStore, useUploadTaskStore }; export default pinia; diff --git a/src/store/modules/upload/index.ts b/src/store/modules/upload/index.ts new file mode 100644 index 0000000..54b991e --- /dev/null +++ b/src/store/modules/upload/index.ts @@ -0,0 +1,127 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import { Message } from '@arco-design/web-vue'; +import { uploadFile } from '@/api/file'; + +export interface UploadTask { + id: string | number; + file: File; + status: 'pending' | 'uploading' | 'success' | 'error'; + progress: number; + parentId: string | null; + errorMessage?: string; +} + +let taskIdCounter = 0; + +export const useUploadTaskStore = defineStore('uploadTask', () => { + // 正在进行和已完成的上传任务列表 + const taskList = ref([]); + // 面板是否展开 + const isExpanded = ref(false); + // 是否显示面板 + const showPanel = ref(false); + + /** + * 内部私有方法:执行单个文件上传 + */ + const doUpload = async (task: UploadTask) => { + try { + task.status = 'uploading'; + + const formData = new FormData(); + formData.append('file', task.file); + if (task.parentId) { + formData.append('parentId', task.parentId); + } + + await uploadFile(task.file, task.parentId ?? '', (progressEvent) => { + task.progress = Math.round( + (progressEvent.loaded * 100) / (progressEvent.total ?? 1) + ); + }); + + task.status = 'success'; + task.progress = 100; + } catch (error) { + task.status = 'error'; + task.errorMessage = (error as Error).message || '上传失败'; + Message.error(`${task.file.name} 上传失败`); + } + }; + + /** + * 添加新文件到上传队列 + * @param files File[] 文件列表 + * @param parentId 目标目录ID + */ + const addUploadTasks = (files: File[], parentId: string) => { + if (!showPanel.value) { + showPanel.value = true; + isExpanded.value = true; + } + + const existingFingerprints = new Set( + taskList.value.map((task) => { + const { file } = task; + return `${file.name}-${file.size}-${file.lastModified}`; + }) + ); + + const newFilesToUpload = files.filter((file) => { + const fingerprint = `${file.name}-${file.size}-${file.lastModified}`; + + if (existingFingerprints.has(fingerprint)) { + return false; + } + existingFingerprints.add(fingerprint); + return true; + }); + + if (newFilesToUpload.length === 0) { + Message.info('所选文件均已在上传列表中'); + return; + } + if (newFilesToUpload.length < files.length) { + Message.warning('已自动跳过列表中已存在的文件'); + } + + const newTasks: UploadTask[] = newFilesToUpload.map((file) => { + taskIdCounter += 1; + return { + id: `upload-task-${taskIdCounter}`, + file, + status: 'pending', + progress: 0, + parentId, + } + }); + taskList.value.push(...newTasks); + + taskList.value.forEach((task) => { + doUpload(task); + }); + }; + + // 展开/收起 + const toggleExpand = () => { + isExpanded.value = !isExpanded.value; + }; + + // 关闭/清空 + const closePanel = () => { + showPanel.value = false; + taskList.value = []; + }; + + return { + taskList, + isExpanded, + showPanel, + addUploadTasks, + toggleExpand, + closePanel, + }; +}); + +export default useUploadTaskStore; diff --git a/src/views/files/components/index.ts b/src/views/files/components/index.ts index 73f4713..f674131 100644 --- a/src/views/files/components/index.ts +++ b/src/views/files/components/index.ts @@ -17,3 +17,5 @@ export { default as ShareModal } from './share-modal.vue'; export { default as DeleteConfirmModal } from './delete-confirm-modal.vue'; export { default as RecycleBinView } from './recycle-bin-view.vue'; export { default as MySharesView } from './my-shares-view.vue'; +export { default as UploadModalV2 } from './upload-modal-v2.vue'; +export { default as UploadPanel } from './upload-panel.vue'; \ No newline at end of file diff --git a/src/views/files/components/upload-modal-v2.vue b/src/views/files/components/upload-modal-v2.vue new file mode 100644 index 0000000..a7ae6bf --- /dev/null +++ b/src/views/files/components/upload-modal-v2.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/src/views/files/components/upload-panel.vue b/src/views/files/components/upload-panel.vue new file mode 100644 index 0000000..ef8a91d --- /dev/null +++ b/src/views/files/components/upload-panel.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/src/views/files/index.vue b/src/views/files/index.vue index 665b25c..f94db19 100644 --- a/src/views/files/index.vue +++ b/src/views/files/index.vue @@ -145,8 +145,21 @@ + + + + + - + + + @@ -207,6 +223,7 @@ IconFileVideo, IconMore, IconShareAlt, + IconUpload } from '@arco-design/web-vue/es/icon'; import type { FileItem } from '@/types/modules/file'; import { useFileList, useFileOperations } from './hooks'; @@ -223,6 +240,8 @@ DeleteConfirmModal, RecycleBinView, MySharesView, + UploadModalV2, + UploadPanel } from './components'; const route = useRoute(); @@ -595,4 +614,16 @@ overflow-y: auto; } } + + /** 上传按钮 */ + .custom-upload-fab { + height: 56px; + width: 56px; + position: fixed; + right: 40px; + bottom: 40px; + z-index: 99; + + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + } -- Gitee