|
|
@@ -66,42 +66,9 @@
|
|
|
</el-scrollbar>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 上传按钮和历史记录 -->
|
|
|
+ <!-- 上传按钮 -->
|
|
|
<div class="upload-actions">
|
|
|
<el-button type="primary" :disabled="isUploading || fileList.length === 0" @click="handleUpload">上传</el-button>
|
|
|
- <el-button @click="clearHistory" type="info" plain v-if="uploadHistory.length > 0">清空历史</el-button>
|
|
|
- </div>
|
|
|
- <div v-if="uploadHistory.length > 0" class="history-list">
|
|
|
- <h3>上传历史记录 (最近 {{ MAX_HISTORY_ITEMS }} 条):</h3>
|
|
|
- <el-scrollbar height="250px">
|
|
|
- <el-table :data="uploadHistory" style="width: 100%" :cell-style="{ textAlign: 'center' }">
|
|
|
- <el-table-column prop="name" label="文件名" header-align="center">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-tooltip :content="row.name" placement="top">
|
|
|
- <span class="history-filename">{{ row.name }}</span>
|
|
|
- </el-tooltip>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="size" label="大小" width="120" header-align="center">
|
|
|
- <template #default="{ row }">
|
|
|
- {{ formatFileSize(row.size || 0) }}
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="status" label="状态" width="100" header-align="center">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-tag v-if="row.status === 'success'" type="success">成功</el-tag>
|
|
|
- <el-tag v-else-if="row.status === 'error'" type="danger">失败</el-tag>
|
|
|
- <el-tag v-else type="info">{{ row.status }}</el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <!-- <el-table-column prop="uploadTime" label="上传时间" width="180" /> -->
|
|
|
- <el-table-column label="操作" width="60" header-align="center">
|
|
|
- <template #default="{ $index }">
|
|
|
- <el-button type="danger" link @click="removeHistoryItem($index)">删除</el-button>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
- </el-scrollbar>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
@@ -134,15 +101,6 @@ interface UploadState {
|
|
|
totalChunks?: number;
|
|
|
uploadedChunks?: Set<number>;
|
|
|
finalUrl?: string | null;
|
|
|
- historySaved?: boolean;
|
|
|
-}
|
|
|
-interface UploadHistoryEntry {
|
|
|
- name: string;
|
|
|
- size: number;
|
|
|
- status: string;
|
|
|
- uploadTime: string;
|
|
|
- url?: string | null;
|
|
|
- uploadId?: string;
|
|
|
}
|
|
|
|
|
|
// ================= props, emits, 变量声明 =================
|
|
|
@@ -154,14 +112,6 @@ const props = defineProps({
|
|
|
}); // only declared once
|
|
|
const emit = defineEmits(['update:modelValue']); // only declared once
|
|
|
|
|
|
-const fileTypes = ref([
|
|
|
- { label: '图片', value: 'image' },
|
|
|
- { label: '视频', value: 'video' },
|
|
|
- { label: '文档', value: 'document' },
|
|
|
- { label: '其它', value: 'other' }
|
|
|
-]);
|
|
|
-const selectedType = ref('');
|
|
|
-
|
|
|
const fileList = ref<any[]>([]);
|
|
|
const displayFileList = computed(() => fileList.value);
|
|
|
|
|
|
@@ -172,11 +122,8 @@ const ossId = computed({
|
|
|
});
|
|
|
|
|
|
const uploadStates = reactive<Record<string | number, UploadState>>({});
|
|
|
-const uploadHistory = ref<UploadHistoryEntry[]>([]);
|
|
|
-const MAX_HISTORY_ITEMS = 10;
|
|
|
|
|
|
// 分片上传相关常量
|
|
|
-// const API_ENDPOINT = "http://localhost:8087/api/upload"; // TODO: 替换为实际后端API
|
|
|
const CHUNK_SIZE = 1 * 1024 * 1024; // 1MB
|
|
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|
|
import { uploadChunk } from '@/api/smsb/source/minioData';
|
|
|
@@ -200,46 +147,6 @@ function formatFileSize(bytes: number): string {
|
|
|
return (bytes / 1024 / 1024 / 1024).toFixed(1) + ' GB';
|
|
|
}
|
|
|
|
|
|
-// TODO: 上传历史的持久化与回显逻辑可根据业务扩展
|
|
|
-function loadHistory() {
|
|
|
- const raw = localStorage.getItem('smsb-upload-history');
|
|
|
- if (raw) {
|
|
|
- try {
|
|
|
- uploadHistory.value = JSON.parse(raw);
|
|
|
- } catch {
|
|
|
- uploadHistory.value = [];
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-function saveHistory() {
|
|
|
- localStorage.setItem('smsb-upload-history', JSON.stringify(uploadHistory.value.slice(0, MAX_HISTORY_ITEMS)));
|
|
|
-}
|
|
|
-// TODO: 可扩展:支持多文件上传/批量操作时的历史记录批量追加
|
|
|
-function addHistoryEntry(file: any, status: string, url?: string | null, uploadId?: string) {
|
|
|
- uploadHistory.value.unshift({
|
|
|
- name: file.name,
|
|
|
- size: file.size,
|
|
|
- status,
|
|
|
- uploadTime: new Date().toLocaleString(),
|
|
|
- url: url || null,
|
|
|
- uploadId: uploadId || undefined
|
|
|
- });
|
|
|
- uploadHistory.value = uploadHistory.value.slice(0, MAX_HISTORY_ITEMS);
|
|
|
- saveHistory();
|
|
|
-}
|
|
|
-function clearHistory() {
|
|
|
- uploadHistory.value = [];
|
|
|
- saveHistory();
|
|
|
-}
|
|
|
-function removeHistoryItem(index: number) {
|
|
|
- uploadHistory.value.splice(index, 1);
|
|
|
- saveHistory();
|
|
|
-}
|
|
|
-
|
|
|
-onMounted(() => {
|
|
|
- loadHistory();
|
|
|
-});
|
|
|
-
|
|
|
// 上传成功逻辑示例(请根据实际上传API修改)
|
|
|
// TODO: 对接实际上传API,处理后端返回、进度、错误、合并等流程
|
|
|
function handleUploadSuccess(response: any) {
|
|
|
@@ -251,11 +158,9 @@ function handleUploadSuccess(response: any) {
|
|
|
if (response?.data?.ossId) {
|
|
|
ossId.value = response.data.ossId;
|
|
|
uploadStates[fileUid] = { status: 'success', progress: 100 };
|
|
|
- addHistoryEntry({ ...fileList.value[fileIndex], ossId: response.data.ossId }, 'success');
|
|
|
}
|
|
|
// Remove successfully uploaded file
|
|
|
fileList.value.splice(fileIndex, 1);
|
|
|
- delete uploadStates[fileUid];
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -293,18 +198,11 @@ async function sendChunkRequest(formData: FormData, fileUid: string | number, ch
|
|
|
const isLastChunk = chunkIndex === (state.totalChunks || 1) - 1;
|
|
|
const fileUrl = response.data.data?.fileUrl;
|
|
|
if (isLastChunk && fileUrl) {
|
|
|
- if (state.status !== "success" && !state.historySaved) {
|
|
|
+ if (state.status !== "success") {
|
|
|
state.finalUrl = fileUrl;
|
|
|
state.status = "success";
|
|
|
state.progress = 100;
|
|
|
- addHistoryEntry(file, "success", state.finalUrl, state.uploadId);
|
|
|
- state.historySaved = true;
|
|
|
- // Remove successfully uploaded file
|
|
|
- const fileIndex = fileList.value.findIndex(f => f.uid === fileUid);
|
|
|
- if (fileIndex !== -1) {
|
|
|
- fileList.value.splice(fileIndex, 1);
|
|
|
- delete uploadStates[fileUid];
|
|
|
- }
|
|
|
+ // delete uploadStates[fileUid];
|
|
|
}
|
|
|
} else if (isLastChunk && !fileUrl) {
|
|
|
if (
|
|
|
@@ -315,10 +213,8 @@ async function sendChunkRequest(formData: FormData, fileUid: string | number, ch
|
|
|
state.progress = 100;
|
|
|
setTimeout(() => {
|
|
|
const currentState = uploadStates[fileUid];
|
|
|
- if (currentState?.status === "merging" && !currentState.historySaved) {
|
|
|
+ if (currentState?.status === "merging") {
|
|
|
currentState.status = "success";
|
|
|
- addHistoryEntry(file, "success", currentState.finalUrl, currentState.uploadId);
|
|
|
- currentState.historySaved = true;
|
|
|
ElMessage.success(`${file.name} 上传并合并成功!`);
|
|
|
}
|
|
|
}, 5000);
|
|
|
@@ -334,6 +230,8 @@ async function sendChunkRequest(formData: FormData, fileUid: string | number, ch
|
|
|
state.errorMessage = error.response?.data?.message || error.message || "上传分片时发生网络或服务器错误";
|
|
|
}
|
|
|
throw error;
|
|
|
+ } finally {
|
|
|
+ // Add finally block to ensure cleanup or other necessary actions
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -357,7 +255,6 @@ async function uploadFileChunks(file: any) {
|
|
|
errorMessage: null,
|
|
|
uploadedChunks: new Set(),
|
|
|
finalUrl: null,
|
|
|
- historySaved: false,
|
|
|
};
|
|
|
const chunkPromises: Promise<void>[] = [];
|
|
|
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
|
|
|
@@ -394,18 +291,19 @@ async function uploadFileChunks(file: any) {
|
|
|
const finalState = uploadStates[fileUid];
|
|
|
if (finalState && finalState.status === "uploading") {
|
|
|
if (finalState.uploadedChunks!.size === totalChunks) {
|
|
|
- if (!finalState.historySaved) {
|
|
|
- finalState.status = "success";
|
|
|
- finalState.progress = 100;
|
|
|
- addHistoryEntry(file, "success", finalState.finalUrl, finalState.uploadId);
|
|
|
- finalState.historySaved = true;
|
|
|
- }
|
|
|
+ finalState.status = "success";
|
|
|
+ finalState.progress = 100;
|
|
|
+ // 上传成功后移除 fileList 中的该文件,防止再次被上传
|
|
|
+ const fileIndex = fileList.value.findIndex(f => f.uid === fileUid);
|
|
|
+ if (fileIndex !== -1) fileList.value.splice(fileIndex, 1);
|
|
|
} else {
|
|
|
finalState.status = "error";
|
|
|
finalState.errorMessage = "内部状态不一致";
|
|
|
}
|
|
|
} else if (finalState && (finalState.status === "success" || finalState.status === "merging")) {
|
|
|
- // 已完成
|
|
|
+ // 已完成,但确保移除 fileList 中的该文件
|
|
|
+ const fileIndex = fileList.value.findIndex(f => f.uid === fileUid);
|
|
|
+ if (fileIndex !== -1) fileList.value.splice(fileIndex, 1);
|
|
|
}
|
|
|
}
|
|
|
} catch (error) {
|
|
|
@@ -434,6 +332,8 @@ async function handleUpload() {
|
|
|
"开始上传",
|
|
|
{ confirmButtonText: "确定", cancelButtonText: "取消", type: "info" }
|
|
|
);
|
|
|
+ // 缓存本次上传的文件uid数组
|
|
|
+ const fileUidsToUpload = filesToUpload.map(f => f.uid);
|
|
|
filesToUpload.forEach((file) => {
|
|
|
if (
|
|
|
file.uid !== undefined &&
|
|
|
@@ -448,17 +348,16 @@ async function handleUpload() {
|
|
|
errorMessage: null,
|
|
|
uploadedChunks: new Set(),
|
|
|
finalUrl: null,
|
|
|
- historySaved: false,
|
|
|
};
|
|
|
}
|
|
|
});
|
|
|
const allUploadPromises = filesToUpload.map((file) => uploadFileChunks(file));
|
|
|
await Promise.allSettled(allUploadPromises);
|
|
|
- const successfulUploads = fileList.value.filter(
|
|
|
- (f) => f.uid !== undefined && uploadStates[f.uid]?.status === "success"
|
|
|
+ const successfulUploads = fileUidsToUpload.filter(
|
|
|
+ uid => uploadStates[uid]?.status === "success"
|
|
|
).length;
|
|
|
- const failedUploads = fileList.value.filter(
|
|
|
- (f) => f.uid !== undefined && uploadStates[f.uid]?.status === "error"
|
|
|
+ const failedUploads = fileUidsToUpload.filter(
|
|
|
+ uid => uploadStates[uid]?.status === "error"
|
|
|
).length;
|
|
|
const stillProcessing = fileList.value.filter(
|
|
|
(f) =>
|