Vue.js学習メモ(画像アップロード)
ログイン機能を実装しているタスク管理アプリで、 個々のユーザーがプロフィール画像を登録できるように、画像アップロード機能を実装することを考えます。 RailsのActiveStorageを使って画像登録します。
- ①userモデルにプロフィール画像用のカラム(avatar)を設定
- ②ユーザーに紐づくプロフィール画像のurlを返すメソッドをモデルに
- ③プロフィールのコントローラーとルーティングを設定
- ④userコントローラーにapiのエンドポイントとストロングパラメータを追加
- ⑤storeにユーザー更新のAPIリクエストを送る処理を記載
- ⑥プロフィールページの作成
- ⑦プロフィールページのルーティング
- ⑧ヘッダーにプロフィール画像の表示+プロフィールページへのリンクを設定
①userモデルにプロフィール画像用のカラム(avatar)を設定
user.rb
class User< ApplicationRecord has_one_attached :avatar end
②ユーザーに紐づくプロフィール画像のurlを返すメソッドをモデルに
class User< ApplicationRecord def avatar_url avatar.attached? ? Rails.application.routes.url_helpers.rails_blob_path(avatar, only_path: true) : nil end
③プロフィールのコントローラーとルーティングを設定
api/profile_controller.rb
class Api::ProfileController < ApplicationController before_action :authenticate! def update user = User.find(current_user.id) if user.update(user_params) render json: user, methods: [:avatar_url] else render json: user.errors, status: :bad_request end end private def user_params params.require(:user).permit(:name, :avatar) end end
Rails.application.routes.draw do namespace :api, format: 'json' do resource :profile end end
④userコントローラーにapiのエンドポイントとストロングパラメータを追加
api/users_controller.rb
class Api::UsersController < ApplicationController def me render json: current_user, methods: [:avatar_url] end private def user_params params.require(:user).permit(:email, :password, :password_confirmation, :name, :avatar) end end
⑤storeにユーザー更新のAPIリクエストを送る処理を記載
const actions = { . . . updateUser({ commit, state }, user) { return axios.patch(`profile/${state.authUser.id}`, user) .then(res => { commit('setUser', res.data) }) } }
⑥プロフィールページの作成
profile/index.vue
<template> <div id="profile-form"> <ValidationObserver v-slot="{ handleSubmit }"> <div class="form-group text-left"> <ValidationProvider v-slot="{ errors }" rules="required"> <label for="name">ユーザー名</label> <input id="name" v-model="user.name" type="text" class="form-control" placeholder="username" > <span class="text-danger">{{ errors[0] }}</span> </ValidationProvider> </div> <div class="form-group text-left"> <ValidationProvider v-slot="{ errors }" ref="provider" rules="image"> <label for="avatar" class="d-block >プロフィール画像</label> <img :src="user.avatar_url" class="my-3" width="150px"> <input id="avatar" type="file" accept="image/png,image/jpeg" class="form-control-file" @change="handleChange" > <span class="text-danger">{{ errors[0] }}</span> </ValidationProvider> </div> <button type="submit" class="btn btn-primary" @click="handleSubmit(update)" > 更新 </button> </ValidationObserver> </div> </template> <script> import { mapGetters, mapActions } from 'vuex' export default { name: 'ProfileIndex', data() { return { user: { name: "", avatar_url: "" }, uploadAvatar: "" } }, computed: { ...mapGetters('users', ['authUser']) }, created() { this.user = Object.assign({}, this.authUser) }, methods: { ...mapActions('users', ['updateUser']), async handleChange(event) { const { valid } = await this.$refs.provider.validate(event) if (valid) this.uploadAvatar = event.target.files[0] }, update() { const formData = new FormData() formData.append("user[name]", this.user.name) if (this.uploadAvatar) formData.append("user[avatar]", this.uploadAvatar) try { this.updateUser(formData) this.$router.push({ name: "TaskIndex" }) } catch (error) { console.log(error); } }, } } </script> <style scoped> </style>
⑦プロフィールページのルーティング
router/index.js
import ProfileIndex from "../pages/profile/index" const router = new Router({ routes: [ . . . { path: '/profile', component: ProfileIndex, name: 'ProfileIndex', meta: { requiredAuth: true }, } ] })
⑧ヘッダーにプロフィール画像の表示+プロフィールページへのリンクを設定
components/TheHeader.vue
<li class="nav-item active avatar-image-wrapper"> <img :src="authUser.avatar_url" class="rounded avatar-image" > </li> <li class="nav-item active"> <router-link :to="{ name: 'ProfileIndex' }" class="nav-link" > プロフィール </router-link> </li>
ハイライトが上手く表示されてないので、次の土日に直します(直した)。