vue2组件库
上传组件
核心思路: 监控整个上传的流程
上传成功 上传失败
类型:拖拽 多个文件上传
上传必备属性 & 钩子属性
跟上传强关联的属性,上传必备的字段
name: 提交的那个formData字段名
action:ajax接口路径
limit:限制提交个数
钩子函数
上传fileList数据构造
dom: this.$refs
选中文件 上传
按照整个上传的流程
fileList中每个对象的状态
- 刚放进去,准备好了待上传
- 上传中
- 上传完成
自己创建的一个文件对象
数据层fileList
弄一个数据同步v-model或.async,我就给你一个数据不希望它有什么同步的功能 ,我自己身上有一份数据,用户的数据也格式化放到这个数组里不涉及什么子改父父改子,自己处理自己的数据。
文件变化了,触发文件变化的钩子。
发起ajax上传请求
httpPost的处理
处理上传前+上传中+上传成功的各状态展示
file.status percent
onProgress onSuccess onError
upload.vue
<template>`
` <div class="zh-upload">`
` <div class="zh-upload-button" @click="upload">`
` <slot></slot>`
` </div>`
` <div><slot name="tip"></slot></div>`
` <input ref="file" type="file" :accept="accept" :multiple="multiple" @change="changeFile">`
` <ul>`
` <li v-for="file in files">`
` {{file.name}}`
` <zh-progress v-if="file.status==='uploading'" :percent="file.percent"></zh-progress>`
` </li>`
` </ul>`
` </div>`
`</template>`
`<script>`
`import _ from 'lodash'`
`import {ajax} from './upload'`
`export default {`
` name:'zh-upload',`
` props:{`
` name:{`
` type:String,`
` default:'file'`
` },`
` action:{`
` type:String,`
` default:''`
` },`
` accept:{`
` type:String,`
` default:''`
` },`
` multiple:{`
` type:Boolean,`
` default:false`
` },`
` limit:{`
` type:Number,`
` default:0`
` },`
` onExceed:{`
` type:Function,`
` },`
` beforeUpload:{`
` type:Function,`
` },`
` httpRequest:{`
` type:Function,`
` default:ajax,`
` },`
` fileList:{`
` type:Array,`
` default:[]`
` }`
` },`
` data(){`
` return {`
` files:[],`
` uniqueId:1,`
` }`
` },`
` watch:{`
` fileList:{`
` deep:true,`
` immediate:true,`
` handler(val){`
` this.files=val.map(item=>{`
` item.uid=`${+new Date}${this.uniqueId++}``
` item.status='success'`
` return item;`
` // const file={`
` // uid:item.uid,`
` // name:item.name,`
` // url:item.url,`
` // status:'success', // 完成成功态时只关心 name & url`
` // percent:0,`
` // }`
` // return file;`
` })`
` }`
` }`
` },`
` methods:{`
` upload(){`
` this.$refs.file.value=''`
` this.$refs.file.click()`
` },`
` changeFile(ev){`
` let files=ev.target.files;`
` // 限制最多上传的文件数`
` if(this.limit && this.files.length+files.length>this.limit){`
` return this.onExceed();`
` }`
` // [...files].forEach`
` _.forEach(files,rawFile=>{`
` this.uploadStart(rawFile)`
` this.uploadFile(rawFile)`
` })`
` },`
` uploadStart(rawFile){`
` rawFile.uid=`${+new Date}${this.uniqueId++}``
` // 构造新的文件对象`
` const fileNew={`
` uid:rawFile.uid,`
` name:rawFile.name,`
` size:rawFile.size,`
` type:rawFile.type,`
` status:'uploadstart',`
` percent:0,`
` rawFile,`
` }`
` this.files.push(fileNew)`
` },`
` uploadFile(rawFile){`
` // @todo beforeUpload`
` if(typeof this.beforeUpload === 'function'){`
` let flag=this.beforeUpload(rawFile) // 目前没考虑promise的情况`
` if(!flag) return`
` }`
` this.post(rawFile)`
` },`
` post(rawFile){`
` const options={`
` filename:this.name,`
` file:rawFile,`
` action:this.action,`
` onSuccess:(res)=>{`
` this.handleSuccess(res,rawFile)`
` },`
` onError:(res)=>{`
` },`
` onProgress:(ev)=>{`
` this.handleProgress(ev,rawFile)`
` },`
` }`
` this.httpRequest(options)`
` },`
` handleSuccess(res,rawFile){`
` const file=this.files.find(f=>f.uid===rawFile.uid)`
` file.status='success'`
` },`
` handleProgress(ev,rawFile){`
` // file是原生file文件,找到files中对应的file对象`
` const file=this.files.find(f=>f.uid===rawFile.uid)`
` file.status='uploading'`
` file.percent=Math.round(ev.loaded/ev.total*100)`
` }`
` }`
`}`
`</script>`
`<style scoped lang="scss">`
`.zh-upload{`
` &-button{`
` display: inline-block;`
` }`
` input[type=file]{`
` display: none;`
` }`
`}`
`</style>`
`
upload.js
export function ajax(options){`
` let xhr=new XMLHttpRequest()`
` const {filename,file,action,onSuccess,onError,onProgress}=options;`
` const fd=new FormData`
` fd.append(filename,file)`
` xhr.open('post',action)`
` xhr.onload=()=>{`
` onSuccess(JSON.parse(xhr.responseText))`
` }`
` xhr.onerror=()=>{`
` onError(JSON.parse(xhr.errorText))`
` }`
` xhr.upload.onprogress=(ev)=>{`
` onProgress(ev)`
` }`
` xhr.send(fd)`
` return xhr;`
`}`
`
progress.vue
<template>`
`<div class="progress-outer" :style="outerStyle">`
` <div class="progress-inner" :style="innerStyle"></div>`
`</div>`
`</template>`
`<script>`
`export default {`
` name:'zh-progress',`
` props:{`
` strokeWidth:{`
` type:Number,`
` default:10`
` },`
` strokeColor:{`
` type:String,`
` default:'blue'`
` },`
` percent:{`
` type:Number,`
` default:0`
` }`
` },`
` computed:{`
` outerStyle(){`
` return {`
` height:`${this.strokeWidth}px`,`
` }`
` },`
` innerStyle(){`
` return {`
` width:`${this.percent}%`,`
` background:this.strokeColor`
` }`
` }`
` },`
` watch:{`
` percent(val){`
` console.log(val,'percent');`
` }`
` }`
`}`
`</script>`
`<style scoped lang="scss">`
`.progress-outer{`
` width: 100%;`
` background: grey;`
` position: relative;`
` .progress-inner{`
` position: absolute;`
` left: 0;`
` top: 0;`
` height: 100%;`
` transition:width .3s ease;`
` }`
`}`
`.progress-outer,.progress-inner{`
` border-radius: 5px;`
`}`
`</style>`
`
设计组件思想:
用户要有那些功能
暴露用户那些功能
用户有哪些行为
拖拽上传
主要就是onDrop事件
ondragover.prevent ondragleave.prevent
Popover组件
appendChild insertBefore都会对dom有移动性
事件:事件机制 ,谁在谁里面,怎么触发这个事件,事件都有哪些问题 。
具体位置:用js算left top的值