# 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 } ```