效果图

版本
- vue: 3.2.37
- element-plus: 2.2.14
代码(vue3)
<template>
<el-upload
:class="[props.uploadBtnShow ? '' : 'hidden-upload-btn']"
action="#"
:file-list="uploadFileList"
list-type="picture-card"
:multiple="props.multiple"
:before-upload="beforeUpload"
:accept="uploadAcceptFileType.join()"
:on-change="uploadFileChange"
:on-success="uploadFileSuccess"
:http-request="customUploadFile"
:auto-upload="true"
>
<template #file="{ file }">
<div class="custom-file-cover flex flex-col items-center w-full">
<img :src="uploadIconDics[file.raw.iconType]" width="60" class="mt-3" />
<p class="el-upload-list__item-file-name mt-2 flex items-center text-12px">
<span class="inline-block overflow-hidden whitespace-nowrap text-ellipsis max-w-72px">{{
file.raw.frontName
}}</span>
<span>{{ '.' + file.raw.suffix }}</span>
</p>
<span class="el-upload-list__item-actions">
<div v-if="file.status !== 'uploading'">
<span class="el-upload-list__item-preview" v-if="props.previewBtnShow && !file.raw.previewBtnHidden">
<el-icon @click="uploadFileHandler('preview', file)"><ZoomIn /></el-icon>
</span>
<span class="el-upload-list__item-delete" v-if="props.downBtnShow">
<el-icon @click="uploadFileHandler('download', file)"><Download /></el-icon>
</span>
<span class="el-upload-list__item-delete" v-if="props.deleteBtnShow">
<el-icon @click="uploadFileHandler('delete', file)"><Delete /></el-icon>
</span>
</div>
</span>
</div>
</template>
<div class="flex flex-col items-center">
<el-icon :size="24"><Plus style="color: #1d1d1dff" /></el-icon>
<p class="text-12px text-#00000066 mt-2">上传文件</p>
</div>
</el-upload>
<el-empty :image-size="emptyImageSize" v-if="emptyShow && !props.uploadBtnShow && !uploadFileList.length" />
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, watchEffect } from 'vue';
import { ElMessage } from 'element-plus';
import { downFileByAElement, beforeUploadHandler } from '@/utils/util';
import iconXls from '@/assets/img/icon-xls.png';
import iconPdf from '@/assets/img/icon-pdf.png';
import iconDoc from '@/assets/img/icon-doc.png';
import iconImg from '@/assets/img/icon-img.png';
import iconOther from '@/assets/img/icon-file.png';
import iconTxt from '@/assets/img/icon-txt.png';
import iconCsv from '@/assets/img/icon-csv.png';
import iconHtml from '@/assets/img/icon-html.png';
const props = defineProps({
uploadFileList: {
type: Array,
required: true,
default: [],
},
autoUpload: {
type: Boolean,
required: false,
default: true,
},
uploadBtnShow: {
type: Boolean,
required: false,
default: true,
},
downBtnShow: {
type: Boolean,
required: false,
default: false,
},
deleteBtnShow: {
type: Boolean,
required: false,
default: false,
},
previewBtnShow: {
type: Boolean,
required: false,
default: false,
},
multiple: {
type: Boolean,
required: false,
default: false,
},
emptyShow: {
type: Boolean,
required: false,
default: false,
},
emptyImageSize: {
type: Number,
required: false,
default: 96,
},
});
const emit = defineEmits([
'update:uploadFileList',
'customUploadFile',
'downUploadFile',
'deleteUploadFile',
'previewUploadFile',
'uploadFileSuccess',
]);
const currentFile = ref([]);
const uploadFileList = ref([]);
const uploadFileListTemp = ref([]);
const uploadAcceptFileType = [
'.doc',
'.docx',
'.xlsx',
'.xls',
'.txt',
'.csv',
'.png',
'.jpg',
'.gif',
'.pdf',
'.html',
];
const uploadIconDics = {
xls: iconXls,
pdf: iconPdf,
doc: iconDoc,
img: iconImg,
other: iconOther,
html: iconHtml,
csv: iconCsv,
txt: iconTxt,
};
const customUploadFile = () => {
if (!props.autoUpload) return;
emit('customUploadFile', currentFile.value, uploadFileList.value);
};
const uploadFileHandler = (flag, file) => {
if (flag === 'delete') {
let id = 'id';
if (!file.id) {
id = 'uid';
}
const index = uploadFileList.value.findIndex(item => item[id] === file[id]);
if (index > -1) {
uploadFileList.value.splice(index, 1);
}
if (!file.id) {
const currentFile = uploadFileListTemp.value.find(item => item.affixName === file.name);
if (!currentFile) return;
file.id = currentFile.id;
}
emit('deleteUploadFile', file);
} else if (flag === 'download') {
if (file.id) {
emit('downUploadFile', file);
} else {
downFileByAElement(file.url, file.name);
}
} else if (flag === 'preview') {
let id = file.id;
if (!id) {
const currentFile = uploadFileListTemp.value.find(item => item.affixName === file.name);
id = currentFile?.id;
}
if (!id) return;
file.id = id;
emit('previewUploadFile', file);
}
};
const beforeUpload = (file, flag = false) => {
beforeUploadHandler(file);
if (flag) return;
if (!uploadAcceptFileType.includes('.' + file.suffix)) {
ElMessage.error('文件类型不支持');
return false;
} else if (file.size / 1024 / 1024 > 5) {
ElMessage.error('文件大小不能超过5M');
return false;
}
};
const uploadFileSuccess = () => {
emit('uploadFileSuccess');
};
const uploadFileChange = (file, files) => {
emit('update:uploadFileList', files);
uploadFileList.value = files;
currentFile.value = file;
};
const init = list => {
uploadFileList.value = list;
uploadFileList.value.forEach((item: any) => {
if (!item.affixName) return;
item.name = item.affixName;
const temp = {
name: item.affixName,
};
beforeUpload(temp, true);
item.raw = temp;
});
};
watchEffect(() => {
const list = props.uploadFileList;
init(list);
});
</script>
<style scoped lang="less">
.hidden-upload-btn :deep(.el-upload) {
display: none;
}
:deep(.el-upload-list) {
.el-upload,
.el-upload-list__item {
width: 120px;
height: 120px;
border-style: dashed;
}
}
</style>
</script>
<style scoped lang="less">
.hidden-upload-btn :deep(.el-upload) {
display: none;
}
:deep(.el-upload-list) {
.el-upload,
.el-upload-list__item {
width: 120px;
height: 120px;
border-style: dashed;
}
}
</style>
export function getFileExtensionName(filename) {
return filename.slice(((filename.lastIndexOf('.') - 1) >>> 0) + 2);
}
export function getFileFrontName(filename) {
const index = filename.lastIndexOf('.');
return filename.slice(0, index);
}
export function beforeUploadHandler(file) {
const fileName = file.name;
const type = getFileExtensionName(fileName);
file.suffix = type;
file.frontName = getFileFrontName(fileName);
if (['xlsx', 'xls'].includes(type)) {
file.iconType = 'xls';
file.previewBtnHidden = true;
} else if (['jpg', 'png', 'jpeg', 'gif', 'webp', 'svg'].includes(type)) {
file.iconType = 'img';
} else if (['pdf'].includes(type)) {
file.iconType = 'pdf';
} else if (['doc', 'docx'].includes(type)) {
file.iconType = 'doc';
} else if (['txt'].includes(type)) {
file.iconType = 'txt';
} else if (['htm', 'html'].includes(type)) {
file.iconType = 'html';
} else if (['csv'].includes(type)) {
file.iconType = 'csv';
} else {
file.iconType = 'other';
}
}
export function downFileByAElement(data, fileName?) {
let url = null;
if (typeof data === 'string') {
url = data;
} else {
const blob = new Blob([data]);
url = window.URL.createObjectURL(blob);
}
const a = document.createElement('a');
a.href = url;
if (fileName) {
a.setAttribute('download', fileName);
}
a.click();
}
组件调用
<template>
<UploadCustom
:emptyShow="true"
:uploadBtnShow="uploadBtnShow"
v-model:uploadFileList="uploadFileList"
:downBtnShow="true"
:previewBtnShow="true"
:deleteBtnShow="true"
@previewUploadFile="previewUploadFile"
@downUploadFile="downUploadFile"
@deleteUploadFile="deleteUploadFile"
@uploadFileSuccess="uploadFileSuccess"
@customUploadFile="customUploadFile"
></UploadCustom>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import UploadCustom from '@/components/UploadCustom.vue'
const uploadBtnShow = ref(true)
const uploadFileList = ref([])
const customUploadFile = (file,files) => {
const uploadData={
file:file.raw
}
uploadMetricsFileApi(uploadData)
};
const uploadFileSuccess = () => {
};
const previewUploadFile = file => {
};
const deleteUploadFile = file => {
deleteUploadFileApi({ id: file.id });
};
const downUploadFile = file => {
const params = {
id: file.id,
};
downloadUploadFileApi(params);
};
</script>
图标附件下载
问题
参考文档