477 lines
No EOL
10 KiB
Vue
477 lines
No EOL
10 KiB
Vue
<template>
|
|
<div
|
|
class="card"
|
|
:class="{ 'card__list': isList }"
|
|
>
|
|
<div
|
|
class="card__wishlist"
|
|
@click="overwriteWishlist({
|
|
type: (isProductInWishlist ? 'remove' : 'add'),
|
|
productUuid: product.uuid,
|
|
productName: product.name
|
|
})"
|
|
>
|
|
<icon name="mdi:cards-heart" size="16" v-if="isProductInWishlist" />
|
|
<icon name="mdi:cards-heart-outline" size="16" v-else />
|
|
</div>
|
|
<div class="card__wrapper">
|
|
<nuxt-link-locale
|
|
:to="`/product/${product.slug}`"
|
|
class="card__link"
|
|
>
|
|
<div class="card__block">
|
|
<client-only>
|
|
<swiper
|
|
v-if="images.length"
|
|
@swiper="onSwiper"
|
|
:modules="[EffectFade, Pagination]"
|
|
effect="fade"
|
|
:slides-per-view="1"
|
|
:pagination="paginationOptions"
|
|
class="card__swiper"
|
|
>
|
|
<swiper-slide
|
|
v-for="(img, i) in images"
|
|
:key="i"
|
|
class="card__swiper-slide"
|
|
>
|
|
<nuxt-img
|
|
:src="img"
|
|
:alt="product.name"
|
|
loading="lazy"
|
|
class="card__swiper-image"
|
|
format="webp"
|
|
densities="x1"
|
|
/>
|
|
</swiper-slide>
|
|
</swiper>
|
|
<div class="card__image-placeholder" />
|
|
<div
|
|
v-for="(image, idx) in images"
|
|
:key="idx"
|
|
class="card__block-hover"
|
|
:style="{ left: `${(100/ images.length) * idx}%`, width: `${100/ images.length}%` }"
|
|
@mouseenter="goTo(idx)"
|
|
@mouseleave="goTo(0)"
|
|
/>
|
|
</client-only>
|
|
</div>
|
|
</nuxt-link-locale>
|
|
</div>
|
|
<div class="card__content">
|
|
<div class="card__content-inner">
|
|
<div class="card__brand">{{ product.brand.name }}</div>
|
|
<p class="card__name">{{ product.name }}</p>
|
|
<el-rate
|
|
class="card__rating"
|
|
v-model="rating"
|
|
size="large"
|
|
disabled
|
|
/>
|
|
<div class="card__price">{{ product.price }} $</div>
|
|
</div>
|
|
<div class="card__bottom">
|
|
<div class="card__bottom-inner">
|
|
<div class="tools" v-if="isProductInCart">
|
|
<button
|
|
class="tools__item tools__item-button"
|
|
@click="overwriteOrder({
|
|
type: 'remove',
|
|
productUuid: product.uuid,
|
|
productName: product.name
|
|
})"
|
|
>
|
|
-
|
|
</button>
|
|
<span class="tools__item tools__item-count" v-text="'X' + productInCartQuantity" />
|
|
<button
|
|
class="tools__item tools__item-button"
|
|
@click="overwriteOrder({
|
|
type: 'add',
|
|
productUuid: product.uuid,
|
|
productName: product.name
|
|
})"
|
|
>
|
|
+
|
|
</button>
|
|
</div>
|
|
<ui-button
|
|
v-else
|
|
class="card__bottom-button"
|
|
@click="overwriteOrder({
|
|
type: 'add',
|
|
productUuid: product.uuid,
|
|
productName: product.name
|
|
})"
|
|
:type="'button'"
|
|
:isLoading="addLoading"
|
|
>
|
|
{{ t('buttons.addToCart') }}
|
|
</ui-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type {IProduct} from '@types';
|
|
import { Swiper, SwiperSlide } from 'swiper/vue';
|
|
import { EffectFade, Pagination } from 'swiper/modules';
|
|
import {useWishlistOverwrite} from '@composables/wishlist';
|
|
import {useOrderOverwrite} from '@composables/orders';
|
|
|
|
const props = defineProps<{
|
|
product: IProduct;
|
|
isList?: boolean;
|
|
}>();
|
|
|
|
const {t} = useI18n();
|
|
const wishlistStore = useWishlistStore();
|
|
const cartStore = useCartStore();
|
|
|
|
const { overwriteWishlist } = useWishlistOverwrite();
|
|
const { addLoading, removeLoading, overwriteOrder } = useOrderOverwrite();
|
|
|
|
const isProductInWishlist = computed(() => {
|
|
const el = wishlistStore.wishlist?.products?.edges.find(
|
|
(el) => el?.node?.uuid === props.product.uuid
|
|
);
|
|
|
|
return !!el;
|
|
});
|
|
const isProductInCart = computed(() => {
|
|
return cartStore.currentOrder?.orderProducts?.edges.find((prod) => prod.node.product.uuid === props.product?.uuid);
|
|
});
|
|
const productInCartQuantity = computed(() => {
|
|
return cartStore.currentOrder?.orderProducts?.edges.filter((prod) => prod.node.product.uuid === props.product.uuid)[0].node.quantity;
|
|
});
|
|
|
|
const rating = computed(() => {
|
|
return props.product.feedbacks.edges[0]?.node?.rating ?? 5;
|
|
});
|
|
|
|
const images = computed(() =>
|
|
props.product.images.edges.map(e => e.node.image)
|
|
);
|
|
const paginationOptions = computed(() =>
|
|
images.value.length > 1
|
|
? {
|
|
clickable: true,
|
|
bulletClass: 'swiper-pagination-line',
|
|
bulletActiveClass: 'swiper-pagination-line--active'
|
|
}
|
|
: false
|
|
);
|
|
|
|
const swiperRef = ref<any>(null);
|
|
|
|
function onSwiper(swiper: any) {
|
|
swiperRef.value = swiper;
|
|
}
|
|
|
|
function goTo(index: number) {
|
|
swiperRef.value?.slideTo(index);
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.card {
|
|
border-radius: 8px;
|
|
border: 1px solid #e5e7eb;
|
|
width: 100%;
|
|
background-color: $white;
|
|
transition: 0.2s;
|
|
position: relative;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
&__list {
|
|
flex-direction: row;
|
|
align-items: stretch;
|
|
gap: 50px;
|
|
padding: 15px;
|
|
|
|
& .card__link {
|
|
width: fit-content;
|
|
padding: 0;
|
|
}
|
|
|
|
& .card__block {
|
|
width: 150px;
|
|
height: 150px;
|
|
}
|
|
|
|
& .card__content {
|
|
width: 100%;
|
|
flex-direction: row;
|
|
align-items: flex-end;
|
|
justify-content: space-between;
|
|
padding: 0;
|
|
|
|
&-inner {
|
|
align-self: flex-start;
|
|
}
|
|
}
|
|
|
|
& .tools {
|
|
width: 136px;
|
|
}
|
|
|
|
& .card__bottom {
|
|
margin-top: 0;
|
|
width: fit-content;
|
|
flex-shrink: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
align-items: flex-end;
|
|
|
|
&-inner {
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
gap: 10px;
|
|
}
|
|
|
|
&-button {
|
|
width: fit-content;
|
|
padding-inline: 25px;
|
|
}
|
|
}
|
|
|
|
& .card__wrapper {
|
|
flex-direction: row;
|
|
}
|
|
}
|
|
|
|
@include hover {
|
|
border-color: #b7b8bb;
|
|
box-shadow: 0 0 10px 1px #e5e7eb;
|
|
}
|
|
|
|
&__wrapper {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
&__link {
|
|
display: block;
|
|
width: 100%;
|
|
}
|
|
|
|
&__block {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 200px;
|
|
overflow: hidden;
|
|
|
|
&-hover {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 20%;
|
|
height: 100%;
|
|
z-index: 2;
|
|
cursor: pointer;
|
|
background: transparent;
|
|
}
|
|
}
|
|
|
|
&__swiper {
|
|
width: 100%;
|
|
height: 100%;
|
|
position: relative;
|
|
z-index: 1;
|
|
padding-bottom: 10px;
|
|
|
|
&-image {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
}
|
|
}
|
|
|
|
&__image {
|
|
&-placeholder {
|
|
width: 100%;
|
|
height: 200px;
|
|
background-color: $accentLight;
|
|
}
|
|
}
|
|
|
|
&__content {
|
|
padding: 10px 20px 20px 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
|
|
&-inner {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
}
|
|
|
|
&__brand {
|
|
font-size: 12px;
|
|
font-weight: 400;
|
|
letter-spacing: -0.2px;
|
|
color: #6b7280;
|
|
}
|
|
|
|
&__price {
|
|
font-weight: 600;
|
|
font-size: 16px;
|
|
letter-spacing: -0.5px;
|
|
color: #1a1a1a;
|
|
}
|
|
|
|
&__rating {
|
|
margin-block: 4px 10px;
|
|
}
|
|
|
|
&__name {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
color: $black;
|
|
font-weight: 500;
|
|
font-size: 14px;
|
|
letter-spacing: -0.5px;
|
|
}
|
|
|
|
&__quantity {
|
|
width: fit-content;
|
|
background-color: rgba($contrast, 0.5);
|
|
border-radius: $default_border_radius;
|
|
padding: 5px 10px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
&__wishlist {
|
|
cursor: pointer;
|
|
width: fit-content;
|
|
background-color: $white;
|
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
|
|
position: absolute;
|
|
top: 16px;
|
|
right: 16px;
|
|
z-index: 3;
|
|
border-radius: 50%;
|
|
padding: 12px;
|
|
transition: 0.2s;
|
|
|
|
@include hover {
|
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
& span {
|
|
color: #4b5563;
|
|
display: block;
|
|
}
|
|
}
|
|
|
|
&__bottom {
|
|
&-inner {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 5px;
|
|
}
|
|
|
|
&-button {
|
|
padding-block: 10px !important;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
&-wishlist {
|
|
cursor: pointer;
|
|
width: 34px;
|
|
height: 34px;
|
|
flex-shrink: 0;
|
|
background-color: $accent;
|
|
border-radius: $default_border_radius;
|
|
display: grid;
|
|
place-items: center;
|
|
transition: 0.2s;
|
|
|
|
font-size: 22px;
|
|
color: $white;
|
|
|
|
@include hover {
|
|
background-color: $accentLight;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.tools {
|
|
width: 100%;
|
|
border-radius: 4px;
|
|
background-color: #111827;
|
|
display: grid;
|
|
grid-template-columns: 1fr 2fr 1fr;
|
|
height: 40px;
|
|
|
|
&__item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
height: 100%;
|
|
|
|
&-count {
|
|
border-left: 1px solid $accent;
|
|
border-right: 1px solid $accent;
|
|
|
|
color: $white;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
&-button {
|
|
cursor: pointer;
|
|
background-color: #111827;
|
|
border-radius: 4px 0 0 4px;
|
|
transition: 0.2s;
|
|
|
|
color: $white;
|
|
font-size: 18px;
|
|
font-weight: 500;
|
|
|
|
@include hover {
|
|
background-color: #222c41;
|
|
color: $white;
|
|
}
|
|
|
|
&:last-child {
|
|
border-radius: 0 4px 4px 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
:deep(.swiper-pagination) {
|
|
bottom: 0;
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
:deep(.swiper-pagination-line) {
|
|
display: inline-block;
|
|
width: 24px;
|
|
height: 2px;
|
|
background-color: rgba($accentDark, 0.3);
|
|
border-radius: 0;
|
|
opacity: 1;
|
|
transition: 0.2s;
|
|
}
|
|
|
|
:deep(.swiper-pagination-line--active) {
|
|
background-color: $accentDark;
|
|
}
|
|
</style> |