오늘의 Vue.js 실습 목표는 "공지사항/게시판 만들어보기!" 입니다.
본 게시글에서 다루는 사항은 공지사항/게시판 기본(테이블) 화면의 이벤트와 API Server를 연동하기 위해 Axios를 이용하여 구현하는 과정입니다.
공지사항/게시판 구현하기 - Axios로 REST API 호출/연동하기
사전 준비
아래 사항에 대해서 사전 준비가 완료되지 않으신 분들은 아래 링크를 참조하여 사전 준비를 진행합니다.
- [실습] 뷰(Vue)로 공지사항/게시판 구현하기 - Root/상위 컴포넌트 화면 기능 개발
- [실습] 스프링부트(Spring Boot)로 공지사항/게시판 구현하기 - REST API(Controller, Service) 구현하기
결과 이미지
구현하기에 앞서 본 글을 따라하시면 아래와 같이 공지사항 화면과 API Server가 연동된 결과를 확인하실 수 있습니다.
▶ 초기 상태
DB (MySQL)
화면
▶ REGISTER
DB (MySQL)
화면
▶ MODIFY
DB (MySQL)
화면
▶ DELETE
DB (MySQL)
화면
소스 코드
src\views\user-web\notice\BaseNotice.vue
전체 코드를 확인하실 분들은 아래 더보기를 클릭해주세요.
더보기
<template>
<v-container>
<div class="text-h4 font-weight-medium ma-2">NOTICE</div>
<v-divider></v-divider>
<v-row>
<v-col cols="8">
<NoticeList v-on:changeMode="changeMode"></NoticeList>
</v-col>
<v-col cols="4">
<NoticeDetail
:mode="mode"
:notice="notice"
v-on:finishProcess="finishProcess"
></NoticeDetail>
</v-col>
</v-row>
</v-container>
</template>
<script>
import NoticeList from "./../../../components/user-web/notice/NoticeList.vue";
import NoticeDetail from "./../../../components/user-web/notice/NoticeDetail.vue";
const MODE_INIT = "INIT";
const MODE_DELETE = "DELETE";
export default {
data() {
return {
mode: MODE_INIT,
notice: [],
};
},
methods: {
changeMode: function (mode, notice) {
this.mode = mode;
if (mode !== MODE_DELETE && mode !== MODE_INIT) {
if (notice[0] !== undefined) {
this.notice = notice[0];
} else {
this.notice = [];
}
} else {
this.notice.id = notice[0].id;
}
},
finishProcess: function () {
window.location.reload();
this.mode = MODE_INIT;
},
},
components: {
NoticeList,
NoticeDetail,
},
};
</script>
<style></style>
변경 사항
<script>
...
finishProcess: function () {
window.location.reload();
...
},
...
</script>
- Axios로 REST API 처리 후 정상 처리 응답코드(200) 회신시 윈도우를 Refresh하도록 설정하는 코드 추가
src\components\user-web\notice\NoticeList.vue
전체 코드를 확인하실 분들은 아래 더보기를 클릭해주세요.
더보기
<!-- NoticeList.vue -->
<template>
<div class="ma-md-5">
<div class="d-flex justify-end mt-5">
<!-- For Notice List Manipulation > View Detail -->
<v-btn
text
outlined
class="mx-md-1 elevation-2"
:disabled="!hasSelectedRow()"
@click="viewNotice"
>VIEW DETAIL
</v-btn>
<!-- For Notice List Manipulation > Register -->
<v-btn text outlined class="mx-md-1 elevation-2" @click="registerNotice"
>REGISTER
</v-btn>
<!-- For Notice List Manipulation > Modify -->
<v-btn
text
outlined
class="mx-md-1 elevation-2"
:disabled="!hasSelectedRow()"
@click="modifyNotice"
>MODIFY
</v-btn>
<!-- For Notice List Manipulation > Delete -->
<v-dialog v-model="dialogDelete" persistent max-width="290">
<template v-slot:activator="{ on, attrs }">
<v-btn
text
outlined
class="mx-md-1 elevation-2"
:disabled="!hasSelectedRow()"
v-bind="attrs"
v-on="on"
>DELETE
</v-btn>
</template>
<v-card>
<v-card-title class="text-h5">
Are you sure you want to delete this notice?
</v-card-title>
<v-card-text
>Deleted notices cannot be restored, and are immediately reflected
on the user portal after deletion.</v-card-text
>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="dialogDelete = false">
Cancel
</v-btn>
<v-btn color="blue darken-1" text @click="deleteNotice">
Confirm
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
<!-- For Notice List -->
<v-data-table
single-select
show-select
class="mt-md-2 elevation-5"
v-model="selectedRow"
:headers="headers"
:items="data"
:items-per-page="10"
>
</v-data-table>
</div>
</template>
<script>
const MODE_VIEW = "VIEW";
const MODE_REGISTER = "REGISTER";
const MODE_MODIFY = "MODIFY";
const MODE_DELETE = "DELETE";
export default {
data() {
return {
selectedRow: [],
dialogDelete: false,
// For Notice List
headers: [
{
text: "Category",
value: "category",
},
{
text: "Title",
value: "title",
},
{
text: "Date",
value: "date",
},
{
text: "Name",
value: "name",
},
],
data: [],
};
},
created: function () {
this.getNotices();
},
methods: {
getNotices: function () {
this.$axios
.get("/api/notices")
.then((response) => {
this.data = [];
for (let idx in response.data) {
let res = {
id: response.data[idx].id,
category: response.data[idx].category,
title: response.data[idx].title,
content: response.data[idx].content,
date: response.data[idx].updateDatetime.substr(0, 10), // FORMAT: yyyy-MM-dd
name: response.data[idx].updateEmployeeNo,
};
this.data.push(res);
}
})
.catch(function (error) {
console.log(error);
});
},
hasSelectedRow: function () {
return this.selectedRow.length > 0;
},
changeMode: function (mode, notice) {
this.$emit("changeMode", mode, notice);
},
viewNotice: function () {
this.changeMode(MODE_VIEW, this.selectedRow);
},
registerNotice: function () {
if (this.hasSelectedRow()) {
this.selectedRow = [];
}
this.changeMode(MODE_REGISTER, this.selectedRow);
},
modifyNotice: function () {
this.changeMode(MODE_MODIFY, this.selectedRow);
},
deleteNotice: function () {
this.dialogDelete = false;
this.changeMode(MODE_DELETE, this.selectedRow);
},
},
};
</script>
<style>
tr.v-data-table__selected {
background: lightgoldenrodyellow !important;
}
</style>
변경 사항
<script>
...
data() {
...
data: [],
},
created: function () {
this.getNotices();
},
methods: {
getNotices: function () {
this.$axios
.get("/api/notices")
.then((response) => {
this.data = [];
for (let idx in response.data) {
let res = {
id: response.data[idx].id,
category: response.data[idx].category,
title: response.data[idx].title,
content: response.data[idx].content,
date: response.data[idx].updateDatetime.substr(0, 10), // FORMAT: yyyy-MM-dd
name: response.data[idx].updateEmployeeNo,
};
this.data.push(res);
}
})
.catch(function (error) {
console.log(error);
});
},
...
</script>
- data 영역
- data: 하드코딩된 값 제거 및 [] 로 초기화 → Axios의 GET API를 통해서 획득한 데이터가 담길 변수
- created 훅 영역
- 화면 요소 생성 후 Axios의 GET API 호출 함수를 호출하여 데이터 획득
- methods 영역
- Axios의 GET API를 통해서 데이터 획득하는 함수 추가
- DB의 데이터와 화면에 보여질 데이터가 다른 경우 포맷 변환 작업 추가
src\components\user-web\notice\NoticeDetail.vue
전체 코드를 확인하실 분들은 아래 더보기를 클릭해주세요.
더보기
<template>
<div class="ma-md-5">
<div class="d-flex justify-end">
<v-btn
text
outlined
class="mx-md-1 elevation-2"
:class="{ visibility: getVisibleStatusBtns() }"
:disabled="getEnableStatusElements()"
@click="refreshElements"
>REFRESH
</v-btn>
</div>
<div class="mt-5">
<v-combobox
outlined
dense
hide-selected
required
label="Category"
:items="categories"
v-model="category"
:disabled="getEnableStatusElements()"
></v-combobox>
<v-text-field
required
label="Title"
v-model="title"
:disabled="getEnableStatusElements()"
></v-text-field>
<v-textarea
required
label="Content"
v-model="content"
:disabled="getEnableStatusElements()"
></v-textarea>
</div>
<div class="d-flex justify-end">
<v-btn
text
outlined
class="mx-md-1 elevation-2"
:class="{ visibility: getVisibleStatusBtns() }"
:disabled="getEnableStatusElements()"
@click="clickCancelBtn"
>CANCEL
</v-btn>
<v-btn
text
outlined
class="mx-md-1 elevation-2"
:class="{ visibility: getVisibleStatusBtns() }"
:disabled="getEnableStatusElements()"
@click="clickOkBtn"
>OK
</v-btn>
</div>
</div>
</template>
<script>
const MODE_INIT = "INIT";
const MODE_VIEW = "VIEW";
const MODE_DELETE = "DELETE";
const MODE_MODIFY = "MODIFY";
const MODE_REGISTER = "REGISTER";
export default {
props: ["mode", "notice"],
data() {
return {
id: -1,
category: [],
title: "",
content: "",
categories: ["Competition", "System Check", "Etc"],
};
},
updated: function () {
this.$nextTick(function () {
if (this.id != this.notice.id) {
this.id = this.notice.id;
this.refreshElements();
if (this.mode == MODE_DELETE) {
this.clickOkBtn();
}
}
});
},
methods: {
getEnableStatusElements: function () {
return (
this.mode == MODE_INIT ||
this.mode == MODE_DELETE ||
this.mode == MODE_VIEW
);
},
getVisibleStatusBtns: function () {
return this.mode == MODE_VIEW;
},
initializeElements: function () {
this.category = [];
this.title = "";
this.content = "";
},
refreshElements: function () {
this.category = this.notice.category;
this.title = this.notice.title;
this.content = this.notice.content;
},
doAxiosProcess: function () {
let responseStatus = 200;
switch (this.mode) {
case MODE_REGISTER:
this.$axios
.post("/api/notices", {
// TODO: EmployeeNo는 로그인한 유저의 사번으로 변경
registerEmployeeNo: "000000",
updateEmployeeNo: "000000",
category: this.category,
title: this.title,
content: this.content,
})
.catch(function (error) {
responseStatus = error.response.status;
console.log("[ERR/REG]" + error);
});
break;
case MODE_MODIFY:
this.$axios
.put("/api/notices/" + this.id, {
id: this.id,
// TODO: EmployeeNo는 로그인한 유저의 사번으로 변경
updateEmployeeNo: "000000",
category: this.category,
title: this.title,
content: this.content,
})
.catch(function (error) {
console.log("[ERR/MOD]" + error);
responseStatus = error.response.status;
});
break;
case MODE_DELETE:
this.$axios
.delete("/api/notices/" + this.id, {
id: this.id,
})
.catch(function (error) {
console.log("[ERR/DEL]" + error);
responseStatus = error.response.status;
});
break;
default:
// do nothing
break;
}
return responseStatus;
},
clickOkBtn: function () {
if (this.doAxiosProcess() == 200) {
this.initializeElements();
this.$emit("finishProcess");
}
},
clickCancelBtn: function () {
this.initializeElements();
},
},
};
</script>
<style scoped>
.visibility {
visibility: hidden;
}
</style>
변경 사항
<script>
...
updated: function () {
this.$nextTick(function () {
...
if (this.mode == MODE_DELETE) {
this.clickOkBtn();
}
});
},
methods: {
...
doAxiosProcess: function () {
let responseStatus = 200;
switch (this.mode) {
case MODE_REGISTER:
this.$axios
.post("/api/notices", {
// TODO: EmployeeNo는 로그인한 유저의 사번으로 변경
registerEmployeeNo: "000000",
updateEmployeeNo: "000000",
category: this.category,
title: this.title,
content: this.content,
})
.catch(function (error) {
responseStatus = error.response.status;
console.log("[ERR/REG]" + error);
});
break;
case MODE_MODIFY:
this.$axios
.put("/api/notices/" + this.id, {
id: this.id,
// TODO: EmployeeNo는 로그인한 유저의 사번으로 변경
updateEmployeeNo: "000000",
category: this.category,
title: this.title,
content: this.content,
})
.catch(function (error) {
console.log("[ERR/MOD]" + error);
responseStatus = error.response.status;
});
break;
case MODE_DELETE:
this.$axios
.delete("/api/notices/" + this.id, {
id: this.id,
})
.catch(function (error) {
console.log("[ERR/DEL]" + error);
responseStatus = error.response.status;
});
break;
default:
// do nothing
break;
}
return responseStatus;
},
clickOkBtn: function () {
if (this.doAxiosProcess() == 200) {
this.initializeElements();
this.$emit("finishProcess");
}
},
...
</script>
- updated 훅 영역
- 전달받은 모드(mode)가 DELETE 모드면, 바로 'OK' 버튼을 클릭했을 때와 동일하도록 함수 호출
- methods 영역
- doAxiosProcess 함수 추가
- 현재 선택된 모드(mode)에 맞춰 Axios를 통해 REST API 호출
- 정상 처리되지 않은 경우 화면의 변화가 없어야 하므로, REST API Response code를 따로 관리
- clickOkBtn: 기존에 추가한 코드가 doAxiosProcess가 정상적으로 수행했을 때만 진행하도록 수정
- doAxiosProcess 함수 추가
References
'Programming > Vue' 카테고리의 다른 글
[실습] 뷰(Vue)로 회원/사용자 관리 화면 구현하기 - UI/Layout 구현하기 (0) | 2023.02.17 |
---|---|
[실습] 뷰(Vue)로 회원/사용자 관리 화면 구현하기 - 기획/설계하기 (0) | 2023.02.16 |
"'response' is defined but never used" 오류 해결 방법 (0) | 2023.02.08 |
뷰(Vue.js) 프로젝트에 뷰 액시오스(Vue Axios) 설치하기 (0) | 2023.02.07 |
[실습] 뷰(Vue)로 공지사항/게시판 구현하기 - Root/상위 컴포넌트 화면 기능 개발 (0) | 2023.02.06 |
댓글