<template> <div class="content"> <el-upload v-loading="isUploading" :disabled="componentError" class="upload-demo" drag :headers="headers" :file-list="viewFileList" :name="name" :show-file-list="showFileList" :list-type="showFileListType" :multiple="multiple" :action="uploadUrl" :limit="limit" :on-success="handleUploadSuccess" :before-upload="handleBeforeUpload" :on-error="handleUploadError" :on-exceed="handleExceed" :before-remove="handleBeforeRemove" :on-remove="handleRemove" :on-preview="handlePreviewOpen" > <i class="el-icon-upload"></i> <div class="el-upload__text" v-if="!componentError" >将文件拖到此处,或<em>点击上传</em></div> <div class="error-text" v-else > 组件配置错误,请查看控制台 </div> <div class="el-upload__tip" slot="tip" > 文件大小不超过{{ limitSize }}m,文件类型为{{ types.toString() }} </div> </el-upload> <!-- 预览 --> <el-dialog title="预览" :visible.sync="previewObj.show" width="60%" :before-close="handlePreviewClose" > <div class="preview-content"> <template v-if="previewObj.file && previewObj.file.type.includes('image')"> <el-image class="preview-item" :src="previewObj.file.url" /> </template> <template v-if="previewObj.file && previewObj.file.type.includes('video')"> <video class="preview-item" controls :src="previewObj.file.url" /> </template> </div> </el-dialog> </div> </template> <script> import mime from 'mime' let fullLoading = null const baseURL = process.env.VUE_APP_DOMAIN_PREFIX export default { name: "Upload", props: { headers:{ type:Object, default:()=>({}) }, /** 上传时候表单的KEY */ name: { type: String, default: () => "file" }, /** 限制上传数量 */ limit: { type: Number, default: () => 5 }, /** 限制的那张大小 单位M */ limitSize: { type: Number, default: () => 5 }, /** 是否多选 */ multiple: { type: Boolean, default: () => true }, /** 是否展示文件列表 */ showFileList: { type: Boolean, default: () => true }, /** 文件展示方式 text/picture/picture-card */ showFileListType:{ type:String, default:()=>'text' }, /** 允许上传的文件尾缀 string[] */ types: { type: Array, default: () => (['jpg', 'png', 'gif']) }, /** 默认的文件列表 string[] */ defaultFileList: { type: Array, default: () => ([]) }, /** 上传成功后端返回的字段名称 */ responseFileName: { type: String, default: () => 'url' }, /** 是否需要全屏loading */ needFullScreenLoading:{ type:Boolean, default:()=>true } }, data() { return { uploadUrl: `${ baseURL }/file/upload`, // 真实文件列表 fileList: [], // 默认展示的list,解决多上传只回调success一次的问题 viewFileList:[], // 组件是否部署错误 componentError: false, // 是否正在上传 isUploading:false, // 预览对象 previewObj:{ show:false, file:null } } }, watch: { defaultFileList: { handler() { // 判断类型 const flag = Object.prototype.toString.call(this.defaultFileList) === '[object Array]' && this.defaultFileList.length > 0 && Object.prototype.toString.call(this.defaultFileList[0]) !== '[object String]' if (flag) { this.componentError = true throw new Error('defaultFileList格式错误,应为string[]格式') }else{ this.componentError = false } this.viewFileList = this.defaultFileList.map(defaultFilePath => ({name: defaultFilePath, url: defaultFilePath})) this.viewFileList.forEach(item=>{ this.fileList.push(item) }) }, deep: true, immediate: true }, fileList:{ handler(){ this.handleNotifyFather() }, deep:true, immediate:false } }, methods: { /** * 检查type是否符合types的mime * @param type 文件后缀 * @param types 可用文件后缀集合 */ handleCheckFileMime(type, types) { const typeMimes = types.map(item => mime.getType(item)) return typeMimes.includes(type) }, handleCheckFileSize(fileSize, limitSize) { const limitByteSize = limitSize * 1024 * 1024 return limitByteSize > fileSize }, /** * 上传之前的钩子 * @param file * @return {undefined} */ handleBeforeUpload(file) { // 检查mime const fileType = file.type || mime.getType(file.name.slice(file.name.lastIndexOf('.') + 1)) const checkFileMime = this.handleCheckFileMime(fileType, this.types) const checkFileSize = this.handleCheckFileSize(file.size, this.limitSize); !checkFileSize ? file.isJumpRemove = true : undefined !checkFileSize ? this.$notify.warning(`文件大小不得超出${ this.limitSize }m`) : undefined !checkFileMime ? file.isJumpRemove = true : undefined !checkFileMime ? this.$notify.warning(`文件类型不在合法列表 ${ this.types }`) : undefined if(checkFileSize && checkFileMime){ // 开启loading this.isUploading = true if(this.needFullScreenLoading){ fullLoading = this.$loading({ background:`rgba(255,255,255,0.5)`, text:'上传中', fullscreen:true }) } } return checkFileSize && checkFileMime }, /** * 上传成功钩子 * @param response * @param file * @param fileList */ handleUploadSuccess(response, file, fileList) { this.isUploading = false if(this.needFullScreenLoading){ fullLoading?.close() } const successObj = { url: response.data[this.responseFileName], name: file.name } file.url = response.data[this.responseFileName] this.fileList.push(successObj) }, /** * 上传失败的钩子 * @param err * @param file * @param fileList */ handleUploadError(err, file, fileList) { }, /** * 超出数量的钩子 * @param files * @param fileList */ handleExceed(files, fileList) { this.$notify.warning(`文件总数大于可上传数量 ${ this.limit }`) }, /** * 文件即将移除的钩子 * @param file * @param fileList */ async handleBeforeRemove(file, fileList) { // 如果是超出文件大小调用,放行 if (file?.raw?.isJumpRemove) { return true } return await this.$confirm('此操作将会删除已上传的文件, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }) }, /** * 移除文件的钩子 */ handleRemove(file, fileList) { if (file?.raw?.isJumpRemove) { return } this.fileList.splice(this.fileList.findIndex(fileItem => file?.response?.data[this.responseFileName] === fileItem.url || file.url === fileItem.url), 1) }, /** * 通知父组件 */ handleNotifyFather(){ this.$emit('change',this.fileList) }, /** * 预览 * 图片视频直接预览,其他下载 * @param file */ handlePreviewOpen(file){ if(!file.type){ file.type = mime.getType(file?.url?.slice(file?.url?.lastIndexOf('.')+1)) || mime.getType(file?.name?.slice(file?.name.lastIndexOf('.')+1)) || undefined } if(file.type.includes('image') || file.type.includes('video')){ this.previewObj.file = file this.previewObj.show = true }else{ this.$confirm('需要下载才能预览此文件, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { let htmlAnchorElement = document.createElement('a'); htmlAnchorElement.download = file?.url.slice(file?.url.lastIndexOf('/')+1) htmlAnchorElement.target='_bank' htmlAnchorElement.href = file?.url htmlAnchorElement.click() htmlAnchorElement = null }).catch(() => { }); } }, handlePreviewClose(){ this.previewObj.file = null this.previewObj.show = false } } } </script> <style lang="scss" scoped > ::v-deep .el-upload { width: 100% !important; .el-upload-dragger { width: 100% !important; } } .error-text { font-size: 18px; font-weight: bolder; color: red; animation: error-animation 2.5s ease-in-out infinite; } @keyframes error-animation { 0%, 100% { font-size: 18px; color: red; } 25%, 75% { font-size: 16px; color: #b9b1b1; } 50% { font-size: 18px; color: #500000; } } .preview-content{ display: flex; align-items: center; justify-content: center; .preview-item{ min-width: 800px; } } </style>