본문 바로가기
Front-end/Vue

Vue 3 쇼핑몰 만들기 - 2 (+ Spring Boot)

by javapp 자바앱 2023. 4. 20.
728x90

 

기능 추가

  • 장바구니
  • 주문하기 

 

상품

장바구니 추가

Card.vue

<template xmlns:>
    <div class="card shadow-sm">
      <span class="img" :style="{backgroundImage: `url(${item.imgPath})`}" />
      <div class="card-body">
        <p class="card-text">{{item.name}}</p>
        <div class="d-flex justify-content-between align-items-center">
          <div class="btn-group">
            <button type="button" class="btn btn-sm btn-primary" @click="addToCart(item.id)">
              <i class="fa fa-shopping-cart" aria-hidden="true"></i>

            </button>
          </div>
          <small class="price text-muted">{{ lib.getNumberFormatted(item.price) }}원</small>
          <small class="discount badge bg-danger ">{{ lib.getNumberFormatted(item.price - (item.price * item.discountPer / 100)) }}</small>
          <small class="real text-danger ">{{ item.discountPer }}%</small>
        </div>
      </div>
    </div>
</template>

<script>
  import lib from "@/scripts/lib";
  import axios from "axios";

  export default {
    name: "Card",
    props:{
      item: Object
    },
    setup(){
      const addToCart = (itemId)=>{
        axios.post(`/api/cart/items/${itemId}`).then(()=>{
          console.log("success")
        });
      };

      return {lib, addToCart}
    }
  }
</script>

<style scoped>
.card .img{
  display: inline-block;
  width: 100%;
  height: 250px;
  background-size: cover;
  background-position: center;
}
.card .card-body .price{
  text-decoration: line-through;
}
</style>

 

스프링부트 컨트롤러

@PostMapping("/api/cart/items/{itemId}")
public ResponseEntity<Cart> pushCartItem(@PathVariable int itemId, @CookieValue(value = "token", required = false) String token){
    isTokenValid(token);
    int memberId = jwtService.getId(token);
    Cart cart = cartRepository.findByMemberIdAndItemId(memberId, itemId).orElseGet(()->{
        Cart newCart = Cart.builder()
                .memberId(memberId)
                .itemId(itemId)
                .build();
        cartRepository.save(newCart);
        return newCart;
    });
    return new ResponseEntity<>(cart, HttpStatus.OK);
}

 

장바구니

내가 추가한 상품들

Cart.vue

<template>
  <div class="cart">
    <div class="container">
      <ul>
        <li v-for="(i, idx) in state.items" :key="idx">
          <img :src="i.imgPath"/>
          <span class="name">{{i.name}}</span>
          <span class="price">{{ lib.getNumberFormatted(i.price - i.price * i.discountPer / 100)}} 원</span>
          <i class="fa fa-trash" @click="remove(i.id)"></i>
        </li>
      </ul>
      <router-link to="/order" class="btn btn-primary">구입하기</router-link>
    </div>
  </div>
</template>

<script>
import {reactive} from "vue";
import axios from "axios";
import lib from "@/scripts/lib";

export default {
  name: "Cart",
  setup(){
    const state = reactive({
      items: []
    })

    const load = () =>{
      axios.get('/api/cart/items').then(({data})=>{
        state.items = data;
      })
    }

    const remove = (itemId)=>{
      axios.delete(`/api/cart/items/${itemId}`).then(()=>{
        load();
      })
    }

    load();

    return {state, lib, remove}
  }
}
</script>

<style scoped>
.cart ul{
  list-style: none;
  margin:0;
  padding:0;
}

.cart ul li{
  border: 1px solid #eee;
  margin-top: 25px;
  margin-bottom: 25px;
}

.cart ul li img{
  width: 150px;
  height: 150px;
}

.cart ul li .name{
  margin-left: 25px;
}

.cart ul li .price{
  margin-left: 25px;
}

.cart ul li i{
  float: right;
  font-size: 20px;
  margin-top: 65px;
  margin-right: 50px;
}

.cart .btn{
  display: block;
  margin: 0 auto;
}
</style>

 

 

주문하기

Order 주문하기

Order.vue

<template>
  <div class="order">
    <div class="container">
      <div class="py-5 text-center"><h2>주문하기</h2></div>
      <div class="row">
        <div class="col-md-4 order-md-2 mb-4"><h4 class="d-flex justify-content-between align-items-center mb-3"><span
            class="text-muted">주문 정보</span><span
            class="badge badge-secondary badge-pill">{{ state.items.length }}</span></h4>
          <ul class="list-group mb-3">
            <li class="list-group-item d-flex justify-content-between lh-condensed" v-for="(i, idx) in state.items"
                :key="idx">
              <div><h6 class="my-0">{{ i.name }}</h6></div>
              <span class="text-muted">{{ lib.getNumberFormatted(i.price - i.price * i.discountPer / 100) }}</span></li>
          </ul>
          <h4 class="text-center total-price">
            {{ lib.getNumberFormatted(computedPrice) }} 원
          </h4>
        </div>
        <div class="col-md-8 order-md-1"><h4 class="mb-3">주문자 정보</h4>
          <div class="needs-validation" novalidate="">
            <div class="row"></div>
            <div class="mb-3"><label for="username">이름</label>
              <div class="input-group">
                <input type="text" class="form-control" id="username" v-model="state.form.name"
                >
                <div class="invalid-feedback" style="width:100%;"> Your username is required.</div>
              </div>
            </div>
            <div class="mb-3"><label for="address">주소</label>
              <input type="text" class="form-control" id="address" v-model="state.form.address">
            </div>
            <div class="row"></div>
            <div class="custom-control custom-checkbox"></div>
            <div class="custom-control custom-checkbox"></div>
            <hr class="mb-4">
            <h4 class="mb-3">결제 수단</h4>
            <div class="d-block my-3">
              <div class="custom-control custom-radio">
                <input id="card" name="paymentMethod" type="radio"
                       class="custom-control-input" value="card" v-model="state.form.payment"><label
                  class="custom-control-label" for="card">
                신용카드</label></div>
              <div class="custom-control custom-radio">
                <input id="bank" name="paymentMethod" type="radio" value="bank" v-model="state.form.payment"
                       class="custom-control-input"><label class="custom-control-label" for="bank"> 무통장입금</label></div>
            </div>
            <div class="row">
              <div class="col-md-6 mb-3"><label for="cc-name">카드 번호</label>
                <input type="text" class="form-control" id="cc-name" v-model="state.form.cardNumber">
                <div class="invalid-feedback"> Name on card is required</div>
              </div>
            </div>
            <div class="row"></div>
            <hr class="mb-4">
            <button class="btn btn-primary btn-lg btn-block" @click="submit()">결제하기</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import {computed, reactive} from "vue";
import axios from "axios";
import lib from "@/scripts/lib";
import router from "@/scripts/router";

export default {
  name: "Order",
  setup() {
    const state = reactive({
      items: [],
      form: {
        name: "",
        address: "",
        payment: "",
        cardNumber: "",
        items: ""
      }
    })

    const load = () => {
      axios.get('/api/cart/items').then(({data}) => {
        state.items = data;
      })
    }

    load();

    const submit = ()=>{
      const args = JSON.parse(JSON.stringify(state.form))
      args.items =JSON.stringify(state.items)
      axios.post("/api/orders", args).then(()=>{
        alert('주문완료')
        router.push({path: "/orders"})
      })
    }

    const computedPrice = computed(() => { // 데이터 값을 계산하고, 그 값을 캐시하여 성능 개선
      let result = 0;
      for (let i of state.items) {
        result += i.price - i.price * i.discountPer / 100;
      }
      return result;
    })

    return {state, lib, computedPrice, submit}
  }
}
</script>

<style scoped>

</style>

 


 

벡엔드 디버그 

 

네트워크엔 보류중으로 확인

 

run to Cursor 를 통해 이어서 디버그 가능

이후 F9 누르면 완전히 실행 

 

 


 

주문 내역

Orders.vue

<template>
  <div class="order">
    <div class="container">
      <table class="table table-bordered">
        <thead>
        <tr>
          <th>번호</th>
          <th>주문자명</th>
          <th>주소</th>
          <th>결제 수단</th>
          <th>구입 항목</th>
        </tr>
        </thead>
        <tbody>
        <tr v-for="(order, idx) in state.orders" :key="idx">
          <td>{{ state.orders.length - idx }}</td>
          <td>{{ order.name }}</td>
          <td>{{ order.address }}</td>
          <td>{{ order.payment }}</td>
          <td>
            <div v-for="(i, idx2) in order.items" :key="idx2">{{ i.name }}</div>
          </td>
        </tr>
        </tbody>
      </table>
    </div>
  </div>
</template>

<script>
import axios from "axios";
import lib from "@/scripts/lib";
import {reactive} from "vue";

export default {
  setup() {
    const state = reactive({
      orders: []
    })

    axios.get('/api/orders').then(({data}) => {
      state.orders = [];

      for (let d of data) {
        if (d.items) {
          d.items = JSON.parse(d.items);
        }
        state.orders.push(d)
      }
    })

    return {state, lib}
  }
}
</script>

<style scoped>

</style>

 

 

헤더에 주문 내역 링크 추가

Header.vue

<template>
  <header>
    <div class="collapse bg-dark" id="navbarHeader">
      <div class="container">
        <div class="row">
          <div class="col-sm-4 py-4">
            <h4 class="text-white">사이트맵</h4>
            <ul class="list-unstyled">
              <li>
                <router-link to="/" class="text-white">메인 화면</router-link>
              </li>
              <li v-if="$store.state.account.id">
                <router-link to="/orders" class="text-white">주문 내역</router-link>
              </li>
              <li>
                <router-link to="/login" class="text-white" v-if="!$store.state.account.id">로그인</router-link>
                <a to="/login" class="text-white" @click="logout()" v-else>로그아웃</a>
              </li>
            </ul>
          </div>
        </div>
      </div>
    </div>
    <div class="navbar navbar-dark bg-dark shadow-sm">
      <div class="container">
        <router-link to="/" class="navbar-brand d-flex align-items-center">
          <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" stroke="currentColor"
               stroke-linecap="round" stroke-linejoin="round" stroke-width="2" aria-hidden="true" class="me-2"
               viewBox="0 0 24 24">
            <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
            <circle cx="12" cy="13" r="4"/>
          </svg>
          <strong>Gallery</strong>
        </router-link>
        <router-link to="/cart" class="cart btn">
          <i class="fa fa-shopping-cart" aria-hidden="true"></i>
        </router-link>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarHeader"
                aria-controls="navbarHeader" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
      </div>
    </div>
  </header>
</template>

<script>
import store from "@/scripts/store";
import router from "@/scripts/router";
import axios from "axios";

export default {
  name: 'Header',
  setup() {
    const logout = () => {
      axios.get(('/api/account/logout')).then((res)=>{
        if(res){
          store.commit('setAccount', 0);
          router.push({path: "/login"})
        }
      })
    }
    return {logout}
  }
}
</script>

<style scoped>
  header ul li a {
    cursor:pointer;
  }
  header .navbar .cart{
    margin-left: auto;
    color: #fff
  }
</style>

 

 

 

로그인 엔터시 자동로그인 기능 추가

Login.vue

<label for="inputEmail" class="sr-only">Email address</label>
<input type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus
       v-model="state.form.email"
       @keyup.enter="submit()">
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="inputPassword" class="form-control" placeholder="Password" required
       v-model="state.form.password"
       @keyup.enter="submit()">

 

 

 

 

깃허브: https://github.com/llsrrll96/gallery-shop-vue.git

 

GitHub - llsrrll96/gallery-shop-vue: Gallery Shop Vue-project

Gallery Shop Vue-project. Contribute to llsrrll96/gallery-shop-vue development by creating an account on GitHub.

github.com

 

'Front-end > Vue' 카테고리의 다른 글

Vue 3 쇼핑몰 만들기 - 1 (+ Spring Boot)  (0) 2023.04.16
VUE 3 간단한 메모앱 실습  (0) 2023.04.13

댓글