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.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
'Front-end > Vue' 카테고리의 다른 글
Vue 3 쇼핑몰 만들기 - 1 (+ Spring Boot) (0) | 2023.04.16 |
---|---|
VUE 3 간단한 메모앱 실습 (0) | 2023.04.13 |
댓글