Vue encapsulates a simple and lightweight File Upload Component example.
I. Problems encountered earlier
There is a need to upload files in the project. When using the existing UI framework for implementation, there will always be some inexplicable bugs due to unknown reasons. For example, if you use an upload component and explicitly specify (: multiple = "false"), you can still select multiple files. multiple files are still sent during the upload. For example, if you add (: file-list = "fileList") attribute, you want to manually control the upload event this. refs. [upload (Component ref)]. submit () does not work and cannot be passed. In short, I am too lazy to see how it is implemented. I use a function and the interface itself needs to be rewritten. If I stick to it, it will make the project more unnecessary logic and style code ......
Previously, the view framework used for project using Vue includes element-ui, zp-ui supplemented by the team, and iview. The framework is easy to use, but it cannot be used for all of our own projects. In particular, the interface provided by our design sister is very different from the existing framework, the low efficiency of source code change may easily lead to unknown bugs, so I took the time to encapsulate the Upload Component.
Ii. Code and introduction
Parent component
<Template> <div class = "content"> <label for = "my-upload"> <span> upload </span> </label> <my-upload ref =" myUpload ": file-list = "fileList" action = "/uploadPicture": data = "param": on-change = "onChange": on-progress = "uploadProgress ": on-success = "uploadSuccess": on-failed = "uploadFailed" multiple: limit = "5 ": on-finished = "onFinished"> </my-upload> <button @ click = "upload" class = "btn-xs btn-primary"> Upload </button> </div> </template> <script> import myUpload from '. /components/my-upload 'export default {name: 'test', data () {return {fileList: [], // upload the file list, whether single choice or multiple selections are supported, files are saved in the List format: param: {param1: '', param2:''}, // carry parameter list }}, methods: {onChange (fileList) {// listen for file changes. When adding or removing files, the quilt component calls this. fileList = [... fileList] ;}, uploadSuccess (index, response) {// This method is executed when a file is uploaded successfully. index indicates the console of the index file in the list. log (index, response) ;}, upload () {// trigger the sub-component upload method this. $ refs. myUpload. submit () ;}, removeFile (index) {// remove a file this. $ refs. myUpload. remove (index) ;}, uploadProgress (index, progress) {// the upload progress, which is continuously triggered during the upload process. When you need progress instructions, const {percent} = progress is useful; console. log (index, percent) ;}, uploadFailed (index, err) {// if a file fails to be uploaded, index indicates the console of the index file in the list. log (index, err) ;}, onFinished (result) {// after all files are uploaded (whether successful or not), result: {success: Successful count, failed: number of failures} console. log (result) ;}, components: {myUpload }}</script>
The parent component processes the business-related logic. I specifically add index parameters to facilitate the interface to display the first few values when uploading results. Not all methods are required, use as needed.
Child Widgets
<template><div> <input style="display:none" @change="addFile" :multiple="multiple" type="file" :name="name" id="my-upload"/></div></template>
The html part is a pair of tags and does not like complex code.
<Script> export default {name: 'My-upload', props: {name: String, action: {type: String, required: true}, fileList: {type: Array, default: []}, data: Object, multiple: Boolean, limit: Number, onChange: Function, onBefore: Function, onProgress: Function, onSuccess: Function, onFailed: Function, onFinished: function}, methods :{}// The following describes methods. Skipped here.} </script>
The attribute value that the parent component needs to pass to the child component is defined here. Note that the method is also used as the attribute transfer here.
Components written by myself are not as complete and comprehensive as those released by the popular framework, in addition, I want to solve this problem by binding file-list at the beginning and try my best to solve it, therefore, we hope to have absolute control over the file list. In addition to action, we also use file-list as the attribute that the parent component must pass. (The property name parent component uses "-" to connect and corresponds to the hump name in the child component prop)
Iii. Main upload Functions
methods: { addFile, remove, submit, checkIfCanUpload}
There are a total of four methods in methods: add, remove, submit, and detect files (check before uploading). The following describes them one by one:
1. Add a file
AddFile ({target: {files}) {// when the input tag triggers the onchange event, add the file to the list to be uploaded for (let I = 0, l = files. length; I <l; I ++) {files [I]. url = URL. createObjectURL (files [I]); // create a blob address. Otherwise, how does one display the image? Files [I]. status = 'ready'; // start by giving the file a field to indicate the steps for uploading. It seems that the file is not used either ......} let fileList = [... this. fileList]; if (this. multiple) {// when multiple values are selected, all files are compressed as fileList = [... fileList ,... files]; let l = fileList. length; let limit = this. limit; if (limit & typeof limit = "number" & Math. ceil (limit)> 0 & l> limit) {// when there is a limit on the number of limit files, take the following limit files limit = Math. ceil (limit); // limit = limit> 10? 10: limit; fileList = fileList. slice (l-limit) ;}} when else {// single choice, only the last file is taken. Note that this is not written as fileList = files because files have multiple elements (for example, a bunch of elements are displayed when a file is selected, only one fileList = [files [0];} this. onChange (fileList); // call the parent component method to cache the list to the fileList attribute in the upper-level data },
2. Remove files
This is simple. Sometimes, when the parent component forks a file, you can pass an index.
remove(index){ let fileList = [...this.fileList]; if(fileList.length){ fileList.splice(index, 1); this.onChange(fileList); }},
3. Submit for upload
Two methods are used here: fetch and native. Because fetch does not support obtaining the upload progress, if you do not need a progress bar or simulate the progress yourself or the XMLHttpRequest object does not exist, it is easier to use fetch to request the upload logic.
submit(){ if(this.checkIfCanUpload()){ if(this.onProgress && typeof XMLHttpRequest !== 'undefined') this.xhrSubmit(); else this.fetchSubmit(); }},
4. Based on the two sets of upload logic, two methods xhrSubmit and fetchSubmit are encapsulated here.
FetchSubmit
FetchSubmit () {let keys = Object. keys (this. data), values = Object. values (this. data), action = this. action; const promises = this. fileList. map (each => {each. status = "uploading"; let data = new FormData (); data. append (this. name | 'file', each); keys. forEach (one, index) => data. append (one, values [index]); return fetch (action, {method: 'post', headers: {"Content-Type ": "application/x-www-form-u Rlencoded "}, body: data }). then (res => res. text ()). then (res => JSON. parse (res); // res. text () is used based on the return value type and should be determined according to the situation}); Promise. all (promises ). then (resArray => {// starts with multiple threads at the same time. If there is a limit on the number of concurrent threads, you can use the synchronous method to upload data one by one. I will not go into details here. Let success = 0, failed = 0; resArray. forEach (res, index) => {if (res. code = 1) {success ++; // counts the number of successfully uploaded data records. The index can tell which results have been successfully uploaded this. onSuccess (index, res);} else if (res. code = 520) {// the return value for an upload failure is 520 failed ++; // count the number of failed uploads. The index can tell which failures this. onFailed (index, res) ;}}); return {success, failed}; // After the upload is complete, the result is passed to the following }). then (this. onFinished); // return the upload Summary Result },
XhrSubmit
XhrSubmit () {const _ this = this; let options = this. fileList. map (rawFile, index) => ({file: rawFile, data: _ this. data, filename: _ this. name | "file", action: _ this. action, onProgress (e) {_ this. onProgress (index, e); // closure, store the index}, onSuccess (res) {_ this. onSuccess (index, res) ;}, onError (err) {_ this. onFailed (index, err) ;}}); let l = this. fileList. length; let send = async options =>{ for (let I = 0; I <l; I ++) {await _ this. sendRequest (options [I]); // an asynchronous method is used to execute this. sendRequest method. The parameter is the object encapsulated in the file list. this. next to sendRequest, follow the instructions }}; send (options );},
The upload source code of element-ui is used for reference.
sendRequest(option){ const _this = this; upload(option); function getError(action, option, xhr) { var msg = void 0; if (xhr.response) { msg = xhr.status + ' ' + (xhr.response.error || xhr.response); } else if (xhr.responseText) { msg = xhr.status + ' ' + xhr.responseText; } else { msg = 'fail to post ' + action + ' ' + xhr.status; } var err = new Error(msg); err.status = xhr.status; err.method = 'post'; err.url = action; return err; } function getBody(xhr) { var text = xhr.responseText || xhr.response; if (!text) { return text; } try { return JSON.parse(text); } catch (e) { return text; } } function upload(option) { if (typeof XMLHttpRequest === 'undefined') { return; } var xhr = new XMLHttpRequest(); var action = option.action; if (xhr.upload) { xhr.upload.onprogress = function progress(e) { if (e.total > 0) { e.percent = e.loaded / e.total * 100; } option.onProgress(e); }; } var formData = new FormData(); if (option.data) { Object.keys(option.data).map(function (key) { formData.append(key, option.data[key]); }); } formData.append(option.filename, option.file); xhr.onerror = function error(e) { option.onError(e); }; xhr.onload = function onload() { if (xhr.status < 200 || xhr.status >= 300) { return option.onError(getError(action, option, xhr)); } option.onSuccess(getBody(xhr)); }; xhr.open('post', action, true); if (option.withCredentials && 'withCredentials' in xhr) { xhr.withCredentials = true; } var headers = option.headers || {}; for (var item in headers) { if (headers.hasOwnProperty(item) && headers[item] !== null) { xhr.setRequestHeader(item, headers[item]); } } xhr.send(formData); return xhr; }}
Finally, add the verification before the request
checkIfCanUpload(){ return this.fileList.length ? (this.onBefore && this.onBefore() || !this.onBefore) : false;},
If the parent component defines the onBefore method and returns false, or the file list is empty, the request will not be sent.
When the code is complete, as long as the on-progress attribute is available and the XMLHttpRequest object is accessible, the request is sent in native mode. Otherwise, the request is sent using fetch (no progress is displayed ).
The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.