1. 程式人生 > 實用技巧 >Vue+antd 上傳附件a-upload元件化,附送C#非同步上傳檔案程式碼【NetFrm4.5+】

Vue+antd 上傳附件a-upload元件化,附送C#非同步上傳檔案程式碼【NetFrm4.5+】

十年河東,十年河西,莫欺少年窮

學無止境,精益求精

這是一個後端開發人員的倔強

因專案需要,我從一個純後端變成了前端+後端,也俗稱全棧。

在實際專案中,上傳附件幾乎是必不可少的,因此,我們有必要將上傳控制元件進行元件化。

設計理念,通過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>
View Code

父元件

<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 {
  width: 100%;
  height: auto;
}
.arow {
  text-align: left;
}

.arowLat {
  text-align: left;
  margin-top: 25px;
}
</style>
View Code

下面來簡單分析下上述原始碼

父元件Data中fileinfo的屬性用來校驗檔案大小,副檔名

  data() {
    return {
      checked:false,
      fileinfo: {
        maxsize: 1024 * 1024 * 100,
        filetype: ["jpg", "png"],
        uploadfiles: [],
      },
    };
  }

注:uploadfiles 用於接收子元件請求後臺的結果,我的後臺返回值為:

{
    Data {
        LocalFilePath: "E:\IOT\IotApi\Content\OTA\4dccdff9bc17413c96d98031a8a451ec.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);
        });
    },
View Code

以上便是整個上傳元件。效果圖有點醜

在父元件,點選提交,即可得到請求上傳介面的返回值

這樣整個元件也就做完了,有了這個元件,以後我們只需在父元件中引用子元件,並新增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; }
    }
}
View Code

@天才臥龍的部落格