preface

Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”. [√] – Juejin (juejin. Cn), a universal file-upload component, with multiple file upload, upload file undo function, thinking it is very simple, But in the actual development process or stepped on some very egg pain pit ((((ToT)†~~~, this article will be developed in the process of experience and experience to share with you, shortcomings, welcome to correct!

Demand analysis

There are three main requirements, as follows:

  • File drag upload

  • Not only can single file upload, multiple files can also be uploaded at the same time

  • Display upload list, you can cancel uploaded files

Results demonstrate

Code implementation

template

    <div class="upload-file">
        <el-upload
                :action="uploadFileUrl"
                :before-upload="handleBeforeUpload"
                :file-list="fileList"
                show-file-list
                drag
                multiple
                :limit="limit"
                :on-error="handleUploadError"
                :on-exceed="handleExceed"
                :on-success="handleUploadSuccess"
                :on-preview="handleUploadedPreview"
                :before-remove="beforeDelete"
                :on-remove="handleDelete"
                class="uploader"
                ref="upload"
        >
            <i class="el-icon-upload"></i>
            <div class="el-upload__text">Drag the file here, or<em>Select the file</em>upload</div>

            <! Upload button -->
            <! - < el - button size = "mini" type = "primary" > select file < / el - button > -- >

            <! -- Upload prompt -->
            <div class="el-upload__tip" slot="tip" v-if="showTip">
                <template v-if="fileSize">Please upload the size does not exceed<b style="color: #f56c6c"> {{ fileSize }} KB</b> </template>
                <template v-if="fileType">Format for<b style="color: #f56c6c">{{ fileType.join("/") }}</b>The file</template>
            </div>
        </el-upload>
    </div>
Copy the code

script

Props custom property to receive data from the parent component

props: {
    // Limit the number of uploaded files
    limit: {
        type: Number.default: 5
    },
    // Limit the size of a single uploaded file
    fileSize: {
        type: Number.default: 500
    },
    // The type of file that can be uploaded, e.g. [' PNG ', 'JPG ', 'jpeg']
    fileType: {
        type: Array.default: () = > ["doc"."xls"."ppt"."txt"."pdf".'png'.'jpg'.'jpeg']},// Whether file upload prompt is displayed
    isShowTip: {
        type: Boolean.default: true}}Copy the code

The parent component uses the properties defined by the V-bind binding props to customize the maximum number of files to be uploaded, the maximum size of a single file to be uploaded, the type of files to be uploaded, and whether to display an upload prompt.

Data Data definition

data() {
    return {
        // Upload image request address
        uploadFileUrl: "http://localhost:8088/file/upload".fileList: [].notifyPromise: Promise.resolve()
    };
}
Copy the code
NotifyPromise: promise.resolve () resolves the component height collapse problem

When the file format verification fails before multiple files are uploaded, a warning message is displayed. However, Element simultaneously calls this.$notify for several times, causing the notification message boxes to collapse and overlap with ↓

After programming for Baidu, I found a callback method using Promise to solve the height collapse problem of Element Notification component. For the specific cause and solution, please see o( ̄)  ̄) O patiently

Computed properties

computed: {
    // Whether to display prompts
    showTip() {
        return this.isShowTip && (this.fileType || this.fileSize); }}Copy the code

No upload prompt is displayed when isShowTip = false or fileType and fileSize are not defined.

Method the methods

Analysis of each method
  • HandleBeforeUpload () Hook before uploading the file. The upload is stopped if false or a Promise is returned and reject is rejected.

  • HandleExceed () executes a pop-up warning notification box when the number of files exceeds the limit.

  • When the handleUploadError() file fails to upload, execute the pop-up warning notification box and close the upload and loading.

  • HandleUploadSuccess () executes when a single file has been successfully uploaded.

  • BeforeDelete () Hook before deleting a file. Arguments are uploaded files and list of files. Stop deleting if false or Promise is returned and reject is rejected.

  • HandleDelete () file list is executed when a file is removed, calling the Delete file interface to delete the specified uploaded file.

  • UploadFileDelete () uploadFileDelete() passes in the url of a file and the index of the file in a fileList. The back end deletes the uploaded file according to the path of the file.

  • WarningNotify () receives an argument, the warning message, to bring up the warning box, with a delay of 2 seconds.

methods: {
    // Check the format and size before uploading
    handleBeforeUpload(file) {
        // check the file type
        if (this.fileType) {
            let fileExtension = "";
            if (file.name.lastIndexOf(".") > -1) {
                fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
            }
            const isTypeOk = this.fileType.some((type) = > {
                return fileExtension && fileExtension.indexOf(type) > -1;

            });
            if(! isTypeOk) {this.warningNotify('File format is not correct, please uploadThe ${this.fileType.join("/")}Format file! `);
                return false; }}// check the file size
        if (this.fileSize) {
            // KB
            const fileSize = file.size / 1024;
            const isLt = fileSize < this.fileSize;
            if(! isLt) {this.warningNotify('Upload file size cannot exceedThe ${this.fileSize}KB! `);
                return false; }}// Start uploading
        this.loading = this.$loading({
            lock: true.text: "On...".background: "Rgba (0, 0, 0, 0.7)"});return true;
    },
    // The number of files exceeds the limit
    handleExceed() {
        this.$message.warning('The number of uploaded files cannot exceedThe ${this.limit}A!!!! `);
    },
    // Upload failed
    handleUploadError(err) {
        this.$message.error('Upload failed [${err}], please try again);
        this.loading.close();
    },
    // Upload success callback
    handleUploadSuccess(res, file, fileList) {
        if (res.resultCode === 200) {
            file['url'] = res.data.path;
            //this.fileList.push(file); Cannot set properties of null (setting 'status')
            this.$message.success("Upload successful");
            this.loading.close();
        } else {
            this.handleUploadError(res.message); }},// Delete the file before uploading
    beforeDelete(file, fileList) {
        this.fileList = fileList;
        if (file.status === 'success') {
            return this.$confirm('Confirm file deletion [${file.name}】 `); }},// Delete the uploaded file
    handleDelete(file, fileList) {
        if (file.status === 'success') {
            let filePath = file.url;
            let fileIndex;
            this.fileList.forEach((it, index) = > {
                if(it.url === filePath) { fileIndex = index; }});// Delete the uploaded files
            this.uploadFileDelete(filePath, fileIndex); }},uploadFileDelete(filePath, fileIndex) {
        let _this = this;
        if (fileIndex >= 0) {
            this.axios({
                method: 'DELETE'.url: '/file/upload/delete'.headers: {'content-type': 'application/json'},
                data: filePath
            }).then((response) = > {
                let data = response.data;
                if (data.resultCode === 200) {
                    this.$message({
                        type: 'success'.message: data.message
                    });
                    _this.fileList.splice(fileIndex, 1);
                } else {
                    this.$message.error(data.message);
                }
            }).catch(error= > {
                this.$message.error(error);
            });
        } else {
            this.$message.error("Upload file not found, cannot be deleted"); }}}Copy the code
$notify

$notify: when the notification spacing is calculated, the height of the current element is taken, but the dom is not updated on the first call because of the buffer mechanism in the vue asynchronous update queue, so the height is 0, so the second notification box just moves the default offset 16px up.

    warningNotify(msg) {
        let _this = this;
        this.notifyPromise = this.notifyPromise.then(_this.$nextTick).then(() = > {
            _this.$notify({
                type: 'warning'.title: 'warning'.message: msg,
                duration: 2000
            });
        });
    }
Copy the code

The nextTick method provided by VUE is used to ensure that the DOM of the first notification is updated, and then the code of the second notification is executed. At this time, the height of the notification box will be added to the height of the first notification box to get the correct calculation height, and then the box overlap problem will be solved.

Multi-file upload

W( ̄_ ̄)W BUG

Cause analysis,

The main reason is that I push the current uploaded file to the instance fileList in the callback function of successfully uploading the file. I originally thought that the uploaded file could be added to the uploaded file list after the successful uploading, but actually there is no need to manually add the file. I this wave operation is simply off pants farts O (╥﹏╥)o or open-backed pants that kind!

There is no need to push the file again. Otherwise, the fileList will be interfered with during the asynchronous multi-file upload process. The status of the uploaded file will be null.

BUG to solve

Method 1: Combine… Push (file) annotations and then assign a fileList to the instance in the callback function before deleting the file

Method 2: define another file uploadList, uploadList, to store files that have been successfully uploaded

Sample test

<template>
    <upload :limit="10" :file-size="100" :is-show-tip="false"/>
</template>
<script>
    import Upload from ".. /file/Upload";
    export default {
        name: 'Example'.components: {Upload},
        data() {
            return{}},methods: {}}</script>
Copy the code

(O ゜ – ゜▽゜)

At the end

Writing is not easy, welcome everyone to like, comment, your attention, like is my unremitting power, thank you to see here! Peace and Love.