Files
EasyFlow/easyflow-ui-usercenter/app/src/components/upload/ChatFileUploader.vue
2026-02-22 18:56:10 +08:00

159 lines
3.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 setup lang="ts">
import type { FilesCardProps } from 'vue-element-plus-x/types/FilesCard';
import { defineExpose, defineProps, nextTick, ref, watch } from 'vue';
import { Attachments } from 'vue-element-plus-x';
import { ElMessage } from 'element-plus';
import { api } from '#/api/request';
const props = defineProps({
maxSize: {
type: Number,
default: 2,
},
action: {
type: String,
default: '/api/v1/commons/upload',
},
externalFiles: {
type: Array as () => File[],
default: () => [],
},
});
const emit = defineEmits(['deleteAll']);
type SelfFilesCardProps = {
fileSize: number;
id?: number;
name: string;
uid: number | string;
url: string; // 上传后的文件地址
} & FilesCardProps;
const files = ref<SelfFilesCardProps[]>([]);
/**
* 上传前校验
*/
function handleBeforeUpload(file: File) {
const maxSizeBytes = props.maxSize * 1024 * 1024;
if (file.size > maxSizeBytes) {
ElMessage.error(`文件大小不能超过 ${props.maxSize}MB!`);
return false;
}
return true;
}
/**
* 拖拽上传处理
*/
async function handleUploadDrop(dropFiles: File[]) {
if (dropFiles?.length) {
if (dropFiles[0]?.type === '') {
ElMessage.error('禁止上传文件夹!');
return false;
}
for (const file of dropFiles) {
if (handleBeforeUpload(file)) {
await handleHttpRequest({ file });
}
}
}
}
/**
* 自定义上传请求
*/
async function handleHttpRequest(options: { file: File }) {
const { file } = options;
const formData = new FormData();
formData.append('file', file);
try {
const res = await api.upload(props.action, { file }, {});
const fileUrl = res.data.path;
const fileItem: SelfFilesCardProps = {
id: files.value.length,
uid: Date.now() + Math.random(),
name: file.name,
fileSize: file.size,
url: fileUrl,
showDelIcon: true,
imgVariant: 'square',
};
files.value.push(fileItem);
} catch (error) {
ElMessage.error(`${file.name} 上传失败`);
console.error('上传失败:', error);
}
}
/**
* 批量上传外部文件(父组件传递的文件数组)
*/
async function uploadExternalFiles(fileList: File[]) {
if (fileList.length === 0) return;
for (const file of fileList) {
if (handleBeforeUpload(file)) {
await handleHttpRequest({ file });
}
}
}
/**
* 组件内部完成删除逻辑
*/
function handleDeleteCard(item: FilesCardProps) {
const targetItem = item as SelfFilesCardProps;
files.value = files.value.filter((file) => file.id !== targetItem.id);
if (files.value.length === 0) {
emit('deleteAll');
}
}
/**
* 暴露给父组件的核心方法仅返回URL字符串数组
* @returns string[] 纯文件地址列表
*/
function getFileList(): string[] {
return files.value.map((file) => file.url);
}
watch(
() => props.externalFiles,
async (newFiles) => {
if (newFiles.length > 0) {
await nextTick();
await uploadExternalFiles(newFiles);
}
},
{ deep: true, immediate: true },
);
defineExpose({
getFileList,
clearFiles() {
files.value = [];
},
});
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<Attachments
:http-request="handleHttpRequest"
:items="files"
drag
:before-upload="handleBeforeUpload"
:hide-upload="false"
@upload-drop="handleUploadDrop"
@delete-card="handleDeleteCard"
/>
</div>
</template>
<style scoped lang="less"></style>