Files
EasyFlow/easyflow-ui-admin/app/src/components/upload/DragFileUpload.vue
陈子默 c3673ece46 fix: 统一文档解析文件格式校验
- 统一知识库和工作流支持格式并增加前后端上传拦截

- 拒绝 XLS 与伪装 XLSX,避免空内容解析成功
2026-09-02 19:15:01 +08:00

127 lines
3.4 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 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>