127 lines
3.4 KiB
Vue
127 lines
3.4 KiB
Vue
<script setup lang="ts">
|
||
import type { UploadProps } from 'element-plus';
|
||
|
||
import { ref } from 'vue';
|
||
|
||
import { useAppConfig } from '@easyflow/hooks';
|
||
import { $t } from '@easyflow/locales';
|
||
import { useAccessStore } from '@easyflow/stores';
|
||
|
||
import { UploadFilled } from '@element-plus/icons-vue';
|
||
import { ElIcon, ElMessage, ElUpload } from 'element-plus';
|
||
|
||
import { DocumentParseFilePolicy } from '#/utils/document-parse-file-policy';
|
||
import {
|
||
normalizeUploadError,
|
||
resolveUploadPath,
|
||
} from '#/utils/upload-response';
|
||
|
||
const props = defineProps({
|
||
action: {
|
||
type: String,
|
||
default: '/api/v1/commons/upload',
|
||
},
|
||
visible: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
});
|
||
|
||
const emit = defineEmits(['success', 'error', 'onChange']);
|
||
const MAX_FILE_SIZE_BYTES = 100 * 1024 * 1024;
|
||
const accessStore = useAccessStore();
|
||
const headers = ref({
|
||
'easyflow-token': accessStore.accessToken,
|
||
});
|
||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||
|
||
// 核心:获取ElUpload组件实例
|
||
const uploadRef = ref<InstanceType<typeof ElUpload>>();
|
||
|
||
// 上传成功回调
|
||
const handleSuccess: UploadProps['onSuccess'] = (response) => {
|
||
try {
|
||
emit('success', resolveUploadPath(response));
|
||
} catch (error) {
|
||
const normalizedError = normalizeUploadError(error);
|
||
ElMessage.error(normalizedError.message);
|
||
emit('error', normalizedError);
|
||
}
|
||
};
|
||
const handleError: UploadProps['onError'] = (error) => {
|
||
const normalizedError = normalizeUploadError(error);
|
||
ElMessage.error(normalizedError.message);
|
||
emit('error', normalizedError);
|
||
};
|
||
|
||
const beforeUpload: UploadProps['beforeUpload'] = async (rawFile) => {
|
||
try {
|
||
await DocumentParseFilePolicy.validateFiles([rawFile]);
|
||
} catch (error: any) {
|
||
ElMessage.warning(error?.message || $t('message.notSupported'));
|
||
return false;
|
||
}
|
||
if (rawFile.size > MAX_FILE_SIZE_BYTES) {
|
||
ElMessage.warning($t('message.upload.fileTooLarge'));
|
||
return false;
|
||
}
|
||
return true;
|
||
};
|
||
|
||
// 文件状态变化回调
|
||
const handleChange: UploadProps['onChange'] = (file, fileList) => {
|
||
emit('onChange', file, fileList);
|
||
};
|
||
|
||
// 暴露给父组件的方法:手动触发文件选择
|
||
const triggerFileSelect = () => {
|
||
if (uploadRef.value) {
|
||
// 调用ElUpload内部的上传按钮点击事件
|
||
const uploadInput = uploadRef.value.$el.querySelector('input[type="file"]');
|
||
if (uploadInput) {
|
||
uploadInput.click(); // 触发原生文件选择框
|
||
}
|
||
}
|
||
};
|
||
|
||
const clearFiles = () => {
|
||
uploadRef.value?.clearFiles?.();
|
||
};
|
||
|
||
// 对外暴露方法(父组件可通过ref调用)
|
||
defineExpose({
|
||
clearFiles,
|
||
triggerFileSelect,
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<!-- 给ElUpload添加ref引用 -->
|
||
<ElUpload
|
||
ref="uploadRef"
|
||
class="upload-demo"
|
||
drag
|
||
:headers="headers"
|
||
:action="`${apiURL}${props.action}`"
|
||
:accept="DocumentParseFilePolicy.accept"
|
||
:before-upload="beforeUpload"
|
||
:on-success="handleSuccess"
|
||
:on-error="handleError"
|
||
:on-change="handleChange"
|
||
multiple
|
||
:style="{ display: props.visible ? 'block' : 'none' }"
|
||
>
|
||
<ElIcon size="48" color="hsl(var(--primary))">
|
||
<UploadFilled />
|
||
</ElIcon>
|
||
<div class="flex flex-col gap-1">
|
||
<span class="text-base">{{ $t('message.upload.title') }}</span>
|
||
<span class="text-muted-foreground text-sm">{{
|
||
$t('message.upload.description', {
|
||
types: DocumentParseFilePolicy.supportedTypeLabel,
|
||
})
|
||
}}</span>
|
||
</div>
|
||
</ElUpload>
|
||
</template>
|