본문 바로가기
개발/게시판 만들기

[Vue] Vue.js 게시판 만들기 11 - 로그인 화면 만들기

by onethejay 2022. 6. 21.
728x90

안녕하세요.
이전에 작성한 Vue.js 게시판 만들기를 정말 많은 분들이 찾아봐주셨습니다.
이번 포스팅부터는 로그인과 댓글 기능을 구현하면서 게시판 이외에도 실습해볼 수 있는 샘플 프로젝트를 만들어보려 합니다.
이전의 프론트엔드와 백엔드 프로젝트를 사용하므로 전의 포스팅들을 참고해주세요.
(이후 포스팅부터는 챕터별로 브랜치를 업로드할 예정이며, 이번 포스팅까지의 화면 소스는 vue-frontend의 chap11 브랜치에 업로드 되어있습니다.)

로그인 화면 생성

우선 로그인을 진행할 화면을 만들도록 하겠습니다.

vue-frontboard 프로젝트를 열고 views 폴더 아래로 common 폴더와 안에 Login.vue 파일을 생성합니다.

로그인 화면 소스를 간단하게 작성합니다.

<template>
  <div>
    <div>
      <h2>Please Log In</h2>
      <div id="loginForm">
        <form @submit.prevent="fnLogin">
          <p>
            <input class="w3-input" name="uid" placeholder="Enter your ID" v-model="user_id"><br>
          </p>
          <p>
            <input name="password" class="w3-input" placeholder="Enter your password" v-model="user_pw" type="password">
          </p>
          <p>
            <button type="submit" class="w3-button w3-green w3-round">Login</button>
          </p>
        </form>
      </div>
    </div>
  </div>
</template>

<script>

export default {
  data() {
    return {
      user_id: '',
      user_pw: ''
    }
  },
  methods: {
    fnLogin() {
      if (this.user_id === '') {
        alert('ID를 입력하세요.')
        return
      }

      if (this.user_pw === '') {
        alert('비밀번호를 입력하세요.')
        return
      }

      alert('로그인 되었습니다.')
    }
  }
}
</script>

<style>
#loginForm {
  width: 500px;
  margin: auto;
}
</style>

로그인 화면에 접근할 수 있게 router/index.js에 Login 컴포넌트를 추가합니다.

import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import List from "@/views/board/List"
import Detail from "@/views/board/Detail"
import Write from "@/views/board/Write"
import Login from "@/views/common/Login"

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/login',
    name: 'Login',
    component: Login  //로그인 컴포넌트 추가
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {
    path: '/board/list',
    name: 'List',
    component: List
  },
  {
    path: '/board/detail',
    name: 'Detail',
    component: Detail
  },
  {
    path: '/board/write',
    name: 'Write',
    component: Write
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

Header.vue의 메뉴 목록에 로그인을 추가합니다.

<template>
  <header>
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link> |
      <router-link to="/board/list">게시판</router-link> |
      <router-link to="/login">로그인</router-link>
    </div>
  </header>
  <hr/>
</template>

<script>
export default {

}
</script>

<style scoped>

</style>

서버를 실행하고 화면을 확인합니다.


상태 관리 패키지 Vuex 설치와 세팅

위에서 작성한 코드는 단순하게 화면을 만들고 버튼을 클릭하면 로그인 되었다는 Alert만 표시하였습니다.
실제 로그인 기능을 구현하기 전에, Vue에서 변수 상태 관리를 도와주는 Vuex 패키지를 설치하고 기본 세팅을 진행하겠습니다.

먼저 vuex 패키지를 설치합니다.

npm install vuex

Vuex로 관리될 변수 혹은 데이터들을 위해 src 폴더 아래에 vuex 폴더를 추가하고 store.js 파일을 생성합니다.

// src/vuex/store.js
import {createStore} from "vuex"
import getters from "./getters"
import mutations from "./mutations"

export default createStore({
  state: {
    user: null,
    isLogin: false,
  },
  mutations,
  getters,

})

import 되어있는 getters와 mutations(+ mutation_types)도 같이 생성합니다.

// src/vuex/getters.js
export default {
  getUserId: state => state.userId,
  getErrorState: state => state.errorState,
  getIsAuth: state => state.isAuth,
  loggedIn(state) {
    return !!state.user
  }
}
// src/vuex/mutation.js
import * as types from './mutation_types'

export default {
    [types.USER_ID] (state, userId) {
        state.userId = userId
    },
    [types.ERROR_STATE] (state, errorState) {
        state.errorState = errorState
    },
    [types.IS_AUTH] (state, isAuth) {
        state.isAuth = isAuth
    }
}
// src/vuex/mutation_types.js
export const USER_ID = 'USER_ID'
export const ERROR_STATE = 'ERROR_STATE'
export const IS_AUTH = 'IS_AUTH'

main.js에서 store를 사용할 수 있도록 Vue app에 등록합니다.

import './assets/w3.css'
import './assets/common.css'

import { createApp } from 'vue'
import App from './App.vue'
import axios from 'axios'
import router from './router'
import store from './vuex/store'  //1. store 추가

const app = createApp(App)
app.config.globalProperties.$axios = axios  //전역변수로 설정 컴포넌트에서 this.$axios 호출할 수 있음
app.config.globalProperties.$serverUrl = '//localhost:8081' //api server
app.config.globalProperties.$store = store
app
  .use(router)
  .use(store)   //2. store 등록
  .mount('#app')

현재까지의 vuex 폴더 구성입니다.

로그인 API 구현

입력된 ID와 PW로 로그인 API를 호출하는 Service 파일을 구현하겠습니다.

아직 백엔드는 구현하지 않았으므로 로그인에 성공했다고 가정한 샘플 데이터를 세팅하겠습니다.
src 폴더 아래에 service 폴더를 추가하고 loginAPI.js 파일을 생성합니다.

// src/service/loginAPI.js
const getUserInfo = (userId, userPw) => {
  const reqData = {
    'user_id': userId,
    'user_pw': userPw
  }

  return {
    'data': {
      'user_id': reqData.user_id,
      'user_token': 'user_test_token',
      'user_role': 'ADM'
    }
  }
}

export default {
  async doLogin(userId, userPw) {
    try {
      const getUserInfoPromise = getUserInfo(userId, userPw)
      const [userInfoResponse] = await Promise.all([getUserInfoPromise])
      if (userInfoResponse.data.length === 0) {
        return 'notFound'
      } else {
        localStorage.setItem('user_token', userInfoResponse.data.user_token)
        localStorage.setItem('user_role', userInfoResponse.data.user_role)
        return userInfoResponse
      }
    } catch (err) {
      console.error(err)
    }
  }
}

vuex 폴더 아래에 actions.js 파일을 생성합니다.

// src/vuex/actions.js
import {USER_ID, IS_AUTH, ERROR_STATE} from './mutation_types'
import loginAPI from '../service/loginAPI'

let setUserId = ({commit}, data) => {
  commit(USER_ID, data)
}

let setErrorState = ({commit}, data) => {
  commit(ERROR_STATE, data)
}

let setIsAuth = ({commit}, data) => {
  commit(IS_AUTH, data)
}

// 백엔드에서 반환한 결과값을 가지고 로그인 성공 실패 여부를 vuex에 넣어준다.
let processResponse = (store, loginResponse) => {
  switch (loginResponse) {
    case 'notFound':
      setErrorState(store, 'Wrong ID or Password')
      setIsAuth(store, false)
      break
    default:
      setUserId(store, loginResponse.user_id)
      setErrorState(store, '')
      setIsAuth(store, true)
  }
}

export default {
  async login (store, {user_id, user_pw}) {
    let loginResponse = await loginAPI.doLogin(user_id, user_pw)
    processResponse(store, loginResponse)
    return store.getters.getIsAuth  // 로그인 결과를 리턴한다
  }
}

vuex/store.js에 actions를 추가합니다.

// src/vuex/store.js
import {createStore} from "vuex";
import getters from "./getters";
import mutations from "./mutations";
import actions from "./actions";    //추가

export default createStore({
  state: {
    user: null,
    isLogin: false,
  },
  mutations,
  getters,
  actions     //추가
});

Vuex store에 등록된 login 함수를 호출하여 로그인을 진행할 수 있도록 Login.vue 소스를 수정합니다.

<script>
import {mapActions, mapGetters} from 'vuex'   //vuex 추가

export default {
  data() {
    return {
      user_id: '',
      user_pw: ''
    }
  },
  methods: {
    ...mapActions(['login']),     //vuex/actions에 있는 login 함수

    async fnLogin() {       //async 함수로 변경
      if (this.user_id === '') {
        alert('ID를 입력하세요.')
        return
      }

      if (this.user_pw === '') {
        alert('비밀번호를 입력하세요.')
        return
      }

      //로그인 API 호출 
      try {
        let loginResult = await this.login({user_id: this.user_id, user_pw: this.user_pw})
        if (loginResult) alert('로그인 결과 : ' + loginResult)
      } catch (err) {
        if (err.message.indexOf('Network Error') > -1) {
          alert('서버에 접속할 수 없습니다. 상태를 확인해주세요.')
        } else {
          alert('로그인 정보를 확인할 수 없습니다.')
        }
      }
    }
  },
  computed: {
    ...mapGetters({
      errorState: 'getErrorState'
    })
  }
}
</script>

임의의 ID와 비밀번호를 입력하고 로그인을 진행해봅니다.

현재는 loginAPI.js의 getUserInfo 함수에서 무조건 로그인에 성공한 데이터를 return하고 있습니다.

다음 포스팅에서는 백엔드 서버에서의 로그인 처리를 구현하기 전에 DB와 시큐리티 설정을 진행하도록 하겠습니다.

728x90

댓글