+ {ellipsizeFileName(task.fileName, 30)} +
+{task.noticeMessage}
+ )} + + {task.status === 'uploading' && ( +上传完成
+ )} + {task.status === 'cancelled' && ( +已取消上传
+ )} + {task.status === 'error' && ( +{task.errorMessage ?? '上传失败,请稍后重试'}
+ )} +此文件夹为空
| 名称 | -修改日期 | -类型 | -大小 | -+ | 名称 | +修改日期 | +类型 | +大小 | +
-
+
+ |
@@ -926,7 +929,7 @@ export default function Files() {
+ {selectedFile.name}++ {selectedFile.name} +
@@ -1030,108 +1035,6 @@ export default function Files() {
)}
- setIsUploadPanelOpen((previous) => !previous)}
- >
-
-
-
-
-
-
-
-
-
- {uploads.map((task) => (
-
-
- {task.status === 'uploading' && (
-
- )}
-
-
- ))}
-
-
-
-
-
-
- {task.fileName} -
- {task.status === 'completed' ? (
-
-
-
- {task.typeLabel}
-
- 上传至: {task.destination}
-
- {task.noticeMessage && (
- {task.noticeMessage} - )} - - {task.status === 'uploading' && ( -
- {Math.round(task.progress)}%
- {task.speed}
-
- )}
- {task.status === 'completed' && (
- 上传完成 - )} - {task.status === 'error' && ( -{task.errorMessage ?? '上传失败,请稍后重试'} - )} -
diff --git a/front/src/pages/files-upload-store.test.ts b/front/src/pages/files-upload-store.test.ts
new file mode 100644
index 0000000..c9740ea
--- /dev/null
+++ b/front/src/pages/files-upload-store.test.ts
@@ -0,0 +1,70 @@
+import assert from 'node:assert/strict';
+import test from 'node:test';
+
+import {
+ cancelFilesUploadTask,
+ clearFilesUploads,
+ getFilesUploadStoreSnapshot,
+ registerFilesUploadTaskCanceler,
+ replaceFilesUploads,
+ resetFilesUploadStoreForTests,
+ setFilesUploadPanelOpen,
+ subscribeFilesUploadStore,
+ unregisterFilesUploadTaskCanceler,
+ updateFilesUploadTask,
+} from './files-upload-store';
+import { createUploadTask } from './files-upload';
+
+test('files upload store keeps tasks after page-level subscriber unmounts', () => {
+ resetFilesUploadStoreForTests();
+ const task = createUploadTask(new File(['hello'], 'manual.pdf', {type: 'application/pdf'}), ['文档'], 'task-1');
+
+ const unsubscribe = subscribeFilesUploadStore(() => undefined);
+ replaceFilesUploads([task]);
+ unsubscribe();
+
+ const snapshot = getFilesUploadStoreSnapshot();
+ assert.equal(snapshot.uploads.length, 1);
+ assert.equal(snapshot.uploads[0].fileName, 'manual.pdf');
+});
+
+test('files upload store supports task updates and panel visibility toggles', () => {
+ resetFilesUploadStoreForTests();
+ const task = createUploadTask(new File(['hello'], 'manual.pdf', {type: 'application/pdf'}), ['文档'], 'task-2');
+
+ replaceFilesUploads([task]);
+ updateFilesUploadTask(task.id, (current) => ({
+ ...current,
+ progress: 80,
+ }));
+ setFilesUploadPanelOpen(false);
+
+ const snapshot = getFilesUploadStoreSnapshot();
+ assert.equal(snapshot.uploads[0].progress, 80);
+ assert.equal(snapshot.isUploadPanelOpen, false);
+
+ clearFilesUploads();
+ assert.equal(getFilesUploadStoreSnapshot().uploads.length, 0);
+});
+
+test('files upload store cancels one task by its id', () => {
+ resetFilesUploadStoreForTests();
+ const cancelled: string[] = [];
+ registerFilesUploadTaskCanceler('task-1', () => {
+ cancelled.push('task-1');
+ });
+ registerFilesUploadTaskCanceler('task-2', () => {
+ cancelled.push('task-2');
+ });
+
+ const task = createUploadTask(new File(['hello'], 'manual.pdf', {type: 'application/pdf'}), ['文档'], 'task-1');
+ replaceFilesUploads([task]);
+
+ const didCancel = cancelFilesUploadTask('task-1');
+ const didCancelUnknown = cancelFilesUploadTask('missing-task');
+ unregisterFilesUploadTaskCanceler('task-2');
+
+ assert.equal(didCancel, true);
+ assert.equal(didCancelUnknown, false);
+ assert.deepEqual(cancelled, ['task-1']);
+});
diff --git a/front/src/pages/files-upload-store.ts b/front/src/pages/files-upload-store.ts
new file mode 100644
index 0000000..3f5ede7
--- /dev/null
+++ b/front/src/pages/files-upload-store.ts
@@ -0,0 +1,132 @@
+import { useSyncExternalStore } from 'react';
+
+import type { UploadTask } from './files-upload';
+
+interface FilesUploadStoreSnapshot {
+ uploads: UploadTask[];
+ isUploadPanelOpen: boolean;
+}
+
+const listeners = new Set<() => void>();
+const uploadTaskCancelers = new Map |
|---|