效果
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() {// 清空inputFilethis.$refs.inputFile.value = nullthis.$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, // 是否允许cooikeuid: 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)}//创建 xhrconst 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: '#'},// 是否允许cooikewithCredentials: {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}...}
最后
整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。
有需要的小伙伴,可以点击下方卡片领取,无偿分享