feat: 优化上传组件,增加文件元数据获取和加载状态,更新路由和国际化支持

This commit is contained in:
2026-01-09 14:13:45 +07:00
parent 5308020003
commit 1be06c6f23
6 changed files with 126 additions and 40 deletions

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { onMounted, ref, watch } from 'vue';
import type { UploadCustomRequestOptions, UploadFileInfo } from 'naive-ui';
import { type UploadFetchOptions, uploadToS3 } from '@/utils/aws/s3';
import { type UploadFetchOptions, getS3FileMetadata, uploadToS3 } from '@/utils/aws/s3';
defineOptions({ name: 'UploadS3' });
@@ -32,15 +32,63 @@ const emit = defineEmits<{
}>();
const fileList = ref<UploadFileInfo[]>([]);
const loading = ref(false);
// 将 modelValue 转换为文件列表显示
const initFileList = computed(() => {
return props.modelValue.map((fileId, index) => ({
id: fileId,
name: `文件 ${index + 1}`,
status: 'finished' as const,
url: fileId
}));
// 初始化文件列表
async function initFileList() {
const fileIds = props.modelValue.filter(id => id && id.trim() !== '');
if (!props.modelValue || fileIds.length === 0) {
fileList.value = [];
return;
}
loading.value = true;
try {
const fileInfoPromises = props.modelValue.map(async fileId => {
try {
const metadata = await getS3FileMetadata(fileId);
return {
id: fileId,
name: metadata.fileName || fileId,
status: 'finished' as const,
url: fileId,
file: null as any
};
} catch (error) {
return {
id: fileId,
name: fileId,
status: 'error' as const,
url: fileId,
file: null as any
};
}
});
fileList.value = await Promise.all(fileInfoPromises);
} catch (error) {
window.$message?.error('加载文件列表失败');
} finally {
loading.value = false;
}
}
// 监听 modelValue 变化
watch(
() => props.modelValue,
(newVal, oldVal) => {
// 只在外部变化时重新加载(避免内部上传/删除时重复加载)
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
initFileList();
}
},
{ deep: true }
);
// 组件挂载时初始化
onMounted(() => {
initFileList();
});
// 自定义上传请求
@@ -76,6 +124,15 @@ async function handleCustomRequest({ file, onProgress, onFinish, onError }: Uplo
}
});
// 添加到文件列表
fileList.value.push({
id: fileId,
name: file.name,
status: 'finished',
url: fileId,
file: null as any
});
// 更新文件 ID 到 modelValue
const newFileIds = [...props.modelValue, fileId];
emit('update:modelValue', newFileIds);
@@ -90,6 +147,9 @@ async function handleCustomRequest({ file, onProgress, onFinish, onError }: Uplo
// 删除文件
function handleRemove({ file }: { file: UploadFileInfo }) {
// 从文件列表中移除
fileList.value = fileList.value.filter(f => f.id !== file.id);
// 更新 modelValue
const newFileIds = props.modelValue.filter(id => id !== file.id);
emit('update:modelValue', newFileIds);
return true;
@@ -109,25 +169,26 @@ function beforeUpload({ file }: { file: UploadFileInfo }) {
<template>
<div class="upload-s3">
<div v-if="label" class="mb-2 text-14px font-medium">{{ label }}</div>
<NUpload
v-model:file-list="fileList"
:default-file-list="initFileList"
:accept="accept"
:multiple="multiple"
:max="maxFiles"
:custom-request="handleCustomRequest"
:on-remove="handleRemove"
:on-before-upload="beforeUpload"
list-type="text"
show-download-button
>
<NButton>
<template #icon>
<icon-ic-round-upload class="text-icon" />
</template>
{{ placeholder || '选择文件' }}
</NButton>
</NUpload>
<NSpin :show="loading">
<NUpload
v-model:file-list="fileList"
:accept="accept"
:multiple="multiple"
:max="maxFiles"
:custom-request="handleCustomRequest"
:on-remove="handleRemove"
:on-before-upload="beforeUpload"
list-type="text"
show-download-button
>
<NButton>
<template #icon>
<icon-ic-round-upload class="text-icon" />
</template>
{{ placeholder || '选择文件' }}
</NButton>
</NUpload>
</NSpin>
<div class="mt-2 text-12px text-gray-400">
<div v-if="maxSize">单个文件大小不超过 {{ maxSize }}MB</div>
<div v-if="maxFiles">最多上传 {{ maxFiles }} 个文件</div>