Nuxt.js(Vuetify) + Rails 이미지 파일 업로드 하기
vue.js 단 form 구성
폼을 하나 만들고 v-file-input
을 하나 배치 한다. @change
이벤트를 잡는다.
<template>
<v-form v-if="User" @submit.prevent="updateUser">
<v-row>
<v-col cols="12" sm="6">
<v-img
:src="photoPreviewUrl"
:loading="uploading"
min-height="180"
class="pink lighten-4 rounded border"
/>
</v-col>
<v-col cols="12" sm="6">
<v-file-input
v-model="selectedFile"
show-size
outlined
truncate-length="15"
prepend-icon="mdi-camera"
label="프로필사진"
placeholder="PNG, GIF, JPG"
accept="image/png, image/gif, image/jpeg"
:loading="uploading"
@change="onFileChange"
>
<template #selection="{ text }">
<v-chip small label color="primary">
{{ text }}
</v-chip>
</template>
</v-file-input>
</v-col>
</v-row>
<v-btn
color="red"
outlined
aria-label="사진 삭제"
@click.stop="deletePhoto"
>
사진 삭제
</v-btn>
</v-col>
</v-row>
</v-form>
</template>
<script>
export default {
name: 'PhotoEdit',
data: () => ({
sending: false, // 폼 제출시
selectedFile: null,
selectedFileLocalUrl: null,
photoOri: null,
}),
computed: {
// 페이지에 보여주는 썸네일의 url을 리턴한다
photoPreviewUrl() {
// 만약 임시로 업로드한 로컬파일이 있다면 그 url을 우선 리턴한다
if (this.selectedFileLocalUrl) {
return this.selectedFileLocalUrl
}
return null
},
},
mounted() {
this.getMyPhoto()
},
methods: {
getMyPhoto() {
this.$store
.dispatch('user/user_show_myphoto')
.then((resp) => {
const photo = resp.data.content.photo
this.photoOri = photo
})
.catch((err) => {
alert(err)
})
.finally(() => {
this.sending = false
})
},
async updateUserPhoto() {
this.uploading = true
await this.$store
.dispatch('user/user_update_photo', this.selectedFile)
.then((resp) => {
const photo = resp.data.content.photo
this.photoOri = photo
})
.catch((err) => {
alert(err)
})
.finally(() => {
this.uploading = false
})
},
updateUser() {
this.updateUserPhoto()
},
onFileChange(payload) {
this.selectedFile = payload
if (payload) {
// 임시로 로컬에 선택한 이미지 파일 url을 생성한다
this.selectedFileLocalUrl = URL.createObjectURL(payload)
} else {
// 파일 선택 취소 한 경우
this.photoOri = null
}
},
deletePhotoReal() {
if (this.selectedFileLocalUrl) {
// 새 파일 있는 경우 삭제
this.selectedFileLocalUrl = null
} else {
// 기존 파일 삭제
this.photoOri = false // false 면 변경이 있음(강제삭제)
this.uploading = true
// 삭제 API 호출
this.$store
.dispatch('user/user_delete_photo')
.then((resp) => {
this.photoNew = null
alert('保存しました')
})
.catch((err) => {
alert(err)
})
.finally(() => {
this.uploading = false
})
}
},
async deletePhoto() {
this.deletePhotoReal()
},
},
}
</script>
스토어 구성
실질적인 API연동 부분이다. CRUD가 되어야 한다.
store/user/actions.js
...
user_show_myphoto({ commit }, payload) {
return new Promise((resolve, reject) => {
securedAxiosInstance
.get('/api/v1/users/user_show_myphoto', {
params: {},
})
.then((resp) => {
const obj = resp.data.content.photo
commit('set_photo', obj)
resolve(resp)
})
.catch((err) => {
reject(msg)
})
})
},
user_update_photo({ commit }, photo) {
const formData = new FormData()
formData.append('file', photo)
return new Promise((resolve, reject) => {
securedAxiosInstance
.patch('/api/v1/users/user_update_photo', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
.then((resp) => {
const photo = resp.data.content.photo
commit('set_photo', photo)
resolve(resp)
})
.catch((err) => {
reject(msg)
})
})
},
user_delete_photo({ commit }) {
return new Promise((resolve, reject) => {
securedAxiosInstance
.delete('/api/v1/users/user_delete_photo')
.then((resp) => {
commit('set_photo', {})
resolve(resp)
})
.catch((err) => {
reject(msg)
})
})
},
...
스토어 세터이다.
store/user/mutations.js
...
set_photo(state, photo) {
state.photo = photo
},
...
스토어 메인파일이다.
store/user/index.js
import actions from './actions'
import getters from './getters'
import mutations from './mutations'
export const state = () => ({
photo: null,
})
export default {
namespaced: true,
state,
actions,
mutations,
getters,
}
스토어 게터이다.
store/user/getters.js
export default {
photo: (state) => state.photo,
}
루비온레일즈 : 백엔드 쪽 레퍼런스 설명
잼파일 설치
Gemfile
파일에 아래 라인 추가. 이미지 업로드 라이브러리는 carrierwave
이다.
...
gem 'carrierwave'
...
bundle install
실행
API용 라우터 추가.
routes.rb
...
get 'users/user_show_myphoto'
patch 'users/user_update_photo'
delete 'users/user_delete_photo'
...
유저 컨트롤러 구현
users_controller.rb
...
# GET
def user_show_myphoto
user = User.find params[:id]
if user
content = {
photo: user.thumbnails
}
return render_200 content
else
return render_401
end
render_404
end
# PATCH
def user_update_photo
photo = params[:file]
if @user
user = User.find @user.id
if photo.present?
if user.push_photo photo
photo = user.thumbnails
content = {
photo: photo # 방금 업데이트 한 사진을 리턴 해야 한다.
}
return render_200 content
else
return render_422(user.errors)
end
end
else
return render_401
end
render_422
end
# DELETE
'''
프로필 사진을 삭제 한다.
'''
def user_delete_photo
if @user
if @user.delete_photo
return render_200
end
end
render_422
end
...
유저 모델 user.rb
구현 : uploader 마운트를 잘 해준다.
...
mount_uploaders :avatars, AvatarUploader
serialize :avatars, JSON
...
def thumbnails
photo = self.last_photo
end
def push_photo photo
user = self
user.photos += [ photo ]
user.save
end
def delete_photo
user = self
user.avatars = []
user.save
end
...
마이그레이션 만들기
rails g migration add_avatars_to_users avatars:string
rake db:migrate
로 마이그레이트 실행
uploaders/avatar_uploader.rb
class AvatarUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
# 썸네일 만들기
version :thumb_xs do
process resize_to_fill: [8, 8]
end
version :thumb_sm do
process resize_to_fill: [64, 64]
end
def extension_allowlist
%w(jpg jpeg gif png bmp)
end
end
이미지 썸네일 제작 라이브러리는 MiniMagick
을 사용 했다.