为什么需要自定义实现
当然是原生的element-ui不满足
- 展示的列表,elementui里面的请求错误的,不会展示在列表里面
- element判断上传成功和失败是通过最外层的服务端返回的状态,而对于我们公司的后端,不论是成功还是失败都是返回的200,在返回值里面在封装了一层code来反应真正的成功或者失败,200表示成功,-1表示失败
实现的功能
- 会标明哪些上传成功和哪些是上传失败的
- 上传失败的可以重新上传
- 上传失败有错误提示,相同的错误提示只会提示一次
- 可以调节一排显示的个数
由于时间比较紧张,代码可能有一点乱,后期有时间在优化
其中用到的前端后端代码都有
后端代码
这里你可以随意返回,我是根据公司这边需求来做的 启动服务
node ./app.js
// app.js
const express = require('express')
const app = express()
const path = require('path')
//引入multer
const multer = require('multer')
//注册一个对象,dest里放的是上传的文件存储的位置,可以在当前目录下,建立一个static目录,上传的文件都放在这里
const upload = multer({dest: './static/'})
//使用中间件,没有挂载路径,应用的每个请求都会执行该中间件。any表示接受一切,具体参考文档。
app.use(upload.any())
let errorNum = 0
//在req.files中获取文件数据
app.post('/api/upload',function(req, res){
const { originalname } = req.files[0]
console.log(originalname, req.files)
if (path.extname(originalname) === '.pdf') {
res.send({
code: '-1',
message: '上傳格式不能是txt和pdf'
})
}
else if(path.extname(originalname) === '.txt' && errorNum < 5) {
errorNum++
res.status(500).send({
code: '500',
message: '上传失败'
})
} else {
res.send({
code: '200',
message: '成功'
})
}
})
app.listen(3000)
前端组件的封装
// CommonUpload/index.vue
<template>
<div>
<el-upload
:show-file-list="false"
:on-success="handleSuccess"
v-bind="$attrs"
v-on="$listeners"
:on-progress="handleProgress"
:on-error="handleError"
ref="upload-outer"
>
<slot>
<el-button size="small" type="primary">点击上传</el-button>
</slot>
</el-upload>
<UploadList
:file-list="fileList"
@refreshUpload="refreshUpload"
@handleDelete="handleDelete"
></UploadList>
</div>
</template>
<script>
import UploadList from '@/components/CommonUpload/UploadList';
export default {
name: 'CommonUpload',
components: {
UploadList
},
props: {
attachList: {
type: Array,
default: () => []
}
},
data() {
return {
fileList: [],
errTipsMap: {},
refreshUploadFileInfo: { // 主要是为了上传失败,上传失败过后位置会发生变化
file: '',
index: 0,
progressing: false // 是否在重新上传
}
}
},
watch: {
attachList: {
handler(newVal) {
this.fileList = newVal
},
immediate: true,
deep: true
},
fileList: {
handler(newVal) {
this.$emit('update:attachList', newVal)
},
immediate: true,
deep: true
}
},
methods: {
changeResponseStatus(fileList) {
fileList.forEach(item => {
if ((item.status !== 'success' || item.status !== 'fail') && item.response && item.response.code !== '200') {
item.status = 'fail'
}
})
return fileList
},
// 成功回调函数
handleSuccess(response, file, fileList) {
this.fileList = this.changeResponseStatus(fileList)
// 这里给出错误提示
const statusList = this.fileList.filter(item => ['success', 'fail'].includes(item.status) && item.response)
const isAllEnd = statusList.length === fileList.length
// 错误提示等全部上传完了,在给出提示,并且去除重复的提示
if (isAllEnd) {
const errFileList = this.fileList.filter(item => item.status === 'fail')
const errMessageList = []
errFileList.forEach(item => {
if (!this.errTipsMap[item.uid]) {
errMessageList.push(item.response.message)
this.errTipsMap[item.uid] = true
}
})
// 错误信息去重
const noRepeatMsgList = [...new Set(errMessageList)]
if (noRepeatMsgList.length) {
this.$message.error(noRepeatMsgList.join(','))
}
}
},
isRefreshUpload() {
if (this.refreshUploadFileInfo.progressing) {
return true
}
},
handleError(err, file, fileList) {
if (file.status !== 'fail') {
return
}
if (this.isRefreshUpload()) {
this.refreshUploadFileInfo.progressing = false
this.fileList.splice(this.refreshUploadFileInfo.index, 0, file)
} else {
this.fileList.push(file)
}
this.errTipsMap[file.uid] = true
this.$message.error(file.response?.message || '上传失败啦~~')
},
handleProgress(event, file, fileList) {
this.fileList = this.changeResponseStatus(fileList)
},
refreshUpload(file) {
this.errTipsMap[file.uid] = false
this.refreshUploadFileInfo = {
file: file,
index: this.fileList.findIndex(item => item.uid === file.uid),
progressing: true
}
this.$refs['upload-outer'].$refs['upload-inner'].upload(file.raw)
},
handleDelete(file) {
const delInd = this.fileList.findIndex(item => item.uid === file.uid)
this.fileList.splice(delInd, 1)
}
}
}
</script>
<style scoped>
</style>
// CommonUpload/UploadList
<template>
<div class="upload-list__container">
<div
v-for="file in fileList"
:key="file.uid"
class="upload-list__item"
>
<div class="upload-list__item-name">
<i class="el-icon-tickets file-prev-icon"></i>
<div class="file-name" :title="file.name" >
<span>{{ file.name }}</span>
</div>
<div class="after-icon-box">
<div class="file-after-icon">
<div v-if="file.status === 'uploading'">{{ parsePercentage(file.percentage) + '%' }}</div>
<div v-else-if="file.status === 'success'">
<i class="el-icon-success"></i>
</div>
<div v-else-if="file.status === 'fail'">
<i class="el-icon-error"></i>
</div>
</div>
<div class="hover-delete">
<i class="el-icon-refresh-right" v-if="file.status === 'fail'" @click="refreshUpload(file)"></i>
<i class="el-icon-close" @click="handleDelete(file)"></i>
</div>
</div>
</div>
<el-progress
class="progress-box"
v-if="file.status === 'uploading'"
:type="'line'"
:stroke-width="2"
:percentage="parsePercentage(file.percentage)"
:show-text="false"
>
</el-progress>
</div>
</div>
</template>
<script>
export default {
name: 'UploadList',
props: {
fileList: {
type: Array,
default: () => []
}
},
methods: {
parsePercentage(val) {
return parseInt(val, 10);
},
refreshUpload(file) {
this.$emit('refreshUpload', file)
},
handleDelete(file) {
this.$emit('handleDelete', file)
}
}
}
</script>
<style scoped lang="scss">
.upload-list__item {
height: 32px;
display: flex;
align-items: center;
position: relative;
font-size: 12px;
color: #2f2725;
overflow: hidden;
}
.upload-list__item-name {
display: flex;
align-items: center;
padding: 0 12px;
height: 100%;
width: 100%;
}
.file-after-icon, file-prev-icon, hover-delete {
flex-shrink: 0;
}
.after-icon-box {
width: 40px;
text-align: end;
}
.file-name {
flex: 1;
margin-right: 10px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.file-prev-icon {
margin-right: 10px;
}
.progress-box {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
}
.hover-delete {
display: none;
.el-icon-close {
color: #c1cfe0;
cursor: pointer;
}
.el-icon-refresh-right {
margin-right: 5px;
cursor: pointer;
}
}
.upload-list__item-name:hover {
background: #eff7ff;
.file-after-icon {
display: none;
}
.hover-delete {
display: block;
}
}
.file-after-icon {
font-size: 12px;
.el-icon-success {
color: #7cd4ab;
}
.el-icon-error {
color: #f66969;
}
}
.upload-list__container {
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 20px;
}
</style>
测试代码
<template>
<div>
<div>jobTest</div>
<div style="width: 800px;">
<CommonUpload
action="/api/upload"
multiple
ref="upload-outer"
:attach-list.sync="attachList"
>
</CommonUpload>
</div>
</div>
</template>
<script>
import CommonUpload from '@/components/CommonUpload'
export default {
name: 'JobTest',
components: { CommonUpload },
data() {
return {
// 附件列表格式
attachList: [
// {
// uid: 1000,
// name: 'vueyuanma',
// raw: 'file',
// response: null,
// status: 'success'
// },
// {
// uid: 1001,
// name: '哈哈',
// raw: 'file',
// response: null,
// status: 'success'
// }
]
}
},
methods: {}
}
</script>
<style scoped>
</style>