1. 程式人生 > >VUE 爬坑之旅 -- 封裝一個簡單的獲取本地圖片並壓縮上傳的元件

VUE 爬坑之旅 -- 封裝一個簡單的獲取本地圖片並壓縮上傳的元件

平時專案開發中,獲取本地圖片並壓縮上傳是一個很常見的需求,最典型的就是修改使用者頭像功能,今天就來封裝一個可以到處通過的元件。

首先分析需求,要達到什麼效果呢?

  • 點選後開啟檔案選擇器,選擇檔案
  • 對獲取的圖片檔案壓縮
  • 前端能夠預覽獲取到的圖片
  • 將壓縮後的圖片上傳給伺服器

分析下來,大致就是上面的幾點,那麼再深入分析一下,這個圖片預覽上傳的功能可能很多地方都需要用,並不只限於修改頭像這一個地方或者這一個專案,能否將這個元件封裝下,讓它能夠適應各種情況且不需要改動,也就是僅僅只作為一個功能元件,而不帶有任何 UI 性質。
所以,上面的需求其實應該壓縮下,

  • 獲取圖片並壓縮
  • 預覽圖片並上傳

獲取圖片並壓縮,這個功能比較獨立且可以不涉及到 UI,應該把它單獨封裝成一個公共的元件以達到通過的目的。
而預覽並上傳,這個就涉及到 Ui 了,並且要上傳的話需要呼叫伺服器介面,具體到不同的業務場景和專案,這介面肯定是不一樣的,所以這二個點並不適合封裝成通用的元件,需要根據不同的業務場景和專案進行相應的變動。

分析到這裡思路就清晰了,下面開始擼碼,首先來看第一個,獲取並壓縮圖片。不囉嗦,直接上程式碼,必要的地方都有註釋,一看就明白

<!--拍照,選擇圖片元件-->

<template>
  <div @click.stop = "addPic" ref
= "upload">
<input type = "file" accept = "image/jpg,image/png,image/jpeg,image/gif" @change = "onFileChange" style = "display: none"> </div> </template> <script> import { Indicator } from 'mint-ui'//載入狀態動畫 import lrz from 'lrz'//壓縮庫,需要通過 npm 安裝下
export default { name: 'UploadImg', data () { return { imgUrl: '', } }, methods: { addPic () { let els = this.$refs.upload.querySelectorAll('input[type=file]') els[0].click() return false }, onFileChange (e) { //獲取圖片檔案 let files = e.target.files || e.dataTransfer.files if (!files.length) return this.createImage(files, e) }, createImage (file, e) { this.imgUrl = '' Indicator.open() lrz(file[0], {width: 480}).then((rst) => { this.imgUrl = rst.base64 //在將圖片壓縮成功後,將資訊傳出去 this.$emit('imgUrl', this.imgUrl) }).always(() => { //每次壓縮完之後要將input內容清空,不然會出現傳出去的內容不對的bug e.target.value = null this.imgUrl = '' Indicator.close() }) }, }, }
</script>

通過程式碼可以看到,這個元件的內容很簡單,就一個隱藏的 input ,用來獲取圖片檔案,獲取到檔案後進行壓縮,然後將壓縮後的資料 emit 傳送出去。
這裡有一點要注意:壓縮之後的圖片是 base64 格式而不是 file 型別,base64 實際上就是一段字串,有很多站長工具都可以做轉換,這裡不多說,不知道的自己查查。所以最後上傳給伺服器的是一段 base64 的字串而不是 file 檔案,這點要跟後端溝通好。如果後端不配合,一定要你傳 file 檔案,那就只有再另找辦法轉檔案或者直接拿檔案上傳了,,,

在將壓縮後的資料傳送出去後,我們就需要對資料進行處理了,也就是預覽並上傳。這二個功能因為根據業務和專案的不同肯定會有變化,所以就沒法作為一個公共的通用元件來設計,
那麼可以在一個父元件裡面寫這二個功能,把獲取並壓縮圖片的元件作為它的子元件。在父元件中觸發子元件的功能,父元件中拿到子元件傳過來的圖片資料顯示出來並上傳給伺服器就行了,程式碼如下:

<template>
  <div>
    <div class = "container">
      <div class = "flex bb avatarLine" @click.stop = "changeAvatar">
        <p>頭像</p>
        <img v-if = "newAvatar" :src = "newAvatar" alt = "">
        <img v-else src = "../../assets/img/img-avatar-default.png" alt = "">
        <UploadImg v-on:imgUrl = "handleImg" ref = "upload" style = "display: none"></UploadImg>
      </div>
  </div>
</template>

<script>
  import UploadImg from '@/components/UploadImg'

  export default {
    name: 'setting',
    components: {
      UploadImg,
    },
    data () {
      return {
        newAvatar: '',
      }
    },
    methods: {
      changeAvatar () {
        //觸發子元件獲取圖片的事件
        this.$refs.upload.addPic()
      },
      handleImg (imgUrl) {
        //拿到子元件中傳過來的資料並上傳
        this.newAvatar = imgUrl
        this.api.post(this, '/user/avatar', {avatar: this.newAvatar}).then((data) => {
          if (data && data.user) {
            this.$store.commit('updateUserInfo', data.user)
          }
        })
      },
  }
</script>

UploadImg 元件可以存在多個,當有多個 UploadImg 元件的時候,它們的 ref ,觸發和接收事件的方法名需要是不同的名字,這點需要注意,如果名字都是一樣的話會發生資料錯亂的 bug