A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

简单写了部分注释,upload-dragger.vue(拖拽上传时显示此组件)、upload-list.vue(已上传文件列表)源码暂未添加多少注释,等有空再补充,先记下来...
index.vue<script>import UploadList from './upload-list';import Upload from './upload';import ElProgress from 'element-ui/packages/progress';import Migrating from 'element-ui/src/mixins/migrating';function noop() {}export default {  name: 'ElUpload',  mixins: [Migrating],  components: {    ElProgress,    UploadList,    Upload  },  provide() {    return {      uploader: this    };  },  inject: {    elForm: {      default: ''    }  },  props: {    action: { //必选参数,上传的地址      type: String,      required: true    },    headers: { //设置上传的请求头部      type: Object,      default() {        return {};      }    },    data: Object, //上传时附带的额外参数    multiple: Boolean, //是否支持多选文件    name: { //上传的文件字段名      type: String,      default: 'file'    },    drag: Boolean, //是否启用拖拽上传    dragger: Boolean,    withCredentials: Boolean, //支持发送 cookie 凭证信息    showFileList: { //是否显示已上传文件列表      type: Boolean,      default: true    },    accept: String, //接受上传的文件类型(thumbnail-mode 模式下此参数无效)    type: {      type: String,      default: 'select'    },    beforeUpload: Function, //上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。    beforeRemove: Function, //删除文件之前的钩子,参数为上传的文件和文件列表,若返回 false 或者返回 Promise 且被 reject,则停止上传。    onRemove: { //文件列表移除文件时的钩子      type: Function,      default: noop    },    onChange: { //文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用      type: Function,      default: noop    },    onPreview: { //点击文件列表中已上传的文件时的钩子      type: Function    },    onSuccess: { //文件上传成功时的钩子      type: Function,      default: noop    },    onProgress: { //文件上传时的钩子      type: Function,      default: noop    },    onError: { //文件上传失败时的钩子      type: Function,      default: noop    },    fileList: { //上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}]      type: Array,      default() {        return [];      }    },    autoUpload: { //是否在选取文件后立即进行上传      type: Boolean,      default: true    },    listType: { //文件列表的类型      type: String,      default: 'text' // text,picture,picture-card    },    httpRequest: Function, //覆盖默认的上传行为,可以自定义上传的实现    disabled: Boolean, //是否禁用    limit: Number, //最大允许上传个数    onExceed: { //文件超出个数限制时的钩子      type: Function,      default: noop    }  },  data() {    return {      uploadFiles: [],      dragOver: false,      draging: false,      tempIndex: 1    };  },  computed: {    uploadDisabled() {      return this.disabled || (this.elForm || {}).disabled;    }  },  watch: {    fileList: {      immediate: true,      handler(fileList) {        this.uploadFiles = fileList.map(item => {          item.uid = item.uid || (Date.now() + this.tempIndex++);          item.status = item.status || 'success';          return item;        });      }    }  },  methods: {    //文件上传之前调用的方法    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      };      //判断文件列表类型      if (this.listType === 'picture-card' || this.listType === 'picture') {        try {          file.url = URL.createObjectURL(rawFile);        } catch (err) {          console.error('[Element Error][Upload]', err);          return;        }      }      this.uploadFiles.push(file);      this.onChange(file, this.uploadFiles);    },    handleProgress(ev, rawFile) {      const file = this.getFile(rawFile);      this.onProgress(ev, file, this.uploadFiles);      file.status = 'uploading';      file.percentage = ev.percent || 0;    },    //文件上传成功后改用该方法,在该方法中调用用户设置的on-success和on-change方法,并将对应的参数传递出去    handleSuccess(res, rawFile) {      const file = this.getFile(rawFile);      if (file) {        file.status = 'success';        file.response = res;        this.onSuccess(res, file, this.uploadFiles);        this.onChange(file, this.uploadFiles);      }    },    //文件上传失败后改用该方法,在该方法中调用用户设置的on-error和on-change方法,并将对应的参数传递出去    handleError(err, rawFile) {      const file = this.getFile(rawFile);      const fileList = this.uploadFiles;      file.status = 'fail';      fileList.splice(fileList.indexOf(file), 1);      this.onError(err, file, this.uploadFiles);      this.onChange(file, this.uploadFiles);    },    //文件列表移除文件时调用该方法    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();        }      }    },    getFile(rawFile) {      let fileList = this.uploadFiles;      let target;      fileList.every(item => {        target = rawFile.uid === item.uid ? item : null;        return !target;      });      return target;    },    abort(file) {      this.$refs['upload-inner'].abort(file);    },    clearFiles() {      this.uploadFiles = [];    },    submit() {      this.uploadFiles        .filter(file => file.status === 'ready')        .forEach(file => {          this.$refs['upload-inner'].upload(file.raw);        });    },    getMigratingConfig() {      return {        props: {          'default-file-list': 'default-file-list is renamed to file-list.',          'show-upload-list': 'show-upload-list is renamed to show-file-list.',          'thumbnail-mode': 'thumbnail-mode has been deprecated, you can implement the same effect according to this case: http://element.eleme.io/#/zh-CN/ ... u-xiang-shang-chuan        }      };    }  },  beforeDestroy() {    this.uploadFiles.forEach(file => {      if (file.url && file.url.indexOf('blob:') === 0) {        URL.revokeObjectURL(file.url);      }    });  },  render(h) {    let uploadList;    //如果用户设置showFileList为true,则显示上传文件列表    if (this.showFileList) {      uploadList = (        <UploadList          disabled={this.uploadDisabled}          listType={this.listType}          files={this.uploadFiles}          on-remove={this.handleRemove}          handlePreview={this.onPreview}>        </UploadList>      );    }    const uploadData = {      props: {        type: this.type,        drag: this.drag,        action: this.action,        multiple: this.multiple,        'before-upload': this.beforeUpload,        'with-credentials': this.withCredentials,        headers: this.headers,        name: this.name,        data: this.data,        accept: this.accept,        fileList: this.uploadFiles,        autoUpload: this.autoUpload,        listType: this.listType,        disabled: this.uploadDisabled,        limit: this.limit,        'on-exceed': this.onExceed,        'on-start': this.handleStart,        'on-progress': this.handleProgress,        'on-success': this.handleSuccess,        'on-error': this.handleError,        'on-preview': this.onPreview,        'on-remove': this.handleRemove,        'http-request': this.httpRequest      },      ref: 'upload-inner'    };    const trigger = this.$slots.trigger || this.$slots.default;    const uploadComponent = <upload {...uploadData}>{trigger}</upload>;    return (      <div>        { this.listType === 'picture-card' ? uploadList : ''}        {          this.$slots.trigger            ? [uploadComponent, this.$slots.default]            : uploadComponent        }        {this.$slots.tip}        { this.listType !== 'picture-card' ? uploadList : ''}      </div>    );  }};</script>upload.vue<script>import ajax from './ajax';import UploadDragger from './upload-dragger.vue';export default {  inject: ['uploader'],  components: {    UploadDragger  },  props: {    type: String,    action: { //必选参数,上传的地址      type: String,      required: true    },    name: { //上传的文件字段名      type: String,      default: 'file'    },    data: Object, //上传时附带的额外参数    headers: Object, //设置上传的请求头部    withCredentials: Boolean, //支持发送 cookie 凭证信息    multiple: Boolean, //是否支持多选文件    accept: String, //接受上传的文件类型(thumbnail-mode 模式下此参数无效)    onStart: Function,    onProgress: Function, //文件上传时的钩子    onSuccess: Function, //文件上传成功时的钩子    onError: Function, //文件上传失败时的钩子    beforeUpload: Function, //上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。    drag: Boolean, //是否启用拖拽上传    onPreview: { //点击文件列表中已上传的文件时的钩子      type: Function,      default: function() {}    },    onRemove: { //文件列表移除文件时的钩子      type: Function,      default: function() {}    },    fileList: Array, //上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}]    autoUpload: Boolean, //是否在选取文件后立即进行上传    listType: String, //文件列表的类型    httpRequest: { //覆盖默认的上传行为,可以自定义上传的实现      type: Function,      default: ajax    },    disabled: Boolean,//是否禁用    limit: Number,//最大允许上传个数    onExceed: Function //文件超出个数限制时的钩子  },  data() {    return {      mouseover: false,      reqs: {}    };  },  methods: {    isImage(str) {      return str.indexOf('image') !== -1;    },    handleChange(ev) {      const files = ev.target.files;      if (!files) return;      this.uploadFiles(files);    },    uploadFiles(files) {      //文件超出个数限制时,调用onExceed钩子函数      if (this.limit && this.fileList.length + files.length > this.limit) {        this.onExceed && this.onExceed(files, this.fileList);        return;      }      //将files转成数组      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);        //选取文件后调用upload方法立即进行上传文件        if (this.autoUpload) this.upload(rawFile);      });    },    upload(rawFile) {      this.$refs.input.value = null;      //beforeUpload 上传文件之前的钩子不存在就直接调用post上传文件      if (!this.beforeUpload) {        return this.post(rawFile);      }      // beforeUpload 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传      const before = this.beforeUpload(rawFile);      // 在调用beforeUpload钩子后返回的是true,则继续上传      if (before && before.then) {        before.then(processedFile => {          //processedFile转成对象          const fileType = Object.prototype.toString.call(processedFile);          if (fileType === '[object File]' || fileType === '[object Blob]') {            if (fileType === '[object Blob]') {              processedFile = new File([processedFile], rawFile.name, {                type: rawFile.type              });            }            for (const p in rawFile) {              if (rawFile.hasOwnProperty(p)) {                processedFile[p] = rawFile[p];              }            }            this.post(processedFile);          } else {            this.post(rawFile);          }        }, () => {          this.onRemove(null, rawFile);        });      } else if (before !== false) { //调用beforeUpload之后没有返回值,此时before为undefined,继续上传        this.post(rawFile);      } else {  //调用beforeUpload之后返回值为false,则不再继续上传并移除文件        this.onRemove(null, rawFile);      }    },    abort(file) {      const { reqs } = this;      if (file) {        let uid = file;        if (file.uid) uid = file.uid;        if (reqs[uid]) {          reqs[uid].abort();        }      } else {        Object.keys(reqs).forEach((uid) => {          if (reqs[uid]) reqs[uid].abort();          delete reqs[uid];        });      }    },    //上传文件过程的方法    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 => { //文件上传成功的钩子函数          //上传成功调用onSuccess方法,即index.vue中的handleSuccess方法          this.onSuccess(res, rawFile);          delete this.reqs[uid];        },        onError: err => { //文件上传失败的钩子函数          this.onError(err, rawFile);          delete this.reqs[uid];        }      };      //httpRequest可以自定义上传文件,如果没有定义,默认通过ajax文件中的方法上传      const req = this.httpRequest(options);      this.reqs[uid] = req;      if (req && req.then) {        req.then(options.onSuccess, options.onError);      }    },    handleClick() {      //点击组件时调用input的click方法      if (!this.disabled) {        this.$refs.input.value = null;        this.$refs.input.click();      }    },    handleKeydown(e) {      if (e.target !== e.currentTarget) return;      //如果当前按下的是回车键和空格键,调用handleClick事件      if (e.keyCode === 13 || e.keyCode === 32) {        this.handleClick();      }    }  },  render(h) {    let {      handleClick,      drag,      name,      handleChange,      multiple,      accept,      listType,      uploadFiles,      disabled,      handleKeydown    } = this;    const data = {      class: {        'el-upload': true      },      on: {        click: handleClick,        keydown: handleKeydown      }    };    data.class[`el-upload--${listType}`] = true;    return (      //判断是否允许拖拽,允许的话显示upload-dragger组件,不允许就显示所有插槽中的节点      <div {...data} tabindex="0" >        {          drag            ? <upload-dragger disabled={disabled} on-file={uploadFiles}>{this.$slots.default}</upload-dragger>            : this.$slots.default        }        <input class="el-upload__input" type="file" ref="input" name={name} on-change={handleChange} multiple={multiple} accept={accept}></input>      </div>    );  }};</script>ajax.jsfunction getError(action, option, xhr) {  let msg;  if (xhr.response) {    msg = `${xhr.response.error || xhr.response}`;  } else if (xhr.responseText) {    msg = `${xhr.responseText}`;  } else {    msg = `fail to post ${action} ${xhr.status}`;  }  const err = new Error(msg);  err.status = xhr.status;  err.method = 'post';  err.url = action;  return err;}function getBody(xhr) {  const text = xhr.responseText || xhr.response;  if (!text) {    return text;  }  try {    return JSON.parse(text);  } catch (e) {    return text;  }}//默认的上传文件的方法export default function upload(option) {  //XMLHttpRequest 对象用于在后台与服务器交换数据。  if (typeof XMLHttpRequest === 'undefined') {    return;  }  //创建XMLHttpRequest对象  const xhr = new XMLHttpRequest();  const action = option.action; //上传的地址  //XMLHttpRequest.upload 属性返回一个 XMLHttpRequestUpload对象,用来表示上传的进度。这个对象是不透明的,但是作为一个XMLHttpRequestEventTarget,可以通过对其绑定事件来追踪它的进度。  if (xhr.upload) {    //上传进度调用方法,上传过程中会频繁调用该方法    xhr.upload.onprogress = function progress(e) {      if (e.total > 0) {        // e.total是需要传输的总字节,e.loaded是已经传输的字节        e.percent = e.loaded / e.total * 100;      }      //调文件上传时的钩子函数      option.onProgress(e);    };  }  // 创建一个FormData 对象  const formData = new FormData();  //用户设置了上传时附带的额外参数时  if (option.data) {    Object.keys(option.data).forEach(key => {      // 添加一个新值到 formData 对象内的一个已存在的键中,如果键不存在则会添加该键。      formData.append(key, option.data[key]);    });  }  formData.append(option.filename, option.file, option.file.name);  //请求出错  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));    }    //调用upload.vue文件中的onSuccess方法,将上传接口返回值作为参数传递    option.onSuccess(getBody(xhr));  };  //初始化请求  xhr.open('post', action, true);  if (option.withCredentials && 'withCredentials' in xhr) {    xhr.withCredentials = true;  }  const headers = option.headers || {};  for (let item in headers) {    if (headers.hasOwnProperty(item) && headers[item] !== null) {      //设置请求头      xhr.setRequestHeader(item, headers[item]);    }  }  //发送请求  xhr.send(formData);  return xhr;}upload-dragger.vue<template>  <!--拖拽上传时显示此组件-->  <div    class="el-upload-dragger"    :class="{      'is-dragover': dragover    }"    @drop.prevent="onDrop"    @dragover.prevent="onDragover"    @dragleave.prevent="dragover = false"  >    <slot></slot>  </div></template><script>  export default {    name: 'ElUploadDrag',    props: {      disabled: Boolean    },    inject: {      uploader: {        default: ''      }    },    data() {      return {        dragover: false      };    },    methods: {      onDragover() {        if (!this.disabled) {          this.dragover = true;        }      },      onDrop(e) {        if (this.disabled || !this.uploader) return;        //接受上传的文件类型(thumbnail-mode 模式下此参数无效),此处判断该文件是都符合能上传的类型        const accept = this.uploader.accept;        this.dragover = false;        if (!accept) {          this.$emit('file', e.dataTransfer.files);          return;        }        this.$emit('file', [].slice.call(e.dataTransfer.files).filter(file => {          const { type, name } = file;          //获取文件名后缀,与设置的文件类型进行对比          const extension = name.indexOf('.') > -1            ? `.${ name.split('.').pop() }`            : '';          const baseType = type.replace(/\/.*$/, '');          return accept.split(',')            .map(type => type.trim())            .filter(type => type)            .some(acceptedType => {              if (/\..+$/.test(acceptedType)) {                //文件名后缀与设置的文件类型进行对比                return extension === acceptedType;              }              if (/\/\*$/.test(acceptedType)) {                return baseType === acceptedType.replace(/\/\*$/, '');              }              if (/^[^\/]+\/[^\/]+$/.test(acceptedType)) {                return type === acceptedType;              }              return false;            });        }));      }    }  };</script>upload-list.vue<template>  <!--这里主要显示已上传文件列表-->  <transition-group    tag="ul"    :class="[      'el-upload-list',      'el-upload-list--' + listType,      { 'is-disabled': disabled }    ]"    name="el-list">    <li      v-for="file in files"      :class="['el-upload-list__item', 'is-' + file.status, focusing ? 'focusing' : '']"      :key="file.uid"      tabindex="0"      @keydown.delete="!disabled && $emit('remove', file)"      @focus="focusing = true"      @blur="focusing = false"      @click="focusing = false"    >      <img        class="el-upload-list__item-thumbnail"        v-if="file.status !== 'uploading' && ['picture-card', 'picture'].indexOf(listType) > -1"        :src="file.url" alt=""      >      <a class="el-upload-list__item-name" @click="handleClick(file)">        <i class="el-icon-document"></i>{{file.name}}      </a>      <label class="el-upload-list__item-status-label">        <i :class="{          'el-icon-upload-success': true,          'el-icon-circle-check': listType === 'text',          'el-icon-check': ['picture-card', 'picture'].indexOf(listType) > -1        }"></i>      </label>      <i class="el-icon-close" v-if="!disabled" @click="$emit('remove', file)"></i>      <i class="el-icon-close-tip" v-if="!disabled">{{ t('el.upload.deleteTip') }}</i> <!--因为close按钮只在li:focus的时候 display, li blur后就不存在了,所以键盘导航时永远无法 focus到 close按钮上-->      <el-progress        v-if="file.status === 'uploading'"        :type="listType === 'picture-card' ? 'circle' : 'line'"        :stroke-width="listType === 'picture-card' ? 6 : 2"        :percentage="parsePercentage(file.percentage)">      </el-progress>      <span class="el-upload-list__item-actions" v-if="listType === 'picture-card'">        <span          class="el-upload-list__item-preview"          v-if="handlePreview && listType === 'picture-card'"          @click="handlePreview(file)"        >          <i class="el-icon-zoom-in"></i>        </span>        <span          v-if="!disabled"          class="el-upload-list__item-delete"          @click="$emit('remove', file)"        >          <i class="el-icon-delete"></i>        </span>      </span>    </li>  </transition-group></template><script>  import Locale from 'element-ui/src/mixins/locale';  import ElProgress from 'element-ui/packages/progress';  export default {    name: 'ElUploadList',    mixins: [Locale],    data() {      return {        focusing: false      };    },    components: { ElProgress },    props: {      files: {        type: Array,        default() {          return [];        }      },      disabled: {        type: Boolean,        default: false      },      handlePreview: Function,      listType: String    },    methods: {      parsePercentage(val) {        return parseInt(val, 10);      },      handleClick(file) {        this.handlePreview && this.handlePreview(file);      }    }  };</script>

2 个回复

正序浏览
或者添加学姐微信
DKA-2018
回复 使用道具 举报
有任何问题欢迎在评论区留言
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马