使用ElementUI和Axios以formData格式提交帶有文件的表單的錯誤示範及分析解決

作Vue項目的時候, 提交數據基本上都是用Axios, 以前作過的表單方面的提交, 並無過多關注客戶端和服務器之間的通訊過程. 因此一直對HTTP的head請求頭, body內容之類的不明不白, 爲了短時間(是的估計過半年又忘了😂)解決這個疑惑, 再次複習了一遍. 順便總結了 Vue中使用Axios處理包含上傳文件的表單提交

場景說明

項目使用的Vue(Nuxt)框架, 數據請求用的Axios插件, 表單包含了一些基本的用戶信息填寫, 同時還有身份證上傳, 和後端溝經過, 提交數據的時候, 接口所有使用POST請求, 那麼有文件上傳的通常來講只能用formData格式.javascript

實踐及代碼示例

我項目中使用的是ElementUI, 對於上傳組件不熟悉的朋友, 須要注意幾個事情:html

  • action是必填, 那麼留空也許是個不錯的作法.
  • 獲取到上傳的文件的辦法不少. 官方提供了幾種事件來獲取, 例如: on-success, on-change(首次上傳會觸發兩次), 我這裏使用了on-success
  • 那麼拿到上傳的回調, 這裏特別須要注意的, 我以on-success三個參數來看vue

    • response是服務器返回的響應
    • file一個文件
    • fileList存放多個文件的數組
可能看到有 file或者 fileList會直接將它的數據提交給後臺, 一開始我也是沒注意到這點, 始終沒法正確提交數據. 那麼通過一番研究和排查, 得知: 真正的File對象是fileList數組中某個元素的raw屬性!, 那麼下面先看一段錯誤的示範:

頁面部分結構代碼以下:java

<el-form ref="form" :model="form" label-width="120px">
  <el-form-item label="活動名稱">
    <el-input v-model="form.name"></el-input>
  </el-form-item>
  <el-form-item label="活動區域">
    <el-select v-model="form.region" placeholder="請選擇活動區域">
      <el-option label="區域一" value="shanghai"></el-option>
      <el-option label="區域二" value="beijing"></el-option>
    </el-select>
  </el-form-item>
  <el-form-item label="身份證正面">
    <el-upload 
      action="" 
      :on-success    ="handleSuccess"
      :multiple="false" 
      :limit="1" 
      :on-exceed="handleExceed" 
      :file-list="fileList">
      <el-button size="small" type="primary">點擊上傳</el-button>
      <div slot="tip" class="el-upload__tip">只能上傳jpg/png文件,且不超過500kb</div>
    </el-upload>
  </el-form-item>
  <el-form-item>
    <el-button type="primary" @click="onSubmit">提交</el-button>
    <el-button>取消</el-button>
  </el-form-item>
</el-form>

這裏我將上傳文件數量限制爲1個, 接下來是JavaScript部分:ios

import AppLogo from '~/components/AppLogo.vue'
export default {
  components: {
    AppLogo
  },
  data() {
    return {
      form: {
        name: '',
        region: ''
      },
      fileList: []
    }
  },
  methods: {
    handleSuccess(response, file, fileList) {
      this.fileList = fileList
    },
    handleExceed(files, fileList) {
      this.$message.warning(`最多上傳 ${files.length} 個文件`)
    },
    onSubmit() {
      this.$axios
        .$post('/api/active', {
          name: this.form.name,
          region: this.form.region,
          file: this.fileList
        })
        .then(response => {
          if (response.code === 200) {
            // 提交成功將要執行的代碼
          }
        })
        .catch(function(error) {
          // console.log(error)
        })
    }
  }
}
上面的這段 onSubmit能提交成功就是真的見了鬼呢

問題分析

問題在哪呢, 前面提到, 後臺接受數據的格式是multipart/form-data, 你發個json對象是什麼鬼, 沒有這方面經驗的人確定就搞不清怎麼回事了. 因此通常對這塊不熟悉的人容易犯如下的幾個錯誤:git

  • 不瞭解上傳文件應該以什麼方式提交, 好比後臺是multipart/form-data, 而習慣性以json對象發送數據(由於大量插件對數據對象也封裝了方法, 因此容易忽略)
  • 不知道上傳文件提交的格式, 覺得將this.fileList改爲this.fileList[0]就萬事大吉
  • 當多種疑惑沒法解決的時候, 可能會嘗試不少次都不行, 陷入誤區而久久沒法解決困難. 開始懷疑是不是Axios插件不支持文件上傳.
  • 其餘各類別的問題...

問題解決

其實, 熟悉的話, 解決這個問題很簡單. 前面也說過, elementUI將返回的file對象封裝了一下, 首先咱們要拿到真正的文件對象, 實際上就是file.raw或者fileList[0].raw!github

不要覺得這樣就能夠提交數據了. 咱們還要使用form-data特有的提交方式來提交帶有文件內容的表單. 廢話很少說上一段, 修正後的部分代碼:element-ui

<el-upload 
  action="" 
  :http-request="handleFile" 
  :multiple="false" 
  :limit="1" 
  :on-exceed="handleExceed" 
  :file-list="fileList">
  <el-button size="small" type="primary">點擊上傳</el-button>
  <div slot="tip" class="el-upload__tip">只能上傳jpg/png文件,且不超過500kb</div>
</el-upload>
onSubmit() {
  let form = this.$refs['form'].$el
  let formData = new FormData(form)
  formData.append('name', this.form.name)
  formData.append('region', this.form.region)
  formData.append('file', this.fileList[0])
  this.$axios
    .$post('/api/active', formData)
    .then(response => {
      if (response.code === 200) {
        // 提交成功將要執行的代碼
      }
    })
    .catch(function(error) {
      // console.log(error)
    })
}

簡單說明下json

  • 其實elementUI中提供了一個http-request事件來覆蓋默認的action, 這樣很好的避免了一些異常(好比我在測試環境的時候, 用了不太好的的on-success經過了驗證, 可是在生產環境中因爲action地址空因此默認請求當前地址, 出現了404).
  • formData彷佛只能一個個對應的append進去, console出來也看不到, 具體用法能夠參考文章末尾的MDN~
  • axios是能夠很好地完成formData表單數據提交的, 這裏雖然也是一個對象, 可是不是普通的json對象, 他對這個數據處理很正常, 因此放心使用.

額外補充: axios配置

Axios能夠說在Vue中至關重要, 常常咱們對簡單的從新封裝或者配置, 就這個插件來講徹底能夠寫一篇新文章了, 這裏他不是重點我就簡單介紹下我用它作的配置
import qs from 'qs'
import { Message } from 'element-ui'
export default function({ $axios, redirect }) {
  let apiUrl = process.env.apiUrl
  $axios.defaults.baseURL = apiUrl
  $axios.defaults.timeout = 15000
  $axios.defaults.headers.post['Content-Type'] = 'multipart/form-data'
  $axios.onRequest(config => {
    // 與後臺配合post請求字符串傳參
    let reqParams = qs.stringify(config.data)
    let url = config.url + (reqParams ? '?' : '') + reqParams
    config.url = url
  })

  $axios.onResponse(res => {
    if (res.data.code !== 200) {
      // 後臺返回session過時或異常的狀況
      if (res.data.code === 401 || res.config.url === apiUrl + '/logout') {
        window.sessionStorage.clear()
        redirect('/platform/login')
      } else {
        // 返回到一個錯誤頁面或者提示錯誤
        Message.error(res.data.message)
        // redirect('/')
      }
    }
  })

  $axios.onError(error => {
    Message.error('服務器異常,請稍後再試')
  })
}

上面對發送數據請求的相關參數配置了, 也作了攔截器. qs插件是個亮點, 我爲了vue代碼書寫更清晰, 將json對象傳過來處理爲name=whidy&age=30相似這樣的拼接到url後再發送請求給服務器的.axios

總結

好了說了一大堆, 其實最重要的事情是, 理解如下幾點

  • 和後端溝通好, 請求格式
  • 瞭解上傳文件須要的表單數據格式
  • 儘可能多的去熟悉第三方UI插件, 尤爲像elementUI這樣相對完善的組件, 應該是有較好的處理方法的
  • 耐心的一步步查找錯誤

最後獻上一些參考資料:

文中不免也有一些描述不許確的地方, 但願大佬們多多指點~ 本文提到的代碼的存放在GitHub上面nuxt-spa-demo項目的分支nuxt-axios-formdata, 有興趣也能夠看看~

相關文章
相關標籤/搜索