Fixes: None; Extra: 1) Create Pinia stores for app, user, category, and company management; 2) Add utility functions for error handling and category slug lookups; 3) Include German locale file and robots.txt for improved SEO and accessibility; 4) Add SVG assets and improve general folder structure for better maintainability.
308 lines
No EOL
6.3 KiB
Vue
308 lines
No EOL
6.3 KiB
Vue
<template>
|
|
<div
|
|
class="card"
|
|
:class="{ 'card__list': productView === 'list' }"
|
|
>
|
|
<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"
|
|
>
|
|
<SwiperSlide
|
|
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"
|
|
/>
|
|
</SwiperSlide>
|
|
</Swiper>
|
|
<div class="card__image-placeholder" />
|
|
<div
|
|
v-for="(_, i) in images"
|
|
:key="i"
|
|
class="card__block-hover"
|
|
:style="{ left: `${(100/ images.length) * i}%`, width: `${100/ images.length}%` }"
|
|
@mouseenter="goTo(i)"
|
|
@mouseleave="goTo(0)"
|
|
/>
|
|
</client-only>
|
|
</div>
|
|
</nuxt-link-locale>
|
|
<div class="card__content">
|
|
<div class="card__price">{{ product.price }}</div>
|
|
<p class="card__name">{{ product.name }}</p>
|
|
<el-rate
|
|
v-model="rating"
|
|
size="large"
|
|
allow-half
|
|
disabled
|
|
/>
|
|
<div class="card__quantity">{{ t('cards.product.stock') }} {{ product.quantity }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="card__bottom">
|
|
<ui-button class="card__bottom-button">
|
|
{{ t('buttons.addToCart') }}
|
|
</ui-button>
|
|
<div class="card__bottom-wishlist">
|
|
<Icon name="mdi:cards-heart-outline" size="28" />
|
|
<!-- <Icon name="mdi:cards-heart" size="28" />-->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type {IProduct} from "~/types/app/products";
|
|
import { useAppConfig } from '~/composables/config';
|
|
import { Swiper, SwiperSlide } from 'swiper/vue';
|
|
import { EffectFade, Pagination } from 'swiper/modules';
|
|
import 'swiper/css';
|
|
import 'swiper/css/effect-fade';
|
|
import 'swiper/css/pagination'
|
|
|
|
const props = defineProps<{
|
|
product: IProduct;
|
|
}>();
|
|
|
|
const {t} = useI18n();
|
|
|
|
const { COOKIES_PRODUCT_VIEW_KEY } = useAppConfig()
|
|
|
|
const productView = useCookie<string>(
|
|
COOKIES_PRODUCT_VIEW_KEY as string,
|
|
{
|
|
default: () => 'grid',
|
|
path: '/',
|
|
}
|
|
)
|
|
|
|
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: $default_border_radius;
|
|
border: 2px solid $accentDark;
|
|
width: 100%;
|
|
background-color: $white;
|
|
transition: 0.2s;
|
|
position: relative;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20px;
|
|
|
|
&__list {
|
|
flex-direction: row;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
padding: 10px;
|
|
|
|
& .card__link {
|
|
width: fit-content;
|
|
padding: 0;
|
|
}
|
|
|
|
& .card__block {
|
|
width: 150px;
|
|
height: 150px;
|
|
}
|
|
|
|
& .card__bottom {
|
|
margin-top: 0;
|
|
width: fit-content;
|
|
flex-shrink: 0;
|
|
padding-inline: 0;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
gap: 10px;
|
|
|
|
&-button {
|
|
width: fit-content;
|
|
padding-inline: 25px;
|
|
}
|
|
}
|
|
|
|
& .card__wrapper {
|
|
flex-direction: row;
|
|
}
|
|
}
|
|
|
|
@include hover {
|
|
box-shadow: 0 0 30px 3px rgba($accentDark, 0.4);
|
|
}
|
|
|
|
&__wrapper {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
&__link {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 20px 15px;
|
|
}
|
|
|
|
&__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-inline: 20px;
|
|
}
|
|
|
|
&__price {
|
|
width: fit-content;
|
|
background-color: rgba($accent, 0.2);
|
|
border-radius: $default_border_radius;
|
|
padding: 5px 10px;
|
|
margin-bottom: 10px;
|
|
|
|
font-weight: 700;
|
|
font-size: 16px;
|
|
}
|
|
|
|
&__name {
|
|
overflow: hidden;
|
|
font-weight: 500;
|
|
font-size: 14px;
|
|
}
|
|
|
|
&__quantity {
|
|
font-size: 14px;
|
|
}
|
|
|
|
&__bottom {
|
|
margin-top: auto;
|
|
padding: 0 20px 20px 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 5px;
|
|
max-width: 100%;
|
|
|
|
&-button {
|
|
width: 84%;
|
|
}
|
|
|
|
&-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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
: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> |