상품 주문 서비스 - vue 적용해보기

꿀이·2022년 6월 7일
0

개요

인프런 김영한님의 스프링 부트와 JPA 활용1 을 들으면서 만들었던 타임리프 기반의 화면들을 vuejs로 만들어보면서 화면개발에 좀 익숙해져 보자!!

추가적으로 스프링 부트의 컨트롤러 부분을 수정해서 내부 view 를 반환하는것이 아니라 Jason 으로 클라이언트 서버로 결과값을 반환하도록 해보자.

깃주소 : https://github.com/baeksu/WebShopService/tree/baeksu


CORS (Cross-Origin Resource Sharing) 문제

아놔... 시작부터 당황했네, 구글링해보니까 다음과 같이 해결할 수 있다. 우선 프론트 단에서 vue.config.js 파일에 다음과 같이 설정정보를 추가해줘야 한다. 지금 보면 8081 로 날리고 있는데, 스프링부트 서버는 8080에 떠있다.

그리고 나서 스프링 부트에도 cors 관련된 config 를 추가해줘야 한다.


회원가입

axios post 요청

우선 api 폴더 밑에 index.js 를 추가하고 여기다가 이제 get, post 요청 메서드들을 정리해둘 거다.

import axios from "axios";

function fetchNewMember(memberForm){
  console.log(memberForm);
  return axios.post('/members/new',memberForm)
}

export {
  fetchNewMember
};

MemberForm.vue 컴포넌트다. 입력폼을 입력하고 FETCH_NEW_MEMBER 를 통해서 서버쪽으로 HTTP POST 요청을 해준다. 근데 이때 서버쪽의 MemberForm의 변수명과 프론트에서 넘겨주는 객체내부의 변수명을 맞춰줘야 한다. (db에 이름이 제대로 안들어가길래... 뭔가 했네)

이후에 정상적으로 post가 완료된 후에 서버로부터 응답을 받으면 다시 홈화면으로 보내준다. 이때 this.$router.push('/') 이런식으로 하면 router를 동작시킬 수 있다. router-link 도 내부적으로는 push가 실행이 되는듯

<template>
  <div>
    <section>
			<h4>이름</h4>
			<input type="text" v-model="memberForm.name" placeholder="이름을 입력하세요">

			<h4>도시</h4>
			<input type="text" v-model="memberForm.city" placeholder="도시를 입력하세요">

			<h4>거리</h4>
			<input type="text" v-model="memberForm.street" placeholder="거리를 입력하세요">

			<h4>우편번호</h4>
			<input type="text" v-model="memberForm.zipcode" placeholder="우편번호를 입력하세요">
    </section>
    <section>
    	<button type="button" class="btn btn-primary" v-on:click="FETCH_NEW_MEMBER(memberForm)">Submit</button> 
    </section>
  </div>
</template>

<script>

import {fetchNewMember} from '../api/index.js';

export default {
	data(){
		return {
			//멤버폼 객체로 받아버릴 수도 있을거 같은데?
			memberForm: {
				name : "",
				city : "",
				street : "",
				zipcode : ""
			},
		}
	},
	methods:{
		//멤버폼을 입력하고 post 요청을 서버로 보내자
		FETCH_NEW_MEMBER : function(memberForm){
			fetchNewMember(memberForm)
				.then(Response=>{
					console.log(Response.data);
					//여기서 만약에 "ok" 를 리턴받으면 홈화면으로 다시 보내주자
					this.$router.push('/');

				})
				.catch(error=>{
					console.log(error);
				})
		}
	}
}

서버쪽 컨트롤러 소스는 다음과 같다.

    @PostMapping("/members/new")
    @ResponseBody
    public String create(@RequestBody MemberForm form) {

        Address address = new Address(form.getCity(), form.getStreet(), form.getZipcode());
        Member member = new Member(form.getName(), address);
        memberService.join(member);

        return "ok";
    }

회원 목록 : 페이징 처리

우선 컨트롤러 부분 수정을 해야하는데 json 으로 넘겨줄거기 때문에 회원을 조회한다음에 MemberForm 형식으로 변환한다음에 List 에 담아서 반환해줄거다. 포스트맨으로 확인해 보니 일단 제대로 넘어가는걸 확인했다.

애초에 memberService 에서 조회하면서 MemberForm 으로 반환하는 메서드를 만드는게 좋은거 같다는 생각이 드네

@RestController
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;

    @GetMapping("/members/{page}")
    public List<MemberForm> list(@PathVariable(value = "page") Integer page) {

        List<Member> members = memberService.findMembers(page);

        //과연 이대로 memberList 로 넘기면 json 으로 갈까?
        List<MemberForm> memberList = new ArrayList<>();
        for (Member member : members) {
            memberList.add(new MemberForm(member.getName(),member.getAddress()));
        }

        return memberList;
    }
}

이제 vue 쪽을 어떻게 로직을 짤지 생각을 해보자. 대충 요런느낌으로 작성했다.

  • store/index.js
    페이지검사에서 어떻게 데이터가 넘어오나 보니까 response.data 가 배열 형태로 넘어오는걸 확인함. 요걸 SET_MEMBERSLIST 뮤테이션에서 기존 memberList 에 push 해줌. 근데 넣기전에 배열을 초기화를 해주고 넣어줬음 항상 10개씩만 가지고 있으려고 (근데 이게 10개씩 자주 요청하는거 vs 100개씩 가져오는거 어떤게 더 효율이 좋을지는 좀더 생각해봐야할듯 네트워크비용이 메모리비용 보다는 엄청 크니까...)
import Vue from 'vue';
import Vuex from 'vuex';
import {fetchMemberList} from '../api/index.js'

Vue.use(Vuex);

export const store = new Vuex.Store({
  state:{
    pageIdx : 0,
    membersList : [],
  },
  mutations : {
    SET_PAGE_IDX(state, index){
      state.pageIdx = index;
    },
    SET_MEMBERSLIST(state, data){
      //와나... 이렇게 넣는거였구나... 아이구 머리야...
      state.membersList=[];
      for(let i = 0 ; i< data.length ; i++){
        state.membersList.push(data[i]);
      }
    }
  },
   actions :{
    FETCH_MEMBERS(context,index){
      fetchMemberList(index)
        .then( response =>{
          context.commit('SET_MEMBERSLIST',response.data);
        })
        .catch(error=>{
          console.log(error);
        })

    }
   }
})

MemberList.vue 컴포넌트 부분은... 뭐 별 내용은 없다. v-for 로 돌려주고 목록 리스트의 인덱스를 맞춰주기 위해 startIdx 를 data() 속성으로 가지고 있다.

<template>
  <div>
    <table class="table">
      <thead>
        <tr>
          <th scope="col">#</th>
          <th scope="col">이름</th>
          <th scope="col">도시</th>
          <th scope="col">주소</th>
          <th scope="col">우편번호</th>
        </tr>
      </thead>
      <tbody>
        <!-- 하... membersList 에 접근해서 v-for로 돌려주자 -->
        <tr v-for="(member,index) in this.$store.state.membersList" :key="index">
          <th  scope="row">{{startIdx*10 + index + 1}}</th>
          <th>{{member.name}}</th>
          <th>{{member.city}}</th>
          <th>{{member.street}}</th>
          <th>{{member.zipcode}}</th>
        </tr>
      </tbody>
    </table>

    <!-- 페이징 -->
    <ul class="pagination">
      <li class="page-item"><a class="page-link" href="#">Previous</a></li>
      <li v-for=" n in 10" :key="n" class="page-item"><a class="page-link" v-on:click="addPageIdx(n)">{{n}}</a></li>
      <li class="page-item"><a class="page-link" href="#">Next</a></li>
    </ul>
  </div>
</template>

<script>
export default {
  data(){
    return{
      //이거 리스트 번호 매길때 쓰려고
      startIdx:0,
    }
  },
  methods:{
    addPageIdx(index){
      //그럼 여기서 다시 서버로 10개를 끌어와야겠지?
      this.$store.dispatch('FETCH_MEMBERS', index-1);
      this.$store.commit('SET_PAGE_IDX',index-1)
      this.startIdx = this.$store.state.pageIdx;
    },
    
  },
  computed:{
    getStartIdx(){
      return (this.$store.$state.pageIdx)/10;
    },
  },
  created(){
    //맨처음 시작할때 맴버 10명 및 페이지 0으로 초기화
    this.$store.commit('SET_PAGE_IDX',0);
    this.$store.dispatch('FETCH_MEMBERS', 0);
  },

}
</script>

(참고) 부트스트랩 페이지네이션 가운데 정렬

아놔 이거 뭐야 ㅋㅋㅋ 부트스트랩에서 가져온 페이징이 가운데 정렬이 안먹길래 구글링해보니까 다음과 같이 justify-contest:center 를 해주면 된다고 함

 ul{
   /* 아놔.. 이건 또 뭐야 */
   justify-content: center;

	list-style-type: none; 
	padding-left: 0px;
	margin-top: 0;
	/* text-align: left; */
}

상품 등록

이부분은 회원등록이랑 똑같고, 컨트롤러 부분에 기존에 Book 엔티티에 set으로 주입하던거를, 생성자를 하나 따로 만들었다.

//컨트롤러 수정
   @PostMapping("/items/new")
   public String create(@RequestBody BookForm form) {
       Book book = new Book(form);
       itemService.saveItem(book);
       return "ok";//책을 저장하고 나면 아이템 리스트로 리다이렉트 하자
   }
-----------------------------------------------------------------------------------
//생성자 추가
   public Book(BookForm bookForm) {
       super(bookForm.getName(), bookForm.getPrice(), bookForm.getStockQuantity());
       this.author = bookForm.getAuthor();
       this.isbn = bookForm.getIsbn();
   }

상품 수정

와나... 이게 객체로 안묶으면 안되는거였나..? 객체로 안묶고 각각 매개변수로 던지니까 뮤테이션 쪽에서 값이 제대로 안나오길래 뭔가 했네

얘도 일단 되기는 하는데... 상품 수정을 눌렀을 때 클릭한 값을 디폴트로 뿌려주고 싶다. 근데 이값이 vuex store 에 있어가지고 v-model 이 적용이 안되네. 이거 방법좀 생각해보고 다시 적용하자.

https://v3.vuex.vuejs.org/guide/forms.html#two-way-computed-property 여기 한번 확인하고 해보자

profile
내게 맞는 옷을 찾는중🔎

0개의 댓글