저번 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
};
'JAVA' 카테고리의 다른 글
68일차 2024-06-05 (Vue.js 12일차 & SpringBoot 5일차) (0) | 2024.06.05 |
---|---|
66일차 2024-06-03 (Vue.js 10일차 & SpringBoot 3일차) (0) | 2024.06.03 |
65일차 2024-05-31 (Vue 9일차 & SpringBoot 2일차) (0) | 2024.05.31 |
64일차 2024-05-30 (Vue 8일차 & SpringBoot 1일차) (0) | 2024.05.30 |
63일차 2024-05-29 (Vue 7일차) (0) | 2024.05.29 |