简单写了部分注释,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/component/upload#yong-hu-tou-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.js

function 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>

element-ui Upload 上传组件源码分析整理笔记(十四)的更多相关文章

  1. element-ui Progress、Badge、Alert组件源码分析整理笔记(四)

    Progress进度条组件 <template> <!--最外层--> <div class="el-progress" :class="[ ...

  2. element-ui 组件源码分析整理笔记目录

    element-ui button组件 radio组件源码分析整理笔记(一) element-ui switch组件源码分析整理笔记(二) element-ui inputNumber.Card .B ...

  3. element-ui button组件 radio组件源码分析整理笔记(一)

    Button组件 button.vue <template> <button class="el-button" @click="handleClick ...

  4. element-ui Rate组件源码分析整理笔记(十三)

    Rate组件源码比较简单,有添加部分注释 main.vue <template> <!--valuenow当前的评分 valuetext当前显示的文本--> <div c ...

  5. element-ui input组件源码分析整理笔记(六)

    input 输入框组件 源码: <template> <div :class="[ type === 'textarea' ? 'el-textarea' : 'el-in ...

  6. element-ui MessageBox组件源码分析整理笔记(十二)

    MessageBox组件源码,有添加部分注释 main.vue <template> <transition name="msgbox-fade"> < ...

  7. element-ui Message组件源码分析整理笔记(八)

    Message组件源码: main.js import Vue from 'vue'; import Main from './main.vue'; import { PopupManager } f ...

  8. element-ui Steps步骤条组件源码分析整理笔记(九)

    Steps步骤条组件源码: steps.vue <template> <!--设置 simple 可应用简洁风格,该条件下 align-center / description / ...

  9. element-ui switch组件源码分析整理笔记(二)

    源码如下: <template> <div class="el-switch" :class="{ 'is-disabled': switchDisab ...

随机推荐

  1. Flask 异步化

    web网站包含前端和后端, 异步处理可以用在前端, 也可以用在后端.  前端 jquery 进行 ajax 请求时, 可设置 async 属性为 true, 并为 success 设置一个 callb ...

  2. Asp.Net Core 开发之旅之.net core 连接数据库

    数据库连接字符串放入配置文件中 打开appsettings.json 添加ConnectionStrings 例子如下: { "Logging": { "IncludeS ...

  3. Linux-TCP之深入浅出send和recv【转】

    转自:https://www.cnblogs.com/JohnABC/p/7238417.html 内容摘自:TCP之深入浅出send和recv.再次深入理解TCP网络编程中的send和recv 建议 ...

  4. 【PAT甲级】1008 Elevator (20分)

    1008 Elevator 题目: The highest building in our city has only one elevator. A request list is made up ...

  5. pytest-2:allure 的安装、使用

    版本对应: python3.4==>allure-pytest2.7.0 python3.6==>allure0pytest2.8.6 环境安装: 1.先安装好对应的python,准备好p ...

  6. 201871010113-刘兴瑞《面向对象程序设计(java)》第四周学习总结

    项目 内容 这个作业属于哪个课程 <任课教师博客主页链接>https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 <作业链接地址>http ...

  7. day49_9_10jQuery剩余

    一.表单筛选器. 在jQuery中有,专门对表单中的元素,进行筛选的表单筛选器. 其实使用基本筛选器也可以筛选出相应的元素,但是,为了jQuery的简便性,以及对表单操作的频繁,这里可以使用专门的筛选 ...

  8. 1.web2

    听说聪明的人都能找到答案http://123.206.87.240:8002/web2/ 直接查看源码~~~

  9. Linux 下 make 的时候,老是一堆warning

    用下面的方法只显示error : 1) export CFLAGS="-w" 2) ./configure 3) make

  10. MySQL实战45讲学习笔记:第三十七讲

    一.本节概况 今天是大年初二,在开始我们今天的学习之前,我要先和你道一声春节快乐! 在第 16和第 34篇文章中,我分别和你介绍了 sort buffer.内存临时表和 join buffer.这三个 ...