十年河东,十年河西,莫欺少年穷
学无止境,精益求精
这是一个后端开发人员的倔强
因项目需要,我从一个纯后端变成了前端+后端,也俗称全栈。
在实际项目中,上传附件几乎是必不可少的,因此,我们有必要将上传控件进行组件化。
设计理念,通过vue的父子传值进行封装,上传组件为子组件,调用方为父组件,由父组件传值给子组件,用来校验上传的文件扩展名及大小。
子组件
<template> <a-upload :beforeUpload="beforeUpload" :multiple="false" @change="filechange" :customRequest="customRequest" :fileList="fileList" :remove="fileRemove" > <a-button> <a-icon type="upload" /> 选择文件 </a-button> </a-upload> </template> <script> export default { props: ["fileinfo"], created() {}, data() { return { fileList: [], }; }, methods: { beforeUpload(file) { var that = this; //console.log(file); return new Promise((resolve, reject) => { const isLt100M = file.size / 1024 / 1024 > that.fileinfo.maxsize; if (isLt100M) { this.$message.warning( "上传附件大小不能超过" + that.fileinfo.maxsize + "M。" ); return reject(false); } var index = file.name.lastIndexOf("."); var suffix = file.name.substring(index + 1); if( that.fileinfo.filetype.indexOf(suffix)<0){ this.$message.warning( "上传的文件格式不符合要求" ); return reject(false); } return resolve(true); }); }, filechange(info) { //console.log(info); this.fileList = info.fileList; //console.log(this.fileList); if (info.file.status == "uploading") { } if (info.file.status === "done") { } else if (info.file.status === "error") { } }, customRequest(data) { // 上传提交 const formData = new FormData(); formData.append("file", data.file); this.saveFile(formData); }, saveFile(formData) { let that = this; this.$axios({ url: "/api/File/UploadFileStream", method: "post", data: formData, }) .then(function (result) { //console.log(result); //that.fileList.push(result.Data) if (result.IsSuccess) { for (var i = 0; i < that.fileList.length; i++) { that.fileList[i].status = "done"; } console.log(that.fileinfo.uploadfiles); that.fileinfo.uploadfiles.push(result.Data); that.$message.success("上传附件成功。"); } else { //that.fileList = []; for (var i = 0; i < that.fileList.length; i++) { if (that.fileList[i].status != "done") { that.fileList.splice(i, 1); } } that.$message.warning(result.ResultMessage); } }) .catch(function (error) { console.log(error); }); }, fileRemove(file) { //console.log(file); var that = this; for (var i = 0; i < that.fileinfo.uploadfiles.length; i++) { if ( that.fileinfo.uploadfiles[i].oldfilename == file.name && that.fileinfo.uploadfiles[i].filesize == file.size ) { that.fileinfo.uploadfiles.splice(i, 1); //alert(1) } } }, }, }; </script>
父组件
<template> <div> <a-row align="middle" class="arowLat"> <a-col :span="12"> <a-checkable-tag v-model="checked" @change="handleChange"> OTA固件包 </a-checkable-tag> <file-upload v-bind:fileinfo="fileinfo"></file-upload> </a-col> <a-col :span="12"> <a-button @click="submit">提交</a-button> </a-col> </a-row> </div> </template> <script> export default { name: "Echarts", data() { return { checked:false, fileinfo: { maxsize: 1024 * 1024 * 100, filetype: ["jpg", "png"], uploadfiles: [], }, }; }, methods: { submit() { console.log(this.fileinfo.uploadfiles); } }, mounted() {}, }; </script> <style scoped> .img { 100%; height: auto; } .arow { text-align: left; } .arowLat { text-align: left; margin-top: 25px; } </style>
下面来简单分析下上述源码
父组件Data中fileinfo的属性用来校验文件大小,扩展名
data() { return { checked:false, fileinfo: { maxsize: 1024 * 1024 * 100, filetype: ["jpg", "png"], uploadfiles: [], }, }; }
注:uploadfiles 用于接收子组件请求后台的结果,我的后台返回值为:
{ Data { LocalFilePath: "E:IOTIotApiContentOTA4dccdff9bc17413c96d98031a8a451ec.bin", filedata: [80, 19, 0, 32, 1, 33, 0, 8, 1, 139, 0, 8, 69, 114, 0, 8, 229, 138, 0, 8, 43, 74, 0, 8, 219, 182, 0, 8, 0, 0...], filename: "4dccdff9bc17413c96d98031a8a451ec.bin", filepath: "~/Content/OTA/4dccdff9bc17413c96d98031a8a451ec.bin", filesize: 49336, oldfilename: "LBS-1522-YQ4501.jpg", }
IsSuccess: true, ResultCode: 0, ResultMessage: "请求成功...", }
fileinfo 用于传值给子组件,子组件中有对应的 props,如下
props: ["fileinfo"],
a-upload 组件中有个名叫 beforeUpload 的钩子函数,我们用来校验上传的文件,如果通过校验,则上传
beforeUpload(file) { var that = this; //console.log(file); return new Promise((resolve, reject) => { const isLt100M = file.size / 1024 / 1024 > that.fileinfo.maxsize; if (isLt100M) { this.$message.warning( "上传附件大小不能超过" + that.fileinfo.maxsize + "M。" ); return reject(false); } var index = file.name.lastIndexOf("."); var suffix = file.name.substring(index + 1); if( that.fileinfo.filetype.indexOf(suffix)<0){ this.$message.warning( "上传的文件格式不符合要求" ); return reject(false); } return resolve(true); }); },
fileRemove 方法用来同步移除 uploadfiles 的元素,保持和组件显示的上传文件一致。
fileRemove(file) { //console.log(file); var that = this; for (var i = 0; i < that.fileinfo.uploadfiles.length; i++) { if ( that.fileinfo.uploadfiles[i].oldfilename == file.name && that.fileinfo.uploadfiles[i].filesize == file.size ) { that.fileinfo.uploadfiles.splice(i, 1); //alert(1) } } },
在组件开发阶段,遇到一个问题,上传成功后,上传的状态一直是 uploading ,为了解决这个问题,我们需要在 filechange、customRequest、saveFile 一起解决。
主要是在 filechange 中给filelist赋值,在 saveFile 中当文件上传成功后,修改状态为done,如果上传失败,我们需要将filelist中上传失败的元素移除。
filechange(info) { //console.log(info); this.fileList = info.fileList; //console.log(this.fileList); if (info.file.status == "uploading") { } if (info.file.status === "done") { } else if (info.file.status === "error") { } }, customRequest(data) { // 上传提交 const formData = new FormData(); formData.append("file", data.file); this.saveFile(formData); }, saveFile(formData) { let that = this; this.$axios({ url: "/api/File/UploadFileStream", method: "post", data: formData, }) .then(function (result) { //console.log(result); //that.fileList.push(result.Data) if (result.IsSuccess) { for (var i = 0; i < that.fileList.length; i++) { that.fileList[i].status = "done"; } console.log(that.fileinfo.uploadfiles); that.fileinfo.uploadfiles.push(result.Data); that.$message.success("上传附件成功。"); } else { //that.fileList = []; for (var i = 0; i < that.fileList.length; i++) { if (that.fileList[i].status != "done") { that.fileList.splice(i, 1); } } that.$message.warning(result.ResultMessage); } }) .catch(function (error) { console.log(error); }); },
以上便是整个上传组件。效果图有点丑
在父组件,点击提交,即可得到请求上传接口的返回值
这样整个组件也就做完了,有了这个组件,以后我们只需在父组件中引用子组件,并添加Props传值对象fileinfo即可,从而节省了大量的重复代码。
下面是C#的后端,不做解释,只贴代码。详细可参考:https://www.cnblogs.com/chenwolong/p/Uplode.html
using Iot.Common; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using System.Web; using System.Web.Http; using System.Web.Http.Cors; namespace Iot.WebSite.Controllers.Apis { [RoutePrefix("api/file")] [EnableCors(origins: "*", headers: "*", methods: "GET,POST,PUT,DELETE")] public class FileController : ApiController { /// <summary> /// 上传文件 /// </summary> /// <returns></returns> [HttpPost] public async Task<BaseResponse> UploadFileStream() { var returns = CommonBaseResponse.SetResponse<fileInfoModel>(null, false); string fileType = "OTA";//要创建的子文件夹的名字 var uploadPath = "~/Content"; string filePath = System.Web.HttpContext.Current.Server.MapPath(uploadPath + "/" + fileType + "/");//绝对路径 //string filePath = uploadPath + "\" + fileType + "\"; //E:Fileup 居家 if (Directory.Exists(filePath) == false) { Directory.CreateDirectory(filePath); } try { var provider = new ReNameMultipartFormDataStreamProvider(filePath); await Request.Content.ReadAsMultipartAsync(provider).ContinueWith(o => { foreach (var file in provider.FileData) { string orfilename = file.Headers.ContentDisposition.FileName.TrimStart('"').TrimEnd('"');//待上传的文件名 FileInfo fileinfo = new FileInfo(file.LocalFileName); //判断开始 string oldName = orfilename;//选择的文件的名称 string fileExt = orfilename.Substring(orfilename.LastIndexOf('.')); string Extension = fileExt; string CreateTime = DateTime.Now.ToString("yyyyMMddHHmmss"); fileInfoModel fileResult = new fileInfoModel() { oldfilename = oldName, LocalFilePath = file.LocalFileName, filesize = fileinfo.Length, filename = fileinfo.Name, filepath = uploadPath + "/" + fileType + "/" + fileinfo.Name }; var fs = fileinfo.OpenRead(); byte[] buffur = new byte[fs.Length]; fs.Read(buffur, 0, (int)fs.Length); fileResult.filedata = buffur.ToList(); returns = CommonBaseResponse.SetResponse<fileInfoModel>(fileResult, true); } }); } catch (Exception ex) { LogHelper.WriteLog("上传附件出错:" + ex.ToString()); } return returns; } } /// <summary> /// 重命名上传的文件 /// </summary> public class ReNameMultipartFormDataStreamProvider : MultipartFormDataStreamProvider { public ReNameMultipartFormDataStreamProvider(string root) : base(root) { } public override string GetLocalFileName(System.Net.Http.Headers.HttpContentHeaders headers) { string extension = !string.IsNullOrWhiteSpace(headers.ContentDisposition.FileName) ? Path.GetExtension(GetValidFileName(headers.ContentDisposition.FileName)) : ""; return Guid.NewGuid().ToString().Replace("-", "") + extension; } private string GetValidFileName(string filePath) { char[] invalids = System.IO.Path.GetInvalidFileNameChars(); return String.Join("_", filePath.Split(invalids, StringSplitOptions.RemoveEmptyEntries)).TrimEnd('.'); } } public class fileInfoModel { /// <summary> /// 新文件名称 /// </summary> public string filename { get; set; } /// <summary> /// 老文件名称 /// </summary> public string oldfilename { get; set; } /// <summary> /// 服务器绝对地址 /// </summary> public string LocalFilePath { get; set; } /// <summary> /// 文件大小 字节 /// </summary> public long filesize { get; set; } /// <summary> /// 问价数据 /// </summary> public List<byte> filedata { get; set; } /// <summary> /// 文件相对路径 /// </summary> public string filepath { get; set; } } }
增删改查代码,只做记录,方便自己以后查看
<template> <div> <a-card hoverable="true" title="" headStyle="text-align:left;color:#606266;font-size:14px" bodyStyle="border:none" > <a slot="extra" href="#" style="float: left"> <a-breadcrumb separator=">"> <a-breadcrumb-item>OTA升级</a-breadcrumb-item> <a-breadcrumb-item>电池OTA升级 </a-breadcrumb-item> </a-breadcrumb> </a> <div> <a-row align="middle" class="arow"> <a-col :span="6"> <a-checkable-tag v-model="checked" @change="handleChange"> OTA版本号 </a-checkable-tag> <a-input placeholder="请输入OTA版本号" style=" 180px" v-model="SearchInfo.SoftWare" /> </a-col> <a-col :span="6"> <a-checkable-tag v-model="checked" @change="handleChange"> BMS版本号 </a-checkable-tag> <a-input placeholder="请输入BMS版本号" style=" 180px" v-model="SearchInfo.OldSoftWare" /> </a-col> <a-col :span="6"> </a-col> <a-col :span="6"> <a-button type="primary" @click="Search"> 查询 </a-button> <a-button type="danger" @click="() => setModal1Visible(true)"> 新增 </a-button> </a-col> </a-row> <a-table :columns="columns" :data-source="data" style="margin-top: 20px" > <template slot="action" slot-scope="text, record"> <a slot="action" href="javascript:;" @click="deleterow(record)" >删除</a > <a slot="action" href="javascript:;" @click="onEdit(record)" >编辑</a> <!-- <span slot="action" slot-scope="text" href="javascript:;" @click="onEdit()" >删除</span> --> </template> </a-table> </div> </a-card> <a-modal title="新增OTA升级信息" :width="760" :okText="提交" :cancelText="取消" :dialog-style="{ top: '20px' }" :visible="modal1Visible" @ok="() => Save()" @cancel="() => setModal1Visible(false)" > <a-card hoverable="true" title="" headStyle="text-align:left;color:#606266;font-size:14px" bodyStyle="border:none" > <a-row align="middle" class="arow"> <a-col :span="12"> <a-checkable-tag v-model="checked" @change="handleChange"> OTA版本号 </a-checkable-tag> <a-input placeholder="请输入OTA版本号" style=" 180px" v-model="OtaModel.SoftWare" /> </a-col> <a-col :span="12"> <a-checkable-tag v-model="checked" @change="handleChange"> 单包大小 </a-checkable-tag> <a-input-number v-model="OtaModel.SigPackSize" placeholder="请输入单包大小" :min="64" :max="2048" style=" 180px" /> </a-col> </a-row> <a-row align="middle" class="arowLat"> <a-col :span="12"> <a-checkable-tag v-model="checked" @change="handleChange"> BMS版本号 </a-checkable-tag> <a-input placeholder="请输入BMS版本号" style=" 180px" v-model="OtaModel.OldSoftWare" /> </a-col> <a-col :span="12"> <a-checkable-tag v-model="checked" @change="handleChange"> 升级描述 </a-checkable-tag> <a-input placeholder="请输入升级描述" style=" 180px" v-model="OtaModel.OtaFileInfo" /> </a-col> </a-row> <a-row align="middle" class="arowLat"> <a-col :span="12"> <a-checkable-tag v-model="checked" @change="handleChange"> OTA固件包 </a-checkable-tag> <file-upload v-bind:fileinfo="fileinfo"></file-upload> </a-col> <a-col :span="12"> </a-col> </a-row> </a-card> </a-modal> </div> </template> <script> import moment from "moment"; import "moment/locale/zh-cn"; import locale from "ant-design-vue/es/date-picker/locale/zh_CN"; const formatterTime = (val) => { alert(val); return val ? moment(val).format("YYYY-MM-DD HH:mm:ss") : ""; }; const columns = [ { title: "升级描述", 200, dataIndex: "OtaFileInfo", key: "OtaFileInfo", align: "center", }, { title: "固件大小(字节)", dataIndex: "FileSize", key: "1", align: "center" }, { title: "BMS版本号", dataIndex: "OldSoftWare", key: "2", align: "center" }, { title: "OTA版本号", dataIndex: "SoftWare", key: "3", align: "center" }, { title: "单包大小(字节)", dataIndex: "SigPackSize", key: "4", align: "center", }, { title: "创建日期", dataIndex: "CreateTime", key: "8", align: "center", customRender: function (val) { return val ? moment(val).format("YYYY-MM-DD") : ""; }, }, { title: "操作", key: "operation", align: "center", 100, scopedSlots: { customRender: "action"}, }, ]; const data = []; export default { name: "Echarts", data() { return { locale, checked: false, checkedmodal: true, dateFormat: "YYYY/MM/DD", monthFormat: "YYYY/MM", data, columns, modal1Visible: false, fileinfo: { maxsize: 1024 * 1024 * 10, filetype: ["bin"], uploadfiles: [], }, OtaModel: { OldSoftWare: "", SoftWare: "", SigPackSize: 512, OtaFileInfo: "", FileName: "", oldfilename: "", LocalFilePath: "", filesize: 0, filedata: [], FilePath: "", }, SearchInfo: { SoftWare: "", OldSoftWare: "", PageNumber: 1, PageSize: 1, Stime: "2020-01-01", Etime: "2030-01-01", }, }; }, methods: { moment, onChange(dates, dateStrings) { console.log("From: ", dates[0], ", to: ", dates[1]); console.log("From: ", dateStrings[0], ", to: ", dateStrings[1]); }, handleChange(checked) {}, setModal1Visible(modal1Visible) { this.modal1Visible = modal1Visible; }, Save() { let that = this; if (!that.OtaModel.OldSoftWare) { that.$message.error("请输入BMS版本号。"); return; } else if (!that.OtaModel.SoftWare) { that.$message.error("请输入OTA版本号。"); return; } else if (!that.OtaModel.SigPackSize) { that.$message.error("请输入单包大小。"); return; } else if (!that.OtaModel.OtaFileInfo) { that.$message.error("请输入OTA版本描述"); return; } else if ( !that.fileinfo.uploadfiles || that.fileinfo.uploadfiles.length != 1 ) { that.$message.error("请上传升级固件包【有且仅能上传一个固件包】"); return; } else { that.OtaModel.FileName = that.fileinfo.uploadfiles[0].filename; that.OtaModel.oldfilename = that.fileinfo.uploadfiles[0].oldfilename; that.OtaModel.LocalFilePath = that.fileinfo.uploadfiles[0].LocalFilePath; that.OtaModel.filesize = that.fileinfo.uploadfiles[0].filesize; that.OtaModel.filedata = that.fileinfo.uploadfiles[0].filedata; that.OtaModel.FilePath = that.fileinfo.uploadfiles[0].filepath; //console.log(that.OtaModel); that .$axios({ url: "/api/BatteryOta/CreateBatteryOtaSoftWareInfo", method: "post", data: that.OtaModel, }) .then(function (result) { if (result.IsSuccess) { that.OtaModel = { OldSoftWare: "", SoftWare: "", SigPackSize: 512, OtaFileInfo: "", FileName: "", oldfilename: "", LocalFilePath: "", filesize: 0, filedata: [], FilePath: "", }; that.modal1Visible = false; } else { that.$message.error(result.ResultMessage); that.OtaModel = { OldSoftWare: "", SoftWare: "", SigPackSize: 512, OtaFileInfo: "", FileName: "", oldfilename: "", LocalFilePath: "", filesize: 0, filedata: [], FilePath: "", }; } }) .catch(function (error) { console.log(error); }); } }, Search() { let that = this; that .$axios({ url: "/api/BatteryOta/SearchOtaSoftWareInfo", method: "post", data: that.SearchInfo, }) .then(function (result) { console.log(result); if (result.IsSuccess) { that.data = result.Data.Data; } else { that.$message.error(result.ResultMessage); } }) .catch(function (error) { console.log(error); }); }, deleterow(record) { console.log(record); }, }, mounted() {}, created() { this.Search(); }, }; </script> <style scoped> .img { 100%; height: auto; } .arow { text-align: left; } .arowLat { text-align: left; margin-top: 25px; } </style>
@天才卧龙的博客