Image upload

No matter what project, there is probably no picture upload. As a common requirement, it is used in many places and should be wrapped in a separate upload component for easy reuse.

Here, use Vue + Element-UI-Upload + QiniuCloud to complete the upload

The front end calls the seven niu API

Now the mainstream Qiniuyun uploading method is probably authorized uploading, which is probably the following process:

  • Request the backend interface to obtain the upload certificate token (backend generates token through accessKey, secretKey, bucket)
  • Request Qiniuyun’s interface address to complete uploading
  • Qiniu cloud server to return the image address (return hash, key, need to be stitched)

If the back end is good and the server side directly calls the Qiniu upload interface, then the front end only needs to pass a file to the back end, and the front end will be much simpler.

But I have not encountered, and the front-end call upload interface, the controllability will be stronger

About Qiniuyun upload address:

  • https://up-z2.qiniu.com
  • https://up-z2.qiniup.com
  • https://upload-z2.qiniu.com
  • https://upload-z2.qiniup.com

After testing, the above four interfaces are all available (HTTPS or HTTP). The space I have here is in South China, different regions will be different, you can refer to

Qiniu API has three parameters, token, file, key (optional), in which the key is the file name and will be automatically generated if not transmitted

The Upload component should be combined with the form, which means to realize two-way binding. When calling this component, it only needs to bind the value (image URL) property. After uploading inside the component, it can change the value by using $emit(‘input’, URL). That’s very convenient

The El-Upload component is described below:

  1. The action property is the interface address of the upload. Use the above upload address directly
  2. The name field is the parameter field name of the file stream, and the default value is FILE. The parameter field of the file stream uploaded by Qiniuyun is FILE, so it can be ignored here
  3. Data attribute is the parameter that needs to be passed. The parameters required for the upload address of Qiniu Cloud include File (automatically passed by the plug-in), Key and Token. The Key can not be passed (automatically generated by Qiniu Cloud)
  4. The before-upload property is the hook before uploading the file. Here, we call the backend interface to retrieve the token needed to upload the file, insert it into the data, and remember to resolve. Since retrieving the token is an asynchronous process, we return a Promise inside the hook. Resolve to upload after successful request. (If you need to verify file size and file type, this can also be done in the hook. If you need to verify file type, return false in advance.)

Here’s the code:

<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 // If the key parameter is not passed, the hash value is automatically generated. If the key parameter is passed, the hash value is used. Const imageURL = 'your domain prefix' + hash // $this.$emit('input', imageUrl)}; HandleBeforeUpload (file) {const isImg = /^image\/\w+$/i.test(file.type) if (! IsImg) {this.$message.error(' Only JPG, PNG, GIF! ') return false } return new Promise((resolve, Reject) => {this.loading = true // get 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>

How to use:

<template> <el-form ref="form" :model="form"> <el-form-item label=" avatar" prop="avatar"> < Upload V-model ="form.avatar"></ Upload ></ el-form-item> <el-form-item label=" name "prop="userName"> <el-input V-model =" form.username "></el-input> </el-form> < el-form type="primary" @click="onSubmit"> </el-form> </el-form> < el-form type="primary" @click="onSubmit"> </el-form> </el-form> < el-form type="primary" @click="onSubmit"> </el-button> </template> <script> import Upload from '@/components/Upload' export default {components: { Upload }, data () { return { form: { avatar: '', userName: '' } } }, methods: $this.$refs.form.validata(valid => {if (valid) {}})}}} </script>

After two-way binding is implemented, it is very convenient to collect the data and simply call the backend interface to pass the binding value directly

The front end calls the backend upload interface directly

If the back end is good and the front end only needs to pass the file object, then it is even easier. There is no need to get the token in the upload hook, this part of the work is handled by the back end, we just need to call the upload interface

<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) {// No operation is required to return true return true}}} </script>

In fact, when we use a V-model on a component, it actually looks like this:

<custom-upload
  :value="form.avatar"
  @input="form.avatar = $event"
></custom-upload>

In order for it to work properly, the custom-upload component must:

  • itvalueThe property is bound to a namevalueOn the prop of the
  • Need to repair valueWhen the new value is passed by the custominputEvent throw (here is the successful upload after$emit('input', image address);)

So the V-model is a syntactic sugar, a shorthand form. If you want two-way binding, but also want to customize, you can use the above method:

<custom-upload
  :url="form.avatar"
  @update:url="form.avatar = $event"
></custom-upload>

$emit(‘update:url’, image address) $emit(‘update:url’, image address); update:propName (‘update:url’, image address); The purpose is to remind developers that this is a two-way binding property.

Of course, for convenience, Vue provides an abbreviation for this pattern, the.sync modifier:

<custom-upload :url.sync="form.avatar"></custom-upload>

with
.syncThe value of a modifier cannot be an expression (for example
:url.sync="domain + form.avatar"Is invalid)

Upload photos

Sometimes requirements such as uploading information and credentials require uploading multiple images, so we can encapsulate a component for uploading multiple images

For El-Upload, the following points should be noted:

  1. Props value is no longerstring, should be an array whose members are image addresses['url1', 'url2']
  2. The file-list property is a list of uploaded files. We cannot assign value to it directly. File-list should be an array, for example[{name: 'foo.jpg', url: 'xxx'}, {name: 'bar.jpg', url: 'xxx'}]. This is different from the data we passed in, we need to handle the value (of course, we can use the component to directly pass the required format, do not have to deal with)
  3. The show-file-list is set to true, but it can’t be passed. It defaults to true
  4. List-type can be set to'picture-card', the picture will be displayed as a card
  5. The on-remove property is the hook for removing a file from a file list, where you need to fire an event to update the value
  6. The on-preview property is the hook for clicking on an uploaded file in the file list to preview the image
  7. The limit property specifies the maximum number of files that are allowed to be uploaded. This is used with the on-exceed property (the hook for files that exceed the limit). If an upload exceeds the limit, the hook is used to indicate the maximum number of files that are uploaded

Here’s the code:

<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: "", // Preview ImageUrl: false, // Preview popup visible param: {token: '}}}, computed: {/ / / 'XXX', 'XXX' convert [{url: 'XXX'}, {url: 'xxx'}] fileList() { return this.value.map(url => ({ url })) } }, methods: {handleUploadExceed() {this. $message.error(' Upload ${this.limit} image ')}, handleMove (file, 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 // If this.value. _ (props) _ (props) _ (props) _ (props) _ (props) _ (props) _ (props) _ (props) _ (props) _ $emit('input', [...this.value, imageUrl])}}} </script>

How to use:

<template> <el-form ref="form" :model="form"> <el-form-item label=" amount" prop="amount"> <el-input V-model ="form.amount"></el-input> </el-form-item> <el-form-item label=" voucherUrllist "prop=" voucherUrllist ">< multi-upload V-model ="form.voucherUrlList"></ El-form ></ 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: $this.$refs.form.validata(valid => {if (valid) {}})}}} </script>

Encapsulating the upload component as a two-way binding makes it easier for us to use and reuse.