본문 바로가기
Programming/Vue

[실습] 뷰(Vue)로 공지사항/게시판 구현하기 - Axios 이용하여 REST API 호출/연동하기

by 돌방로그 2023. 2. 10.


오늘의 Vue.js 실습 목표는 "공지사항/게시판 만들어보기!" 입니다.

본 게시글에서 다루는 사항은 공지사항/게시판 기본(테이블) 화면의 이벤트와 API Server를 연동하기 위해 Axios를 이용하여 구현하는 과정입니다.

 


공지사항/게시판 구현하기 - Axios로 REST API 호출/연동하기

사전 준비

아래 사항에 대해서 사전 준비가 완료되지 않으신 분들은 아래 링크를 참조하여 사전 준비를 진행합니다.


결과 이미지

구현하기에 앞서 본 글을 따라하시면 아래와 같이 공지사항 화면과 API Server가 연동된 결과를 확인하실 수 있습니다.

 

▶ 초기 상태

DB (MySQL)

 

화면

 

▶ REGISTER

DB (MySQL)

화면

좌: Axios 처리 전 우: Axios 처리 후

 

▶ MODIFY

DB (MySQL)

화면

좌: Axios 처리 전 우: Axios 처리 후

 

▶ DELETE

DB (MySQL)

화면

좌: Axios 처리 전 우: Axios 처리 후


소스 코드

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가 정상적으로 수행했을 때만 진행하도록 수정

 

 


References

 

 

댓글