效果
1、服务文件(tmp为保存上传文件文件夹)
2、点击上传
3、图片列表
4、拖拽
5、手动上传
5、上传失败
6、服务
问题
1、如何打开文件列表
2、如何取出文件
3、对取出的文件校验?
4、如何发送请求(多文件上传?)
5、如何完成上传列表展示
6、拖拽上传?
7、可扩展?
思路
在实现的过程中大致思考了一下流程
1 点击或取文件(input:file)
2 获取文件进行校验(大小和类型)
3 创建初始化列表(对上传文件进行包装)
4 根据是否自动上传决定是否直接添加上传列表,还是手动上传至上传列表
5 监视初始化列表,取出上传列表数据,使用formdata转换为二进制数据通过ajax发送至后台
6 请求过程中监视请求结果动态展示列表
7 多文件使用原生的multipart可实现
8 拖拽可使用drop,dragover,dragenter,dragleave,dragover的事件
。。。。
实现
1、利用input:file获取文件
<input ref="inputFile" type="file" name="" id="" @change="selectfilesChange" :multiple="multiple">
// 点击触发弹窗的事件
pushUploadFrame() {
// 清空inputFile
this.$refs.inputFile.value = null
this.$refs.inputFile.click();
},
2、取出文件+文件校验
// 文件选中变化
selectfilesChange(e, files) {
if (e) {
// 转换为真实数组以便使用数组的方法
this.selectFileList = Array.from(e.target.files);
} else {
this.selectFileList = Array.from(files);
console.log(files);
}
// 判断文件是否已上传
// 前去校验
let { isResult, content } = this.handlerFileAccept(this.selectFileList);
if (isResult) {
// 文件校验成功事件
this.$emit('acceptAfter', this.selectFileList)
// 添加到初始化上传列表
this.addUpList(this.selectFileList)
} else {
// 触发文件上传失败事件
this.$emit('uploadError', content)
}
},
// 处理文件校验相关
handlerFileAccept(accEptList) {
// console.log(accEptList);
// 没有文件时不做处理
if (!accEptList.length) return;
let { size, accept } = this;
// 类型校验分割为数组
let accepts = accept.split(',')
// 校验处理
let sizeResult = accEptList.every(child => child.size <= size);
// 类型校验
let typeResult = accEptList.every(child => {
if (/\/\*$/.test(accepts)) {
return true
} else {
return accepts.includes(child.type)
}
})
if (sizeResult && typeResult) {
return { isResult: true, content: '文件校验成功' }
} else if (!typeResult) {
return { isResult: false, content: `文件类型不符合要求, 需要${accept}类型文件` }
} else if (!sizeResult) {
return { isResult: false, content: `文件大小不得超过${size / 1024}kb` }
}
},
3、对初始化列表的数据进行包装
// 添加到上传列表
addUpList(files) {
// 生成符合上传要求的文件
let uploadList = files.map((child, index) => {
// console.log(child);
// 生成传输请求格式
return {
header: this.headers, // 请求头部
withCredentials: this.withCredentials, // 是否允许cooike
uid: Date.now() + index,
// status: 0, // 上传状态 // 0:就绪, 1: 上传中, -1 :上传失败,2:上传成功
file: child, // 文件
size: child.size, // 文件大小
name: child.name, // 文件名称
type: child.type, // 文件类型
onProgress: (e, uid) => {
// 监视上传进度
// console.log('上传中', e);
this.$emit('uploadProgress', e, child)
this.$children.filter(item => item.$el.className == 'fileList')[0].uploadPropress(e, uid)
},
onError: (err, uid) => {
// 上传失败触发的事件
// console.log('上传失败', err, child);
this.$emit('uploadError', err)
this.$children.filter(item => item.$el.className == 'fileList')[0].uploadError(uid)
},
onSuccess: (res, uid) => {
// 上传成功后触发的事件
// console.log('上传成功', res, child);
this.$emit('uploadSuccess', res, child)
this.$children.filter(item => item.$el.className == 'fileList')[0].uploadSuccess(uid)
}
}
})
if (this.autoUpload) {
// 添加到生成的上传文件列表中
uploadList.forEach(item => {
this.initFileList.push(item)
});
} else {
uploadList.forEach(item => {
this.uploadFileList.unshift(item)
})
}
// 清空添加的文件列表
this.clearFileList()
},
4 监视初始化列表,一旦有数据将其添加入上传列表
initFileList: {
handler(newV) {
// console.log('待上传', newV);
if (newV.length && this.autoUpload) {
// console.log('触发上传');
// 取出第一个文件
let startFile = this.initFileList.shift()
// 触发上传方法
this.upload(startFile)
} else {
console.log('初始化列表没有待上传的文件了');
}
}, immediate: true
}
5、创建请求并发送
// 上传方法
upload(file) {
if (this.autoUpload) {
// 添加到上传列表
this.uploadFileList.unshift(file)
}
//创建 xhr
const xhr = new XMLHttpRequest();
// 配置请求方法和路径
xhr.open('POST', this.action)
// 上传成功
xhr.addEventListener('load', () => {
if (xhr.status < 200 || xhr.status >= 300) {
return file.onError('未知错误', file.uid);
}
// 触发上传成功
file.onSuccess(xhr.response, file.uid);
})
// 上传失败
xhr.addEventListener('error', (e) => {
file.onError(e, file.uid)
})
// 监视上传进度
if (xhr.upload) {
xhr.upload.onprogress = function progress(e) {
if (e.total > 0) {
e.percent = e.loaded / e.total * 100;
}
file.onProgress(e, file.uid);
};
}
// 转换为二进制文件
let fd = new FormData()
fd.append(file.name, file.file)
// 携带的额外参数
if (this.data) {
for (const key in this.data) {
if (Object.hasOwnProperty.call(this.data, key)) {
fd.append(key, this.data[key])
}
}
}
// 上传的文件
xhr.send(fd)
},
6、动态展示列表
<template>
<div class='fileList' :style="{ 'max-width': maxWidth + 'px' }">
<!-- 文件列表 -->
<template v-if="!preView">
<div class="fileDetail" v-for="item in uploadList" :key="item.uid" :data-uid="item.uid">
<!-- 左侧文件图标 -->
<span class="leftIcon">
<tyIcon type="file"></tyIcon>
</span>
<!-- 文件名称 -->
<p class="detail" :title="item.name" v-text="item.name"></p>
<!-- 右侧成功/删除图标 -->
<span class="rightIcon">
<tyIcon type="close" @click.native="handlerDeteleFile(item.uid)"></tyIcon>
<tyIcon type="success" v-if="item.isSuccess" style="color:#94d82d;"></tyIcon>
<tyIcon type="warring" v-if="item.isError" style="color:#ffa94d;"></tyIcon>
</span>
</div>
</template>
<template v-else>
<div class="preView" v-for="item in uploadList" :key="item.uid" :data-uid="item.uid">
<!-- 左侧文件图标 -->
<div class="view">
<img class="img" :src='item.base64' />
</div>
<!-- 文件名称 -->
<p class="detail" :title="item.name" v-text="item.name"></p>
<!-- 右侧成功/删除图标 -->
<span class="rightIcon">
<tyIcon type="close" @click.native="handlerDeteleFile(item.uid)"></tyIcon>
<tyIcon type="success" v-if="item.isSuccess" style="color:#94d82d;"></tyIcon>
<tyIcon type="warring" v-if="item.isError" style="color:#ffa94d;"></tyIcon>
</span>
</div>
</template>
</div>
</template>
<script>
import tyIcon from '../../icon/src/main.vue'
export default {
name: 'fileList',
props: {
files: {
type: Array,
default: () => []
},
// 最大宽度
maxWidth: {
type: [Number]
},
// 删除文件事件
deleteFile: {
type: Function
},
preView: {
type: Boolean,
default: false
}
},
data() {
return {
uploadList: []
}
},
methods: {
// 点击删除图标触发
handlerDeteleFile(uid) {
// 触发对应的删除方法
this.$emit('deleteFile', uid)
},
// 触发更改进度
uploadPropress(e, uid) {
console.log(e, uid);
},
// 上传成功
uploadSuccess(uid) {
this.uploadList = this.uploadList.map(child => {
if (child.uid == uid) {
child.isSuccess = true
}
return child
})
},
// 上传失败
uploadError(uid) {
this.uploadList = this.uploadList.map(child => {
if (child.uid == uid) {
child.isError = true
}
return child
})
// 删除上传的文件
},
},
watch: {
files: {
handler(newV) {
if (this.preView) {
this.uploadList = newV.map(child => {
if (child.file) {
let reader = new FileReader();
reader.readAsDataURL(child.file); //将文件读取为 DataURL,也就是base64编码
reader.onload = function (ev) { //文件读取成功完成时触发
var dataURL = ev.target.result; //获得文件读取成功后的DataURL,也就是base64编码
child.base64 = dataURL
}
}
return child
})
}
this.uploadList = newV.map(child => {
if (!child.isSuccess) {
child.isSuccess = false;
}
if (!child.isError) {
child.isError = false;
}
return child
})
}
}
},
components: {
tyIcon
}
}
</script>
<style lang='less' scoped>
@import '../../../css/upload.less';
</style>
7、拖拽
// 设置拖拽
setDarg() {
let uploadRef = this.$refs.uploadRef
// 在元素内结束拖拽触发
uploadRef.addEventListener('drop', (e) => {
e.stopPropagation()
e.preventDefault()
// console.log('drop', e.dataTransfer.files);
// 触发文件上传
this.selectfilesChange(undefined, e.dataTransfer.files)
// 触发事件
this.$emit('handlerDrop', e.dataTransfer.files)
}, false)
// 拖拽离开元素时触发
uploadRef.addEventListener('dragleave', (e) => {
e.stopPropagation()
e.preventDefault()
this.$emit('dropLeave')
})
// 拖拽进入元素时触发
uploadRef.addEventListener('dragenter', (e) => {
e.stopPropagation()
e.preventDefault()
this.$emit('dropEnter')
})
// 拖拽在元素内时持续触发
uploadRef.addEventListener('dragover', (e) => {
e.stopPropagation()
e.preventDefault()
this.$emit('dropChange')
})
},
8 可提供的扩展
props: {
// 限制上传文件的大小
size: {
type: [Number],
default: 500 * 1024
},
// 限制上传文件类型
accept: {
type: String,
default: 'image/*'
},
// 是否允许多文件上传
multiple: {
type: Boolean,
default: false
},
// 上传成功方法
uploadSuccess: {
type: Function
},
// 上传失败事件
uploadError: {
type: Function
},
// 文件校验通过后触发
acceptAfter: {
type: Function
},
// 移除文件时触发
fileRemove: {
type: Function
},
// 上传的请求头
headers: {
type: Object,
default: () => { }
},
// 是否自动上传文件
autoUpload: {
type: Boolean,
default: true
},
// 上传的服务器地址
action: {
type: String,
default: '#'
},
// 是否允许cooike
withCredentials: {
type: Boolean,
default: false,
},
// 开启预览图
preView: {
type: Boolean,
default: false
},
// 携带的参数
data: {
},
// 上传时触发的钩子
uploadProgress: {
type: Function
},
// 是否展示上传列表
showList: {
type: Boolean,
default: true
},
// 开启拖动上传
drag: {
type: Boolean,
default: false
},
// 拖拽文件进入是触发
dropEnter: {
type: Function
},
// 拖拽文件离开时触发
dropLeave: {
type: Function
},
// 拖拽进入时持续触发
dropChange: {
type: Function
},
// 拖拽在指定文件中触发
handlerDrop:{
type: Function
}
...
}
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
暂无评论内容