作为后端开发兼顾前端页面的实现,说实话,遇到的问题绝不会少,但方法总比问题多,坚持努力总会有成果的。
最近项目使用vue的el-upload上传文件,因为后期对文件的加载速度及并发数有较高要求,所以文件需上传至阿里云,在实现过程主要有两个功能点需自己实现,其他均可参考文档
1、实现进度条展示
API文档中进度条的实现是通过上传接口返回的,但我们实际的接口基本很难满足这个数据返回,所以我们必须通过监控请求进度从而实现进度条的展示,主要使用onUploadProgress,具体实现如下(省略全部代码,只只展示相关实现,全部实现在最后展示)
<template>
<div>
<!-- 进度条 -->
<el-progress :percentage="uploadPercentage"></el-progress>
</div>
</template>
<script>
import axios from 'axios'
// 创建实例
const onUploadProgress = axios.onUploadProgress
export default {
name: 'FileUpload',
data(){
return{
uploadPercentage: 0
}
},
methods: {
// 文件上传
fileUpload(url, data){
axios.post(url, data, {
onUploadProgress: progressEvent => {
if (event.lengthComputable) {
var percent = Math.floor(event.loaded / event.total * 100);
if (percent >= 100) {
// 不给100,避免延迟误导用户用户
percent = 97;
}
this.uploadPercentage = percent;
}
}
}).then(res => {
this.uploadPercentage = 100;
}).catch(error => {
})
}
}
}
2、中断上传,如取消文件列表数据或关闭上传页面时,应该撤回上传,不然将造成大量垃圾数据,消耗资源,具体实现与进度条类似,都是利用axios的功能,主要使用CancelToken函数,具体如下:
<script>
import axios from 'axios'
const CancelToken = axios.CancelToken
let uploadCancel
export default {
name: 'FileUpload',
data(){
return{
uploadPercentage: 0
}
},
methods: {
// 文件上传
fileUpload(url, data){
axios.post(url, data, {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
uploadCancel = c;
})
}).then(res => {
this.uploadPercentage = 100;
}).catch(error => {
})
}
}
}
注:两者实现一定要先创建实例
3、组件封装全实现
<template>
<div>
<el-upload class="upload-demo" :id="id" action="" :accept="ext" :limit="countLimit"
:on-remove="handleRemove" :file-list="fileList" :auto-upload="false" :on-change="onChange"
:on-exceed="overCountLimit">
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload" :disabled="uploadFlag">上传到服务器</el-button>
<div slot="tip" class="el-upload__tip">{{tip}}</div>
</el-upload>
<!-- 进度条 -->
<el-progress v-if="progressFlag" :percentage="uploadPercentage"></el-progress>
</div>
</template>
<script>
import {uploadAliyun } from '@/api/api'
import axios from 'axios'
const CancelToken = axios.CancelToken
// 创建实例
const onUploadProgress = axios.onUploadProgress
let uploadCancel
export default {
name: 'FileUpload',
props: {
id: {
type: String,
default: function(){
return "filePicker";
}
},
//上传提示
tip: {
type: String,
default: function(){
return "";
}
},
// 文件类型限制
ext: {
type: String,
default: function(){
return "jpg,jpeg,png,pdf,mp4,avi.mp3";
}
},
//上传个数限制
countLimit: {
type: Number,
default: function(){
return 5;
}
},
// 文件大小限制
sizeLimit: {
type: Number,
default: function(){
return 209715200;
}
},
},
data(){
return{
fileList: [],
tempIndex: 1,
loadProgress: 0, // 动态显示进度条
progressFlag: false, // 关闭进度条
uploadFlag: true,
uploadPercentage: 0,
chooseFileFlag: false
}
},
methods: {
submitUpload() {
if(this.fileList.length == 0){
this.$message.error("请选择文件")
return
}
//上传,只能一个一个上传
this.progressFlag = true
this.uploadFlag = true
this.chooseFileFlag = true
for(let i=0; i<this.fileList.length; i++){
let pro = new Promise((resolve, rej) => {
//获取签名
uploadAliyun({}).then( (res) => {
resolve(res.data)
})
})
pro.then( success => {
this.fileUpload(success, this.fileList[i], i)
})
}
},
// 上传
fileUpload(data,file, i) {
let str_start = "website_"
let filename = file.name
filename = filename.substring(filename.lastIndexOf("."))
filename = str_start +new Date().getFullYear()+new Date().getMonth() + '_' + new Date().getTime() + filename
let fileUrl = data.dir+"/"+filename
let allUrl = data.host + data.dir + "/"+filename
var ossData = new FormData()
// 添加配置参数
ossData.append('OSSAccessKeyId', data.accessid)
ossData.append('policy', data.policy)
ossData.append('signature', data.signature)
ossData.append( "key",fileUrl)
ossData.append('success_action_status', 200) // 指定返回的状态码
ossData.append('file', file.raw, file.name)
axios.post(data.host, ossData, {
headers: {
"Content-Type": "multipart/form-data",
"Access-Control-Allow-Origin": "*"
},
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
uploadCancel = c;
}),
onUploadProgress: progressEvent => {
this.updateProgress(progressEvent)
}
}).then(res => {
if (res.status == 200) { //上传成功 上传的路径就是要使用的路径
if (this.fileList){
this.fileList[i].url = allUrl;
this.fileList[i].status = 'success'
this.fileList[i].percentage = 100
this.progressFlag = false
this.uploadPercentage = 0
this.chooseFileFlag = false
}
}
}).catch(error => {
var tempList = this.fileList.filter(item => item.status != 'success')
if(tempList.length > 0){
this.$message.error("上传失败,请重新上传!")
this.uploadFlag = false
}else{
this.uploadFlag = true
}
this.progressFlag = false
this.uploadPercentage = 0
this.chooseFileFlag = false
})
},
// 删除文件
handleRemove(file, fileList) {
this.uploadFlag = true
for(let i=0; i<this.fileList.length; i++){
if(file.uid === this.fileList[i].uid){
this.fileList.splice(i, 1)
}
if(file.status != 'success'){
this.uploadFlag = true
this.progressFlag = false
uploadCancel()
}
}
},
// 获取上传成功的list
getFileList(){
var returnList = []
for(let i=0; i<this.fileList.length; i++){
if(this.fileList[i].status != 'success'){
this.$message({
message: '有文件未上传,请先上传服务器',
type: 'error'
})
return
}
returnList.push(this.fileList[i])
}
return returnList
},
//文件超出个数限制时的钩子
overCountLimit(files, fileList){
if(this.countLimit == 1){
if(this.chooseFileFlag){
this.$message({
message: '文件上传中,请稍后选择文件',
type: 'error'
})
return
}
for(let i=0; i<files.length; i++){
let file = files[i]
if(this.ext.indexOf(file.name.substring(file.name.lastIndexOf('.')+1)) < 0){
this.$message({
message: '文件格式错误,请选择文件',
type: 'error'
})
return
} else if(file.size > this.sizeLimit){
this.$message({
message: '大小不符',
type: 'error'
})
return
}
this.fileList = []
this.fileObject = {}
//如果是限制文件个数为1的话,直接替换原来的文件此时往需要上传的文件列表中添加文件
let tempFile = {
uid: Date.now() + this.tempIndex++,
name: files[0].name,
type: files[0].type,
fileSize: files[0].size,
status: "ready",
percentage: 0,
raw: files[0]
}
this.fileObject = tempFile
this.fileList.push(this.fileObject)
this.uploadFlag = false
}
}else{
this.$message({
message: '已经达到上传文件限制数量',
type: 'error'
});
}
},
//文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
onChange(file, fileList){
if(this.chooseFileFlag){
for(let i=0; i<fileList.length; i++){
if(file.uid === fileList[i].uid){
fileList.splice(i, 1)
}
}
this.$message({
message: '文件上传中,请稍后选择文件',
type: 'error'
})
return
}
if(this.ext.indexOf(file.name.substring(file.name.lastIndexOf('.')+1)) < 0){
for(let i=0; i<fileList.length; i++){
if(file.uid === fileList[i].uid){
fileList.splice(i, 1)
}
}
this.$message({
message: '文件格式错误,请选择文件',
type: 'error'
})
return
}else if(file.size > this.sizeLimit){
for(let i=0; i<fileList.length; i++){
if(file.uid === fileList[i].uid){
fileList.splice(i, 1)
}
}
this.$message({
message: '文件超过指定大小',
type: 'error'
});
return
}else if(this.fileList.length > 0 && this.fileList[this.fileList.length -1].status != 'success'){
for(let i=0; i<fileList.length; i++){
if(file.uid === fileList[i].uid){
fileList.splice(i, 1)
}
}
this.$message({
message: '有文件未上传,请先上传列表中的文件',
type: 'error'
});
return
}
this.fileList.push(file)
this.uploadFlag = false
},
// 取消上传任务
cancelUpload(){
let tempFileList = this.fileList.filter(item => item.status != 'success')
if(tempFileList.length > 0 && this.uploadFlag){
uploadCancel()
}
},
// 上传进度
updateProgress(event) {
// 设置进度显示
if (event.lengthComputable) {
var percent = Math.floor(event.loaded / event.total * 100);
if (percent >= 100) {
percent = 97;
}
this.uploadPercentage = percent;
}
}
},
mounted(){
}
}
</script>
该组件是基于vue-cli3实现的,直传阿里云先通过请求获取了相关签名认证信息,后端获取签名不明白可查看相关文档。