본문 바로가기

웹 프로그래밍/Front-End

Vue + Spring Pagination(Paging) 구현

반응형

Vue와 Spring을 이용하여 Paging을 다음과 같은 페이징을 구현하는 방법을 알아보겠습니다.


Vue 프로젝트의 파일 구조와 router.js 의 코드입니다.

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "@/views/Home.vue";
import Page from "@/views/Page.vue";
import List from "@/components/List.vue";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home
  },
  {
    path: "/page",
    name: "Page",
    component: Page,
    children:[
      {
        path: '/list',
        component: List,
      }
    ]
  }
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes
});

export default router;

 


Page.vue - 페이지 상단의 Page 를 클릭 했을 때의 코드 입니다.

<template>
  <div class="about">
    <h1>Pagination ( Page-Link )</h1>
    <router-view/>
    <page-link/>
  </div>
</template>

<script>
// @ is an alias to /src
import PageLink from "@/components/PageLink.vue";

export default {
  name: "Page",
  components: {
    PageLink
  }
};
</script>

PageLink.vue - Pagination 바를 만들어주고 번호 목록을 클릭했을 때 동작하는 코드

<template>
  <ul class="pagination justify-content-center">

    <li class="page-item" v-if="prev">
      <router-link :to="`/list?no=${ (startPageIndex - 1) * listRowCount }`" class="page-link" aria-label="Previous" @click.native="movePage(startPageIndex - 1)">
        <span aria-hidden="true">&laquo;</span>
      </router-link>
    </li>

    <li v-for="index in endPageIndex-startPageIndex + 1 " :key="index" class="page-item" :class="{active:( (startPageIndex + index - 1) == currentPageIndex)}">
      <router-link :to="`/list?no=${ (startPageIndex + index - 1) * listRowCount }`" class="page-link"  @click.native="movePage(startPageIndex + index - 1)">{{ startPageIndex + index - 1 }}</router-link>
      <!-- <a class="page-link" href="javascript:movePage(' + i + ')">' + i + '</a> -->
    </li>

    <li class="page-item" v-if="next">
      <router-link :to="`/list?no=${ (endPageIndex + 1) * listRowCount }`" class="page-link" aria-label="Next"  @click.native="movePage(endPageIndex + 1)">
        <span aria-hidden="true">&raquo;</span>
      </router-link>
    </li>
  </ul>
</template>

<script>
import http from "@/util/http-common";

export default {
  name: "row",
  data() {
    return {
      totalListItemCount: 0,
      listRowCount: 10,
      pageLinkCount: 10,
      currentPageIndex: 1,

      pageCount: 0,
      startPageIndex: 0,
      endPageIndex: 0,
      prev: false,
      next: false

    };
  },
  methods: {
    movePage( param ) {
      this.currentPageIndex = param;
      this.initComponent();
    },

    initComponent(){
      http
        .get("/board/pagelink/count")
        .then(({ data }) => {
          this.totalListItemCount = data;
          this.initUI();
        })
        .catch(() => {
          alert("에러가 발생했습니다.");
        });
      
      
    },
    initUI(){

      this.pageCount = Math.ceil(this.totalListItemCount/this.listRowCount);

      if( (this.currentPageIndex % this.pageLinkCount) == 0 ){
        this.startPageIndex = ((this.currentPageIndex / this.pageLinkCount)-1)*this.pageLinkCount + 1
      }else{
        this.startPageIndex = Math.floor(this.currentPageIndex / this.pageLinkCount)*this.pageLinkCount + 1
      }

      if( (this.currentPageIndex % this.pageLinkCount) == 0 ){ //10, 20...맨마지막
        this.endPageIndex = ((this.currentPageIndex / this.pageLinkCount)-1)*this.pageLinkCount + this.pageLinkCount
      }else{
        this.endPageIndex =  Math.floor(this.currentPageIndex / this.pageLinkCount)*this.pageLinkCount + this.pageLinkCount;
      }

      if(this.endPageIndex > this.pageCount){
        this.endPageIndex = this.pageCount
      }

      if( this.currentPageIndex <= this.pageLinkCount ){
        this.prev = false;
      }else{
        this.prev = true;
      }

      if(this.endPageIndex >= this.pageCount){
        this.endPageIndex = this.pageCount;
        this.next = false;
      }else{
        this.next = true;
      }
    }
  },
  watch:{
    currentPageIndex: function(){
      this.initUI();
    }
  },
  created() {
    this.initComponent();
  },
  mounted(){
    this.$router.push('list?no=' + this.listRowCount)
  }
};
</script>

List.vue - 목록을 출력해주기 위한 틀이 되는 컴포넌트. axios로 backend와 통신하여 데이터를 가져옵니다.

<template>
  <div>
    <div v-if="items.length">
      <table class="table table-bordered table-condensed">
        <colgroup>
          <col :style="{ width: '5%' }" />
          <col :style="{ width: '50%' }" />
          <col :style="{ width: '10%' }" />
          <col :style="{ width: '25%' }" />
        </colgroup>
        <tr>
          <th>번호</th>
          <th>제목</th>
          <th>작성자</th>
          <th>날짜</th>
        </tr>
        <list-row
          v-for="(item, index) in items"
          :key="`${index}_items`"
          :no="item.no"
          :title="item.title"
          :writer="item.writer"
          :regtime="item.regtime"
        />
      </table>
    </div>
    <div v-else>글이 없습니다.</div>

  </div>
</template>

<script>
import http from '@/util/http-common';
import ListRow from '@/components/Row.vue';
export default {
  name: 'list',
  components: {
    ListRow,
  },
  data: function() {
    return {
      items: [],
      pageLimit : 10,
      pageOffet : 0
    };
  },
  created() {

    this.initComponent();
  },
  watch: {
    '$route.query': function(){
      this.initComponent();
    }
  },
  methods: {
    initComponent(){
      http
        .get('/board/pagelink',{
            params: { limit: this.pageLimit, offset: `${this.$route.query.no - this.pageLimit}`}
          })
        .then(({ data }) => {
          this.items = data
        })
        .catch(() => {
          alert('에러가 발생했습니다.');
        });
    }
  }
};
</script>

<style></style>

Row.vue - 가져온 하나하나의 데이터를 출력하기 위한 컴포넌트

<template>
  <tr>
    <td>{{ no }}</td>
    <td>
      <router-link :to="`/read?no=${no}`">{{ title }}</router-link>
    </td>
    <td>{{ writer }}</td>
    <td>{{ getFormatDate(regtime) }}</td>
  </tr>
</template>

<script>
import moment from 'moment';
export default {
  name: 'row',
  props: {
    no: { type: Number },
    writer: { type: String },
    title: { type: String },
    regtime: { type: String },
  },
  methods: {
    getFormatDate(regtime) {
      return moment(new Date(regtime)).format('YYYY.MM.DD');
    },
  },
};
</script>

이제부터 Vue와 axios 로 통신하는 Backend Spring 코드를 알아 보겠습니다.  참고로 Mybatis 퍼시스턴트 프레임워크를 사용하였습니다.

Controller.java

	@ApiOperation(value = "limit offset 에 해당하는 게시글의 정보를 반환한다.  ", response = List.class)
	@GetMapping(value = "/pagelink")
	public ResponseEntity<List<Board>> selectBoardLimitOffset(int limit, int offset) throws Exception {
		logger.debug("selectBoardLimitOffset - 호출");
		System.out.println("limit : " + limit + " / offset : " + offset);
		return new ResponseEntity<List<Board>>(boardService.selectBoardLimitOffset(limit, offset), HttpStatus.OK);
	}

	@ApiOperation(value = "게시글의 전체 건수를 반환한다.  ", response = List.class)
	@GetMapping(value = "/pagelink/count")
	public ResponseEntity<Integer> selectBoardTotalCount() throws Exception {
		logger.debug("selectBoardTotalCount - 호출");
		return new ResponseEntity<Integer>(boardService.selectBoardTotalCount(), HttpStatus.OK);
	}

DAO.java

	// for pagination (page-link)
	public List<Board> selectBoardLimitOffset(@Param("limit") int limit, @Param("offset") int offset);

	public int selectBoardTotalCount();

mappers - Mybatis 를 사용하여 구현했습니다.

	<!-- for vue pagination (page-link) -->
	<!-- 게시글 limit offset 조회 -->
	<select id="selectBoardLimitOffset" parameterType="map"
		resultType="board">
		select no, title, writer, content, regtime
		from vue_board
		limit #{limit} offset #{offset}
	</select>

	<!-- 게시글 limit offset 조회 -->
	<select id="selectBoardTotalCount" resultType="int">
		select count(*) from vue_board
	</select>
반응형