# Vue & Rails 上傳檔案
###### tags: `Rails` `Vue`
前端使用 `vue-upload-component`
後端使用 `carrierwave` gem
### 目的
讓使用者能上傳檔案(手動選擇/拖曳)至後台,並可依不同檔案類型做限制、呈現。
### vue-upload-component
安裝
```yarn add vue-upload-component```
在需要用到的 vue 檔案中 import 就可以使用
```import FileUpload from 'vue-upload-component'```
這個套件有很多功能,可以參考官方文件:
[vue-upload-component](https://lian-yue.github.io/vue-upload-component/#/)
### carrierwave
安裝
```
# add to Gemfile
gem 'carrierwave'
$ bundle install
```
生成 uploader,裡面會給定一些預設值
```
$ rails generate uploader Avatar
class AvatarUploader < CarrierWave::Uploader::Base
storage :file
end
```
接下來就可以在 model 裡面去指定 uploader 以及欄位
```
class User < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader
end
```
##### 限制上傳檔案類型
可用 `content_type_whitelist` 指定能上傳的 MIME type 白名單
**但最近被改成 [`allow & deny`](https://github.com/carrierwaveuploader/carrierwave/commit/4c3cac75f3a473e941045c23ebb781f61af67d79)了XD**
```
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def content_type_whitelist
/image\//
end
# json file type
def content_type_whitelist
['application/json']
end
```
如果上傳的檔案類型不符,這時 carrierwave 會進行錯誤處理
```
校驗失敗: Button icon file translation missing: zh-TW.errors.messages.content_type_whitelist_error (ActiveRecord::RecordInvalid)
```
錯誤的類型是正確的,但仔細看看錯誤的內容好像不是說檔案類型錯誤,而是翻譯有問題
這時候需要安裝另一個 gem `carriewave i18n` ,錯誤訊息就會正確顯示了
[carrierwave issue](https://github.com/carrierwaveuploader/carrierwave/issues/2173)
```
校驗失敗: Button icon file 不可上傳 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet 文件 (ActiveRecord::RecordInvalid)
```
### 前端實作
把上傳打包成一個元件,可在其他地方引用
```
# FileUpload component
<template>
<div class="fileUpload">
<label class="file-upload all-radius">{{ buttonText }}檔案
<file-upload
:input-id="column"
:value="data.file"
:drop="true"
:accept="accept"
@input="input"
name="file"
/>
</label> {{ fileName }}
<br>
<img v-if="thumbnail" :src="thumbnail" style="width: 20%;">
<a v-if="file_link" :href="file_link" target="_blank">檔案連結</a>
</div>
</template>
```
##### input-id
會用到這個是因為在同個頁面如果有兩個以上的 `FileUpload` 元件,必須把 input tag 的 id 改掉,這邊是把傳進來的欄位名稱當做 id
##### accept
input tag 原生屬性,可限制能上傳的 MIME type
e.g. "image/png,image/gif,image/jpeg,image/webp", "image/*"
##### drop
可透過拖曳來加入檔案
但不受 `accept` 屬性限制,也就是如果我只 `accept img/*` 的檔案,我還是可以透過拖曳的方式亂丟其他類型的檔案 XD
##### files
上傳後會是一個 array of objects,其中一個 object 長相如下
```
{
"fileObject":true,
"size":2307,
"name":"date.svg",
"type":"image/svg+xml",
"active":false,
"error":"",
"success":false,
"putAction":"__vue_devtool_undefined__",
"postAction":"__vue_devtool_undefined__",
"timeout":0,
"file":"[object File]",
"response":{},
"progress":"0.00",
"speed":0,
"data":{},
"headers":{},
"id":"ljrhhuq72y"
}
```
後端 carrierwave 的 uploader 只要接到物件中的 `file`,就可以輕鬆的完成上傳檔案
##### thumbnail
這段從 source code 裡面挖的,大意是把剛選擇的 image 類型檔案變為可預覽的縮圖
```
const URL = (window.URL || window.webkitURL)
let blob = ''
if (URL) {
blob = URL.createObjectURL(this.data.file[0].file)
}
if (blob && this.data.file[0].type.substr(0, 6) === 'image/') {
return blob
}
```