版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wcghhk/article/details/86132801
一直找不到合适的上传插件,要么和其他插件有冲突,要么文件类型不支持......。干脆自己写一个。
先上代码
<template>
<div class="upload">
<section class="list">
<transition-group tag="ul" name="slide">
<li v-for="(item, index) of fileList" :key="item.id">
<div class="left">
<img :src="item.preview" alt="">
</div>
<div class="center">
<span>{{item.name}}</span>
<el-progress :percentage="item.progress"
v-show="item.progress !== undefined && item.progress !== 100"></el-progress>
</div>
<div class="right" @click="deleteMe(item, index)">
<i class="iconfont icon-cancel"></i>
</div>
</li>
</transition-group>
</section>
<footer class="add">
<div class="layer">
<i class="iconfont icon-Plus"></i>
添加附件
</div>
<input type="file" ref="file" name="" id=""
@change="selectFile" :accept="fileType">
</footer>
<div class="tip">
支持以下类型文件:
<p>{{fileType.join(', ')}}</p>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'upload',
props: {
file: String,
name: String
},
data () {
return {
fileType: ['.png', '.jpg', '.jpeg', '.gif', '.mp4', '.xls', '.xlsx', '.doc', '.docx', '.ppt', '.pptx', '.pdf'],
fileList: [],
others: { // 其他办公文件预览图
doc: require('@/assets/imgs/word.png'),
docx: require('@/assets/imgs/word.png'),
ppt: require('@/assets/imgs/ppt.png'),
pptx: require('@/assets/imgs/ppt.png'),
xls: require('@/assets/imgs/excel.png'),
xlsx: require('@/assets/imgs/excel.png'),
pdf: require('@/assets/imgs/pdf.png')
},
/**
* 用于生成列表唯一的ID,由于transition-group不支持index作为key(不唯一)
*/
count: 0,
maxSize: 100 * 1000 * 1000
}
},
methods: {
selectFile () {
let file = this.$refs.file.files[0]
if (!file) return
if (file.size > this.maxSize) {
alert('文件太大')
return
}
let arr = file.name.split('.')
let type = arr[arr.length - 1]
let item = {
id: this.count++,
// 生成预览图
preview: this.others[type] || URL.createObjectURL(file),
name: file.name,
progress: 0,
cancel: undefined
}
this.fileList.push(item)
this.upload(file, item)
},
upload (file, item) {
let data = new FormData()
data.append('file', file)
let config = {
onUploadProgress: e => {
item.progress = (100 * e.loaded / e.total) | 0
},
// 取消上传
cancelToken: new axios.CancelToken(function executot (c) {
item.cancel = c
})
}
axios.post(this.uploadURL, data, config).then(res => {
if (res.data.data) {
item.src = res.data.data.site_url
// 每次上传成功后向父组件更新一次list
this.$emit('uploadSuccess', this.fileList)
} else {
alert('不支持此类文件上传')
}
}).catch(err => {
console.log(err)
})
},
deleteMe (item, index) { // 删除或取消上传
this.fileList.splice(index, 1)
if (item.progress !== 100 && item.progress !== undefined) {
item.cancel()
}
this.$emit('uploadSuccess', this.fileList)
}
},
mounted () {
// 处理默认存在的文件列表
if (this.file) {
let fileArr = this.file.split('|')
let nameArr = this.name ? this.name.split('|') : []
this.fileList = []
fileArr.forEach((e, i) => {
let arr = e.split('.')
let type = arr[arr.length - 1]
this.fileList.push({
id: i,
src: e,
preview: this.others[type] || e,
name: nameArr[i] || e
})
})
this.count = this.fileList.length
}
},
destroyed () {
// 组件上传未完成但被销毁,比如直接提交,要把上传请求取消
this.fileList.forEach(e => {
if (e.progress !== undefined && e.progress !== 100) {
e.cancel && e.cancel()
}
})
},
computed: {
uploadURL () {
return this.$store.state.origin + '/duty/bill_data/upload'
}
}
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.slide-enter
transform translateY(-100%)
opacity 0
.slide-leave-active
transform translateX(100%)
opacity 0
.upload
padding .2rem .4rem
.list
ul
li
display flex
border 1px solid $baseColor
background #ffffff
padding .2rem
border-radius .1rem
margin-top .2rem
transition .4s
.left
width 1rem
height 1rem
margin-right .3rem
img
width 100%
height 100%
.center
flex 1
overflow hidden
display flex
flex-direction column
justify-content center
// align-items center
line-height 1.2
&:first-child
margin-top 0
.add
position relative
height .8rem
line-height .8rem
margin-top .2rem
input
width 100%
height 100%
.layer
pointer-events none
position absolute
background #ffffff
top 0
left 0
width 100%
height 100%
text-align center
border 1px dashed #999
// color $baseColor
font-size .32rem
.iconfont
font-size .4rem
vertical-align middle
font-weight bold
.tip
margin-top .2rem
</style>
需要注意两点:
一是vue的transition-group需要有特定的key才能正常工作,否则执行动画的总是最后一个。
二是在弱网络环境下或者上传文件过大,要根据用户操作适当取消请求,以免占用带宽。
组件基于axios,css预处理器用的stylus