upload

upload

bootstrap4.x+vue2.x 文件上传

VUElopo1983 发表了文章 • 0 个评论 • 1430 次浏览 • 2018-03-16 22:43 • 来自相关话题

template<template>
<div class="upload">
<label class="mb-0" :class="hasSlot ? '' : 'custom-file'" @drop.prevent="onDrop">
<input type="file" :class="hasSlot ? 'd-none' : 'custom-file-input'" @change="onPicker" :multiple="multiple" :accept="accept" v-if="refresh">
<span v-if="hasSlot"><slot></slot></span>
<span v-else class="custom-file-control">选择文件</span>
</label>
<span class="help text-secondary" :class="!!helpblock?`d-block mt-2`:'ml-3'" v-if="help">{{help}}</span>
<cardlayer :class="{'mt-3':files.length}">
<card class="upload-item" v-for="(item,idx) in files" :key="idx" :style="`flex: 0 0 ${1/col*100}%`">
<cardbody>
<div class="upload-cancel">
<div class="icon-mix">
<icon icon="shanchu" @click.native="onCancel(idx)" title="删除"></icon>
<icon icon="reupload" @click.native="upload" v-if="item.stype=='danger'" title="重传"></icon>
</div>
</div>
<div class="upload-preview" :style="{backgroundImage:`url(${item._base64 || item.base64})`}"></div>
<img class="card-img-top" :src="square">
</cardbody>
<cardfooter :class="item.stype!=''&&`bg-${item.stype}`">
<progressbar class="upload-progress" v-bind="item" size="xs"></progressbar>
<div class="d-flex upload-info ">
<div class="text-truncate mb-0 float-left">{{item.name}}</div>
</div>
</cardfooter>
</card>
</cardlayer>
<!--<div class="file-holder">
<div v-for="(item,idx) in files" :key="idx">
<a href="javascript:;" @click="onCancel(idx)">取消</a>
<div class="holder" :style="{backgroundImage:`url(${item.base64})`}">{{ item.name }}</div>
<progressbar v-bind="item" size="xs"></progressbar>
</div>
</div>-->
<button type="button" class="btn btn-ces mt-3" @click="upload" v-if="!autoUpload&&files.length">上传</button>
</div>
</template>javascript<script>
/**
* bootstrap 4.x --> components --> uploader
*
* @param {Boolean} multiple 多选
* @param {Number} max 多选最大值
* @param {Array} maxSize 最大尺寸[width,height] ,不限制不传或者传 0
* @param {Array} allowMime 允许上传的 MIME 类型,默认不限制
* @param {String} url 上传URL
* @param {Boolean} autoUpload 是否选择文件后自动上传
* @param {Function} uploadMethod 自定义上传方法,必须返回Promise,成功resolve
* @param {Function|Object} extraParams
* @param {String} help 上传帮助文字
*
* @event remove 删除
* @event error 错误
* @event success 单个上传成功
* @event completed 全部上传成功
*
* @date 2017-10-24
*
* @requires 依赖 axios icon cards
*
* @version v1.0.0 beta
*
* @author www.bsfans.com 戏子 lopo
*
**/
import icon from './icon'
import progressbar from './progress'
import square from '@/assets/square.png'
import {
cardlayer,
card,
cardheader,
cardbody,
cardfooter
} from '@/components/comp/cards'
import axios from 'axios'

const IMAGE_MIME = ['image/png', 'image/jpg', 'image/gif', 'image/jpeg']

const fnTypeBase64 = text => {
const canvas = document.createElement('canvas')
canvas.width = 200
canvas.height = 200
const ctx = canvas.getContext('2d')
ctx.fillStyle = 'rgba(255,255,255,0.8)'
ctx.fillRect(0, 0, 200, 200)
ctx.stroke()
ctx.font = '40px monaco'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(text, 100, 100)
return canvas.toDataURL('image/jpeg')
}

export default {
name: 'uploader',
props: {
multiple: Boolean,
max: Number,
maxSize: {
type: Array,
default: () =>
},
allowMime: {
type: Array,
default: () =>
},
col: {
type: Number,
default: 6
},
help: String,
helpblock:{
type:Boolean,
default:false
},
url: String,
autoUpload: {
type: Boolean,
default: false
},
uploadMethod: Function,
extraParams: [Function, Object]
},
data() {
return {
files: ,
refresh: true,
square: square
}
},
components: {
progressbar,
icon,
cardlayer,
card,
cardheader,
cardbody,
cardfooter
},
methods: {
onPicked(files) {
if (this.max > 0 && this.files.length + files.length > this.max) {
this.$emit('error','图片太大')
return false
}
const isImage = type => IMAGE_MIME.findIndex(item => item == type) > -1
this.$emit('picked', files)
Array.from(files).forEach(file => {
const o = {
file,
name: file.name,
filesize: file.size,
type: file.name
.substring(file.name.lastIndexOf('.'))
.toLowerCase()
.replace('.', ''),
stype: '',
value: 0,
base64: '',
width: 0,
height: 0,
isimage: isImage(),
completed: false,
uploading: false
}

if (this.allowMime.length) {
if (this.allowMime.findIndex(item => item == file.type) === -1) {
return this.$emit('error')
}
}

const reader = new FileReader()
reader.onload = e => {
o.base64 = e.target.result
const idx = this.files.findIndex(item => {
return item.base64 == o.base64 && item.name == o.name
})
if (idx === -1) {
if (isImage(file.type)) {
const image = new Image()
image.onload = () => {
o.width = image.width
o.height = image.height
const [maxwidth, maxheight] = this.maxSize
if (maxwidth && maxwidth < o.width) {
this.$emit('error')
} else if (maxheight && maxheight < o.height) {
this.$emit('error')
} else {
this.files.push(o)
this.autoUpload && this.fnUpload(o)
}
}
image.src = o.base64
} else {
o._base64 = fnTypeBase64(o.type)
this.autoUpload && this.fnUpload(o)
this.files.push(o)
}
}
}
reader.onerror = () => {
this.$emit('error')
}
reader.readAsDataURL(file)
})
this.reset()
},
reset() {
this.refresh = false
this.$nextTick(() => {
this.refresh = true
})
},
onPicker(e) {
this.onPicked(e.target.files)
},
onDrop(e) {
this.onPicked(e.dataTransfer.files)
},
onCancel(idx) {
this.files.splice(idx, 1)
this.$emit('remove', idx)
},
upload() {
this.files.filter(item => !item.uploading).forEach(this.fnUpload)
},
clearFile() {
this.files =
},
fnUpload(o) {
o.uploading = true
if (this.uploadMethod) {
const p = this.uploadMethod(o)
if (p.then) {
p.then(res => {
o.stype = 'success'
o.value = 100
o.completed = true
this.$emit('success', res)
})
}
} else {
// console.log('no then')
const formData = new FormData()
formData.append('file', o.file)
const params =
typeof this.extraParams == 'function'
? this.extraParams(o)
: this.extraParams
Object.keys(params).forEach(key => formData.append(key, params[key]))
axios
.post(this.url, formData, {
onUploadProgress(evt) {
o.value = 100 * evt.loaded / evt.total
}
})
.then(
res => {
o.stype = 'success'
o.completed = true
this.$emit('success', res)
},
err => {
o.stype = 'danger'
this.$emit('error')
}
)
}
}
},
computed: {
accept() {
return this.allowMime.length ? this.allowMime.join(',') : ''
},
isCompleted() {
if (this.files.length) {
return this.files.filter(item => !item.completed).length == 0
} else {
return false
}
},
hasSlot() {
return !!this.$slots.default
}
},
watch: {
isCompleted(v) {
v && this.$emit('completed')
}
},
mounted() {
const DRAG_EVENTS = ['dragleave', 'drop', 'dragenter', 'dragover']
DRAG_EVENTS.forEach(evtName => {
document.addEventListener(evtName, e => e.preventDefault(), false)
})
}
}
</script>style(less)<style lang="less">
@import (reference) '../../assets/lib/css.less';
.upload {
input[type='file'] {
opacity: 0;
}
.custom-file {
.mgb(1rem);
}
&-progress {
.ps;
top: 0;
left: 0;
right: 0;
}
&-info {
.cp;
align-items: center;
}
/*&-progress {
.ppd;
width:0;
z-index: 2;
.bgc(@ces);
}*/
&-item {
.card-body {
overflow: hidden;
position: relative;
z-index: 1;
}
.card-footer {
.trs;
.pr;
.bgcw;
.pdy(0.3rem);
&[class*='bg'] {
.crw;
}
}
}
&-preview {
.ppd(1.25rem);
.bdr(@cr: rgba(0, 0, 0, 0.1));
background-repeat: no-repeat;
background-position: center center;
background-size: 100%;
}
&-cancel {
.mask;
.trs;
opacity: 0;
.bgc(rgba(0, 0, 0, 0.5));
z-index: 3;
.icon-mix {
.amid;
.iconfont + .iconfont {
.mgl(1.5rem);
}
}
.iconfont {
.fs(1.6rem);
.cp;
.trs;
.trf(scale(0));
.crw;
.trfo(50% 50%);
&:after {
content: '';
.bgc(rgba(0, 0, 0, 0.2));
width: 3rem;
height: 3rem;
.db;
.mask;
.trs;
z-index: -1;
left: -0.7rem;
top: -0.3rem;
.bdrrd(3rem);
}
&:hover:after {
.bgc(@ces);
}
}
.upload .card:hover & {
opacity: 1;
.iconfont {
.trf(scale(1));
}
}
}
}
</style>




? 查看全部

QQ图片20180316224810.png

template
<template>
<div class="upload">
<label class="mb-0" :class="hasSlot ? '' : 'custom-file'" @drop.prevent="onDrop">
<input type="file" :class="hasSlot ? 'd-none' : 'custom-file-input'" @change="onPicker" :multiple="multiple" :accept="accept" v-if="refresh">
<span v-if="hasSlot"><slot></slot></span>
<span v-else class="custom-file-control">选择文件</span>
</label>
<span class="help text-secondary" :class="!!helpblock?`d-block mt-2`:'ml-3'" v-if="help">{{help}}</span>
<cardlayer :class="{'mt-3':files.length}">
<card class="upload-item" v-for="(item,idx) in files" :key="idx" :style="`flex: 0 0 ${1/col*100}%`">
<cardbody>
<div class="upload-cancel">
<div class="icon-mix">
<icon icon="shanchu" @click.native="onCancel(idx)" title="删除"></icon>
<icon icon="reupload" @click.native="upload" v-if="item.stype=='danger'" title="重传"></icon>
</div>
</div>
<div class="upload-preview" :style="{backgroundImage:`url(${item._base64 || item.base64})`}"></div>
<img class="card-img-top" :src="square">
</cardbody>
<cardfooter :class="item.stype!=''&&`bg-${item.stype}`">
<progressbar class="upload-progress" v-bind="item" size="xs"></progressbar>
<div class="d-flex upload-info ">
<div class="text-truncate mb-0 float-left">{{item.name}}</div>
</div>
</cardfooter>
</card>
</cardlayer>
<!--<div class="file-holder">
<div v-for="(item,idx) in files" :key="idx">
<a href="javascript:;" @click="onCancel(idx)">取消</a>
<div class="holder" :style="{backgroundImage:`url(${item.base64})`}">{{ item.name }}</div>
<progressbar v-bind="item" size="xs"></progressbar>
</div>
</div>-->
<button type="button" class="btn btn-ces mt-3" @click="upload" v-if="!autoUpload&&files.length">上传</button>
</div>
</template>
javascript
<script>
/**
* bootstrap 4.x --> components --> uploader
*
* @param {Boolean} multiple 多选
* @param {Number} max 多选最大值
* @param {Array} maxSize 最大尺寸[width,height] ,不限制不传或者传 0
* @param {Array} allowMime 允许上传的 MIME 类型,默认不限制
* @param {String} url 上传URL
* @param {Boolean} autoUpload 是否选择文件后自动上传
* @param {Function} uploadMethod 自定义上传方法,必须返回Promise,成功resolve
* @param {Function|Object} extraParams
* @param {String} help 上传帮助文字
*
* @event remove 删除
* @event error 错误
* @event success 单个上传成功
* @event completed 全部上传成功
*
* @date 2017-10-24
*
* @requires 依赖 axios icon cards
*
* @version v1.0.0 beta
*
* @author www.bsfans.com 戏子 lopo
*
**/
import icon from './icon'
import progressbar from './progress'
import square from '@/assets/square.png'
import {
cardlayer,
card,
cardheader,
cardbody,
cardfooter
} from '@/components/comp/cards'
import axios from 'axios'

const IMAGE_MIME = ['image/png', 'image/jpg', 'image/gif', 'image/jpeg']

const fnTypeBase64 = text => {
const canvas = document.createElement('canvas')
canvas.width = 200
canvas.height = 200
const ctx = canvas.getContext('2d')
ctx.fillStyle = 'rgba(255,255,255,0.8)'
ctx.fillRect(0, 0, 200, 200)
ctx.stroke()
ctx.font = '40px monaco'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(text, 100, 100)
return canvas.toDataURL('image/jpeg')
}

export default {
name: 'uploader',
props: {
multiple: Boolean,
max: Number,
maxSize: {
type: Array,
default: () =>
},
allowMime: {
type: Array,
default: () =>
},
col: {
type: Number,
default: 6
},
help: String,
helpblock:{
type:Boolean,
default:false
},
url: String,
autoUpload: {
type: Boolean,
default: false
},
uploadMethod: Function,
extraParams: [Function, Object]
},
data() {
return {
files: ,
refresh: true,
square: square
}
},
components: {
progressbar,
icon,
cardlayer,
card,
cardheader,
cardbody,
cardfooter
},
methods: {
onPicked(files) {
if (this.max > 0 && this.files.length + files.length > this.max) {
this.$emit('error','图片太大')
return false
}
const isImage = type => IMAGE_MIME.findIndex(item => item == type) > -1
this.$emit('picked', files)
Array.from(files).forEach(file => {
const o = {
file,
name: file.name,
filesize: file.size,
type: file.name
.substring(file.name.lastIndexOf('.'))
.toLowerCase()
.replace('.', ''),
stype: '',
value: 0,
base64: '',
width: 0,
height: 0,
isimage: isImage(),
completed: false,
uploading: false
}

if (this.allowMime.length) {
if (this.allowMime.findIndex(item => item == file.type) === -1) {
return this.$emit('error')
}
}

const reader = new FileReader()
reader.onload = e => {
o.base64 = e.target.result
const idx = this.files.findIndex(item => {
return item.base64 == o.base64 && item.name == o.name
})
if (idx === -1) {
if (isImage(file.type)) {
const image = new Image()
image.onload = () => {
o.width = image.width
o.height = image.height
const [maxwidth, maxheight] = this.maxSize
if (maxwidth && maxwidth < o.width) {
this.$emit('error')
} else if (maxheight && maxheight < o.height) {
this.$emit('error')
} else {
this.files.push(o)
this.autoUpload && this.fnUpload(o)
}
}
image.src = o.base64
} else {
o._base64 = fnTypeBase64(o.type)
this.autoUpload && this.fnUpload(o)
this.files.push(o)
}
}
}
reader.onerror = () => {
this.$emit('error')
}
reader.readAsDataURL(file)
})
this.reset()
},
reset() {
this.refresh = false
this.$nextTick(() => {
this.refresh = true
})
},
onPicker(e) {
this.onPicked(e.target.files)
},
onDrop(e) {
this.onPicked(e.dataTransfer.files)
},
onCancel(idx) {
this.files.splice(idx, 1)
this.$emit('remove', idx)
},
upload() {
this.files.filter(item => !item.uploading).forEach(this.fnUpload)
},
clearFile() {
this.files =
},
fnUpload(o) {
o.uploading = true
if (this.uploadMethod) {
const p = this.uploadMethod(o)
if (p.then) {
p.then(res => {
o.stype = 'success'
o.value = 100
o.completed = true
this.$emit('success', res)
})
}
} else {
// console.log('no then')
const formData = new FormData()
formData.append('file', o.file)
const params =
typeof this.extraParams == 'function'
? this.extraParams(o)
: this.extraParams
Object.keys(params).forEach(key => formData.append(key, params[key]))
axios
.post(this.url, formData, {
onUploadProgress(evt) {
o.value = 100 * evt.loaded / evt.total
}
})
.then(
res => {
o.stype = 'success'
o.completed = true
this.$emit('success', res)
},
err => {
o.stype = 'danger'
this.$emit('error')
}
)
}
}
},
computed: {
accept() {
return this.allowMime.length ? this.allowMime.join(',') : ''
},
isCompleted() {
if (this.files.length) {
return this.files.filter(item => !item.completed).length == 0
} else {
return false
}
},
hasSlot() {
return !!this.$slots.default
}
},
watch: {
isCompleted(v) {
v && this.$emit('completed')
}
},
mounted() {
const DRAG_EVENTS = ['dragleave', 'drop', 'dragenter', 'dragover']
DRAG_EVENTS.forEach(evtName => {
document.addEventListener(evtName, e => e.preventDefault(), false)
})
}
}
</script>
style(less)
<style lang="less">
@import (reference) '../../assets/lib/css.less';
.upload {
input[type='file'] {
opacity: 0;
}
.custom-file {
.mgb(1rem);
}
&-progress {
.ps;
top: 0;
left: 0;
right: 0;
}
&-info {
.cp;
align-items: center;
}
/*&-progress {
.ppd;
width:0;
z-index: 2;
.bgc(@ces);
}*/
&-item {
.card-body {
overflow: hidden;
position: relative;
z-index: 1;
}
.card-footer {
.trs;
.pr;
.bgcw;
.pdy(0.3rem);
&[class*='bg'] {
.crw;
}
}
}
&-preview {
.ppd(1.25rem);
.bdr(@cr: rgba(0, 0, 0, 0.1));
background-repeat: no-repeat;
background-position: center center;
background-size: 100%;
}
&-cancel {
.mask;
.trs;
opacity: 0;
.bgc(rgba(0, 0, 0, 0.5));
z-index: 3;
.icon-mix {
.amid;
.iconfont + .iconfont {
.mgl(1.5rem);
}
}
.iconfont {
.fs(1.6rem);
.cp;
.trs;
.trf(scale(0));
.crw;
.trfo(50% 50%);
&:after {
content: '';
.bgc(rgba(0, 0, 0, 0.2));
width: 3rem;
height: 3rem;
.db;
.mask;
.trs;
z-index: -1;
left: -0.7rem;
top: -0.3rem;
.bdrrd(3rem);
}
&:hover:after {
.bgc(@ces);
}
}
.upload .card:hover & {
opacity: 1;
.iconfont {
.trf(scale(1));
}
}
}
}
</style>

QQ图片20180316224810.png

?

bootstrap4.x+vue2.x 文件上传

VUElopo1983 发表了文章 • 0 个评论 • 1430 次浏览 • 2018-03-16 22:43 • 来自相关话题

template<template>
<div class="upload">
<label class="mb-0" :class="hasSlot ? '' : 'custom-file'" @drop.prevent="onDrop">
<input type="file" :class="hasSlot ? 'd-none' : 'custom-file-input'" @change="onPicker" :multiple="multiple" :accept="accept" v-if="refresh">
<span v-if="hasSlot"><slot></slot></span>
<span v-else class="custom-file-control">选择文件</span>
</label>
<span class="help text-secondary" :class="!!helpblock?`d-block mt-2`:'ml-3'" v-if="help">{{help}}</span>
<cardlayer :class="{'mt-3':files.length}">
<card class="upload-item" v-for="(item,idx) in files" :key="idx" :style="`flex: 0 0 ${1/col*100}%`">
<cardbody>
<div class="upload-cancel">
<div class="icon-mix">
<icon icon="shanchu" @click.native="onCancel(idx)" title="删除"></icon>
<icon icon="reupload" @click.native="upload" v-if="item.stype=='danger'" title="重传"></icon>
</div>
</div>
<div class="upload-preview" :style="{backgroundImage:`url(${item._base64 || item.base64})`}"></div>
<img class="card-img-top" :src="square">
</cardbody>
<cardfooter :class="item.stype!=''&&`bg-${item.stype}`">
<progressbar class="upload-progress" v-bind="item" size="xs"></progressbar>
<div class="d-flex upload-info ">
<div class="text-truncate mb-0 float-left">{{item.name}}</div>
</div>
</cardfooter>
</card>
</cardlayer>
<!--<div class="file-holder">
<div v-for="(item,idx) in files" :key="idx">
<a href="javascript:;" @click="onCancel(idx)">取消</a>
<div class="holder" :style="{backgroundImage:`url(${item.base64})`}">{{ item.name }}</div>
<progressbar v-bind="item" size="xs"></progressbar>
</div>
</div>-->
<button type="button" class="btn btn-ces mt-3" @click="upload" v-if="!autoUpload&&files.length">上传</button>
</div>
</template>javascript<script>
/**
* bootstrap 4.x --> components --> uploader
*
* @param {Boolean} multiple 多选
* @param {Number} max 多选最大值
* @param {Array} maxSize 最大尺寸[width,height] ,不限制不传或者传 0
* @param {Array} allowMime 允许上传的 MIME 类型,默认不限制
* @param {String} url 上传URL
* @param {Boolean} autoUpload 是否选择文件后自动上传
* @param {Function} uploadMethod 自定义上传方法,必须返回Promise,成功resolve
* @param {Function|Object} extraParams
* @param {String} help 上传帮助文字
*
* @event remove 删除
* @event error 错误
* @event success 单个上传成功
* @event completed 全部上传成功
*
* @date 2017-10-24
*
* @requires 依赖 axios icon cards
*
* @version v1.0.0 beta
*
* @author www.bsfans.com 戏子 lopo
*
**/
import icon from './icon'
import progressbar from './progress'
import square from '@/assets/square.png'
import {
cardlayer,
card,
cardheader,
cardbody,
cardfooter
} from '@/components/comp/cards'
import axios from 'axios'

const IMAGE_MIME = ['image/png', 'image/jpg', 'image/gif', 'image/jpeg']

const fnTypeBase64 = text => {
const canvas = document.createElement('canvas')
canvas.width = 200
canvas.height = 200
const ctx = canvas.getContext('2d')
ctx.fillStyle = 'rgba(255,255,255,0.8)'
ctx.fillRect(0, 0, 200, 200)
ctx.stroke()
ctx.font = '40px monaco'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(text, 100, 100)
return canvas.toDataURL('image/jpeg')
}

export default {
name: 'uploader',
props: {
multiple: Boolean,
max: Number,
maxSize: {
type: Array,
default: () =>
},
allowMime: {
type: Array,
default: () =>
},
col: {
type: Number,
default: 6
},
help: String,
helpblock:{
type:Boolean,
default:false
},
url: String,
autoUpload: {
type: Boolean,
default: false
},
uploadMethod: Function,
extraParams: [Function, Object]
},
data() {
return {
files: ,
refresh: true,
square: square
}
},
components: {
progressbar,
icon,
cardlayer,
card,
cardheader,
cardbody,
cardfooter
},
methods: {
onPicked(files) {
if (this.max > 0 && this.files.length + files.length > this.max) {
this.$emit('error','图片太大')
return false
}
const isImage = type => IMAGE_MIME.findIndex(item => item == type) > -1
this.$emit('picked', files)
Array.from(files).forEach(file => {
const o = {
file,
name: file.name,
filesize: file.size,
type: file.name
.substring(file.name.lastIndexOf('.'))
.toLowerCase()
.replace('.', ''),
stype: '',
value: 0,
base64: '',
width: 0,
height: 0,
isimage: isImage(),
completed: false,
uploading: false
}

if (this.allowMime.length) {
if (this.allowMime.findIndex(item => item == file.type) === -1) {
return this.$emit('error')
}
}

const reader = new FileReader()
reader.onload = e => {
o.base64 = e.target.result
const idx = this.files.findIndex(item => {
return item.base64 == o.base64 && item.name == o.name
})
if (idx === -1) {
if (isImage(file.type)) {
const image = new Image()
image.onload = () => {
o.width = image.width
o.height = image.height
const [maxwidth, maxheight] = this.maxSize
if (maxwidth && maxwidth < o.width) {
this.$emit('error')
} else if (maxheight && maxheight < o.height) {
this.$emit('error')
} else {
this.files.push(o)
this.autoUpload && this.fnUpload(o)
}
}
image.src = o.base64
} else {
o._base64 = fnTypeBase64(o.type)
this.autoUpload && this.fnUpload(o)
this.files.push(o)
}
}
}
reader.onerror = () => {
this.$emit('error')
}
reader.readAsDataURL(file)
})
this.reset()
},
reset() {
this.refresh = false
this.$nextTick(() => {
this.refresh = true
})
},
onPicker(e) {
this.onPicked(e.target.files)
},
onDrop(e) {
this.onPicked(e.dataTransfer.files)
},
onCancel(idx) {
this.files.splice(idx, 1)
this.$emit('remove', idx)
},
upload() {
this.files.filter(item => !item.uploading).forEach(this.fnUpload)
},
clearFile() {
this.files =
},
fnUpload(o) {
o.uploading = true
if (this.uploadMethod) {
const p = this.uploadMethod(o)
if (p.then) {
p.then(res => {
o.stype = 'success'
o.value = 100
o.completed = true
this.$emit('success', res)
})
}
} else {
// console.log('no then')
const formData = new FormData()
formData.append('file', o.file)
const params =
typeof this.extraParams == 'function'
? this.extraParams(o)
: this.extraParams
Object.keys(params).forEach(key => formData.append(key, params[key]))
axios
.post(this.url, formData, {
onUploadProgress(evt) {
o.value = 100 * evt.loaded / evt.total
}
})
.then(
res => {
o.stype = 'success'
o.completed = true
this.$emit('success', res)
},
err => {
o.stype = 'danger'
this.$emit('error')
}
)
}
}
},
computed: {
accept() {
return this.allowMime.length ? this.allowMime.join(',') : ''
},
isCompleted() {
if (this.files.length) {
return this.files.filter(item => !item.completed).length == 0
} else {
return false
}
},
hasSlot() {
return !!this.$slots.default
}
},
watch: {
isCompleted(v) {
v && this.$emit('completed')
}
},
mounted() {
const DRAG_EVENTS = ['dragleave', 'drop', 'dragenter', 'dragover']
DRAG_EVENTS.forEach(evtName => {
document.addEventListener(evtName, e => e.preventDefault(), false)
})
}
}
</script>style(less)<style lang="less">
@import (reference) '../../assets/lib/css.less';
.upload {
input[type='file'] {
opacity: 0;
}
.custom-file {
.mgb(1rem);
}
&-progress {
.ps;
top: 0;
left: 0;
right: 0;
}
&-info {
.cp;
align-items: center;
}
/*&-progress {
.ppd;
width:0;
z-index: 2;
.bgc(@ces);
}*/
&-item {
.card-body {
overflow: hidden;
position: relative;
z-index: 1;
}
.card-footer {
.trs;
.pr;
.bgcw;
.pdy(0.3rem);
&[class*='bg'] {
.crw;
}
}
}
&-preview {
.ppd(1.25rem);
.bdr(@cr: rgba(0, 0, 0, 0.1));
background-repeat: no-repeat;
background-position: center center;
background-size: 100%;
}
&-cancel {
.mask;
.trs;
opacity: 0;
.bgc(rgba(0, 0, 0, 0.5));
z-index: 3;
.icon-mix {
.amid;
.iconfont + .iconfont {
.mgl(1.5rem);
}
}
.iconfont {
.fs(1.6rem);
.cp;
.trs;
.trf(scale(0));
.crw;
.trfo(50% 50%);
&:after {
content: '';
.bgc(rgba(0, 0, 0, 0.2));
width: 3rem;
height: 3rem;
.db;
.mask;
.trs;
z-index: -1;
left: -0.7rem;
top: -0.3rem;
.bdrrd(3rem);
}
&:hover:after {
.bgc(@ces);
}
}
.upload .card:hover & {
opacity: 1;
.iconfont {
.trf(scale(1));
}
}
}
}
</style>




? 查看全部

QQ图片20180316224810.png

template
<template>
<div class="upload">
<label class="mb-0" :class="hasSlot ? '' : 'custom-file'" @drop.prevent="onDrop">
<input type="file" :class="hasSlot ? 'd-none' : 'custom-file-input'" @change="onPicker" :multiple="multiple" :accept="accept" v-if="refresh">
<span v-if="hasSlot"><slot></slot></span>
<span v-else class="custom-file-control">选择文件</span>
</label>
<span class="help text-secondary" :class="!!helpblock?`d-block mt-2`:'ml-3'" v-if="help">{{help}}</span>
<cardlayer :class="{'mt-3':files.length}">
<card class="upload-item" v-for="(item,idx) in files" :key="idx" :style="`flex: 0 0 ${1/col*100}%`">
<cardbody>
<div class="upload-cancel">
<div class="icon-mix">
<icon icon="shanchu" @click.native="onCancel(idx)" title="删除"></icon>
<icon icon="reupload" @click.native="upload" v-if="item.stype=='danger'" title="重传"></icon>
</div>
</div>
<div class="upload-preview" :style="{backgroundImage:`url(${item._base64 || item.base64})`}"></div>
<img class="card-img-top" :src="square">
</cardbody>
<cardfooter :class="item.stype!=''&&`bg-${item.stype}`">
<progressbar class="upload-progress" v-bind="item" size="xs"></progressbar>
<div class="d-flex upload-info ">
<div class="text-truncate mb-0 float-left">{{item.name}}</div>
</div>
</cardfooter>
</card>
</cardlayer>
<!--<div class="file-holder">
<div v-for="(item,idx) in files" :key="idx">
<a href="javascript:;" @click="onCancel(idx)">取消</a>
<div class="holder" :style="{backgroundImage:`url(${item.base64})`}">{{ item.name }}</div>
<progressbar v-bind="item" size="xs"></progressbar>
</div>
</div>-->
<button type="button" class="btn btn-ces mt-3" @click="upload" v-if="!autoUpload&&files.length">上传</button>
</div>
</template>
javascript
<script>
/**
* bootstrap 4.x --> components --> uploader
*
* @param {Boolean} multiple 多选
* @param {Number} max 多选最大值
* @param {Array} maxSize 最大尺寸[width,height] ,不限制不传或者传 0
* @param {Array} allowMime 允许上传的 MIME 类型,默认不限制
* @param {String} url 上传URL
* @param {Boolean} autoUpload 是否选择文件后自动上传
* @param {Function} uploadMethod 自定义上传方法,必须返回Promise,成功resolve
* @param {Function|Object} extraParams
* @param {String} help 上传帮助文字
*
* @event remove 删除
* @event error 错误
* @event success 单个上传成功
* @event completed 全部上传成功
*
* @date 2017-10-24
*
* @requires 依赖 axios icon cards
*
* @version v1.0.0 beta
*
* @author www.bsfans.com 戏子 lopo
*
**/
import icon from './icon'
import progressbar from './progress'
import square from '@/assets/square.png'
import {
cardlayer,
card,
cardheader,
cardbody,
cardfooter
} from '@/components/comp/cards'
import axios from 'axios'

const IMAGE_MIME = ['image/png', 'image/jpg', 'image/gif', 'image/jpeg']

const fnTypeBase64 = text => {
const canvas = document.createElement('canvas')
canvas.width = 200
canvas.height = 200
const ctx = canvas.getContext('2d')
ctx.fillStyle = 'rgba(255,255,255,0.8)'
ctx.fillRect(0, 0, 200, 200)
ctx.stroke()
ctx.font = '40px monaco'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(text, 100, 100)
return canvas.toDataURL('image/jpeg')
}

export default {
name: 'uploader',
props: {
multiple: Boolean,
max: Number,
maxSize: {
type: Array,
default: () =>
},
allowMime: {
type: Array,
default: () =>
},
col: {
type: Number,
default: 6
},
help: String,
helpblock:{
type:Boolean,
default:false
},
url: String,
autoUpload: {
type: Boolean,
default: false
},
uploadMethod: Function,
extraParams: [Function, Object]
},
data() {
return {
files: ,
refresh: true,
square: square
}
},
components: {
progressbar,
icon,
cardlayer,
card,
cardheader,
cardbody,
cardfooter
},
methods: {
onPicked(files) {
if (this.max > 0 && this.files.length + files.length > this.max) {
this.$emit('error','图片太大')
return false
}
const isImage = type => IMAGE_MIME.findIndex(item => item == type) > -1
this.$emit('picked', files)
Array.from(files).forEach(file => {
const o = {
file,
name: file.name,
filesize: file.size,
type: file.name
.substring(file.name.lastIndexOf('.'))
.toLowerCase()
.replace('.', ''),
stype: '',
value: 0,
base64: '',
width: 0,
height: 0,
isimage: isImage(),
completed: false,
uploading: false
}

if (this.allowMime.length) {
if (this.allowMime.findIndex(item => item == file.type) === -1) {
return this.$emit('error')
}
}

const reader = new FileReader()
reader.onload = e => {
o.base64 = e.target.result
const idx = this.files.findIndex(item => {
return item.base64 == o.base64 && item.name == o.name
})
if (idx === -1) {
if (isImage(file.type)) {
const image = new Image()
image.onload = () => {
o.width = image.width
o.height = image.height
const [maxwidth, maxheight] = this.maxSize
if (maxwidth && maxwidth < o.width) {
this.$emit('error')
} else if (maxheight && maxheight < o.height) {
this.$emit('error')
} else {
this.files.push(o)
this.autoUpload && this.fnUpload(o)
}
}
image.src = o.base64
} else {
o._base64 = fnTypeBase64(o.type)
this.autoUpload && this.fnUpload(o)
this.files.push(o)
}
}
}
reader.onerror = () => {
this.$emit('error')
}
reader.readAsDataURL(file)
})
this.reset()
},
reset() {
this.refresh = false
this.$nextTick(() => {
this.refresh = true
})
},
onPicker(e) {
this.onPicked(e.target.files)
},
onDrop(e) {
this.onPicked(e.dataTransfer.files)
},
onCancel(idx) {
this.files.splice(idx, 1)
this.$emit('remove', idx)
},
upload() {
this.files.filter(item => !item.uploading).forEach(this.fnUpload)
},
clearFile() {
this.files =
},
fnUpload(o) {
o.uploading = true
if (this.uploadMethod) {
const p = this.uploadMethod(o)
if (p.then) {
p.then(res => {
o.stype = 'success'
o.value = 100
o.completed = true
this.$emit('success', res)
})
}
} else {
// console.log('no then')
const formData = new FormData()
formData.append('file', o.file)
const params =
typeof this.extraParams == 'function'
? this.extraParams(o)
: this.extraParams
Object.keys(params).forEach(key => formData.append(key, params[key]))
axios
.post(this.url, formData, {
onUploadProgress(evt) {
o.value = 100 * evt.loaded / evt.total
}
})
.then(
res => {
o.stype = 'success'
o.completed = true
this.$emit('success', res)
},
err => {
o.stype = 'danger'
this.$emit('error')
}
)
}
}
},
computed: {
accept() {
return this.allowMime.length ? this.allowMime.join(',') : ''
},
isCompleted() {
if (this.files.length) {
return this.files.filter(item => !item.completed).length == 0
} else {
return false
}
},
hasSlot() {
return !!this.$slots.default
}
},
watch: {
isCompleted(v) {
v && this.$emit('completed')
}
},
mounted() {
const DRAG_EVENTS = ['dragleave', 'drop', 'dragenter', 'dragover']
DRAG_EVENTS.forEach(evtName => {
document.addEventListener(evtName, e => e.preventDefault(), false)
})
}
}
</script>
style(less)
<style lang="less">
@import (reference) '../../assets/lib/css.less';
.upload {
input[type='file'] {
opacity: 0;
}
.custom-file {
.mgb(1rem);
}
&-progress {
.ps;
top: 0;
left: 0;
right: 0;
}
&-info {
.cp;
align-items: center;
}
/*&-progress {
.ppd;
width:0;
z-index: 2;
.bgc(@ces);
}*/
&-item {
.card-body {
overflow: hidden;
position: relative;
z-index: 1;
}
.card-footer {
.trs;
.pr;
.bgcw;
.pdy(0.3rem);
&[class*='bg'] {
.crw;
}
}
}
&-preview {
.ppd(1.25rem);
.bdr(@cr: rgba(0, 0, 0, 0.1));
background-repeat: no-repeat;
background-position: center center;
background-size: 100%;
}
&-cancel {
.mask;
.trs;
opacity: 0;
.bgc(rgba(0, 0, 0, 0.5));
z-index: 3;
.icon-mix {
.amid;
.iconfont + .iconfont {
.mgl(1.5rem);
}
}
.iconfont {
.fs(1.6rem);
.cp;
.trs;
.trf(scale(0));
.crw;
.trfo(50% 50%);
&:after {
content: '';
.bgc(rgba(0, 0, 0, 0.2));
width: 3rem;
height: 3rem;
.db;
.mask;
.trs;
z-index: -1;
left: -0.7rem;
top: -0.3rem;
.bdrrd(3rem);
}
&:hover:after {
.bgc(@ces);
}
}
.upload .card:hover & {
opacity: 1;
.iconfont {
.trf(scale(1));
}
}
}
}
</style>

QQ图片20180316224810.png

?