저번 axios 이후로 ... PPT 126p

 

SpringBoot (back)은 켜놓고 있어야 한다.

 

apis - memberAPI.js 파일 코드 추가

// REST API와 통신하는 모듈

import axios from "axios";
import qs from "qs";

// axois함수를 사용하면 Promise형태로 값을 반환한다.

function join(member) {
    /*
    member = {
        mid: "seongminQa",
        mname: "김성민2",
        mpassword: "12345",
        memail: "seongminQa@github.com"
    }
    */
    // POST 방식이며 : raw / json 방식으로 전달
    return axios.post("/member/join", member);
}

 function login(member) {
    /*
    member = {
        mid : "user",
        mpassword : "12345"
    }
    */
   // POST 방식이며 : QueryString 방식으로 전달 (포스트맨 : x-www-from ... )
   // (mid=user&mPassword=12345) 형식..
   return axios.post("/member/login", qs.stringify(member));
 }

 export default {
    join,
    login
 }

 

ppt 131p

main.js에 

import axiosConfig from './apis/axiosConfig';

추가하기.

 

ppt 127p

store 폴더 - index.js에 state에 accessToken 추가, getters에 getAccessToken 추가, mutations에 accessToken 세터 추가,

actions에 코드 추가, 

p 126 ~ p 133

 

* store - index.js

import { createStore } from 'vuex'
import counter from './counter';
import axios from 'axios';
import axiosConfig from '@/apis/axiosConfig';

/*export default createStore({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})*/

// Store 객체를 생성
const store = createStore({
  // 루트 상태를 정의
  state: {
    userId: "",
    accessToken: ""
  },
  //루트 상태 값을 읽는 메소드(Getter) 정의
  getters: {
    getUserId(state, getters, rootState, rootGetters){
      /*
      root의 개념
      */
      return state.userId;
    },

    getAccessToken(state, getters, rootState, rootGetters) {
      return state.accessToken;
    }
  },
  //루트 상태 값을 변화시키는 메소드(Setter) 정의(동기 방식)
  mutations: {
    setUserId(state, payload){ // payload는 변경할 값을 가지고 있는 객체 (전달될 데이터)
      state.userId = payload;
    },

    setAccessToken(state, payload) {
      state.accessToken = payload;
    }
  },
  //비동기 작업을 실행하고 결과에 따라 상태 값을 변화시키는 메소드 정의 
  actions: { // 서버 통신 작업을 많이 할 것이다.
    loginAction(context, payload) { // login작업이니, payload에는 아이디와 비밀번호의 값을 가져올 것이다.
      // 로그인을 한다치면 서버와 통신 작업을 할 것이다.
      // ... 3초를 소요한다면? 다른 작업을 생각하여 비동기로 처리해야 한다. promise 개념
      new Promise((resolve, reject) => {
        // 이 자리에 서버 통신 코드가 들어갈 것이다.
        // 이것을 이용하여 3초라는 시간이 소요되는 시간에 다른 작업을 할 수 있는 것이다.
        
        if(true){
          // 로그인 성공을 했을 경우에
          resolve ({result:"success", userId: payload.userId});
        } else {
          // 로그인 실패를 했을 경우
          resolve ({result:"fail", reason:"password is wrong"});
        }

        // 성공했을 때 resolve함수 내의 매개값들이 변수 data로 들어가게 되고,
        // 실패했을 경우 resolve함수 내의 매개값들이 error로 들어가게 된다.
      })
      .then((data) => { // data는? : 
        // 작업이 성공적으로 처리가 되었을 경우 실행
        // 상태 변경 작성 작업을 한다.
        console.log("login 성공");
        context.commit("setUserId", data.userId); // mutations의 셋터함수와 userId
      })
      .catch((error) => { 
        // 작업이 실패처리가 될 경우 실행
        console.log("login 실패");

      });
    },

    // 브라우저가 재실행될 때 인증 정보를 전역상태로 복구
    // 브라우저를 껐다가 다시 킬때 or 브라우저에서 새로고침을 클릭할 때
    loadAuth(context, payload) {
      context.commit("setUserId", localStorage.getItem("userId") || "");
      
      // apis - axiosConfig.js 에서 코드 수정후 추가한 내용
      // accessToken 전역 상태 설정
      const accessToken = localStorage.getItem("accessToken");
      context.commit("setAccessToken", localStorage.getItem("accessToken") || "");
      
      // Axios 요청 공통 해더에 인증 정보 추가
      if(accessToken != "") {
        axiosConfig.addAuthHeader();
      }
    },

    // 로그인 성공했을 때 인증 정보를 전역 상태 및 로컬 스토리지에 파일로 저장
    saveAuth(context, payload) {
      /*
      payload = {
        userId: user,
        accessToken: "xxxx-yyyy-zzzz"
      }
      */
     
      // 전역 상태값을 변경
      context.commit("setUserId", payload.userId);
      context.commit("setAccessToken", payload.accessToken);
      // 로컬 파일에 저장
      localStorage.setItem("userId", payload.userId);
      localStorage.setItem("accesssToken", payload.accessToken);
      // Axios 요청 공통 해더에 인증 정보 추가
      axiosConfig.addAuthHeader(payload.accessToken);
    },

    // 로그아웃할 때 인증 정보를 모두 삭제한다.
    deleteAuth(context, payload) {
      // 전역 상태값 변경
      context.commit("setUserId", "");
      context.commit("setAccessToken", "");
      // 로컬 파일에서 삭제
      localStorage.removeItem("userId");
      localStorage.removeItem("accessToken");
      // Axios 요청 공통 해더에 인증 정보 삭제
      axiosConfig.removeAuthHeader();
    }
  },
  //루트 하위 상태 모듈 추가
  modules: {
    // counter 이름으로 counter 모듈을 추가
    counter: counter    // ppt 110p의 store.commit("counter/serCount", value); 부분에서 counter는 왼쪽 변수의 이름을 의미한다.
  }
});

// Store 객체를 내보내기
export default store;

 

 

* main.js

import { createApp } from 'vue' // createApp은 export로 내보낸 것.
import App from './App.vue' // App은 export default로 지정되어 있기 때문에 {}로 나타내지 않음
import router from './router/index.js'

// Bootstrap 관련 JavaScript와 CSS 로딩
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import store from './store'

import axiosConfig from './apis/axiosConfig';

//브라우저 재실행시 인증 정보를 가져옴 -------------------------
//인증 정보를 store에 저장
store.dispatch("loadAuth"); //비동기
//-----------------------------------------------------------

// App컴포넌트를 <div id="app"></div>에 내용으로 추가
createApp(App)
    .use(store)
    .use(router)
    .mount('#app') // index.html에서 id가 app인 태그에 적용

 


ppt 136p

# 회원가입 컴포넌트 만들기

 

1. src - views - Ch08RestAPI - Exam02Join - index.vue

2. router 등록

3. AppMenu 리스트 추가

 

* Exam02Join - index.vue

<template>
    <div class="card">
        <div class="card-header">BoardList</div>
        <div class="card-body">
            <div class="mb-3">
                <RouterLink to="/" class="btn btn-success btn-sm">글쓰기</RouterLink>
            </div>

            <table class="table table-striped table-bordered">
                <thead>
                    <th class="text-center" style="width:70px">번호</th>
                    <th class="text-center">제목</th>
                    <th class="text-center" style="width:90px">글쓴이</th>
                    <th class="text-center" style="width:120px">날짜</th>
                    <th class="text-center" style="width:70px">조회수</th>
                </thead>
                <tbody>
                    <tr v-for="board in page.boards" :key="board.bno">
                        <td class="text-center">{{ board.bno }}</td>
                        <td>
                            <RouterLink to="/">{{ board.btitle }}</RouterLink>
                        </td>
                        <!-- board.mid는 DB의 저장된 컬럼명 -->
                        <td class="text-center">{{ board.bwriter }}</td>
                        <td class="text-center">{{ new Date(board.bdate).toLocaleDateString() }}</td>
                        <td class="text-center">{{ board.bhitcount }}</td>
                    </tr>

                    <tr>
                        <td colspan="5" class="text-center">
                            <button class="btn btn-outline-primary btn-sm me-1" @click="changePageNo(1)">처음</button>
                            <button v-if="page.pager.groupNo > 1" class="btn btn-outline-info btn-sm me-1"
                                @click="changePageNo(page.pager.startPageNo - 1)">이전</button>
                            <button v-for="pageNo in page.pager.pageArray" :key="pageNo"
                                :class="(page.pager.pageNo === pageNo) ? 'btn-danger' : 'btn-outline-success'"
                                class="btn btn-sm me-1" @click="changePageNo(pageNo)">
                                {{ pageNo }}
                            </button>
                            <button v-if="page.pager.groupNo < page.pager.totalGroupNo"
                                class="btn btn-outline-info btn-sm me-1"
                                @click="changePageNo(page.pager.endPageNo + 1)">다음</button>
                            <button class="btn btn-outline-info btn-sm me-1"
                                @click="changePageNo(page.pager.totalPageNo)">맨끝</button>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</template>

<script setup>
import { ref, watch } from 'vue';
// 상태 정의
const page = ref({
    boards: [],
    pager: {}
});


import boardAPI from '@/apis/boardAPI';

// GET 방식으로 전달된 파라미터 값 얻기
// http://localhost/Exam04Board/BoardList?pageNo=2
// ppt 61p useRoute를 이용 --> PathVariable로 전달받기

import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const pageNo = route.query.pageNo || 1;  // pageNo값이 없다면 기본값 1로 줌 // route를 이용하여 쿼리스트링 형식으로 받음

// 게시물 목록을 가져오는 메소드 정의
async function getBoardList(pageNo) {
    try {
        const response = await boardAPI.getBoardList(pageNo);  // Pomise를 리턴하기 때문에 await를 붙여줌
        page.value.boards = response.data.boards;
        page.value.pager = response.data.pager;
    } catch (error) {
        console.log(error);
    }
}

// 게시물 목록 가져오기
getBoardList(pageNo);


// 페이저의 버튼을 클릭했을 때 해당 페이지로 이동하는 메소드 정의
const router = useRouter();

function changePageNo(argPageNo) {
    router.push(`/Ch08RestAPI/Exam04Board/BoardList?pageNo=${argPageNo}`);
}

// 요청 경로의 변경을 감시 // 이 코드로인해 페이지 버튼을 눌렀을 때 이동이 가능함..
watch(route, (newRoute, oldRoute) => {
    if (newRoute.path == "/Ch08RestAPI/Exam04Board/BoardList") {
        if (newRoute.query.pageNo) {
            getBoardList(newRoute.query.pageNo);
        } else {
            getBoardList(1);
        }
    }
});

</script>

<style scoped></style>

 

* Exam03Login - index.vue

 

 

 

# 로그아웃 구현하기

* components의 AppHeader.vue 부분 수정하기

<template>
    <div class="card">
        <div class="card-header">Exam03Login</div>
        <div class="card-body">
            <!-- '$'가 들어가면 '내장객체'를 의미함
                store를 이용해보자.
            -->
            <div v-if="$store.state.userId === ''">

                <form @submit.prevent="handleLogin">
                    <div class="input-group mb-2">
                        <span class="input-group-text">아이디</span>
                        <input type="text" class="form-control" v-model="member.mid">
                    </div>

                    <div class="input-group mb-2">
                        <span class="input-group-text">비밀번호</span>
                        <input type="password" class="form-control" v-model="member.mpassword">
                    </div>

                    <input type="submit" value="로그인" class="btn btn-danger btn-sm me-2" />
                    <button type="button" class="btn btn-info btn-sm" @click="handleReset">재작성</button>
                </form>
            </div>

            <div v-if="$store.state.userId !== ''">
                <div class="btn btn-success btn-sm mt-3" @click="handleLogout">로그아웃</div>
            </div>

        </div>
    </div>
</template>

<script setup>
import { ref } from 'vue';

import memberAPI from '@/apis/memberAPI';

import { useStore } from 'vuex';  // Store 사용

// member 초기화 작업 
// v-model값의 기본값을 주어야 한다. (값을 안주면 처음 컴포넌트 진입시 에러남)
const member = ref({
    mid: "",
    // mPassword: ""
    mpassword: ""
});

const store = useStore(); // 전역 상태의 값을 갖는 store

// 로그인 버튼 클릭시 실행
async function handleLogin() {
    try {
        const data = JSON.parse(JSON.stringify(member.value));
        // 
        console.log(data);
        const response = await memberAPI.login(data);
        if (response.data.result === "success") {
            const payload = {
                userId: response.data.mid,
                accessToken: response.data.accessToken
            };
            store.dispatch("saveAuth", payload);
        }
    } catch (error) {
        console.log(error);
    }
}


// 재작성 버튼 클릭시 실행
function handleReset() {
    member.value.mid = "";
    member.value.mpassword = "";

}

// 로그아웃 버튼 클릭시 실행
function handleLogout() {
    store.dispatch("deleteAuth");
}
</script>

<style scoped></style>

 

# 게시판 만들기

* Ch08RestAPI폴더 - Exam04Board폴더 - BoardList.vue

<template>
    <div class="card">
        <div class="card-header">BoardList</div>
        <div class="card-body">
            <div class="mb-3">
                <RouterLink to="/" class="btn btn-success btn-sm">글쓰기</RouterLink>
            </div>

            <table class="table table-striped table-bordered">
                <thead>
                    <th class="text-center" style="width:70px">번호</th>
                    <th class="text-center">제목</th>
                    <th class="text-center" style="width:90px">글쓴이</th>
                    <th class="text-center" style="width:120px">날짜</th>
                    <th class="text-center" style="width:70px">조회수</th>
                </thead>
                <tbody>
                    <tr v-for="board in page.boards" :key="board.bno">
                        <td class="text-center">{{ board.bno }}</td>
                        <td>
                            <RouterLink to="/">{{ board.btitle }}</RouterLink>
                        </td>
                        <!-- board.mid는 DB의 저장된 컬럼명 -->
                        <td class="text-center">{{ board.bwriter }}</td>
                        <td class="text-center">{{ new Date(board.bdate).toLocaleDateString() }}</td>
                        <td class="text-center">{{ board.bhitcount }}</td>
                    </tr>

                    <tr>
                        <td colspan="5" class="text-center">
                            <button class="btn btn-outline-primary btn-sm me-1" @click="changePageNo(1)">처음</button>
                            <button v-if="page.pager.groupNo > 1" class="btn btn-outline-info btn-sm me-1"
                                @click="changePageNo(page.pager.startPageNo - 1)">이전</button>
                            <button v-for="pageNo in page.pager.pageArray" :key="pageNo"
                                :class="(page.pager.pageNo === pageNo) ? 'btn-danger' : 'btn-outline-success'"
                                class="btn btn-sm me-1" @click="changePageNo(pageNo)">
                                {{ pageNo }}
                            </button>
                            <button v-if="page.pager.groupNo < page.pager.totalGroupNo"
                                class="btn btn-outline-info btn-sm me-1"
                                @click="changePageNo(page.pager.endPageNo + 1)">다음</button>
                            <button class="btn btn-outline-info btn-sm me-1"
                                @click="changePageNo(page.pager.totalPageNo)">맨끝</button>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</template>

<script setup>
import { ref, watch } from 'vue';
// 상태 정의
const page = ref({
    boards: [],
    pager: {}
});


import boardAPI from '@/apis/boardAPI';

// GET 방식으로 전달된 파라미터 값 얻기
// http://localhost/Exam04Board/BoardList?pageNo=2
// ppt 61p useRoute를 이용 --> PathVariable로 전달받기

import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const pageNo = route.query.pageNo || 1;  // pageNo값이 없다면 기본값 1로 줌 // route를 이용하여 쿼리스트링 형식으로 받음

// 게시물 목록을 가져오는 메소드 정의
async function getBoardList(pageNo) {
    try {
        const response = await boardAPI.getBoardList(pageNo);  // Pomise를 리턴하기 때문에 await를 붙여줌
        page.value.boards = response.data.boards;
        page.value.pager = response.data.pager;
    } catch (error) {
        console.log(error);
    }
}

// 게시물 목록 가져오기
getBoardList(pageNo);


// 페이저의 버튼을 클릭했을 때 해당 페이지로 이동하는 메소드 정의
const router = useRouter();

function changePageNo(argPageNo) {
    router.push(`/Ch08RestAPI/Exam04Board/BoardList?pageNo=${argPageNo}`);
}

// 요청 경로의 변경을 감시 // 이 코드로인해 페이지 버튼을 눌렀을 때 이동이 가능함..
watch(route, (newRoute, oldRoute) => {
    if (newRoute.path == "/Ch08RestAPI/Exam04Board/BoardList") {
        if (newRoute.query.pageNo) {
            getBoardList(newRoute.query.pageNo);
        } else {
            getBoardList(1);
        }
    }
});

</script>

<style scoped></style>

 

* Ch08RestAPI폴더 - Exam04Board폴더 - BoardWrite.vue

<template>
    <div class="card">
        <div class="card-header">BoardWrite</div>
        <div class="card-body">

            <form @submit.prevent="handleSubmit">
                <div class="form-group row">
                    <label for="btitle" class="col-sm-2 col-form-label">제목</label>
                    <div class="col-sm-10">
                        <input id="btitle" type="text" class="form-control" v-model="board.btitle" />
                    </div>
                </div>
                <div class="form-group row mt-3">
                    <label for="bcontent" class="col-sm-2 col-form-label">내용</label>
                    <div class="col-sm-10">
                        <textarea id="bcontent" type="text" class="form-control" v-model="board.bcontent"></textarea>
                    </div>
                </div>
                <div class="form-group row mt-3">
                    <label for="battach" class="col-sm-2 col-form-label">첨부그림</label>
                    <div class="col-sm-10">
                        <!-- DOM 참조를 위해 ref 속성을 이용 
                            마운트가 된 이후에 battach값이 변함 -->
                        <input id="battach" type="file" class="form-control-file" ref="battach" />
                    </div>
                </div>
                <div class="form-group row mt-3">
                    <div class="col-sm-12 d-flex justify-content-center">
                        <input type="submit" class="btn btn-info btn-sm me-2" value="추가" />
                        <input type="button" class="btn btn-info btn-sm" value="취소" @click="handleCancle" />
                    </div>
                </div>
            </form>

        </div>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import boardAPI from '@/apis/boardAPI';
import { useRouter } from 'vue-router';

// 상태 정의
const board = ref({
    btitle: "",
    bcontent: ""
    // battach: null, // 문자열 데이터가 아님 // 빼도 상관없다
});

const battach = ref(null);
const router = useRouter();

// 추가 버튼을 클릭했을 때 실행
async function handleSubmit() {
    // console.log(board.value);
    // console.log(battach.value);

    // multipart/form-data 객체 생성
    const formData = new FormData();
    // 문자 파트 넣기
    formData.append("btitle", board.value.btitle);
    formData.append("bcontent", board.value.bcontent);
    // 파일 파트 넣기
    const elBattach = battach.value;
    if (elBattach.files.length != 0) {  // 해당 input태그에 'multiple'이라는 속성을 넣을 수 있다. 따라서 files라고 적어줘야함
        formData.append("battach", elBattach.files[0]);
    }

    // 게시물 쓰기 요청하기 -- boardAPI import하고나서.
    try {
        const response = await boardAPI.boardWrite(formData); // 게시물 등록하기
        // 게시글 쓰고 뒤로가기 구현 (router import하기)
        router.back();
    } catch (error) {
        console.log(error);
    }

}

// 취소 버튼을 클릭했을 때 실행
function handleCancle() {

}
</script>

<style scoped></style>

 

 

* src / apis / boardAPI.js 수정

// REST API와 통신하는 모듈

import axios from "axios";
import qs from "qs";

// 게시물 목록 가져오기
function getBoardList(pageNo = 1) {
    // GET : http://localhost/board/list?pageNo=1
    return axios.get("/board/list", {params: {pageNo: pageNo}});  // Promise를 리턴

    // GET : http://localhost/board/list?pno=1 일 경우!!
    // return axios.get("/board/list", {params: {pno: pageNo}});
}

// 게시물 쓰기
function boardWrite(formData) {
    return axios.post("/board/create", formData);
}

export default {
    getBoardList,
    boardWrite
};

 

+ Recent posts