1、El-Upload上传组件的使用场景及数据库设计
在官网地址https://element-plus.org/zh-CN/component/upload.html 上有关于该组件的详细使用代码案例。
大概有个场景我需要根据需要展示文件的,一个是文件展示方式(非图片格式)。
一种是肖像方式处理。
一种方式是图片缩略图列表方式。
还有就是支持拖动方式上传。
我目前大概就是想到应用这几种方式,细节的地方可以根据需要进行微调即可。
一般来说,我们附件信息是单独存储在一个表里面的,附件则是存储在相应的文件系统或者FTP目录中。如果需要了解后端不同方式的文件上传方式,可以了解随笔《基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传》
而实际我们业务表里面,只需要存在一个字符串字段,它是一串GUID的值,用于引用单个或者多个附件列表的AttahmentGUID值即可。
在前端界面使用的的时候,我则希望自定义的组件使用的时候,尽量简单,因此对于附件上传的URL地址、Upload组件的相关设置,应该屏蔽在自定义组件即可,组件只需要提供一个v-model的绑定值和一些需要的属性即可。
2、文件上传后端处理
在介绍前端上传组件自定义前,我们需要提供一个上传文件的URL地址给前端,接收前端的文件流,以及相关FormData里面的参数值。
后端上传处理的函数定义如下所示
/// <summary> /// 多文件上传处理(自动根据配置文件选择合适的上传方式) /// </summary> /// <param name="guid">附件组GUID</param> /// <param name="folder">指定的上传目录</param> /// <returns></returns> [RequestSizeLimit(100000000)] //请求正文最大大小100M [HttpPost] [Route("postupload")] public async Task<List<ResponseFileInfo>> PostUpload()
其中 ResponseFileInfo 是我们定义一个返回给前端使用的对象,记录文件的名称、URL,id信息。
/// <summary> /// 上传后文件返回的结果信息 /// </summary> public class ResponseFileInfo { /// <summary> /// 默认构造函数 /// </summary> public ResponseFileInfo() { } /// <summary> /// 参数化构造函数 /// </summary> /// <param name="id"></param> /// <param name="name"></param> /// <param name="url"></param> public ResponseFileInfo(string id, string name, string url) { this.Id = id; this.Name = name; this.Url = url; } /// <summary> /// 记录ID /// </summary> public string Id { get; set; } /// <summary> /// 文件名称 /// </summary> public string Name { get; set; } /// <summary> /// 文件路径 /// </summary> public string Url { get; set; } }
在后端上传处理中,我们可以通过HTTP上下文获得传过来的对应参数和文件列表信息,如下代码所示。
public async Task<List<ResponseFileInfo>> PostUpload() { var httpContext = this.HttpContext; string guid = httpContext.Request.Form["guid"];//input.guid; string folder = httpContext.Request.Form["folder"];//input.folder; var files = httpContext.Request.Form.Files;
最后根据文件信息和相关参数,构建附件数据库信息,以及存储文件就可以了,如下代码所示。
3、文件上传组件的前端封装和处理
对于编辑状态下的前端处理,我们只需要绑定v-model的值,以及指定的一些参数就可以了。
如果是禁止上传的详细展示,可以设置disabled属性为true就可以了
<el-form-item label="资料文档"> <my-upload v-model="viewForm.attachGUID" :data="{ guid: viewForm.attachGUID, folder: '用户图片' }" disabled /> </el-form-item>
用户界面展示如下所示。
通过简单的封装,我们就可以在极少代码的基础上定制自己的功能界面了。易于阅读和维护,同时也有助于我们后面通过代码生成工具的方式快速生成相关的代码。
自定义组件,我们根据需要定义一些属性,并对它设置默认值,如下所示代码。
<script setup lang="ts"> import { reactive, ref, onMounted, watch, computed, getCurrentInstance} from "vue"; import { ElMessage, ElMessageBox } from "element-plus"; import type { UploadInstance, UploadProps, UploadUserFile } from "element-plus"; import { Plus, UploadFilled } from "@element-plus/icons-vue"; import { isNullOrUnDef, isEmpty } from "/@/utils/is"; import fileupload from "/@/api/fileupload"; defineOptions({ name: "MyUpload" }); //声明Props的接口类型 interface Props { modelValue?: string; // 接受外部v-model传入的值,记录的AttachGUID值 data?: Record<string, any>; //上传时附带的额外参数: {a:b} headers?: Headers | Record<string, any>; //设置上传的请求头部 listType?: "text" | "picture" | "picture-card"; //文件列表的类型 disabled?: boolean; // 是否禁用 multiple?: boolean; // 是否支持多选文件 limit?: number; // 最大允许上传个数 fileSize?: number; // 大小限制(MB) fileType?: Array<string>; // 文件类型, 例如['png', 'jpg', 'jpeg'] isShowTip?: boolean; // 是否显示提示 showFileList?: boolean; //是否显示文件列表 withCredentials?: boolean; //支持发送 cookie 凭证信息 isAvatarUpload?: boolean; // 是否是头像上传 drag?: boolean; //是否启用拖拽上传 buttonText?: string; //按钮文本 } //使用默认值定义Props const props = withDefaults(defineProps<Props>(), { modelValue: "", //对应自定义组件的v-model的值 data: () => { return null; }, headers: () => { //用于上传文件的身份认证 return { Authorization: "Bearer " + getAccessToken() }; }, listType: "text", //"text" | "picture" | "picture-card" disabled: false, multiple: false, // limit: 10, fileSize: 2, //MB fileType: () => { return ["png", "jpg", "jpeg", "gif"]; }, isShowTip: true, showFileList: true, withCredentials: true, //支持发送 cookie 凭证信息 isAvatarUpload: false, //头像上传方式 drag: false, //是否启用拖拽上传 buttonText: "单击上传", }); //定义触发事件 const emit = defineEmits(["error", "success", "remove", "change"]);
在我们展示组件的时候,我们根据v-model绑定的modelValue值进行加载附件列表或者图片信息,
//挂载的时候初始化数据 onMounted(async () => { if (!isEmpty(props.modelValue)) { // 使用字典类型,从服务器请求数据 await fileupload.GetByAttachGUID(props.modelValue).then(data => { // console.log(data); if (data.length > 0) { var list: Array<UploadUserFile> = []; data.map(item => { let url = getUrl(item); let { id, fileName, fileExtend } = item; // 结构对象属性 list.push({ name: fileName, url: url, uid: id }); }); fileList.value = list; //如果是头像上传模式,显示最后一个图片 if (props.isAvatarUpload) { avatarImageUrl.value = list[0]?.url; } } }); } });
在预览文件的时候,我们判断,如果是图片文件,就打开窗口展示图片,否则就下载附件即可。
//预览文件 const handlePreview: UploadProps["onPreview"] = uploadFile => { // console.log(uploadFile); // 当格式为图片就预览图片,否则下载文件 let filename = uploadFile.name; let fileurl = uploadFile.url; let fileExtension = ""; // 校检文件类型 var imageTypes = ["png", "jpg", "jpeg", "gif"]; if (filename.lastIndexOf(".") > -1) { fileExtension = filename.slice(filename.lastIndexOf(".") + 1); } const isTypeOk = imageTypes.some(type => { if (fileExtension && fileExtension.indexOf(type) > -1) return true; return false; }); if (isTypeOk) { //预览图片 dialogImageUrl.value = fileurl; dialogTitle.value = filename; dialogVisible.value = true; } else { //下载文件 dialogVisible.value = false; // openWindow(fileurl, { target: "_self" }); window.open(fileurl, "_self"); } };
移除文件的时候,我们根据文件的id,在数据库后端进行删除附件和附件信息。
//移除文件前操作 const beforeRemove: UploadProps["beforeRemove"] = (uploadFile, uploadFiles) => { // console.log(uploadFile); //${uploadFile.name} return ElMessageBox.confirm(`确认删除该文件吗?`).then( async () => { var result = false; var id = uploadFile.uid; if (!isEmpty(id)) { result = await fileupload.Delete(id); } return result; }, () => false ); };