Files
rwa-admin/src/components/upload/index.vue

175 lines
4.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue';
import type { UploadCustomRequestOptions, UploadFileInfo } from 'naive-ui';
import { client, safeClient } from '@/service/api';
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[]>([]);
const loading = ref(false);
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 { data } = await safeClient(client.api.file_storage.access_urls.post({ fileIds }));
fileList.value =
data.value?.map(item => ({
id: item.id,
name: item.fileName || item.id,
status: 'finished' as const,
url: item.url,
file: null as any
})) || [];
} catch (error) {
window.$message?.error('加载文件列表失败');
} finally {
loading.value = false;
}
}
watch(
() => props.modelValue,
(newVal, oldVal) => {
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
initFileList();
}
},
{ deep: true }
);
onMounted(() => {
initFileList();
});
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;
}
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 });
}
});
// 直接修改 file 对象的 idNaiveUI 会自动同步到 fileList
file.id = fileId;
file.status = 'finished';
file.url = fileId;
const newFileIds = [...props.modelValue, fileId].filter(Boolean);
emit('update:modelValue', newFileIds);
onFinish();
window.$message?.success(`文件 ${file.name} 上传成功`);
} catch (error: any) {
onError();
}
}
function handleRemove({ file }: { file: UploadFileInfo }) {
// 只删除上传成功的文件(有有效 id 的文件)
if (file.id && typeof file.id === 'string' && props.modelValue.includes(file.id)) {
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>
<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>
<div v-if="accept !== '*/*'">支持格式{{ accept }}</div>
</div>
</div>
</template>
<style lang="css" scoped>
.upload-s3 {
width: 100%;
}
</style>