📝 Outline
- Cart 페이지 구현에 이어 Wish 페이지 구현이 완료되었다.
- 해당 페이지는 사용자가 즐겨찾기와 같은 개념으로 상품을 저장할 수 있도록 해주는 페이지로써 장바구니 페이지와는 다르게 단순히 사용자가 찜한 상품 리스트를 출력해주는 기능을 수행한다.
- 페이지 디자인은 다음과 같다.
- 즐겨찾기 기능은 상품 상세보기 페이지 하단에 있는 아래 버튼을 클릭할 시 동작하도록 구현하였다.
- 해당 버튼은 토클 형식으로 동작한다. 즉, 이미 즐겨찾기가 되어있는 상태에서 해당 버튼을 클릭할 경우에는 즐겨찾기가 해제되는 식이다.
- 페이지 동작은 다음과 같이 이루어진다.
📝 Review
💬 Wish
- 우선 Wish 페이지 구현을 위한
Wish
엔티티를 새로 구현해주었다.
@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
public class Wish {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@JsonIgnore
@ManyToOne
@JoinColumn(name = "customer_id")
private Customer customer;
@JsonIgnore
@ManyToOne
@JoinColumn(name = "product_id")
private Product product;
}
- 한 명의
Customer
는 여러 개의Product
를 찜할 수 있고, 한 개의Product
는 여러 명의Customer
에 의해 찜 당할 수 있기 때문에, 해당 엔티티는 N:M 관계에서 중간 테이블의 개념으로 매핑해주었다.
💬 toggleWish()
toggleWish() {
if(this.isWish) {
this.$axios.delete(`/wish/delete/item/${this.productId}/1`).then((res) => {
this.isWish = false;
});
} else {
this.$axios.get("/customer/get/1").then((customer) => {
this.$axios.get(`/product/get/${this.productId}`).then((product) => {
this.$axios.post("/wish/add", {customer: customer.data, product: product.data}).then((res) => {
this.isWish = true;
});
});
});
}
}
- 위 코드는 현재 상품의 즐겨찾기 여부에 따라 분기하여, 즐겨찾기 또는 즐겨찾기 해제를 수행하기 위한 코드이다.
- 그 중에서 즐겨찾기를 위한 코드를 살펴보면
customer
와product
데이터를 각각 요청하여 받아온 뒤 해당 데이터를 통해wish
객체를 저장하고 있는 것을 확인할 수 있다. - 원래 클라이언트 측에서는
Customer
와Product
의 id만 전달하고, 서버측에서 Repository를 통해Customer
와Product
엔티티를WishService
에서 한 번에 찾는 식으로 처리했지만, 이렇게 할 경우WishService
에서CustomerRepository
와ProductRepository
를 DI 해주어야 했다. - 즉, 결합도를 낮추기 위해 위처럼 구현한 것이다.
- 아래는 위 요청을 전달받기 위한 컨트롤러이다.
@RequestMapping("wish")
@RestController
public class WishApiController {
@Autowired
private WishService wishService;
// Detail.vue
@PostMapping("/add")
public ResponseEntity<?> add(@RequestBody WishDto wishDto) {
wishService.addWish(wishDto);
return new ResponseEntity<>(1, HttpStatus.OK);
}
// Wish.vue
@GetMapping("/get/item/{customer_id}")
public List<Long> getWishList(@PathVariable Long customer_id) {
return wishService.getWishList(customer_id);
}
// Detail.vue
@GetMapping("/get/{product_id}/state/{customer_id}")
public Integer isWish(@PathVariable Long product_id, @PathVariable Long customer_id) {
return wishService.isWish(customer_id, product_id);
}
// Wish.vue
@DeleteMapping("/delete/item/{product_id}/{customer_id}")
public ResponseEntity<?> delete(@PathVariable Long product_id, @PathVariable Long customer_id) {
wishService.delete(customer_id, product_id);
return new ResponseEntity<>(1, HttpStatus.OK);
}
}
add()
메서드에서 사용하는WishDto
는 다음과 같이 구현되어 있다.
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class WishDto {
private Customer customer;
private Product product;
}
- 즉, 이미
Customer
와Product
에 대한 정보를 각각의 Repository에서 구해왔기 때문에 이후에는 아래와 같이 엔티티를 저장할 수 있다.
@Service
public class WishService {
@Autowired
private WishRepository wishRepository;
@Transactional
public void addWish(WishDto wishDto) {
Wish wish = Wish.builder()
.customer(wishDto.getCustomer())
.product(wishDto.getProduct())
.build();
wishRepository.save(wish);
}
...
}
💬 Pagination
- 아래 코드는 Pagination을 위한 전체적인 코드이다.
<template>
<!-- Wish Items -->
...
<!-- Wish Items End -->
<!-- Pagination -->
<div class="text-center" v-if="wishItems.length > 0">
<v-pagination
v-model="curPage"
:length="Math.ceil(wishItems.length / numToShow)"
:total-visible="7"
color="black"
@input="paging"
></v-pagination>
</div>
<!-- Pagination End -->
</template>
<script>
export default {
computed: {
filteredList() {
let start = (this.curPage - 1) * this.numToShow
let end = this.curPage * this.numToShow
let result = this.wishItems.slice(start, end)
return result
}
},
data() {
return {
wishItems: [],
curPage: 1,
numToShow: 5,
}
},
mounted() {
this.getWishList();
},
methods: {
getWishList() {
this.$axios.get("/wish/get/item/1").then((productIdList) => {
this.$axios.get(`/product/get/wish-list?productId=${productIdList.data}`).then((res) => {
res.data.forEach(element => {
this.wishItems.push(element);
});
});
});
},
paging(value) {
this.curPage = value;
},
deleteWish(productId, i) {
this.$axios.delete(`/wish/delete/item/${productId}/1`).then((res) => {
this.$delete(this.wishItems, i);
});
}
},
}
</script>
- 하나씩 살펴보자면, 우선 아래는 Vuetify에서 제공하는
<v-pagination>
태그를 사용한 것이다.
<!-- Pagination -->
<div class="text-center" v-if="wishItems.length > 0">
<v-pagination
v-model="curPage"
:length="Math.ceil(wishItems.length / numToShow)"
:total-visible="7"
color="black"
@input="paging"
></v-pagination>
</div>
<!-- Pagination End -->
- 여기서
length
는 페이지의 길이,total-visible
은 최대로 보이는 페이지의 길이,@input
은 페이징 시 발생하는 이벤트를 정의하는 부분이다. - 이때
length
에는 간단한 계산식을 통해 리스트 아이템의 개수에 따라 페이지 길이를 적절하게 설정하도록 해주었다.
computed: {
filteredList() {
let start = (this.curPage - 1) * this.numToShow
let end = this.curPage * this.numToShow
let result = this.wishItems.slice(start, end)
return result
}
}
- 위 코드는 페이지에 출력할 리스트 아이템들을 페이지에 따라 적절히 Slicing하기 위한 코드로,
curPage
는 현재 페이지,numToShow
는 한 페이지에 보여질 최대 아이템의 수를 의미한다. - 해당 메서드의 결과는 다음과 같이
v-for
문에 쓰여진다.
<li class="li-product_list_item" v-for="(item, i) in filteredList" :key="i">
- 버튼을 클릭하여 페이징을 수행할 경우 아래의 이벤트가 호출되도록 하여 간단하게 페이징을 수행할 수 있다.
paging(value) {
this.curPage = value;
}
- 이때
value
에는 사용자가 클릭한 페이지의 번호가 들어오게 된다.
'🚗 Backend Toy Project > Baeg-won Clothing Gallery' 카테고리의 다른 글
[Baeg-won Clothing Gallery] 9. PROFILE 페이지 구현 (0) | 2023.07.31 |
---|---|
[Baeg-won Clothing Gallery] 7. CART 페이지 구현 (0) | 2023.07.24 |
[Baeg-won Clothing Gallery] 6. DETAIL 페이지 구현 (0) | 2023.07.16 |
[Baeg-won Clothing Gallery] 5. SALE, CONTACT 페이지 구현 (0) | 2023.07.15 |
[Baeg-won Clothing Gallery] 4. CLOTHING, FOOTWEAR, ACCESSORIES 페이지 구현 (0) | 2023.07.14 |