📝 Outline
- 지난 시간에 이어 Brands 페이지까지 구현을 완료했다.
- 참고로 New-Arrivals 페이지는 이전에 구현했던 Shop 페이지와 구현이 거의 동일해서 굳이 따로 포스팅하지는 않을 것이다.
- 본론으로 돌아가서, 기본적인 페이지 모습은 다음과 같다.
- 카테고리를 정하여 브랜드별로 혹은 가격별로 원하는 상품을 출력할 수 있도록 구현하였다.
- 꽤 심플해 보이지만 나름대로 구현하기가 까다로웠던 페이지이다.
📝 Review
- 해당 페이지를 구현하면서 조금은 신선한 기능도 구현해보았고, 새롭게 알게된 내용도 있어, 이번에도 역시 리뷰하는 시간을 가져볼까 한다.
💬 List.vue
- 우선 지난 시간에 구현하였던 상품 리스트를 출력하는 코드를 컴포넌트화 하였다.
<template>
<div>
<!-- Product List -->
<div>
<ul class="ul-clothing_list">
<li class="li-clothing_list_item" v-for="i in clothingToShow" :key="i">
<div class="div-clothing_list_item_wrapper" :style="{width: `${itemWidth}px`, height: `${itemHeight}px`}">
<v-img :src="require(`@/assets/${clothing[i - 1].url}`)" />
<div align="left">
<div class="div-clothing_name">{{clothing[i - 1].name}}</div>
<div class="div-clothing_price">₩{{clothing[i - 1].price}}</div>
</div>
</div>
</li>
</ul>
</div>
<!-- Product List End -->
<!-- Load More -->
<div v-if="clothingToShow < totalClothing" class="div-load_more_btn_wrapper" align="center">
<v-btn class="btn-load_more" elevation="0" @click="loadMore">Load More</v-btn>
</div>
<!-- Load More End -->
</div>
</template>
- 이유는 당연하게도 상품 리스트가 여러 페이지에 중복되어서 쓰이기 때문이다.
- 다만 페이지마다 적절한 상품 리스트 아이템의 사이즈가 각각 다르기 때문에 이는 따로 설정해주어야 했다.
- 따라서, 다음과 같이 List 컴포넌트에게 상위 컴포넌트가 원하는 사이즈를 전달해줌으로써 해결하였다.
<!-- Product List -->
<div class="div-brands_product_list">
<List ref="listVue" :itemWidth="itemWidth" :itemHeight="itemHeight" />
</div>
<!-- Product List End -->
💬 Category
- 브랜드별로 카테고리를 나누어 출력하는 기능을 구현해보았다.
- 우선 코드를 살펴보면 다음과 같다.
<template>
<div class="d-flex">
<!-- Designer -->
<div class="ml-2">
<v-list class="list-designer" :active="brands">
<div class="div-list_title">DESIGNERS</div>
<!-- Brand Category -->
<v-list-group :ripple="false" value="brands">
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title class="list-item_title">Category</v-list-item-title>
</v-list-item-content>
</template>
<v-list-item v-for="(brand, index) in brands" :key="index">
<v-list-item-content>
<v-list-item-title class="list-subitem_title" :class="{active: brand.isActive}" @click="setBrand(index)">{{brand.name}}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-group>
<!-- Brand Category End -->
</v-list>
</div>
<!-- Designer End -->
</div>
</template>
<script>
import List from "../components/List.vue";
export default {
components: {
List,
},
data() {
return {
brands: [
{name: "All", isActive: true},
{name: "A.P.C.", isActive: false},
{name: "Advisory Board Crystals", isActive: false},
{name: "Apostle Club", isActive: false},
...,
],
curBrand: "All",
curBrandIndex: 0,
priceRange: [15000, 1000000],
curLow: 15000,
curHigh: 1000000,
};
},
methods: {
setBrand(index) {
this.brands[this.curBrandIndex].isActive = false;
this.brands[index].isActive = true;
this.curBrand = this.brands[index].name;
this.curBrandIndex = index;
this.$refs.listVue.printBy(this.curBrand, this.curLow, this.curHigh);
this.scrollTop();
},
scrollTop() {
window.scrollTo({top : 0, behavior: "smooth"})
}
},
};
</script>
- 코드가 좀 많으니 하나씩 살펴보도록 하자.
<!-- Brand Category -->
<v-list-group :ripple="false" value="brands">
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title class="list-item_title">Category</v-list-item-title>
</v-list-item-content>
</template>
<v-list-item v-for="(brand, index) in brands" :key="index">
<v-list-item-content>
<v-list-item-title
class="list-subitem_title"
:class="{active: brand.isActive}"
@click="setBrand(index)"
>{{brand.name}}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-group>
<!-- Brand Category End -->
- 우선 위 코드는 브랜드 카테고리를 구현하기 위한 기본적인 코드이다.
- 위 코드에서 핵심은
@click
부분인데, 여기서 지정한setBrand()
메서드는 아래와 같이 구현되어 있다.
setBrand(index) {
this.brands[this.curBrandIndex].isActive = false;
this.brands[index].isActive = true;
this.curBrand = this.brands[index].name;
this.curBrandIndex = index;
this.$refs.listVue.printBy(this.curBrand, this.curLow, this.curHigh); // 핵심
this.scrollTop();
}
- 위 코드에서의 핵심은 주석으로 표시한 행이다.
- 해당 코드는
List.vue
에 정의되어 있는printBy()
메서드를 호출하기 위한 코드로,printBy()
메서드는 아래와 같이 구현되어 있다.
printBy(brand, low, high) {
let temp = [];
// 브랜드별 분류
if (brand == "All") {
temp = this.data;
} else {
this.data.forEach(element => {
if (element.brand == brand) {
temp.push({
name: element.name,
price: element.price,
url: element.url,
brand: element.brand,
register_date: element.register_date
})
}
});
}
this.clothing = []
// 가격별 분류
temp.forEach(element => {
let price = Number(element.price.replace(',', ''));
if (low <= price && price <= high) {
this.clothing.push({
name: element.name,
price: element.price,
url: element.url,
brand: element.brand,
register_date: element.register_date
})
}
})
this.totalClothing = this.clothing.length;
this.clothingToShow = Math.min(this.loadDataCount, this.totalClothing);
}
- 이 또한 코드가 꽤 기니, 하나씩 살펴보자면
let temp = [];
// 브랜드별 분류
if (brand == "All") {
temp = this.data;
} else {
this.data.forEach(element => {
if (element.brand == brand) {
temp.push({
name: element.name,
price: element.price,
url: element.url,
brand: element.brand,
register_date: element.register_date
})
}
});
}
- 우선 빈 배열
temp
를 선언하고, 브랜드별로 분류하기 위해 조건문을 포함한 반복문을 수행하며 데이터를 첫 번째로 필터링한다.
this.clothing = []
// 가격별 분류
temp.forEach(element => {
let price = Number(element.price.replace(',', ''));
if (low <= price && price <= high) {
this.clothing.push({
name: element.name,
price: element.price,
url: element.url,
brand: element.brand,
register_date: element.register_date
})
}
})
- 이후 가격별로 분류하기 위해 역시 조건문을 포함한 반복문을 수행하며 두 번째로 데이터를 필터링한 후, 이번에는 실제 출력될 데이터인
clothing
에 담아준다.
this.totalClothing = this.clothing.length;
this.clothingToShow = Math.min(this.loadDataCount, this.totalClothing);
- 마지막으로, 새롭게 형성된 데이터셋에 따라 변수를 알맞게 업데이트 해주어 오류를 방지한다.
- 이렇게 하여 카테고리에 나열된 브랜드 이름을 클릭할 시 그에 맞게 상품을 분류하여 출력하도록 구현해주었으며, 그 결과는 다음과 같다.
💬 Price
- 위에서 잠깐 나왔지만, 가격별로 분류하는 기능도 구현하였다.
- 가격별로 필터링하기 위한 코드는 위에서 설명하였으니, 그 이전에 이벤트가 어떻게 처리되는지에 대해 설명하겠다.
<!-- Set Price -->
<v-list-group :ripple="false" @click="scrollTop">
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title class="list-item_title">Price</v-list-item-title>
</v-list-item-content>
</template>
<v-list-item>
<v-list-item-content>
<v-range-slider
color="black"
track-color="gray"
track-fill-color="black"
v-model="priceRange"
min="15000"
max="1000000"
step="10000"
@change="setPriceRange"
/>
<div class="div-price_range">
<div
v-for="(price, i) in priceRange"
:key="i"
:model-value="price"
@change="$set(priceRange, i, $event)"
v-text="`₩${price.toLocaleString('ko-KR')}`"
/>
</div>
</v-list-item-content>
</v-list-item>
</v-list-group>
<!-- Set Price End -->
- 우선 가격 설정을 위한 Slider는 위와 같이 구현하였는데, 역시 핵심은
@change
부분이다.
<div class="div-price_range">
<div
v-for="(price, i) in priceRange"
:key="i"
:model-value="price"
@change="$set(priceRange, i, $event)"
v-text="`₩${price.toLocaleString('ko-KR')}`"
/>
</div>
- 먼저
div
태그에 설정된@change
를 살펴보면,$set()
메서드를 사용하고 있는 것을 확인할 수 있다. - 이는 객체 또는 배열의 값이 변경되었음을 알려주기 위한 메서드로, 이를 통해
div
태그의 텍스트가 Slider의 값에 따라 변경되도록 할 수 있다.
<v-range-slider
color="black"
track-color="gray"
track-fill-color="black"
v-model="priceRange"
min="15000"
max="1000000"
step="10000"
@change="setPriceRange"
/>
- 다음으로
v-range-slider
에는@change
메서드로priceRange()
가 설정되어 있는 것을 확인할 수 있으며, 해당 메서드는 아래와 같이 구현되어 있다.
setPriceRange(event) {
this.curLow = event[0];
this.curHigh = event[1];
this.$refs.listVue.printBy(this.curBrand, event[0], event[1]);
},
- 매개변수로 들어오는
event
에는 현재 Slider의 Low 값(event[0]
)과 High 값(event[1]
)이 저장되어있다. - 여기서도 마찬가지로
List.vue
에 정의되어 있는printBy()
메서드를 호출함으로써, 상품 리스트를 필터링하여 출력하고 있는 것을 알 수 있다. - 결과를 확인해보면 다음과 같다.
💬 404 Not Found
- 프로젝트를 진행하다가 평소처럼 무심코 새로고침을 눌렀더니 갑자기 404 에러가 발생하였다.
- 잘 진행하던 프로젝트에서 갑자기 404라니... 갑자기 발생한 상황에 당황했지만 우선 침착하게 구글에 접속했다.
- 아니나 다를까 매우 흔한 이슈였고, 나와 비슷한 상황을 맞닥뜨린 사람들이 꽤나 있어 보였다.
- 찾아보니, 해당 이슈는 SPA 앱을 개발할 때 발생하는 이슈로, connect-history-api-fallback 현상이라 한다.
- Vue.js의 경우 라우터에 등록된 Vue 컴포넌트들을 모듈 번들러를 통하여 Javascript로 컴파일하여 WebServer의 Document Root 폴더에 가지고 있다.
- 라우터를 이용한 정상적인 페이지 요청 시에는 해당 페이지에 맞는 js 파일을 클라이언트가 요청하지만, 새로고침 시에는 도메인과 라우터 Path를 가지고 WebServer에 존재하지 않는 요청을 보내기 때문에 발생하는 이슈이다.
- 이를 해결하기 위한 여러 방법이 나와 있었지만, 나 같은 경우 Spring Boot와 Vue를 연동해서 사용하고 있기 때문에 조금은 다른 방법을 사용해야 했다.
- 그 방법은 바로, 서버단에 Spring Boot의 내장 인터페이스인
ErrorController
를 상속받는 컨트롤러를 구현하여 404 Error가 발생할 경우 에러 페이지가 아닌index.html
로 redirect 시키는 것!- SPA에서는 모든 주소가
index.html
을 바라보기 때문
- SPA에서는 모든 주소가
- 따라서 아래와 같이
CustomErrorController
를 구현하여 해결해주었다.
@Controller
public class CustomErrorController implements ErrorController {
@GetMapping("/error")
public String redirectRoot(){
return "index.html";
}
}
'🚗 Backend Toy Project > Baeg-won Clothing Gallery' 카테고리의 다른 글
[Baeg-won Clothing Gallery] 5. SALE, CONTACT 페이지 구현 (0) | 2023.07.15 |
---|---|
[Baeg-won Clothing Gallery] 4. CLOTHING, FOOTWEAR, ACCESSORIES 페이지 구현 (0) | 2023.07.14 |
[Baeg-won Clothing Gallery] 2. SHOP 페이지 구현 (0) | 2023.07.06 |
[Baeg-won Clothing Gallery] 1. 메인 페이지 구현 (0) | 2023.07.04 |
[Baeg-won Clothing Gallery] 0. 프로젝트 개요 (0) | 2023.07.02 |