用vue写轮子的一些心得(十)——upload图片上传组件

用vue写轮子的一些心得(十)——upload图片上传组件_第1张图片

需求分析

  • 支持正常的图片上传功能;
  • 支持图片多选上传;
  • 支持图片预览;
  • 支持配置长传图片大小校验,格式校验;
  • 支持图片上传时提示不同的状态,成功、失败、上传中;
  • 支持可X掉某一张图片;

 

方法实现

一、思路:

1、首先图片上传是需要有服务器支持的,这里我们用node在本地起一个服务,模拟服务器接收上传图片并保存,再通过img标签的url链接拿到服务器的图片地址。

2、多文件上传在前端只需要在类型为file的input的属性中增加multiple属性即可多选,文件格式同理在input上增加accept定义文件格式。服务器上也需要做相应的配置才能支持多文件上传。

3、整个图片上传的流程是:用户点击上传按钮,组件触发类型为file的input,用户选择完文件确定后,组件循环拿到文件数组中的文件并上传给服务器,此时文件的状态为上传中,上传成功后服务器保存文件后接口便返回给前端url路径,此时文件状态为成功,若上传过程中失败则状态为失败。上传成功后,img标签展示接口返回的url链接完成图片预览。整个图片上传流程结束。

那我们依次来看看每一个步骤的代码吧。

 

二、本地起一个存储图片的node服务器

index.js

const express = require('express')
const multer = require('multer')
const cors = require('cors')
const upload = multer({dest: 'images/'})
const app = express()

app.get('/', (req, res) => {
    res.send('hello word')
})

app.options('/upload', cors())
app.post('/upload', cors(), upload.single('file'), (req, res)=> {
    res.send(JSON.stringify({id: req.file.filename}))
})

app.get('/preview/:key', cors(), (req, res)=> {
    res.sendFile(`images/${req.params.key}`, {
        root: __dirname,
        headers: {
            'Content-Type': 'image/jpeg',
        }
    }, (error)=>{
        console.log(error)
    })
})
var port = process.env.PORT || 3000
console.log(port)
app.listen(port)

启动命令 node index

三、upload组件传参定义

export default {
    data() {
        return {
            fileList: [] //图片上传成功的url数组
        }
    },
    methods: {
        parseResponse(response) {
            let object = JSON.parse(response)
            return `http://127.0.0.1:3000/preview/${object.id}`
        },
        xxx(fileList) {
            // console.log('监听到了 update:fileList 事件')
            // console.log(fileList, 'fileList')
        }
    },
}

四、upload组件内部实现

export default {
    props: {
        name: {
            type: String,
            required: true
        },
        action: {
            type: String,
            required: true
        },
        accept: {
            type: String,
            default: 'image/*'
        },
        method: {
            type: String,
            default: 'POST'
        },
        parseResponse: {
            type: Function,
            required: true
        },
        fileList: {
            type: Array,
            default: ()=> []
        },
        sizeLimit: {type: Number}
    },
    methods: {
        onClickUpload() {
            let input = this.createInput()
            input.addEventListener('change', ()=> {
                this.uploadFiles(input.files) //单文件
                input.remove()
            })
            input.click()
        },
        uploadFiles(rawFiles) {
            let newNames = []
            for (let i=0; i {
                    let url = this.parseResponse(response)
                    this.afterUploadFiles(newName, url)
                }, (xhr)=> {
                    this.uploadError(xhr, newName)
                })
            }
        },
        beforeUploadFiles(rawFiles, newNames) {
            rawFiles = Array.from(rawFiles)
            for (let i=0; i< rawFiles.length; i++) {
                let {size, type} = rawFiles[i]
                if (size > this.sizeLimit) {
                    this.$emit('error', `文件不能大于${this.sizeLimit * 1024}M`)
                    return false
                }
            }
            let x = rawFiles.map((rwaFile, i)=> {
                let {type, size} = rwaFile
                return {name: newNames[i], type, size, status: 'uploading'}
            })
            this.$emit('update:fileList', [...this.fileList, ...x])
            return true
        },
        afterUploadFiles(newName, url) {
            let file = this.fileList.filter(f=> f.name === newName)[0]
            let index = this.fileList.indexOf(file)
            let fileCopy = JSON.parse(JSON.stringify(file))
            fileCopy.url = url
            fileCopy.status = 'success'
            let fileListCopy = [...this.fileList]
            fileListCopy.splice(index, 1, fileCopy)
            this.$emit('update:fileList', fileListCopy)
            this.$emit('uploaded') //全部上传完成的回调
        },
        generateName(name) {
            while (this.fileList.filter(f => f.name === name).length > 0) {
                let duIndexOf = name.lastIndexOf('.')
                let nameBefore = name.substring(0, duIndexOf)
                let nameAfter = name.substring(duIndexOf)
                name = nameBefore + '(1)' + nameAfter
            }
            return name
        },
        uploadError(xhr, newName) {
            let file = this.fileList.filter(f => f.name === newName) [0]
            let index = this.fileList.indexOf(file)
            let fileCopy = JSON.parse(JSON.stringify(file))
            fileCopy.status = 'fail'
            let fileListCopy = [...this.fileList]
            fileListCopy.splice(index, 1, fileCopy)
            this.$emit('update:fileList', fileListCopy)
            let error = ''
            if (xhr.status === 0) { // xhr === XMLHttpRequest
                error = '网络无法连接'
            }
            this.$emit('error', error)
        },
        onRemoveFile(file) {
            let answer = window.confirm('你确定要删除它吗?')
            if (answer) {
                let copy = [...this.fileList]
                let index = copy.indexOf(file)
                copy.splice(index, 1)
                this.$emit('update:fileList', copy)
            }
        },
        createInput() {
            this.$refs.box.innerHTML = ''
            let input = document.createElement('input')
            input.accept = this.accept
            input.type= 'file'
            input.multiple = true
            this.$refs.box.appendChild(input)
            return input
        },
        doUploadFile(formData, success, fail) {
            $http[this.method.toLowerCase()](this.action, {success, fail, data: formData}) //本地模拟接口请求
        }
    }
}

 

完整代码可参考我的GitHub:https://github.com/A-Tione/tione/blob/master/src/upload/upload.vue

你可能感兴趣的:(VUE)