|
|
@@ -7,9 +7,7 @@
|
|
|
<div class="upload-text">
|
|
|
<em>拖拽文件到此处或</em>
|
|
|
<br />
|
|
|
- <el-button type="primary" class="select-button">
|
|
|
- 点击选择文件
|
|
|
- </el-button>
|
|
|
+ <el-button type="primary" class="select-button"> 点击选择文件 </el-button>
|
|
|
</div>
|
|
|
</el-upload>
|
|
|
|
|
|
@@ -47,12 +45,13 @@
|
|
|
<el-tag type="danger">上传失败</el-tag>
|
|
|
<el-tooltip v-if="uploadStates[ossId]?.errorMessage"
|
|
|
:content="uploadStates[ossId]?.errorMessage || '未知错误'" placement="top">
|
|
|
- <el-icon style="margin-left: 4px;vertical-align: middle;cursor: help;">
|
|
|
+ <el-icon style="margin-left: 4px; vertical-align: middle; cursor: help">
|
|
|
<QuestionFilled />
|
|
|
</el-icon>
|
|
|
</el-tooltip>
|
|
|
</span>
|
|
|
- <el-tag v-else-if="uploadStates[ossId]?.status === 'merging'" type="warning" effect="light">合并中...</el-tag>
|
|
|
+ <el-tag v-else-if="uploadStates[ossId]?.status === 'merging'" type="warning"
|
|
|
+ effect="light">合并中...</el-tag>
|
|
|
<el-tag v-else-if="uploadStates[ossId]?.status === 'pending'" type="info">待上传</el-tag>
|
|
|
<el-tag v-else type="info" effect="plain">未知状态 ({{ uploadStates[ossId]?.status }})</el-tag>
|
|
|
</div>
|
|
|
@@ -104,8 +103,8 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, computed, reactive, onMounted, watch, defineProps, defineEmits } from "vue";
|
|
|
-import { UploadFilled, QuestionFilled } from "@element-plus/icons-vue";
|
|
|
+import { ref, computed, reactive, onMounted, watch, defineProps, defineEmits } from 'vue';
|
|
|
+import { UploadFilled, QuestionFilled } from '@element-plus/icons-vue';
|
|
|
|
|
|
// ================= 类型声明 =================
|
|
|
interface UploadState {
|
|
|
@@ -127,15 +126,15 @@ const props = defineProps({
|
|
|
default: null
|
|
|
}
|
|
|
}); // only declared once
|
|
|
-const emit = defineEmits(["update:modelValue"]); // 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" },
|
|
|
+ { label: '图片', value: 'image' },
|
|
|
+ { label: '视频', value: 'video' },
|
|
|
+ { label: '文档', value: 'document' },
|
|
|
+ { label: '其它', value: 'other' }
|
|
|
]);
|
|
|
-const selectedType = ref("");
|
|
|
+const selectedType = ref('');
|
|
|
|
|
|
const fileList = ref<any[]>([]);
|
|
|
const displayFileList = computed(() => fileList.value);
|
|
|
@@ -143,7 +142,7 @@ const displayFileList = computed(() => fileList.value);
|
|
|
// ossId 用于文件唯一标识
|
|
|
const ossId = computed({
|
|
|
get: () => props.modelValue,
|
|
|
- set: (val) => emit("update:modelValue", val)
|
|
|
+ set: (val) => emit('update:modelValue', val)
|
|
|
});
|
|
|
|
|
|
const uploadStates = reactive<Record<string | number, UploadState>>({});
|
|
|
@@ -151,9 +150,7 @@ const uploadHistory = ref<UploadHistoryEntry[]>([]);
|
|
|
const MAX_HISTORY_ITEMS = 10;
|
|
|
|
|
|
const isUploading = computed(() => {
|
|
|
- return Object.values(uploadStates).some(
|
|
|
- (state) => state.status === "uploading"
|
|
|
- );
|
|
|
+ return Object.values(uploadStates).some((state) => state.status === 'uploading');
|
|
|
});
|
|
|
|
|
|
// ================= 监听器 =================
|
|
|
@@ -164,17 +161,16 @@ watch(
|
|
|
}
|
|
|
);
|
|
|
|
|
|
-
|
|
|
function formatFileSize(bytes: number): string {
|
|
|
- if (bytes < 1024) return bytes + " B";
|
|
|
- if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
|
|
|
- if (bytes < 1024 * 1024 * 1024) return (bytes / 1024 / 1024).toFixed(1) + " MB";
|
|
|
- return (bytes / 1024 / 1024 / 1024).toFixed(1) + " GB";
|
|
|
+ if (bytes < 1024) return bytes + ' B';
|
|
|
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
|
+ if (bytes < 1024 * 1024 * 1024) return (bytes / 1024 / 1024).toFixed(1) + ' MB';
|
|
|
+ return (bytes / 1024 / 1024 / 1024).toFixed(1) + ' GB';
|
|
|
}
|
|
|
|
|
|
// TODO: 上传历史的持久化与回显逻辑可根据业务扩展
|
|
|
function loadHistory() {
|
|
|
- const raw = localStorage.getItem("smsb-upload-history");
|
|
|
+ const raw = localStorage.getItem('smsb-upload-history');
|
|
|
if (raw) {
|
|
|
try {
|
|
|
uploadHistory.value = JSON.parse(raw);
|
|
|
@@ -184,7 +180,7 @@ function loadHistory() {
|
|
|
}
|
|
|
}
|
|
|
function saveHistory() {
|
|
|
- localStorage.setItem("smsb-upload-history", JSON.stringify(uploadHistory.value.slice(0, MAX_HISTORY_ITEMS)));
|
|
|
+ localStorage.setItem('smsb-upload-history', JSON.stringify(uploadHistory.value.slice(0, MAX_HISTORY_ITEMS)));
|
|
|
}
|
|
|
// TODO: 可扩展:支持多文件上传/批量操作时的历史记录批量追加
|
|
|
function addHistoryEntry(file: any, status: string) {
|
|
|
@@ -192,7 +188,7 @@ function addHistoryEntry(file: any, status: string) {
|
|
|
name: file.name,
|
|
|
size: file.size,
|
|
|
status,
|
|
|
- uploadTime: new Date().toLocaleString(),
|
|
|
+ uploadTime: new Date().toLocaleString()
|
|
|
});
|
|
|
uploadHistory.value = uploadHistory.value.slice(0, MAX_HISTORY_ITEMS);
|
|
|
saveHistory();
|
|
|
@@ -228,7 +224,7 @@ function handleFileChange(uploadFile: any, uploadFiles: any[]) {
|
|
|
// 避免重复添加
|
|
|
if (!fileList.value.some((f) => f.uid === uploadFile.uid)) {
|
|
|
fileList.value.push(uploadFile);
|
|
|
- uploadStates[uploadFile.uid] = { status: "pending" };
|
|
|
+ uploadStates[uploadFile.uid] = { status: 'pending' };
|
|
|
}
|
|
|
}
|
|
|
function handleFileRemove(uploadFile: any, uploadFiles: any[]) {
|
|
|
@@ -243,15 +239,15 @@ function handleManualRemove(fileToRemove: any) {
|
|
|
// TODO: 对接实际上传API,完善错误处理与用户提示,支持多文件上传/批量操作
|
|
|
async function handleUpload() {
|
|
|
for (const file of fileList.value) {
|
|
|
- uploadStates[file.uid] = { status: "uploading", progress: 0 };
|
|
|
+ uploadStates[file.uid] = { status: 'uploading', progress: 0 };
|
|
|
try {
|
|
|
// 这里只是模拟上传,实际应替换为后端API
|
|
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
|
- uploadStates[file.uid] = { status: "success", progress: 100 };
|
|
|
- addHistoryEntry(file, "success");
|
|
|
+ uploadStates[file.uid] = { status: 'success', progress: 100 };
|
|
|
+ addHistoryEntry(file, 'success');
|
|
|
} catch (e: any) {
|
|
|
- uploadStates[file.uid] = { status: "error", errorMessage: e?.message || "上传失败" };
|
|
|
- addHistoryEntry(file, "error");
|
|
|
+ uploadStates[file.uid] = { status: 'error', errorMessage: e?.message || '上传失败' };
|
|
|
+ addHistoryEntry(file, 'error');
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -267,7 +263,11 @@ async function handleUpload() {
|
|
|
box-sizing: border-box;
|
|
|
padding: 0 0 16px 0;
|
|
|
}
|
|
|
-.upload-area, .file-list, .upload-actions, .history-list {
|
|
|
+
|
|
|
+.upload-area,
|
|
|
+.file-list,
|
|
|
+.upload-actions,
|
|
|
+.history-list {
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
@@ -280,35 +280,43 @@ async function handleUpload() {
|
|
|
text-align: center;
|
|
|
width: 100%;
|
|
|
}
|
|
|
+
|
|
|
.upload-icon {
|
|
|
font-size: 40px;
|
|
|
color: #409eff;
|
|
|
margin-bottom: 10px;
|
|
|
}
|
|
|
+
|
|
|
.upload-text {
|
|
|
font-size: 16px;
|
|
|
color: #606266;
|
|
|
}
|
|
|
+
|
|
|
.select-button {
|
|
|
margin-top: 10px;
|
|
|
}
|
|
|
+
|
|
|
.type-selection {
|
|
|
margin-bottom: 12px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
}
|
|
|
+
|
|
|
.type-select {
|
|
|
width: 180px;
|
|
|
}
|
|
|
+
|
|
|
.file-list {
|
|
|
margin-bottom: 16px;
|
|
|
width: 100%;
|
|
|
}
|
|
|
+
|
|
|
.upload-actions {
|
|
|
margin: 18px 0 12px 0;
|
|
|
display: flex;
|
|
|
gap: 12px;
|
|
|
}
|
|
|
+
|
|
|
.history-list {
|
|
|
margin-top: 18px;
|
|
|
}
|