<template>
|
<div class="uploader-file" :status="status">
|
<slot
|
:file="file"
|
:list="list"
|
:status="status"
|
:paused="paused"
|
:error="error"
|
:response="response"
|
:average-speed="averageSpeed"
|
:formated-average-speed="formatedAverageSpeed"
|
:current-speed="currentSpeed"
|
:is-complete="isComplete"
|
:is-uploading="isUploading"
|
:size="size"
|
:formated-size="formatedSize"
|
:uploaded-size="uploadedSize"
|
:progress="progress"
|
:progress-style="progressStyle"
|
:progressing-class="progressingClass"
|
:time-remaining="timeRemaining"
|
:formated-time-remaining="formatedTimeRemaining"
|
:type="type"
|
:extension="extension"
|
:file-category="fileCategory"
|
>
|
<div
|
class="uploader-file-progress"
|
:class="progressingClass"
|
:style="progressStyle"
|
></div>
|
<div class="uploader-file-info">
|
<div class="uploader-file-name">
|
<i class="uploader-file-icon" :icon="fileCategory"></i>
|
{{ file.name }}
|
</div>
|
<div class="uploader-file-size">{{ formatedSize }}</div>
|
<div class="uploader-file-meta"></div>
|
<div class="uploader-file-status">
|
<span v-show="status !== 'uploading'">{{ statusText }}</span>
|
<span v-show="status === 'uploading'">
|
<span>{{ progressStyle.progress }}</span>
|
<em>{{ formatedAverageSpeed }}</em>
|
|
<i> {{ formatedTimeRemaining }}</i>
|
</span>
|
</div>
|
<div class="uploader-file-actions">
|
<span class="uploader-file-pause" @click="pause"></span>
|
<span class="uploader-file-resume" @click="resume">️</span>
|
<el-tooltip effect="dark" content="重新上传" placement="bottom">
|
<span class="uploader-file-retry" @click="retry"></span>
|
</el-tooltip>
|
<el-tooltip effect="dark" content="删除" placement="bottom">
|
<span class="uploader-file-remove" @click="remove"></span>
|
</el-tooltip>
|
</div>
|
</div>
|
</slot>
|
</div>
|
</template>
|
|
<script>
|
import Uploader from "simple-uploader.js";
|
import events from "./common/file-events";
|
import { secondsToStr } from "./common/utils";
|
|
const COMPONENT_NAME = "uploader-file";
|
|
export default {
|
name: COMPONENT_NAME,
|
props: {
|
file: {
|
type: Object,
|
default() {
|
return {};
|
},
|
},
|
list: {
|
type: Boolean,
|
default: false,
|
},
|
},
|
data() {
|
return {
|
response: null,
|
paused: false,
|
error: false,
|
averageSpeed: 0,
|
currentSpeed: 0,
|
isComplete: false,
|
isUploading: false,
|
size: 0,
|
formatedSize: "",
|
uploadedSize: 0,
|
progress: 0,
|
timeRemaining: 0,
|
type: "",
|
extension: "",
|
progressingClass: "",
|
};
|
},
|
computed: {
|
fileCategory() {
|
const extension = this.extension;
|
const isFolder = this.file.isFolder;
|
let type = isFolder ? "folder" : "unknown";
|
const categoryMap = this.file.uploader.opts.categoryMap;
|
const typeMap = categoryMap || {
|
image: ["gif", "jpg", "jpeg", "png", "bmp", "webp"],
|
video: ["mp4", "m3u8", "rmvb", "avi", "swf", "3gp", "mkv", "flv"],
|
audio: ["mp3", "wav", "wma", "ogg", "aac", "flac"],
|
document: [
|
"doc",
|
"txt",
|
"docx",
|
"pages",
|
"epub",
|
"pdf",
|
"numbers",
|
"csv",
|
"xls",
|
"xlsx",
|
"keynote",
|
"ppt",
|
"pptx",
|
],
|
};
|
Object.keys(typeMap).forEach((_type) => {
|
const extensions = typeMap[_type];
|
if (extensions.indexOf(extension) > -1) {
|
type = _type;
|
}
|
});
|
return type;
|
},
|
progressStyle() {
|
const progress = Math.floor(this.progress * 100);
|
const style = `translateX(${Math.floor(progress - 100)}%)`;
|
return {
|
progress: `${progress}%`,
|
webkitTransform: style,
|
mozTransform: style,
|
msTransform: style,
|
transform: style,
|
};
|
},
|
formatedAverageSpeed() {
|
return `${Uploader.utils.formatSize(this.averageSpeed)} / s`;
|
},
|
status() {
|
const isUploading = this.isUploading;
|
const isComplete = this.isComplete;
|
const isError = this.error;
|
const paused = this.paused;
|
if (isComplete) {
|
return "success";
|
} else if (isError) {
|
return "error";
|
} else if (isUploading) {
|
return "uploading";
|
} else if (paused) {
|
return "paused";
|
} else {
|
return "waiting";
|
}
|
},
|
statusText() {
|
const status = this.status;
|
const fileStatusText = this.file.uploader.fileStatusText;
|
let txt = status;
|
if (typeof fileStatusText === "function") {
|
txt = fileStatusText(status, this.response);
|
} else {
|
txt = fileStatusText[status];
|
}
|
return txt || status;
|
},
|
formatedTimeRemaining() {
|
const timeRemaining = this.timeRemaining;
|
const file = this.file;
|
if (timeRemaining === Number.POSITIVE_INFINITY || timeRemaining === 0) {
|
return "";
|
}
|
let parsedTimeRemaining = secondsToStr(timeRemaining);
|
const parseTimeRemaining = file.uploader.opts.parseTimeRemaining;
|
if (parseTimeRemaining) {
|
parsedTimeRemaining = parseTimeRemaining(
|
timeRemaining,
|
parsedTimeRemaining
|
);
|
}
|
return parsedTimeRemaining;
|
},
|
},
|
watch: {
|
status(newStatus, oldStatus) {
|
if (oldStatus && newStatus === "uploading" && oldStatus !== "uploading") {
|
this.tid = setTimeout(() => {
|
this.progressingClass = "uploader-file-progressing";
|
}, 200);
|
} else {
|
clearTimeout(this.tid);
|
this.progressingClass = "";
|
}
|
},
|
},
|
methods: {
|
_actionCheck() {
|
this.paused = this.file.paused;
|
this.error = this.file.error;
|
this.isUploading = this.file.isUploading();
|
},
|
pause() {
|
this.file.pause();
|
this._actionCheck();
|
this._fileProgress();
|
},
|
resume() {
|
this.file.resume();
|
this._actionCheck();
|
},
|
remove() {
|
this.file.cancel();
|
},
|
retry() {
|
this.file.retry();
|
this._actionCheck();
|
},
|
processResponse(message) {
|
let res = message;
|
try {
|
res = JSON.parse(message);
|
} catch (e) {}
|
this.response = res;
|
},
|
fileEventsHandler(event, args) {
|
const rootFile = args[0];
|
const file = args[1];
|
const target = this.list ? rootFile : file;
|
if (this.file === target) {
|
if (this.list && event === "fileSuccess") {
|
this.processResponse(args[2]);
|
return;
|
}
|
this[`_${event}`].apply(this, args);
|
}
|
},
|
_fileProgress() {
|
this.progress = this.file.progress();
|
this.averageSpeed = this.file.averageSpeed;
|
this.currentSpeed = this.file.currentSpeed;
|
this.timeRemaining = this.file.timeRemaining();
|
this.uploadedSize = this.file.sizeUploaded();
|
this._actionCheck();
|
},
|
_fileSuccess(rootFile, file, message) {
|
if (rootFile) {
|
this.processResponse(message);
|
}
|
this._fileProgress();
|
this.error = false;
|
this.isComplete = true;
|
this.isUploading = false;
|
console.log("rootFile, file, message", rootFile, file, message);
|
},
|
_fileComplete() {
|
this._fileSuccess();
|
},
|
_fileError(rootFile, file, message) {
|
this._fileProgress();
|
console.log("rootFile, file, message", rootFile, file, message);
|
this.processResponse(message);
|
this.error = true;
|
this.isComplete = false;
|
this.isUploading = false;
|
},
|
},
|
mounted() {
|
const staticProps = ["paused", "error", "averageSpeed", "currentSpeed"];
|
const fnProps = [
|
"isComplete",
|
"isUploading",
|
{
|
key: "size",
|
fn: "getSize",
|
},
|
{
|
key: "formatedSize",
|
fn: "getFormatSize",
|
},
|
{
|
key: "uploadedSize",
|
fn: "sizeUploaded",
|
},
|
"progress",
|
"timeRemaining",
|
{
|
key: "type",
|
fn: "getType",
|
},
|
{
|
key: "extension",
|
fn: "getExtension",
|
},
|
];
|
staticProps.forEach((prop) => {
|
this[prop] = this.file[prop];
|
});
|
fnProps.forEach((fnProp) => {
|
if (typeof fnProp === "string") {
|
this[fnProp] = this.file[fnProp]();
|
} else {
|
this[fnProp.key] = this.file[fnProp.fn]();
|
}
|
});
|
|
const handlers = (this._handlers = {});
|
const eventHandler = (event) => {
|
handlers[event] = (...args) => {
|
this.fileEventsHandler(event, args);
|
};
|
return handlers[event];
|
};
|
events.forEach((event) => {
|
this.file.uploader.on(event, eventHandler(event));
|
});
|
},
|
destroyed() {
|
events.forEach((event) => {
|
this.file.uploader.off(event, this._handlers[event]);
|
});
|
this._handlers = null;
|
},
|
};
|
</script>
|
|
<style>
|
.uploader-file {
|
position: relative;
|
height: 49px;
|
line-height: 49px;
|
overflow: hidden;
|
border-bottom: 1px solid #cdcdcd;
|
}
|
.uploader-file[status="waiting"] .uploader-file-pause,
|
.uploader-file[status="uploading"] .uploader-file-pause {
|
display: block;
|
}
|
.uploader-file[status="paused"] .uploader-file-resume {
|
display: block;
|
}
|
.uploader-file[status="error"] .uploader-file-retry {
|
display: block;
|
}
|
.uploader-file[status="success"] .uploader-file-remove {
|
display: none;
|
}
|
.uploader-file[status="error"] .uploader-file-progress {
|
background: #ffe0e0;
|
}
|
.uploader-file-progress {
|
position: absolute;
|
width: 100%;
|
height: 100%;
|
background: #e2eeff;
|
transform: translateX(-100%);
|
}
|
.uploader-file-progressing {
|
transition: all 0.4s linear;
|
}
|
.uploader-file-info {
|
position: relative;
|
z-index: 1;
|
height: 100%;
|
overflow: hidden;
|
}
|
.uploader-file-info:hover {
|
background-color: rgba(240, 240, 240, 0.2);
|
}
|
.uploader-file-info i,
|
.uploader-file-info em {
|
font-style: normal;
|
}
|
.uploader-file-name,
|
.uploader-file-size,
|
.uploader-file-meta,
|
.uploader-file-status,
|
.uploader-file-actions {
|
float: left;
|
position: relative;
|
height: 100%;
|
}
|
.uploader-file-name {
|
width: 50%;
|
overflow: hidden;
|
white-space: nowrap;
|
text-overflow: ellipsis;
|
text-indent: 14px;
|
text-align: left;
|
}
|
.uploader-file-icon {
|
width: 24px;
|
height: 24px;
|
display: inline-block;
|
vertical-align: top;
|
margin-top: 13px;
|
margin-right: 8px;
|
}
|
.uploader-file-icon::before {
|
content: "📃";
|
display: block;
|
height: 100%;
|
font-size: 24px;
|
line-height: 1;
|
text-indent: 0;
|
}
|
.uploader-file-icon[icon="folder"]::before {
|
content: "📂";
|
}
|
.uploader-file-icon[icon="image"]::before {
|
content: "🌆";
|
}
|
.uploader-file-icon[icon="video"]::before {
|
content: "🎬";
|
}
|
.uploader-file-icon[icon="audio"]::before {
|
content: "🎵";
|
}
|
.uploader-file-icon[icon="document"]::before {
|
content: "📋";
|
}
|
.uploader-file-size {
|
width: 11%;
|
text-indent: 10px;
|
}
|
.uploader-file-meta {
|
width: 8%;
|
}
|
.uploader-file-status {
|
width: 24%;
|
text-indent: 20px;
|
}
|
.uploader-file-actions {
|
width: 6%;
|
}
|
.uploader-file-actions > span {
|
display: none;
|
float: left;
|
width: 16px;
|
height: 16px;
|
margin-top: 16px;
|
margin-right: 10px;
|
cursor: pointer;
|
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAABkCAYAAAD0ZHJ6AAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAAJcEhZcwAACxMAAAsTAQCanBgAAARkSURBVGje7ZnfS1NRHMAH4ptPkvQSuAdBkCxD8FUQJMEULUgzy1KyyPVQ4JMiiP4Bvg6EwUQQfMmwhwRDshwaKUjDVCgoSdDNHkzTJZ6+Z37Purve8+PeTb2TM/ggu+ew89l33x8H9BBCPG7GowXTJej3+wnDvEm0JuLC04+EYWftVAUv+fiCvDUdQR1BHUEdQR3BTIygvixoQS14XgTtthLVdpNWwXRLqvQ724LplFRtyrYF0yVpFLQrKRVMh6RZ0I6kkmCqklaCqpKZH0FX56Crq9jVfdDVk0RfFrSgFsxkQVmLcdKCVrKySCrryhPEyYShhzOcrFtG0EoilfHHk1CRU5rF6ZjNZhlVOW6RnMSVyyilKies4pO41diVy8wIujoHXV3FGdMHXTtJKLFYTLhZtq4vC1rwXApCZTIqgR6g1PBMCO9DL3bMMSqBHqDU8EyISDAHiGKvWwcCQG2KgjlAFCDAOhAAap0K5gKLphk8mqJgLrCIgoxRJ4J5wKpJ7gAoMkn5EBXBPGDVJHcAFJmkfIhQcAql1oBpTvTol9gG9pm4RHAKpdaAaU706JfYBvaZuJVgPQrt4sFlnOh5MC/p3lmJYD0K7eLBZZzoeTAv6d5ZnuAYHjpgEOnk5F0ufhG6v1ggOIaHDhhEOjl5l4tfhO4vthLcwAMrFNvLJO5vEwhu4IEViu1lEve3WQmyoihQFBzG/V0CQVYUBYqCw7i/SxTBcpsRbFeIYLnNCLZbCY5b5KAnxRwct8hBj9McZFVMW0ihRNBuFdMWUigRlFaxuQ9WWYjRMTiIe5z0wSoLMToGB3GPsA9aTZIJoB+nRgBnM1tzOkkmgH6cGgGczWzNpzqLx3n/aULJJgezeNw07oxQySbVywKjBOgFRnDs+VEsx8FlgVEC9AIjOPb8KJYjvSzoG7UW1IJaUAtqQS14toLNM5fN5APdwBJA8G83Pk/aK/rgzVvXzeQD3cASQPBvNz5P2ssTzAaGUIrHEO6zI5gNDKEUjyHcxxWkh4Ylcowwk1QQpIeGJXKMMJO0EgwqyjGCioJBJvDrxRMSuVOTJEXfbz1/bHwWtBL0yoQehK6RucgE+bGzanzulQh6E3IgQV+xpc8kcrfuSO7eTfJ3ZYmQw0Oy9azVKOk1C/bJ5D5F38YPeLfx0rjWJxHsS0SqsSYuxySjj5qO5Oj7xQWy2VBtFOwzCy6ryH3YfE3uh64Y1xckgstJPydEjkkeHv07Iy4Xaao15+KCWTBx6M/db+T9xivSErqaJDdzXI6yLRE8Vgg0coex/SPJvT0SbWu0KpZtbgSpCH3NRt7I5OxHkObc6heU+/M/J5vrpBFM5GBLqCQux14COXs5CNXK5OjPGm1tSMrJSOMNYQ4mVTGV/L6zTL7+DovkbFUxbSW0Wo05l8hJWsU+cRWfSh+Mt5Lb1ck/J1TvVsdDaR/MiEni+llsdZuZp62EViu+96bpNjNPWwmtVnzvFd5m9IVVC54x/wA7gNvqFG9vXQAAAABJRU5ErkJggg==")
|
no-repeat 0 0;
|
}
|
.uploader-file-actions > span:hover {
|
background-position-x: -21px;
|
}
|
.uploader-file-actions .uploader-file-pause {
|
background-position-y: 0;
|
}
|
.uploader-file-actions .uploader-file-resume {
|
background-position-y: -17px;
|
}
|
.uploader-file-actions .uploader-file-retry {
|
background-position-y: -53px;
|
}
|
.uploader-file-actions .uploader-file-remove {
|
display: block;
|
background-position-y: -34px;
|
}
|
</style>
|