VUE3使用ElementPlus实现Upload 上传及后端代码

VUE3使用ElementPlus实现Upload 上传及后端代码
<template>
<el-table>
//查询附件的列表省略,与一般eltable表格一样,传入数组即可
</el-table>
<!-- 上传格式 -->
<el-dialog v-model="state.dialogVisible" :lock-scroll="false" :width="1000" draggable :close-on-click-modal="false">
<template #header>
    <div style="color: #fff">
        <el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-UploadFilled /> </el-icon>
        <span> 上传文件 </span>
    </div>
</template>
<div>
    <el-upload 
    ref="uploadRef" 
    multiple
    drag 
    :auto-upload="false" 
    :limit="50"
    :show-file-list="false"
    :file-list="state.fileList" 
    action="" 
    :on-change="handleChange" 
    accept=".jpg,.png,.bmp,.gif,.txt,.pdf,.xls,.xlsx,.doc,.docx,.rar,.zip">
    <el-icon class="el-icon--upload">
        <ele-UploadFilled />
    </el-icon>
    <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
    <template #tip>
        <div class="el-upload__tip">请上传大小不超过 10MB 的文件</div>
        <el-form :model="state.listFileType" ref="fileTypeRef" size="default" label-position="left" :inline="true">
            <el-row :gutter="35" v-for="(item, index) in state.fileList" :key="index"
            style="display: flex;flex-wrap: wrap;position: relative;box-sizing: border-box;">
                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" class="mb1">
                    ({{index}})  {{ item.name }}
                </el-col>
                <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" class="mb1">
                    <el-form-item label="上传文件类型" >
                    <el-select v-model="state.listFileType[index]" placeholder="请选择上传文件类型" style="width: 100%">
                        <el-option v-for="item in loaddata.getfileTypeData" :key="item.value" :label="item.label" :value="item.value" />
                    </el-select>
                </el-form-item>
                </el-col>
                <el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4" class="mb1">
                    <el-form-item label="删除">
                    <el-button icon="ele-Delete" type="danger" circle plain size="small" @click="deleteRow(index)"  />
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
    </template>
    </el-upload>
</div>
<template #footer>
    <span class="dialog-footer">
        <el-button @click="state.dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="uploadFile">确定</el-button>
    </span>
</template>
</el-dialog>
<!-- 上传格式 -->
</template>
<script lang="ts" setup name="busorderFile">
  //引用省略...

  //#region 自定义参数
  //字典赋值集合
  const loaddata = reactive({
      //绑定字典
      getfileTypeData: [] as any,
  });
  //上传附件
  const loadingFile = ref(false);//数据是否加载
  const orderFileTableData = ref<any>([]);
  const uploadRef = ref<UploadInstance>();
  const state = reactive({
      fileData: [] as any,
      dialogVisible: false,
      fileList: [] as any,
      listFileType: [] as any,
      selectedTabName: '0', // 选中的 tab 页
  });
  //表格查询参数
  const orderFileQueryParams = ref<any>({id:{},fileType:{},fileName:{}});
  const orderFileTableParams = ref({
  page: 1,
  pageSize: 10,
  total: 0,
  });
  //#endregion

//////////////上传附件///////////////
// 删除文件列表信息行
const deleteRow = (index: number) => {
    state.fileData?.splice(index, 1);
    state.fileList?.splice(index, 1);
    state.listFileType?.splice(index, 1);
};
// 打开上传页面
const openUploadDialog = () => {
    state.fileList = [];
    state.dialogVisible = true;
};
// 上传多文件
const uploadFile = async () => {
    if (state.fileList.length < 1) 
    {
        ElMessage.error('请选择文件');
        return;
    }
    if (orderFileQueryParams.value.orderId <= 0) 
    {
        ElMessage.error('请在编辑页面上传附件');
        return;
    }
    const formData = new FormData()
        //formData.append('file', state.fileList[0].raw)
    for(let i=0;i<state.fileList.length;i++)
    {
        formData.append('files', state.fileList[i].raw)
        if(state.listFileType[i]==undefined)
        {
            ElMessage.error('请选择文件类型');
            return;
        }
    }
    console.log("formData:",formData);
    let Path="OrderInfo";
    let OrderId=orderFileQueryParams.value.orderId;
    let ListFileType=state.listFileType;
    await uploadFilesSysOrderFiles(formData,Path,OrderId,ListFileType);
    handleQuery();
    ElMessage.success('上传成功');
    state.dialogVisible = false;
    state.listFileType=[];
};
// 下载
const downloadFile = async (row: any) => {
    downloadByUrl({ url: row.url });
};
// 删除
const delSysOrderFile = (row: any) => {
ElMessageBox.confirm(`确定要删除吗?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
await deleteSysOrderFile(row);
handleQuery();
ElMessage.success("删除成功");
})
.catch(() => {});
};
//////////////上传附件///////////////
//////////////公共方法///////////////
// 打开弹窗
const openDialog = async(row: any) => {
    //上传附件页面赋值
	orderFileQueryParams.value.orderId=0;
	orderFileQueryParams.value.fileType="";
	orderFileQueryParams.value.fileName="";
	//若订单主键存在
    if(row)
    {
        orderFileQueryParams.value.orderId=row;
		//编辑此页面
		handleQuery();//查询上传附件信息并赋值给页面
	}
	//在进入此页面时才执行此方法
	console.log("现在执行上传附件信息openDialog方法");
	console.log("上传附件信息openDialog-ROW参数:",row);
};
// 页面加载时
onMounted(async () => {
	let fileTypeDictData = await getAPI(SysDictDataApi).apiSysDictDataDataListCodeGet('order_file_type');
	loaddata.getfileTypeData = fileTypeDictData.data.result;
	//在进入该接单模块的菜单时,还未进入此页面,就执行了此方法。
	console.log("现在执行上传附件onMounted方法,获取绑定所在集合loaddata:",loaddata);
});
//将属性或者函数暴露给父组件
defineExpose({ openDialog });
//////////////公共方法///////////////
</script>

1、上传文件el-upload控件用到的各属性解释:
multiple 是否支持多个文件
drag 是否启用拖拽上传
:auto-upload令图片不立即上传
limit 允许上传文件的最大数量
show-file-list 是否展示默认的文件列表,如果需要自定义,我们也可以进行自定义进行处理
file-list 默认上传文件
action 触发行为,一般我们这里是不会使用默认的上传action的。auto-upload为true时,就会触发该行为
on-change 图片上传时,会进行触发该事件
on-exceed 超于limit的限制时候,会触发该事件
on-preview 点击默认的文件列表的时候,会触发该事件,默认返还上传的该文件
accept 处理文件的接受类型,默认上传框内只支持这些类型,但是如果选择全部文件也可以上传其他文件,我们可以通过其他方法进行限制文件处理的类型。

2、样式如下:

3、上传附件

先打开上传界面,再清空state.fileList文件数组数据,然后拖拽或点击上传需要的文件,填充state.fileList数组,并且上传时可以添加指定的判断条件,如此处添加必须选择上传文件类型才能上传。

VUE3调用接口时,引用的ts文件接口代码,注意传输文件时需要限制接口的headers头的类型为multipart/form-data格式,使用其他格式不能传输文件只能接收基础变量参数。

具体传参格式如下:

import request from '/@/utils/request';
enum Api {
  UploadFilesSysOrderFiles = '/api/sysOrderFile/uploadFiles',
}
// 上传多文件
export const uploadFilesSysOrderFiles = (params?: any,Path?:string,OrderId?:any,ListFileType?:any) => 
  request({
          url: Api.UploadFilesSysOrderFiles+'?Path='+Path+'&OrderId='+OrderId+'&ListFileType='+ListFileType,
          method: 'post',
          data: params,
          headers: {
              'Content-Type': 'multipart/form-data',
            }
      });

后端代码:

using AngleSharp.Dom;
using Furion.RemoteRequest.Extensions;
using Furion.VirtualFileServer;
using Microsoft.Extensions.Options;
using OnceMi.AspNetCore.OSS;
using System.Data;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using Yitter.IdGenerator;


/// <summary>
/// 上传多文件
/// </summary>
/// <param name="files"></param>
/// <param name="Path"></param>
/// <param name="OrderId"></param>
/// <param name="ListFileType"></param>
/// <returns></returns>
[HttpPost]
[DisplayName("上传多文件")]
[ApiDescriptionSettings(Name = "UploadFiles")]
public async Task<List<SysOrderFileOutput>> UploadFiles([Required] List<IFormFile> files, [FromQuery] string? Path, [FromQuery] long OrderId, [FromQuery] List<string>? ListFileType)
{
    string[] FileType = null;
    if (ListFileType.Count > 0)
    {
        var aparm = ListFileType[0];
        var bparm = aparm.Split(",");
        FileType = bparm;
    }
    if (FileType != null)
    {
        var filelist = new List<SysOrderFileOutput>();
        int i = 0;
        foreach (var file in files)
        {
            var sysOrderFile = await HandleUploadFile(file, Path, OrderId, FileType[i]);
            var sysOrderFileOutput = new SysOrderFileOutput
            {
                Id = sysOrderFile.Id,
                Url = sysOrderFile.Url,
                SizeKb = sysOrderFile.SizeKb,
                Suffix = sysOrderFile.Suffix,
                FilePath = sysOrderFile.FilePath,
                FileName = sysOrderFile.FileName,
                FileType = FileType[i],
                OrderId = OrderId
            };
            filelist.Add(sysOrderFileOutput);
            i = i + 1;
        }
        return filelist;
    }
    else
    {
        return null;
    }
}

 #region 上传附件前置方法
 /// <summary>
/// 上传文件基础方法
/// </summary>
/// <param name="file">文件</param>
/// <param name="savePath">路径</param>
/// <param name="OrderId">订单主键</param>
/// <param name="FileType">文件类型</param>
/// <returns></returns>
private async Task<SysOrderFile> HandleUploadFile(IFormFile file, string savePath,long OrderId,string FileType)
{
    if (file == null) throw Oops.Oh(ErrorCodeEnum.D8000);

    var path = savePath;
    if (string.IsNullOrWhiteSpace(savePath))
    {
        path = _uploadOptions.Path;
        var reg = new Regex(@"(\{.+?})");
        var match = reg.Matches(path);
        match.ToList().ForEach(a =>
        {
            var str = DateTime.Now.ToString(a.ToString().Substring(1, a.Length - 2)); // 每天一个目录
            path = path.Replace(a.ToString(), str);
        });
    }

    //验证文件类型,ZIP与RAR文件跳过验证
    if (file.ContentType != "application/x-zip-compressed" && file.ContentType != "application/octet-stream")
    {
        //验证文件类型
        if (!_uploadOptions.ContentType.Contains(file.ContentType))
            throw Oops.Oh(ErrorCodeEnum.D8001);
    }

    var sizeKb = (long)(file.Length / 1024.0); // 大小KB
    if (sizeKb > _uploadOptions.MaxSize)
        throw Oops.Oh(ErrorCodeEnum.D8002);

    var suffix = Path.GetExtension(file.FileName).ToLower(); // 后缀
    if (string.IsNullOrWhiteSpace(suffix))
    {
        var contentTypeProvider = FS.GetFileExtensionContentTypeProvider();
        suffix = contentTypeProvider.Mappings.FirstOrDefault(u => u.Value == file.ContentType).Key;
        // image/jpeg 返回的后缀是.jpe ,导致vant上传回显图片失败
        if (suffix == ".jpe")
        {
            suffix = ".jpg";
        }
    }
    if (string.IsNullOrWhiteSpace(suffix))
        throw Oops.Oh(ErrorCodeEnum.D8003);

    var newFile = new SysOrderFile
    {
        Id = YitIdHelper.NextId(),
        // BucketName = _OSSProviderOptions.Enabled ? _OSSProviderOptions.Provider.ToString() : "Local",
        // 阿里云对bucket名称有要求,1.只能包括小写字母,数字,短横线(-)2.必须以小写字母或者数字开头  3.长度必须在3-63字节之间
        // 无法使用Provider
        BucketName = _OSSProviderOptions.Enabled ? _OSSProviderOptions.Bucket : "Local",
        FileName = Path.GetFileNameWithoutExtension(file.FileName),
        Suffix = suffix,
        SizeKb = sizeKb.ToString(),
        FilePath = path,
        OrderId= OrderId,
        FileType=FileType,
    };

    //var finalName = newFile.Id + suffix; // 文件最终名称
    var finalName = file.FileName; // 文件最终名称
    if (_OSSProviderOptions.Enabled)
    {
        newFile.Provider = Enum.GetName(_OSSProviderOptions.Provider);
        var filePath = string.Concat(path, "/", finalName);
        await _OSSService.PutObjectAsync(newFile.BucketName, filePath, file.OpenReadStream());
        //  http://<你的bucket名字>.oss.aliyuncs.com/<你的object名字>
        //  生成外链地址 方便前端预览
        switch (_OSSProviderOptions.Provider)
        {
            case OSSProvider.Aliyun:
                newFile.Url = $"{(_OSSProviderOptions.IsEnableHttps ? "https" : "http")}://{newFile.BucketName}.{_OSSProviderOptions.Endpoint}/{filePath}";
                break;

            case OSSProvider.Minio:
                // 获取Minio文件的下载或者预览地址
                newFile.Url = await GetMinioPreviewFileUrl(newFile.BucketName, filePath); ;
                break;
        }
    }
    else
    {
        newFile.Provider = ""; // 本地存储 Provider 显示为空
        var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, path);
        if (!Directory.Exists(filePath))
            Directory.CreateDirectory(filePath);

        var realFile = Path.Combine(filePath, finalName);
        //IDetector detector;
        using (var stream = File.Create(realFile))
        {
            await file.CopyToAsync(stream);
            //detector = stream.DetectFiletype();
        }
        //var realExt = detector.Extension; // 真实扩展名
        //// 二次校验扩展名
        //if (!string.Equals(realExt, suffix.Replace(".", ""), StringComparison.OrdinalIgnoreCase))
        //{
        //    var delFilePath = Path.Combine(App.WebHostEnvironment.WebRootPath, realFile);
        //    if (File.Exists(delFilePath))
        //        File.Delete(delFilePath);
        //    throw Oops.Oh(ErrorCodeEnum.D8001);
        //}

        // 生成外链
        newFile.Url = GetOrderFileUrl(newFile);

    }
    await _rep.AsInsertable(newFile).ExecuteCommandAsync();
    return newFile;
}
/// <summary>
/// 获取Minio文件的下载或者预览地址
/// </summary>
/// <param name="bucketName">桶名</param>
/// <param name="fileName">文件名</param>
/// <returns></returns>
private async Task<string> GetMinioPreviewFileUrl(string bucketName, string fileName)
{
    return await _OSSService.PresignedGetObjectAsync(bucketName, fileName, 7);
}

/// <summary>
/// 获取Host
/// </summary>
/// <returns></returns>
public string GetHost()
{
    var localhost = $"{_httpContextAccessor.HttpContext.Request.Scheme}://{_httpContextAccessor.HttpContext.Request.Host.Value}";
    return localhost;
}

/// <summary>
/// 获取文件URL
/// </summary>
/// <param name="sysOrderFile"></param>
/// <returns></returns>
public string GetOrderFileUrl(SysOrderFile sysOrderFile)
{
    return $"{GetHost()}/{sysOrderFile.FilePath}/{sysOrderFile.FileName + sysOrderFile.Suffix}";
}
#endregion

4、下载附件

下载附件可以使用工具类中的下载文件方法传输url值即可,或者调用下载文件接口。后端代码:

    /// <summary>
    /// 下载文件(文件流)
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPost]
    [DisplayName("下载文件(文件流)")]
    [ApiDescriptionSettings(Name = "DownloadFile")]
    public async Task<IActionResult> DownloadFile(QueryByIdSysOrderFileInput input)
    {
        var file = await Detail(input);
        var fileName = HttpUtility.UrlEncode(file.FileName, Encoding.GetEncoding("UTF-8"));
        if (_OSSProviderOptions.Enabled)
        {
            var filePath = string.Concat(file.FilePath, "/", file.FileName + file.Suffix);
            var stream = await (await _OSSService.PresignedGetObjectAsync(file.BucketName.ToString(), filePath, 5)).GetAsStreamAsync();
            return new FileStreamResult(stream.Stream, "application/octet-stream") { FileDownloadName = fileName + file.Suffix };
        }
        else
        {
            var filePath = Path.Combine(file.FilePath, file.FileName + file.Suffix);
            var path = Path.Combine(App.WebHostEnvironment.WebRootPath, filePath);
            return new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream") { FileDownloadName = fileName + file.Suffix };
        }
    }