feat: 新增 S3 文件上传组件,支持文件大小和数量限制,优化上传流程
This commit is contained in:
143
src/components/upload/index.vue
Normal file
143
src/components/upload/index.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import type { UploadCustomRequestOptions, UploadFileInfo } from 'naive-ui';
|
||||
import { type UploadFetchOptions, uploadToS3 } from '@/utils/aws/s3';
|
||||
|
||||
defineOptions({ name: 'UploadS3' });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: string[];
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
accept?: string;
|
||||
multiple?: boolean;
|
||||
maxSize?: number; // MB
|
||||
maxFiles?: number;
|
||||
fetchOptions: Partial<UploadFetchOptions>;
|
||||
}>(),
|
||||
{
|
||||
modelValue: () => [],
|
||||
accept: '*/*',
|
||||
multiple: true,
|
||||
maxSize: 10,
|
||||
maxFiles: 5,
|
||||
placeholder: '',
|
||||
label: ''
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: string[]): void;
|
||||
}>();
|
||||
|
||||
const fileList = ref<UploadFileInfo[]>([]);
|
||||
|
||||
// 将 modelValue 转换为文件列表显示
|
||||
const initFileList = computed(() => {
|
||||
return props.modelValue.map((fileId, index) => ({
|
||||
id: fileId,
|
||||
name: `文件 ${index + 1}`,
|
||||
status: 'finished' as const,
|
||||
url: fileId
|
||||
}));
|
||||
});
|
||||
|
||||
// 自定义上传请求
|
||||
async function handleCustomRequest({ file, onProgress, onFinish, onError }: UploadCustomRequestOptions) {
|
||||
try {
|
||||
// 验证文件大小
|
||||
const fileSizeMB = file.file!.size / 1024 / 1024;
|
||||
if (fileSizeMB > props.maxSize) {
|
||||
window.$message?.error(`文件大小不能超过 ${props.maxSize}MB`);
|
||||
onError();
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证文件数量
|
||||
if (fileList.value.length >= props.maxFiles) {
|
||||
window.$message?.error(`最多只能上传 ${props.maxFiles} 个文件`);
|
||||
onError();
|
||||
return;
|
||||
}
|
||||
|
||||
// 上传到 S3
|
||||
const options = {
|
||||
fileName: file.name,
|
||||
fileSize: file.file?.size,
|
||||
businessType: props.fetchOptions.businessType,
|
||||
...props.fetchOptions
|
||||
} as UploadFetchOptions;
|
||||
|
||||
const fileId = await uploadToS3(file.file as File, {
|
||||
fetchOptions: options,
|
||||
onProgress: percent => {
|
||||
onProgress({ percent });
|
||||
}
|
||||
});
|
||||
|
||||
// 更新文件 ID 到 modelValue
|
||||
const newFileIds = [...props.modelValue, fileId];
|
||||
emit('update:modelValue', newFileIds);
|
||||
|
||||
onFinish();
|
||||
window.$message?.success(`文件 ${file.name} 上传成功`);
|
||||
} catch (error: any) {
|
||||
window.$message?.error(error.message || '上传失败');
|
||||
onError();
|
||||
}
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
function handleRemove({ file }: { file: UploadFileInfo }) {
|
||||
const newFileIds = props.modelValue.filter(id => id !== file.id);
|
||||
emit('update:modelValue', newFileIds);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 上传前验证
|
||||
function beforeUpload({ file }: { file: UploadFileInfo }) {
|
||||
const fileSizeMB = file.file!.size / 1024 / 1024;
|
||||
if (fileSizeMB > props.maxSize) {
|
||||
window.$message?.error(`文件大小不能超过 ${props.maxSize}MB`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<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>
|
||||
<div class="mt-2 text-12px text-gray-400">
|
||||
<div v-if="maxSize">单个文件大小不超过 {{ maxSize }}MB</div>
|
||||
<div v-if="maxFiles">最多上传 {{ maxFiles }} 个文件</div>
|
||||
<div v-if="accept !== '*/*'">支持格式:{{ accept }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.upload-s3 {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user