zoukankan      html  css  js  c++  java
  • 浅探element-ui2组件源码之upload

      最近不小心更新了element-ui的版本,已经到了2.1.0,以前修改的源码都失效了。

      于是重新尝试下面的指令重新修改:

    git clone https://github.com/ElemeFE/element.git
    cd element
    npm install
    npm run dist

      这时候会发现,不仅npm run dist的eslint日常报错,连npm install都报错了,不过是普通的operation not permitted,用管理员权限运行下cmd就OK了。

      问题在于这个dist出来的lib文件夹,我去掉了eslint的扫描指令后,可以成功打包,生成的文件夹覆盖后,有一个组件报错了:

      这真的是搞毛,我只是修改了upload的组件,分页组件居然报错了。

      我以为两者是关联的,但是我什么都不改,直接dist后,仍然会报这个错。

      这时只有两个选择,去修改pagination组件源码,尝试修复错误。或者不改源码,尝试从给的方法解决问题。

      因为如果改源码要锁定版本,还是非常坏事的,所以这次我决定从暴露出的API来解决我的问题。

      问题还是那个老的,upload组件点击删除图片时必须弹一个确认框,点击确认后才能删除,如图:

      这里我直接用了element-ui的另一个指令this.$message,之前是在源码里删除图片前直接加了一个判断:

    // 根据变量控制流程
    handleRemove(file, raw) {
      // 添加的确认环节
      if (this.jimmyRemoveTip) {
        this.$confirm('此操作将永久删除图片, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          // ...删除图片
        }).catch(() => {
          this.$message({
            type: 'info',
            message: '已取消删除'
          });          
        });
      }else {
        // 正常流程
      }  
    }

      但是现在这条路行不通了,于是我尝试看看新版本的element-ui有没有加辅助代码。

      直接进入了element-ui的packages/upload/src/index.vue中,翻到handleRemove方法,惊喜的发现多了一个判断:

    handleRemove(file, raw) {
        if (raw) {
            file = this.getFile(raw);
        }
        let doRemove = () => {
            this.abort(file);
            let fileList = this.uploadFiles;
            fileList.splice(fileList.indexOf(file), 1);
            this.onRemove(file, fileList);
        };
    
        if (!this.beforeRemove) {
            doRemove();
        } else if (typeof this.beforeRemove === 'function') {
            const before = this.beforeRemove(file, this.uploadFiles);
            if (before && before.then) {
                before.then(() => {
                    doRemove();
                }, noop);
            } else if (before !== false) {
                doRemove();
            }
        }
    }

      这里有一个新的判断beforeRemove,跑去官网看,是这样解释的:

      这就很爽了,根据源码的情况和官网解释,只要返回一个false或者reject就能让删除操作停止。

      返回false是很简单,但是这里我只能用$message指令,这玩意返回的是一个Promise,于是我要想办法在点击取消的时候reject掉,一开始我是这样写的:

    return this.$confirm('是否确定删除该图片 ?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
    }).then(null, e => {
        throw new Error('reject');
    });

      相当暴力,这个当然解决了问题。但是抛出个错误总觉得很不雅,最后改成了这样:

    return this.$confirm('是否确定删除该图片 ?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
    }).then(null).catch(e=>{
        e();
    });

      

      解决了问题一,还剩另外一个。当上传的图片到上限时,需要把上传按钮隐藏。

      目前upload组件有一个关于上限的参数,是limit,但是这个参数很局限,在上传图片达到上限时,会中止上传,源码如下:

    uploadFiles(files) {
        // 达到上限调用on-exceed钩子函数并返回
        if (this.limit && this.fileList.length + files.length > this.limit) {
            this.onExceed && this.onExceed(files, this.fileList);
            return;
        }
    
        let postFiles = Array.prototype.slice.call(files);
        if (!this.multiple) { postFiles = postFiles.slice(0, 1); }
    
        if (postFiles.length === 0) { return; }
    
        postFiles.forEach(rawFile => {
            this.onStart(rawFile);
            if (this.autoUpload) this.upload(rawFile);
        });
    }

      虽然可以在on-exceed钩子函数中提示用户图片上传达到上限,但是产品要上传按钮消失,这就很头疼了……

      目前还未找到完美的解决办法,先在钩子函数中做上限提示……

      在不久之前,后台也提了一个需求,说多张上传不能太多,最多一次10张,不然图片服务器处理不过来。

      如果能改源码,这个需求分分钟就搞定。但是现在行不通,只能从现存的API来想办法。

      整个组件结构很简单:

    // 根据类型摆放图片展示list的位置
    // uploadComponent就是那个上传按钮
    // $slots.default是白框框中间自定义的图标
    // $slots.tip是下面的文字
    <div>
        { this.listType === 'picture-card' ? uploadList : ''}
        {
            this.$slots.trigger
            ? [uploadComponent, this.$slots.default]
            : uploadComponent
        }
        {this.$slots.tip}
        { this.listType !== 'picture-card' ? uploadList : ''}
    </div>

      示例代码如下图所示:

    <div class="upload-content">
        <el-upload
        /*...*/>
        <!-- slot默认为default -->
        <p>default</p>
        <p slot='tip'>tips</p>
        </el-upload>
    </div>

      效果图:

      而uploadList组件就是已上传的图片展示组件,这个组件没有暴露API,纯展示,所以我的第二个问题暂时不好处理。

      

      接下来看上传流程,整个upload组件的入口代码如下:

    <input class="el-upload__input" type="file" ref="input" name={name} on-change={handleChange} multiple={multiple} accept={accept}></input>

      核心上传按钮,属性直接看就懂了,点击上传按钮后,监听原生on-change事件,触发handleChange:

    handleChange(ev) {
        // 获取事件对应的files
        const files = ev.target.files;
    
        if (!files) return;
        this.uploadFiles(files);
    }

      我这个问题在一开始就可以得到解决在这个地方加一个判断就OK了,如下:

    handleChange(ev) {
        // 获取事件对应的files
        const files = ev.target.files;
    
        if (!files) return;
        // 假设有一个自定义参数concurrenceUploadNumber限制同时上传文件数
        if (files.length > this.concurrenceUploadNumber) {
            this.onConcurrenceUpload && this.onConcurrenceUpload(files, this.fileLise);
            return;
        }
        this.uploadFiles(files);
    }

      这不就解决了么……然而并没有这个东西。

      方法获取对应事件的文件或文件列表,调用uploadFiles方法:

    uploadFiles(files) {
        // 文件超过限制触发钩子函数并返回
        if (this.limit && this.fileList.length + files.length > this.limit) {
            this.onExceed && this.onExceed(files, this.fileList);
            return;
        }
        // 转换为纯数组
        let postFiles = Array.prototype.slice.call(files);
        // 单文件上传模式只取第一个
        if (!this.multiple) { postFiles = postFiles.slice(0, 1); }
    
        if (postFiles.length === 0) { return; }
        // 调用forEach依次上传
        postFiles.forEach(rawFile => {
            this.onStart(rawFile);
            // 根据参数进行自动上传
            if (this.autoUpload) this.upload(rawFile);
        });
    }

      这里后台跟我吐槽为什么要一个一个上传,弄成一个请求多好,同时上传100张就要发100次请求,所以服务器处理不过来。

      因为上传这部分之前了解较少,所以也不明白这里为什么这样处理。

      先不看onStart方法,过一下自动上传流程:

    upload(rawFile, file) {
        // 清空上传按钮内容
        this.$refs.input.value = null;
        // 判断是否存在before-upload钩子函数
        if (!this.beforeUpload) {
            return this.post(rawFile);
        }
        // 钩子函数处理
        const before = this.beforeUpload(rawFile);
        if (before && before.then) {
            before.then(processedFile => {
                const fileType = Object.prototype.toString.call(processedFile);
                if (fileType === '[object File]' || fileType === '[object Blob]') {
                    this.post(processedFile);
                } else {
                    this.post(rawFile);
                }
            }, () => {
                this.onRemove(null, rawFile);
            });
        } else if (before !== false) {
            this.post(rawFile);
        } else {
            this.onRemove(null, rawFile);
        }
    }

      然后调用了post方法做文件上传准备:

    post(rawFile) {
        const { uid } = rawFile;
        // 上传文件的参数与回调函数
        const options = {
            headers: this.headers,
            withCredentials: this.withCredentials,
            file: rawFile,
            data: this.data,
            filename: this.name,
            action: this.action,
            onProgress: e => {
                this.onProgress(e, rawFile);
            },
            onSuccess: res => {
                this.onSuccess(res, rawFile);
                delete this.reqs[uid];
            },
            onError: err => {
                this.onError(err, rawFile);
                delete this.reqs[uid];
            }
        };
        // 上传
        const req = this.httpRequest(options);
        this.reqs[uid] = req;
        // 返回结果
        if (req && req.then) {
            req.then(options.onSuccess, options.onError);
        }
    }

      这里的上传方法httpRequest可以自定义,默认是内置的ajax方法。

      ajax就没什么营养了,所以正常人使用的上传流程大概就是这样。

      但是我不行啊,需要不修改源码的情况下完成特殊需求,还是有点……

      过了一遍流程,发现可操作的地方只有一个地方,那就是auto-upload那里,将自动上传置false,然后进行二次处理,所以之前的流程会断:

    postFiles.forEach(rawFile => {
        // 进入onStart
        this.onStart(rawFile);
        // 不进行上传
        if (this.autoUpload) this.upload(rawFile);
    })

      这里看一下onStart的内容:

    // 'on-start': this.handleStart
    handleStart(rawFile) {
        // 随机数
        rawFile.uid = Date.now() + this.tempIndex++;
        // 包装文件对象
        let file = {
            status: 'ready',
            name: rawFile.name,
            size: rawFile.size,
            percentage: 0,
            uid: rawFile.uid,
            raw: rawFile
        };
        // 转换URL以便展示
        try {
            file.url = URL.createObjectURL(rawFile);
        } catch (err) {
            console.error(err);
            return;
        }
        // 将文件弹入图片展示数组
        this.uploadFiles.push(file);
        // 触发onChange事件
        this.onChange(file, this.uploadFiles);
    }

      这个函数主要是封装了file对象,展示上传的图片但不做上传操作。

      这样的话就有操作空间了,于是我监听了onChange事件并看看返回的参数内容:

    <el-upload
    /*...*/
    :on-change='uploadFile'>
    </el-upload>

      打印内容如下:

      其中第一个参数是封装的file对象,url是转换的本地路径以便展示。

      第二个参数是uploadList的组件参数,会将之前已有的图片也加进来。

      如果同时上传10张照片,会触发10次onChange事件,由于不会做上传操作,所以可以在前端处理掉。

      当时我想着不如在这里把所有上传的请求整合成一个,但是由于每个文件上传都会触发该事件,我又无法获取上传文件数量,所以想着还是限制吧。

      假想代码如下:

    uploadFile(f, fl) {
        let localFileList = this.list,
            len = localFileList.length;
        // 获取上传的图片数量
        let uploadFileList = fl.slice(len);
        if (uploadFileList.length > 10) {
            // 不进行上传
        } else {
            // 正常上传
        }
    }

      然而这是不现实的,问题还是每个上传都会触发该事件,所以会重复上传之前的图片。

      不搞了……不想自己封装组件,木有UI天赋。

      没办法,已经提了Feature Request,不知道有没有用。

      

      啊……年后深圳求职,一年萌新求带走。

  • 相关阅读:
    delphi 调用百度地图api
    Delphi XE5 for android 图片缩放和拖动处理
    利用FMX控件的MakeScreenshot过程实现WAIT效果
    老外写的在桌面添加快捷方式(DELPHI XE5 ANDROID)
    第01组 Alpha冲刺(5/6)
    2019 SDN上机第4次作业
    2019 SDN阅读作业
    第01组 Alpha冲刺(4/6)
    第01组 Alpha冲刺(3/6)
    第01组 Alpha冲刺(2/6)
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/8421323.html
Copyright © 2011-2022 走看看