不管什么项目,大概都少不了图片上传。做为常见的需求,不少地方会使用到,应该单独封装一个上传组件,方便复用。css
这里使用 vue + element-ui-upload + 七牛云完成上传前端
如今主流的七牛云上传方式大概为受权式上传,大概为以下过程:vue
若是说后端很好,在服务器端直接调用七牛上传接口,那么前端只须要传一个文件给后端,前端方面会简单许多。ios
不过我是没有遇到的,并且前端调用上传接口,可控性会更强些element-ui
关于七牛云的上传地址:axios
通过测试,上面四个接口都是可用的(https 或者 http),我这里的空间是华南区域,不一样区域会有所不一样,能够参考后端
七牛 API,有 3 个参数,token、file、key(可选),其中 key 是文件名,不传会自动生成api
首先分析下需求,完成的 upload 组件,要和表单结合起来,意味着要实现双向绑定,调用这个组件的时候,只须要绑定 value(图片url) 属性,组件内部上传完成后经过 $emit('input', url) 改变 value,这样就很方便了数组
下面介绍下 el-upload 组件:服务器
下面是代码:
<template> <el-upload v-loading="loading" class="uploader" :class="{'hover-mask': value}" action="https://up-z2.qiniup.com" :show-file-list="false" :data="param" accept="image/*" :on-success="handleSuccess" :before-upload="handlebeforeUpload"> <img v-if="value" :src="value" class="avatar"> <i class="el-icon-plus uploader-icon"></i> </el-upload> </template> <script> import axios from 'axios' export default { props: { value: String, required: true }, data() { return { loading: '', param: { token: '' } } }, methods: { handleSuccess(res, file) { this.loading = false // 若是不传 key 参数,就使用七牛自动生成的 hash 值,若是传递了 key 参数,那么就用返回的 key 值 const { hash } = res // 拼接获得图片 url const imageUrl = 'your domain prefix' + hash // 触发事件 input,父组件会修改绑定的 value 值 this.$emit('input', imageUrl) }, handlebeforeUpload(file) { // 这里作能够作文件校验操做 const isImg = /^image\/\w+$/i.test(file.type) if (!isImg) { this.$message.error('只能上传 JPG、PNG、GIF 格式!') return false } return new Promise((resolve, reject) => { this.loading = true // 获取token const tokenUrl = 'http://xxx/upload' axios.get(tokenUrl).then(res => { const { token } = res.data.data this.param.token = token resolve(true) }).catch(err => { this.loading = false reject(err) }) }) } } } </script> <style scoped lang="scss"> .uploader { width: 130px; height: 130px; border: 1px dashed #d9d9d9; border-radius: 6px; cursor: pointer; &:hover { border-color: #409EFF; } /deep/ .el-upload { position: relative; width: 100%; height: 100%; overflow: hidden; } } .uploader-icon { position: absolute; top: 0; left: 0; width: 100%; height: 100%; line-height: 128px; text-align: center; font-size: 28px; color: #8c939d; } .avatar + .uploader-icon { opacity: 0; } .avatar { width: 128px; height: 128px; display: block; border-radius: 6px; } .hover-mask:hover .uploader-icon { opacity: 1; background-color: rgba(0, 0, 0, .2); color: #fff; } </style>
如何使用:
<template> <el-form ref="form" :model="form"> <el-form-item label="头像" prop="avatar"> <upload v-model="form.avatar"></upload> </el-form-item> <el-form-item label="姓名" prop="userName"> <el-input v-model="form.userName"></el-input> </el-form-item> </el-form> <el-button type="primary" @click="onSubmit">确 定</el-button> </template> <script> import Upload from '@/components/Upload' export default { components: { Upload }, data () { return { form: { avatar: '', userName: '' } } }, methods: { onSubmit() { this.$refs.form.validata(valid => { if (valid) { // 将 this.form 传给后端 } }) } } } </script>
实现双向绑定后,收集数据会很是方便,调用后端接口直接传递绑定的值就 ok 了
若是后端很好(铁哥们),前端只须要传递 file 对象,那就更简单了,在上传前置钩子中就不用获取 token,这部分工做都由后端去处理,咱们只须要调用上传接口就能够
<template> <el-upload v-loading="loading" class="uploader" :class="{'hover-mask': value}" action="your upload api" :show-file-list="false" :on-success="handleSuccess" :before-upload="handlebeforeUpload"> <img v-if="value" :src="value" class="avatar"> <i class="el-icon-plus uploader-icon"></i> </el-upload> </template> <script> import axios from 'axios' export default { props: { value: String, required: true }, data() { return { loading: '' } }, methods: { handleSuccess(res, file) { this.loading = false const { hash } = res const imageUrl = 'your domain prefix' + hash this.$emit('input', imageUrl) }, handlebeforeUpload(file) { // 不须要操做能够直接 返回 true return true } } } </script>
其实,咱们在组件上使用v-model
的时候,其实是下面这样:
<custom-upload :value="form.avatar" @input="form.avatar = $event" ></custom-upload>
为了让它正常工做,custom-upload
组件内必须:
value
特性绑定到一个名叫 value
的 prop 上(这里就是图片地址) value
时,将新的值经过自定义的 input
事件抛出(这里就是上传成功后的$emit('input', 图片地址)
)因此 v-model
是一个语法糖,一种简写形式。若是想要双向绑定,又想自定义,那么能够使用上面的方式:
<custom-upload :url="form.avatar" @update:url="form.avatar = $event" ></custom-upload>
那么在子组件内部就接受url
属性,做为图片地址就能够了,在更新url
时,也要与绑定的事件名一致$emit('update:url', 图片地址)
,事件名使用update:propName
是vue
推荐的风格,目的是提醒开发者这是一个双向绑定的属性。
固然,为了方便起见,vue
为这种模式提供一个缩写,即 .sync
修饰符:
<custom-upload :url.sync="form.avatar"></custom-upload>
带有.sync
修饰符的值不能为表达式(例如:url.sync="domain + form.avatar"
是无效的)
有时候相似上传资料、凭证这类的需求要求上传多张图片,咱们能够再封装一个多图上传的组件
对于 el-upload,多张图片上传注意以下几点:
string
,应该是一个数组,数组成员为图片地址['url1', 'url2']
[{name: 'foo.jpg', url: 'xxx'}, {name: 'bar.jpg', url: 'xxx'}]
。这与咱们传进来的数据不同,须要处理一下 value(固然咱们使用组件时能够直接传递须要的这种格式,就不用处理了)'picture-card'
,图片将以卡片形式显示下面是代码:
<template> <div> <el-upload :action="QINIU_UPLOAD_URL" :data="param" :file-list="fileList" list-type="picture-card" :limit="limit" :on-exceed="handleUploadExceed" :on-preview="handlePictureCardPreview" :on-remove="handleRemove" :before-upload="handlebeforeUpload" :on-success="handleSuccess"> <i class="el-icon-plus"></i> </el-upload> <el-dialog :visible.sync="dialogVisible"> <img width="100%" :src="dialogImageUrl" alt=""> </el-dialog> </div> </template> <script> import axios from 'axios' export default { props: { value: { type: Array, default: () => [] }, limit: { type: Number, default: 4 } }, data() { return { dialogImageUrl: '', // 当前预览图片地址 dialogVisible: false, // 预览弹框 visible param: { token: '' } } }, computed: { // ['xxx', 'xxx'] 转换为 [{url: 'xxx'}, {url: 'xxx'}] fileList() { return this.value.map(url => ({ url })) } }, methods: { handleUploadExceed() { this.$message.error(`最多上传${this.limit}张图片`) }, handleRemove(file, fileList) { // fileList 为删除后的文件列表 const value = fileList.map(v => v.url) this.$emit('input', value) }, handlePictureCardPreview(file) { this.dialogImageUrl = file.url this.dialogVisible = true }, handlebeforeUpload(file) { return new Promise((resolve, reject) => { axios.get('/upload/qiniuToken').then(res => { const { token } = res.data this.param.token = token resolve(true) }).catch(err => { reject(err) }) }) }, handleSuccess(res, file) { const { hash } = res const imageUrl = this.QINIU_PREFIX + hash // 这里若是 this.value.push(imageUrl) 这么写,vue会报出警告,大概意思是value做为props不该该在子组件中被修改 // 应该根据 value 获得新的值,而不能修改它,this.value.concat(imageUrl)也是能够的,concat方法返回新的数组 this.$emit('input', [...this.value, imageUrl]) } } } </script>
如何使用:
<template> <el-form ref="form" :model="form"> <el-form-item label="还款金额" prop="amount"> <el-input v-model="form.amount"></el-input> </el-form-item> <el-form-item label="凭证" prop="voucherUrlList"> <multi-upload v-model="form.voucherUrlList"></multi-upload> </el-form-item> </el-form> <el-button type="primary" @click="onSubmit">确 定</el-button> </template> <script> import MultiUpload from '@/components/MultiUpload' export default { components: { MultiUpload }, data () { return { form: { amount: '', voucherUrlList: [] } } }, methods: { onSubmit() { this.$refs.form.validata(valid => { if (valid) { // 将 this.form 传给后端 } }) } } } </script>
把上传组件封装成双向绑定的形式后,咱们使用会更方便,也方便复用。