不管什麼項目,大概都少不了圖片上傳。做爲常見的需求,不少地方會使用到,應該單獨封裝一個上傳組件,方便複用。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>
把上傳組件封裝成雙向綁定的形式後,咱們使用會更方便,也方便複用。