记录一个可拖拽,重传,删除的上传组件

import-data.vue
<template>
<div class="import-data">
<!-- 拖拽/点击 上传 -->
<div
v-if="hasDrag"
class="c-f-drag">
<el-upload
class="upload-demo"
drag
:accept="accept"
action=""
:multiple="multiple"
:http-request="GetUploadFile"
:on-progress = "onProgress"
:on-success = "onSuccess"
:on-change="handleChange"
:file-list="filesList"
:show-file-list="true"
:auto-upload="true">
<!-- <i class="el-icon-upload"></i> -->
<div class="el-upload__text">
<img :src="dragFileImg">
</div>
<div
slot="tip"
class="el-upload__tip">
<div class="note">点击上传文书或将文件拖拽到这里上传</div>
<div class="accept">支持扩展名: {{accept}}</div>
</div>
<!-- 文件上传进度展示 -->
<div
slot="file"
slot-scope="{file}"
class="upload-list-item">
<upload-item
:percentage="fixedProgress(file)"
fileUrl=""
:file="{name: file.name}"
:status="file.status"
@handleRefreshIconClick="handleRefreshIconClick(file)"
@handleCloseIconClick="handleCloseIconClick(file)">
</upload-item>
</div>
</el-upload>
</div>
</div>
</template>
<script>
/*
*@description: 案例筛查
*@dependencies:
*@API:
*@ 参数
* accept - 上传文件格式
* hasDrag - 是否展示拖拽
*@ 事件
* this.$emit('handleFilterClick', { filePath: this.filePath })
*/
import axios from 'axios'
import dragFileImg from '@/assets/import/dragFile.png'
import regular from '@/regular'
import UploadItem from '../upload-item/upload-item.vue'
import { startMock, getAuthToken } from '@/config'
export default {
name: 'ImportData',
components: {
UploadItem
},
mixins: [],
props: {
accept: {
type: String,
// default: '.doc,.docx,.pdf,.jpg,.zip'
default: '.zip'
},
multiple: {
type: Boolean,
default: true
},
hasDrag: {
type: Boolean,
default: true
}
},
data: function() {
return {
isMock: startMock, // 是否是模拟上传
dragFileImg,
filesPaths: '', // 已上传的文件的拼接路径
filesList: [], // 文件列表
uploadingFiles: [], // 正在上传的文件
uploadingFileParams: [], // 正在上传文件的params
mockUplaodFileTimers: {} // 正在模拟上传文件的定时器
}
},
computed: {
acceptArr() {
return this.accept.split(',') || []
}
},
watch: {
},
created() {
this.getFileList()
},
mounted() {
},
methods: {
// 获取文件类型
getFileType(name, type) {
const fileType = name.split('.')[name.split('.').length - 1].toLowerCase()
if (!type) {
return fileType
} else {
return type.indexOf(fileType) > -1
}
},
// 上传前验证
handleBefore(file) {
if (!file) {
this.$message.warning('请选择文件')
return false
}
const fileType = '.' + this.getFileType(file.name)
let isJPG = false
const isLt2M = file.size / 1024 / 1024 < 500
this.acceptArr.forEach(element => {
if (element === fileType) {
isJPG = true
}
})
if (!isJPG) {
this.$message.error(`上传文件只能是(${this.acceptArr})格式!`)
}
if (!isLt2M) {
this.$message.error('上传文件大小不能超过 500MB!')
}
return isJPG && isLt2M
},
handleChange(file) {
console.log('handleChange', file)
// 模拟上传
if (this.isMock) {
this.mockUploadFile(file)
}
},
// 判断文件上传个数
handleExceed(files, fileList) {
this.$message.warning(`只能上传 ${this.limit} 个文件!`)
},
// 文件列表改变更新父组件文件路径字段
getFilePath(url) {
this.$emit('handleUploadClick', url)
},
onProgress(event, file, fileList) {
// console.log(file)
this.percent = Number(file.percentage.toFixed(0))
},
onSuccess(res, file, fileList) {
console.log(res, file, fileList)
},
// 获取上传进度
fixedProgress(file) {
if (file.status === 'success') {
return 100
}
return Number(file.percentage.toFixed(0))
},
// 文件上传
GetUploadFile(params, isRefresh = false) {
if (this.isMock) { return }
console.log(params, 'params')
params.file.percentage = 1
let cancel = null
const CancelToken = axios.CancelToken
cancel = new CancelToken(function executor(c) {
params.file.cancel = c
// 这个参数 c 就是CancelToken构造函数里面自带的取消请求的函数,这里把该函数当参数用
})
if (!isRefresh) {
params.file.raw = params.file
this.filesList.push(params.file)
this.uploadingFiles[params.file.uid] = params.file // 记录正在上传的文件
this.uploadingFileParams[params.file.uid] = params // 记录正在上传的文件的params
}
// 上传
const file = params.file
file.percentage = 0
const formData = new FormData()
// 上传文件的名称
formData.append('zipFile', file)
const config = {
noLoading: true,
timeout: 10 * 60 * 1000, // 10分钟
auth_token: getAuthToken(),
headers: {
'Content-Type': 'multipart/form-data;charset=utf-8'
},
notMessageErr: true,
cancelToken: cancel,
onUploadProgress: progressEvent => {
const percent = progressEvent.loaded / progressEvent.total * 100 | 0
params.onProgress({ percent: percent })
this.$forceUpdate()
}
}
this.$store.dispatch('main/upload', { formData, config }).then((res) => {
if (res && res.code === 0) {
const url = res.data
delete this.uploadingFiles[file.uid] // 清除该文件在正在上传的列表记录
delete this.uploadingFileParams[file.uid]
if (this.isFiles) {
this.getDisposePath(url, file.name, file.size, file.uid)
this.getFileList()
} else {
this.filesPaths = url
}
if (this.filesPaths) {
this.getFilePath(this.filesPaths)
}
// 隐藏进度
// params.onProgress({ percent: 101 }) // 多文件
// this.percent = 0 // 单文件
file.status = 'success'
this.$forceUpdate()
this.$message({
type: 'success',
message: '上传成功'
})
}
}).catch(err => {
console.log(err)
this.setStatusFailed(file)
})
},
// 拼接文件url字符串集合
getDisposePath(url, name, size, uid) {
// 上传多个文件,文件url后面要加上name用来显示,uid用来进行删除
if (!this.filesPaths) {
this.filesPaths = url + '?fileName=' + name + '?size=' + size + '?uid=' + uid
} else {
this.filesPaths += ',' + url + '?fileName=' + name + '?size=' + size + '?uid=' + uid
}
},
getFileList() {
if (this.filesPaths) {
// let [ ...oldFileList ] = this.fileList;
const newFileList = []
let fileList = []
const hasUid = this.filesPaths.indexOf('uid') > -1
if (hasUid) {
fileList = this.filesPaths.match(regular.filePathReg[0])
} else {
fileList = this.filesPaths.split(',')
}
if (fileList && fileList.length > 0) {
fileList.forEach((element, index) => {
if (hasUid) {
newFileList[index] = {
url: element.split('?fileName=')[0],
name: element.split('?fileName=')[1].split('?size=')[0],
size: element.split('?fileName=')[1].split('?size=')[1].split('?uid=')[0],
uid: element.split('?uid=')[1]
}
} else {
newFileList[index] = {
url: element,
name: element
}
}
})
for (const uid in this.uploadingFiles) {
newFileList.push(this.uploadingFiles[uid])
}
this.filesList = newFileList
}
}
},
// 删除文件
handleRemove(file, fileList) {
if (file.status === 'uploading') { // 删除下载文件时 触发
file.cancel()
}
fileList.forEach((element, index) => {
if (element.uid === file.uid) {
fileList.splice(index, 1)
}
})
delete this.uploadingFiles[file.uid]
delete this.uploadingFileParams[file.uid]
this.fileList = fileList
this.filesPaths = ''
if (this.fileList.length > 0) {
// 删除操作之后重新拼接文件url集合
this.fileList.forEach(element => {
if (element.url) {
this.getDisposePath(element.url, element.name, element.size, element.uid)
}
})
this.getFilePath(this.filesPaths)
} else {
this.getFilePath(this.filesPaths)
}
},
// 设置状态failed
setStatusFailed(file) {
file.status = 'failed'
this.$forceUpdate()
// if (this.filesList.length > 0) {
// this.filesList.forEach(t_file => {
// if (file.uid === t_file.uid) {
// t_file.status = 'failed'
// }
// })
// }
},
// 刷新上传
handleRefreshIconClick(file) {
console.log(file)
// alert(this.isMock)
if (this.isMock) {
this.mockUploadFile(file)
} else {
file.cancel()
this.GetUploadFile(this.uploadingFileParams[file.uid], true)
}
},
// 关闭上传
handleCloseIconClick(file) {
// console.log(file, this.filesList)
if (this.isMock) {
clearInterval(this.mockUplaodFileTimers[file.uid])
this.mockUplaodFileTimers[file.uid] = null
this.filesList = this.filesList.filter(file_item => file_item.uid !== file.uid)
} else {
this.handleRemove(file, this.filesList)
}
},
// 模拟文件上传
mockUploadFile(file) {
// alert('mockUploadFile')
if (this.mockUplaodFileTimers[file.uid]) {
clearInterval(this.mockUplaodFileTimers[file.uid])
this.mockUplaodFileTimers[file.uid] = null
}
file.percentage = 0
file.status = 'uploading'
this.mockUplaodFileTimers[file.uid] = setInterval(() => {
file.percentage += 2
if (file.percentage >= 100) {
file.status = 'success'
this.$message.success('上传成功')
clearInterval(this.mockUplaodFileTimers[file.uid])
this.mockUplaodFileTimers[file.uid] = null
// 判断是否上传了两个文件
if (Object.keys(this.mockUplaodFileTimers).length >= 2) {
sessionStorage.setItem('isUpload', true)
}
}
this.$forceUpdate()
}, 100)
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.import-data {
.c-f-drag {
display: flex;
justify-content: center;
/* padding: 87px 0 115px 0; */
padding: 0 0 115px 0;
border: 1px dashed #bfc3cd;
border-radius: 4px;
/* margin-top: 40px; */
background: #fff;
.upload-demo {
width: 100%;
padding-top: 85px;
/deep/ .el-upload {
display: block;
width: 343px;
height: 243px;
margin: 0 auto;
}
/deep/ .el-upload-dragger {
width: 100%;
height: 100%;
border: none;
.el-upload__text {
img {
width: 343px;
// margin-top: 50px;
}
}
}
.el-upload__tip {
margin-top: 14px;
font-size: $fs16;
text-align: center;
.note {
color: $tColorContent;
}
.accept {
margin-top: 15px;
color: $tColorSecond;
}
}
/deep/ .el-upload-list {
display: flex;
padding: 10px 40px;
margin-top: 60px;
.el-upload-list__item {
width: 195px;
border: none;
outline: none;
margin-top: 10px;
background: #fff;
line-height: 1.2;
.el-icon-close {
position: static;
display: inline-block;
}
+.el-upload-list__item {
margin-left: 25px;
}
}
}
}
}
}
</style>
upload-item.vue
<template>
<div class="upload-item">
<div>
<slot></slot>
</div>
<!-- 图标 -->
<div class="u-i-icon el-icon-document"></div>
<!-- 文件信息 -->
<div class="u-i-info">
<div class="u-i-i-top">
<!-- 文件名 -->
<div class="u-i-name">
<abbreviated-display
ref="abbreviatedDisplay"
:text="fileName"
:lines="1">
</abbreviated-display>
</div>
<!-- 操作 -->
<div
v-if="fileStatus!=='success'"
class="u-i-handle">
<span
class="u-i-h-icon el-icon-refresh"
@click.stop="handleRefreshIconClick">
</span>
<span
class="u-i-h-icon el-icon-close"
@click.stop="handleCloseIconClick">
</span>
</div>
</div>
<div class="u-i-i-bottom">
<!-- 进度 -->
<template v-if="fileStatus!=='success'">
<el-progress
v-if="showStatusByfile===''"
:stroke-width="strokeWidth"
:percentage="percentage"
:show-text="false">
{{percentage}}
</el-progress>
<el-progress
v-else
:stroke-width="strokeWidth"
:percentage="percentage"
:status="showStatusByfile"
:show-text="false">
{{percentage}}
</el-progress>
</template>
</div>
</div>
</div>
</template>
<script>
/*
*@description: 文件上传列表项展示组件
*@dependencies: 说明引入依赖的组件
*@API:
*@ 参数
* percentage
* status
* file
* fileUrl
*@ 事件
* handleRefreshIconClick
* handleCloseIconClick
*/
import AbbreviatedDisplay from '../abbreviated-display/abbreviated-display.vue'
export default {
name: 'UploadItem',
components: {
AbbreviatedDisplay
},
mixins: [],
props: {
percentage: {
type: Number,
default: 0
},
status: {
type: String,
default: 'ready'
},
file: {
type: Object,
default: () => ({
})
},
fileUrl: {
type: String,
default: ''
},
strokeWidth: {
type: Number,
default: 2
}
},
data: function() {
return {
processStatus: ['ready', 'uploading'],
fileStatus: this.status
}
},
computed: {
fileName() {
return this.file.name || ''
},
showStatusByfile() {
return this.processStatus.includes(this.fileStatus) ? '' : this.fileStatus === 'failed' ? 'exception' : 'success'
}
},
watch: {
status: {
handler(val, old) {
this.fileStatus = val
},
deep: true
},
fileStatus: {
handler(val, old) {
console.log(val, this.processStatus.includes(this.fileStatus), 11111111111)
if (old === 'uploading' && val === 'success') {
this.$nextTick(() => {
this.$refs.abbreviatedDisplay.resetInit()
})
}
},
immediate: true,
deep: true
}
},
created() {
},
mounted() {
},
methods: {
// 刷新点击
handleRefreshIconClick() {
this.$emit('handleRefreshIconClick')
},
// 关闭点击
handleCloseIconClick() {
this.$emit('handleCloseIconClick')
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.upload-item {
display: flex;
width: 195px;
color: #616366;
font-size: $fs14;
.u-i-icon {
margin-top: 2px;
margin-right: 6px;
}
.u-i-info {
display: flex;
flex: 1;
flex-direction: column;
justify-content: flex-start;
// height: 25px;
.u-i-i-top {
position: relative;
display: flex;
justify-content: space-between;
// padding: 3px 40px 3px 0;
// padding-right: 50px;
margin-bottom: 8px;
.u-i-name {
flex: 1;
}
.u-i-handle {
// position: absolute;
// top: 1px;
// right: 0;
width: 50px;
text-align: right;
.u-i-h-icon {
font-size: 18px;
cursor: pointer;
&:hover {
opacity: .8;
}
&:active {
opacity: 1;
}
+.u-i-h-icon {
margin-left: 6px;
}
}
}
}
.u-i-i-bottom {
position: relative;
/deep/ .el-progress {
top: 0;
}
}
}
}
</style>
abbreviated-display.vue
<template>
<div
ref="abbreviatedDisplay"
v-initDom
class="abbreviated-display">
<div v-if="showTooltip">
<el-tooltip
placement="top-start"
effect="light">
<div
slot="content"
class="popper-content"
:style="popperStyle">{{text}}</div>
<span>{{abbreviatedText}}</span>
</el-tooltip>
</div>
<span v-else>{{text}}</span>
</div>
</template>
<script>
/*
*@description: 缩略展示(文字超出)
*@dependencies: 说明引入依赖的组件
*@version V0.1.0
*@API:
*@ 参数
* text
* lines
* popperStyle
*@ 事件
*
*/
import Regular from '@/regular/index.js'
import { getDomStyle, getTextWidth, debounce } from '@/utils/common.js'
export default {
name: 'AbbreviatedDisplay',
components: {
},
directives: {
initDom: {
inserted(el, binding, vnode) {
// console.log(el, 'inserted')
vnode.context.initContainer(el, binding)
},
update(el, binding, vnode) {
// console.log(el, 'update')
// vnode.context.initContainer(el, binding)
},
componentUpdated(el, binding, vnode) {
// console.log(el, 'componentUpdated')
},
unbind(el, binding, vnode) {
//
window.removeEventListener('resize', vnode.context.resetInit)
}
}
},
mixins: [],
props: {
text: {
type: String,
default: ''
},
lines: {
type: Number,
default: 1
},
popperStyle: {
type: Object,
default: () => {
return {
maxWidth: '500px',
fontSize: '14px',
lineHeight: '23px'
}
}
}
},
data: function() {
return {
showTooltip: false,
textContainer: null, // 文字区域容器
containerWidth: null, // 保存初始化时的容器宽度
abbreviatedText: '' // 超出限制 处理
}
},
computed: {
},
watch: {
text: {
handler(val, old) {
this.textContainer && this.initContainer(this.textContainer)
}
},
lines: {
handler(val, old) {
this.textContainer && this.initContainer(this.textContainer)
}
},
popperStyle: {
handler(val, old) {
this.textContainer && this.initContainer(this.textContainer)
},
deep: true
}
},
created() {
this.reg = new RegExp(Regular.Punctuation[0])
},
mounted() {
},
methods: {
// 初始化处理
initContainer(el) {
this.abbreviatedText = ''
this.showTooltip = false
if (!this.textContainer) {
this.textContainer = el
}
if (this.text && this.text.length > 0) {
const containerStyle = getDomStyle(el)
const font = containerStyle.font
const containerWidth = parseFloat(containerStyle.width)
this.containerWidth = containerStyle.width
const abbreviatedPLength = getTextWidth('...', font)
// const maxLength = this.lines * containerWidth - abbreviatedPLength
// console.log(maxLength, containerStyle.width)
// console.log(getTextWidth(this.text, font))
let initLine = 1
let initLength = initLine === this.lines ? abbreviatedPLength : 0 // 初试计算宽度
let curFont = '' // 当前循环位置
let prevFont = '' // 循环前一个位置
for (let i = 0; i < this.text.length; i++) {
curFont = this.text[i]
const fontWidth = getTextWidth(curFont, font)
initLength += fontWidth
if (initLength > containerWidth) {
if (initLine === this.lines) {
this.abbreviatedText = this.text.substr(0, i) + '...'
this.showTooltip = true
break
} else {
if (this.reg.test(curFont) || this.reg.test(prevFont)) {
i -= 2
} else {
i--
}
}
// console.log(curFont, '当前的字符', this.text)
initLine += 1
if (initLine === this.lines) {
initLength = abbreviatedPLength
} else {
initLength = 0
}
}
prevFont = curFont
}
}
// 防抖提升性能
window.addEventListener('resize', debounce(this.resetInit, 300))
this.$once('hook:beforeDestory', () => {
window.removeEventListener('resize', this.resetInit)
})
},
// 重新初始化
resetInit() {
console.log('resetInit')
const el = this.textContainer
if (!el) {
return
}
const containerStyle = getDomStyle(el)
if (this.containerWidth === containerStyle.width) {
return
}
this.initContainer(el)
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.abbreviated-display {
width: 100%;
white-space: normal;
word-break: break-all;
}
</style>
网友评论