Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/product list view best price #217

Merged
merged 5 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/apis/product/ProductDto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export interface CouponInfoItemResponse {
appliedToId: number
discountType: string
discountValue: number
maxPurchaseAmount: number | null
minPurchaseAmount: number
minPurchaseAmount: number // 0이상 number minPurchaseAmount 이상 구매해야 쿠폰 적용가능
maxDiscountAmount: number | null // null이면 maxDiscountAmount 한계가 없다는 뜻
}

export interface ReadProductDetailResponse {
Expand Down
69 changes: 69 additions & 0 deletions src/components/product/ProductListPriceDisplay.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<template>
<div class="product-price">
<template v-if="hasDiscount">
<span class="s-goods-price__original">
<del>{{ originalPrice?.toLocaleString() }}원</del>
</span>
<span class="s-goods-price__discount">{{ discountPercentage }}%</span>
</template>
<strong class="s-goods-price__final">{{ finalPrice?.toLocaleString() }}원</strong>
</div>
</template>

<script setup lang="ts">
import { defineProps, computed } from 'vue'

const props = defineProps({
originalPrice: Number,
discountPercentage: {
type: Number,
default: 0
},
finalPrice: Number
})

const hasDiscount = computed(() => props.discountPercentage > 0)
</script>

<style scoped>
.product-price {
width: auto;
height: auto;
/* display: flex;
flex-direction: column;
align-items: center;
text-align: end; */
font-family: 'TheJamsil';
}

.product-price {
display: flex;
align-items: center;
font-size: 0; /* inline-block elements간의 의도하지않은 spacing방지용 */
}

.product-price__original,
.product-price__discount,
.product-price__final {
font-size: 18px; /* 이 컴포넌트의 기본 폰트크기 */
line-height: 24px; /* 가격은 좀 더 크게 뒀음. */
display: inline-block;
}

.product-price__original {
color: #888;
text-decoration: line-through; /* 긋는 효과 */
margin-right: 6px;
font-size: 16px;
}

.product-price__discount {
color: #c22727;
margin-right: 7px;
}

.product-price__final {
font-weight: 700;
color: #000;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,11 @@ const maxDiscountAmount = computed(() => {

emit('best-promotional-price-updated', maxDiscount) // computed로 변경시 emit

return maxDiscount
return floorToTens(maxDiscount)
})

const floorToTens = (n: number): number => Math.floor(n / 10) * 10

const formattedMaxDiscountAmount = computed(() => {
return maxDiscountAmount.value.toLocaleString() + '원'
})
Expand Down
4 changes: 3 additions & 1 deletion src/views/ProductDetailView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ const bestPromotionalPriceUpdatedHandler = (maxDiscount: number) => {
}
}

const floorToTens = (n: number): number => Math.floor(n / 10) * 10

/**
* 재입고 알림신청 관련
*/
Expand Down Expand Up @@ -398,7 +400,7 @@ watch(selectedProductSize, () => {
<div class="price-info-row">
<h1>데일리온가</h1>
<!-- TODO : 여기에 할인 적용된 금액 들어가나요? -->
<h2>{{ bestPromotionalPrice.toLocaleString() }}</h2>
<h2>{{ floorToTens(bestPromotionalPrice).toLocaleString() }}</h2>
<!-- TODO : 여기에 최대 할인율? 할인 금액 나오는건가요? -->
<h3 v-show="bestPromotionalRate">{{ bestPromotionalRate }}%</h3>
</div>
Expand Down
54 changes: 51 additions & 3 deletions src/views/ProductListView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useRoute } from 'vue-router'
import type { ReadProductResponse, ReadProductSliceResponse } from '@/apis/product/ProductDto'
import { getProductSlice } from '@/apis/product/ProductClient'
import BreadCrumbComponent from '@/components/product/BreadCrumbComponent.vue'
import ProductListPriceDisplay from '@/components/product/ProductListPriceDisplay.vue'
import { Image } from 'ant-design-vue'
import { errorModal } from '@/utils/Modal'

Expand Down Expand Up @@ -80,7 +81,7 @@ const getImageSize = async () => {
if (img.value[0]) {
await handleImageLoad()
} else {
(img.value[0] as HTMLImageElement).onload = handleImageLoad
;(img.value[0] as HTMLImageElement).onload = handleImageLoad
}
}

Expand All @@ -92,6 +93,48 @@ const handleImageLoad = async () => {
}
}
}

/**
혜택가 관련 계산입니다.
*/
const getFloorDiscountPercentage = (product: ReadProductResponse) => {
const discount = getProductDiscount(product)
return Math.floor(discount)
}

const getFinalPrice = (product: ReadProductResponse) => {
const discount = getProductDiscount(product)
return product.price - (product.price * discount) / 100
}

const getProductDiscount = (product: ReadProductResponse) => {
let maxDiscountPercentage = 0

product.coupons.forEach((coupon) => {
if (product.price >= coupon.minPurchaseAmount) {
// Listview에서는 일단 1개 구매를 기준으로 display
// N개 이상 구매했을때를 가정한 최적 할인율과 해당 쿠폰 적용환경의 최소개수도 구할까 고민중임.
let discountPercentage = 0

if (coupon.discountType === 'PERCENTAGE') {
discountPercentage = coupon.discountValue
} else if (coupon.discountType === 'FIXED_AMOUNT') {
discountPercentage = (coupon.discountValue / product.price) * 100
}

// 쿠폰별 maxDiscount cap 적용해서 갱신
if (coupon.maxDiscountAmount !== null) {
const maxDiscountValue = (coupon.maxDiscountAmount / product.price) * 100
discountPercentage = Math.min(discountPercentage, maxDiscountValue)
}

// 직전 iteration과 비교 갱신
maxDiscountPercentage = Math.max(maxDiscountPercentage, Math.floor(discountPercentage))
}
})

return maxDiscountPercentage
}
</script>

<template>
Expand Down Expand Up @@ -122,6 +165,11 @@ const handleImageLoad = async () => {
/>
<h1>{{ product.brandName }}</h1>
<h2>{{ product.name }}</h2>
<PriceDisplay
:original-price="product.price"
:discount-percentage="getFloorDiscountPercentage(product)"
:final-price="getFinalPrice(product)"
/>
<div class="product-third-info">
<div class="product-aggregate">
<svg
Expand All @@ -138,9 +186,9 @@ const handleImageLoad = async () => {
</svg>
<h1>{{ product.avgRating.toFixed(1) }} | ({{ product.reviewCount }})</h1>
</div>
<div class="product-price">
<!-- <div class="product-price">
<h3>{{ product.price.toLocaleString() }}원</h3>
</div>
</div> -->
</div>
</RouterLink>
</div>
Expand Down