.net core 接收 el-upload 文件上传。el-upload配合.net core webapi 文件上传。vue3文件上传,图片上传。文件上传类型限制,文件删除,文件预览 电脑版发表于:2022/8/3 17:26 [TOC] ### 前端就是vue 3的elemnt ui <img src="https://img.tnblog.net/arcimg/aojiancc2/842446092be944b7a885a1baed55b53e.png" style="width:599px;height:auto;"> ``` <template> <div class="upload-container"> <!-- <vab-upload ref="vabUploadRef" :limit="50" name="file" :size="2" url="/upload" /> --> <vab-upload ref="vabUploadRef" :limit="50" name="file" :size="2" url="http://localhost:8002/api/upload" /> <el-button type="primary" @click="handleShow()">模拟上传</el-button> </div> </template> <script> import VabUpload from '@/plugins/VabUpload' export default defineComponent({ name: 'Upload', components: { VabUpload, }, setup() { const vabUploadRef = ref() const handleShow = () => { vabUploadRef.value.handleShow() } return { vabUploadRef, handleShow, } }, }) </script> ``` VabUpload: ``` <template> <el-dialog v-model="dialogFormVisible" :before-close="handleClose" :close-on-click-modal="false" :title="title" width="909px" > <div class="upload"> <el-alert :closable="false" :title="`支持jpg、jpeg、png格式,单次可最多选择${limit}张图片,每张不可大于${size}M,如果大于${size}M会自动为您过滤`" type="info" /> <el-upload ref="uploadRef" accept="image/png, image/jpeg" :action="action" :auto-upload="false" class="upload-content" :close-on-click-modal="false" :data="data" :file-list="fileList" :headers="headers" :limit="limit" list-type="picture-card" :multiple="true" :name="name" :on-change="handleChange" :on-error="handleError" :on-exceed="handleExceed" :on-preview="handlePreview" :on-progress="handleProgress" :on-remove="handleRemove" :on-success="handleSuccess" > <template #trigger> <vab-icon icon="add-line" /> </template> <el-dialog v-model="dialogVisible" append-to-body title="查看大图"> <div> <el-image :src="dialogImageUrl" /> </div> </el-dialog> </el-upload> </div> <template #footer> <div v-if="show" style="position: absolute; top: 10px; left: 15px; color: #999" > 正在上传中... 当前上传成功数:{{ imgSuccessNum }}张 当前上传失败数:{{ imgErrorNum }}张 </div> <el-button type="primary" @click="handleClose">关闭</el-button> <el-button :loading="loading" style="margin-left: 10px" type="success" @click="submitUpload" > 开始上传2 </el-button> </template> </el-dialog> </template> <script> import { useUserStore } from '@/store/modules/user' import _ from 'lodash' export default defineComponent({ name: 'VabUpload', props: { url: { type: String, default: 'http://localhost:8002/upload222', required: true, }, name: { type: String, default: 'file', required: true, }, limit: { type: Number, default: 50, required: true, }, size: { type: Number, default: 1, required: true, }, }, setup(props) { const userStore = useUserStore() const { token } = storeToRefs(userStore) const $baseMessage = inject('$baseMessage') const state = reactive({ uploadRef: null, show: false, loading: false, dialogVisible: false, dialogImageUrl: '', action: '', headers: {}, fileList: [], picture: 'picture', imgNum: 0, imgSuccessNum: 0, imgErrorNum: 0, typeList: null, title: '上传', dialogFormVisible: false, data: {}, }) const submitUpload = () => { state.uploadRef.submit() } const handleProgress = () => { state.loading = true state.show = true } const handleChange = (file, fileList) => { if (fileList && fileList.length > 0) { if (file.size > 1048576 * state.size) { fileList.filter((item) => item !== file) state.fileList = fileList } else { state.allImgNum = fileList.length } } } const handleSuccess = (response, file, fileList) => { state.imgNum = state.imgNum + 1 state.imgSuccessNum = state.imgSuccessNum + 1 if (fileList.length === state.imgNum) { setTimeout(() => { $baseMessage( `上传完成! 共上传${fileList.length}张图片`, 'success', 'vab-hey-message-success' ) }, 1000) } setTimeout(() => { state.loading = false state.show = false }, 1000) } const handleError = (err, file) => { state.imgNum = state.imgNum + 1 state.imgErrorNum = state.imgErrorNum + 1 $baseMessage( `文件[${file.raw.name}]上传失败,文件大小为${_.round( file.raw.size / 1024, 0 )}KB`, 'error', 'vab-hey-message-error' ) setTimeout(() => { state.loading = false state.show = false }, 1000) } const handleRemove = () => { state.imgNum = state.imgNum - 1 state.allNum = state.allNum - 1 } const handlePreview = (file) => { state.dialogImageUrl = file.url state.dialogVisible = true } const handleExceed = (files) => { $baseMessage( `当前限制选择 ${state.limit} 个文件,本次选择了 ${files.length} 个文件`, 'error', 'vab-hey-message-error' ) } const handleShow = (data) => { state.title = '上传' state.data = data state.dialogFormVisible = true } const handleClose = () => { state.fileList = [] state.picture = 'picture' state.allImgNum = 0 state.imgNum = 0 state.imgSuccessNum = 0 state.imgErrorNum = 0 state.headers['Authorization'] = `Bearer ${token}` state.dialogFormVisible = false } onMounted(() => { state.headers['Authorization'] = `Bearer ${token}` //这里给上传地址 state.action = props.url }) const percentage = computed(() => { if (state.allImgNum === 0) return 0 return _.round(state.imgNum / state.allImgNum, 2) * 100 }) return { ...toRefs(state), submitUpload, handleProgress, handleChange, handleSuccess, handleError, handleRemove, handlePreview, handleExceed, handleShow, handleClose, percentage, } }, }) </script> <style lang="scss" scoped> .upload { height: 500px; .upload-content { .el-upload__tip { display: block; height: 30px; line-height: 30px; } :deep() { .el-upload--picture-card { width: 128px; height: 128px; margin: 3px 8px 8px 8px; border: 2px dashed #c0ccda; .ri-add-line { font-size: 24px; } } .el-upload-list--picture { margin-bottom: 20px; } .el-upload-list--picture-card { .el-upload-list__item { width: 128px; height: 128px; margin: 3px 8px 8px 8px; } } } } } </style> ``` 文件上传的类型配置,要加什么类型继续让后面加就行了 ``` accept="image/png,image/jpeg,.xls,.xlsx,.txt" ``` ### .net core webapi后端 和其他上传图片的方式几乎都是一样的。从Request.Form.Files中取出来文件处理保存即可。核心代码如下: ``` using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; namespace WY.JBLand.API.API.V1 { [Route("api/[controller]")] [ApiController] public class UpLoadController : ControllerBase { /// <summary> /// 上传多个文件 /// </summary> /// <param name="Files"></param> /// <returns></returns> [HttpPost] [AllowAnonymous] public string Post(IFormCollection Files) { try { string dd = Files["File"]; var form = Files;//定义接收类型的参数 Hashtable hash = new Hashtable(); IFormFileCollection cols = Request.Form.Files; if (cols == null || cols.Count == 0) { return "没有上传文件"; } foreach (IFormFile file in cols) { //定义图片数组后缀格式 string[] LimitPictureType = { ".JPG", ".JPEG", ".GIF", ".PNG", ".BMP" }; //获取图片后缀是否存在数组中 string currentPictureExtension = Path.GetExtension(file.FileName).ToUpper(); if (LimitPictureType.Contains(currentPictureExtension)) { //这里暂时不重新生成文件名称,图片重命名使用guid或者时间都可以 // var new_path = DateTime.Now.ToString("yyyyMMdd")+ file.FileName; //var newName = Guid.NewGuid().ToString().Replace("-", "") + currentPictureExtension.ToLower(); var new_path = Path.Combine("uploads/images/", file.FileName); var path = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", new_path); //这步之前最好做一下文件夹是否存在的判断,如果不存在就创建一下 using (var stream = new FileStream(path, FileMode.Create)) { file.CopyTo(stream); stream.Flush(); } } else { return "请上传指定格式的图片"; } } return "上传成功"; } catch (Exception ex) { return "上传失败"; } } } } ``` ### 其实简单点来说就是这样使用而已 后端写好一个上传图片文件的接口,这个一般项目里边都是封装好的模块,前端也非常简单,主要的核心代码就是这点: ``` <el-form-item label="封面" prop="CoverImg"> <el-upload class="avatar-uploader" :show-file-list="false" action="/oss/api/TnblogFiles/UpLoadFormFile" :headers="getHeaderToken()" accept="image/*" :data="{ bucketName: 'education', filePath: 'course/imgs', FileType: 1, }" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload" > <img v-if="editFromData.coverImg" :src="getImgUrl(editFromData.coverImg)" class="coverImg"> <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon> </el-upload> </el-form-item> <script setup lang="ts" > import { Plus } from '@element-plus/icons-vue' import { getHeaderToken,getImgUrl } from '/@/utils/toolsFunctions'; </script> ``` - action就是你后端封装的上传的接口 - headers 就是提供一个token嘛,后台接口一般都需要身份验证的,token一般前端都会统一封装一个获取token的方法的 - accept 就是限制一下文件的类型嘛 - data 提供一些额外的数据,比如封装的oss文件上传接口,要提供一些桶的名称,文件存储位置什么的额外信息 - before-upload一个事件嘛,一般就是用来做上传前的验证比如验证一下文件是否超大等 ``` const beforeAvatarUpload = (file:any)=>{ const isMoreThanSize = file.size / 1024 / 1024 < 10 if (!isMoreThanSize) { ElMessage.error('上传封面图片大小不能超过 10MB!'); } return isMoreThanSize } ``` - on-success 也是一个事件上传成功后的事件,比如做上传封面什么的,可以在这个事件里边处理上传成功后马上显示图片 ``` const handleAvatarSuccess = (res:any, file:any)=>{ if (res.success) { // 上传成功之后给变量赋值,显示出来需要上传的图片 state.editFromData.coverImg = res.data.id } } ``` #### 这个上传封面的效果图 <img src="https://img.tnblog.net/arcimg/aojiancc2/d92802ca0f7047d195db1b17f50fadb3.png" style="width:366px;height:auto;"> 样式也贴一下 ``` <style scoped="scoped" lang="scss"> // 最外层的样式自己修改一下 .sub-program-update-container { .coverImg{ width: 109px; height: 109px; } } </style> <!-- 这里不要scoped="scoped", 因为涉及到修改element plus 上传组件的样式,当然加上scoped也可以,就要用到样式穿透了 --> <style lang="scss"> // 最外层的样式自己修改一下 .sub-program-update-container { .avatar-uploader .el-upload { border: 1px dashed var(--el-border-color); border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; transition: var(--el-transition-duration-fast); } .avatar-uploader .el-upload:hover { border-color: var(--el-color-primary); } .el-icon.avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 98px; height: 98px; text-align: center; } } </style> ``` ### 只允许上传一个文件,并且后上传的覆盖前面上传的 官方文档:https://element-plus.org/zh-CN/component/upload.html#%E8%A6%86%E7%9B%96%E5%89%8D%E4%B8%80%E4%B8%AA%E6%96%87%E4%BB%B6 **设置 limit 和 on-exceed 可以在选中时自动替换上一个文件。** 核心代码如下 ``` <el-upload ref="uploadVedioRef" :limit="1" :on-exceed="handleVedioExceed" > <el-button type="primary" size="default">请选择视频上传</el-button> </el-upload> const uploadVedioRef = ref<UploadInstance>() const handleVedioExceed: UploadProps['onExceed'] = (files) => { uploadVedioRef.value!.clearFiles() const file = files[0] as UploadRawFile file.uid = genFileId() uploadVedioRef.value!.handleStart(file) // 直接自动上传,覆盖前面一个文件 uploadVedioRef.value!.submit() } ``` ### 处理文件预览 #### 使用自带的handlePreview事件实现文件预览 先绑定好handlePreview事件 ``` <el-upload ref="uploadVedioRef" :on-preview="handleVedioPreview" > <el-button type="primary" size="default">请选择视频上传</el-button> </el-upload> ``` **然后在handlePreview事件中就可以实现文件预览了** ``` const handleVedioPreview: UploadProps['onPreview'] = (uploadFile:any) => { console.log(uploadFile) // 如果有vedioId属性就直接调用封装的视频预览方法去视频预览 if(uploadFile.vedioId){ openFileNewWindow(uploadFile.vedioId) } } ``` 具体的预览逻辑自己去实现我这里的视频预览其实就是使用视频的id去调用接口获取视频预览的地址,然后在浏览器的新标签打开一个地址即可 **注意上面那个只是基础的核心代码,真正要处理预览需要自己去实现,比如要预览上次已经上传好了的文件** 应该从接口从获取以前已经上次好了的文件绑定给upload,核心代码如下 ``` <template> <el-upload ref="uploadVedioRef" :file-list="state.vedioFileList" :on-preview="handleVedioPreview" > <el-button type="primary" size="default">请选择视频上传</el-button> </el-upload> </template> <script setup lang="ts" name="sub-program-update"> const state = reactive({ vedioFileList:[], }) const initData = (selectRowData: any) => { let row = selectRowData // 给已经上传的文件添加到文件列表中进行管理 state.vedioFileList.push({ name:row.vedioInfo, vedioId:row.vedio }) } const handleVedioPreview: UploadProps['onPreview'] = (uploadFile:any) => { console.log(uploadFile) // 如果有vedioId属性就直接调用封装的视频预览方法去视频预览 if(uploadFile.vedioId){ openFileNewWindow(uploadFile.vedioId) } } </script> ``` 如果是预览新上传的文件,可以在上传成功的事件里边给文件对象中添加一个vedioId的属性,方便在预览的时候保持和预览前面已经上次的文件逻辑一致 ``` <template> <el-upload ref="uploadVedioRef" :file-list="state.vedioFileList" :on-preview="handleVedioPreview" :show-file-list="true" :on-success="handleVedioSuccess" > <el-button type="primary" size="default">请选择视频上传</el-button> </el-upload> </template> <script setup lang="ts" name="sub-program-update"> const state = reactive({ vedioFileList:[], }) const initData = (selectRowData: any) => { let row = selectRowData // 给已经上传的文件添加到文件列表中进行管理 state.vedioFileList.push({ name:row.vedioInfo, vedioId:row.vedio }) } // 视频上传成功的处理,给相关内容赋值 const handleVedioSuccess = (res: any, file: any) => { if (res.success) { // 上传成功之后给变量赋值,点击保存的时候才能更新到数据库去 state.editFromData.vedio = res.data.id state.editFromData.vedioInfo = file.name // *** 给文件对象中添加一个vedioId的属性,方便在预览的时候保持和预览前面已经上次的文件逻辑一致*** file.vedioId = res.data.id } } const handleVedioPreview: UploadProps['onPreview'] = (uploadFile:any) => { console.log(uploadFile) if(uploadFile.vedioId){ openFileNewWindow(uploadFile.vedioId) } else{ ElMessage.error('预览失败,请检查文件是否存在!') } } </script> ``` #### 自己去写模板自己去解析filelist的方式 在 Element Plus 中,el-upload 组件的 file-list 属性允许你自定义文件列表的显示,你可以通过插槽(slots)来自定义文件列表,并手动添加点击事件进行你想要的操作。 下面是一个简单的示例,实现图片预览的: ### 处理删除的逻辑 #### 如果不想让文件可以点击自带的删除按钮删除,可以这样做 主要就是绑定一个before-remove事件,当然还要设置一下show-file-list为true,才会显示文件列表右边就有删除的操作按钮 ``` <el-upload :before-remove="beforeVedioRemove" :show-file-list="true" > <el-button type="primary" size="default">请选择视频上传</el-button> </el-upload> ``` 事件里边提示一句话,在返回false阻止删除行为即可 ``` const beforeVedioRemove: UploadProps['beforeRemove'] = (uploadFile, uploadFiles) => { ElMessage.error('暂不支持删除,可以重新上传覆盖!'); return false } ``` tn2> 当然如果不想让文件被删除,可以直接把show-file-list设置为false就没有删除的操作了,但是有些时候即想要显示文件的上传进度,又不想要文件被删除就可以这样做,因为自带的show-file-list里边有自带的文件上传进度显示使用起来体验还是不错的 #### 如果想要真正的删除文件,就可以这样去写逻辑 调用接口去删除存储的文件以及更新需要更新的数据,具体逻辑根据自己的修改即可 ``` const beforeVedioRemove: UploadProps['beforeRemove'] = (uploadFile, uploadFiles) => { return ElMessageBox.confirm(`是否删除文件视频`).then( () => { let updateData = { id: state.editFromData.id, Vedio: '', VedioInfo: '' } // 调用接口去删除存储的文件以及更新需要更新的数据,具体逻辑根据自己的修改即可 request.post('/xxxApi/api/TrainingSubProgram/UpdateVedio', updateData).then( (res: any) => { if (res.success) { emitWays('refreshData') ElMessage.success('视频删除成功!') } }, (res) => { ElMessage.error('视频删除失败!') } ) return true }, () => { return false } ) } ``` ### 常用的文件限制 #### 图片相关 ``` accept="image/*" accept="image/png, image/jpeg" ``` #### 压缩包相关 ``` accept=".zip,.rar,.tar,.7z" ``` #### 各种类型的组合 ``` accept="image/png,image/jpeg,.xls,.xlsx,.txt" ```