Refine catalog detail layout

This commit is contained in:
Ruslan Bakiev
2026-04-09 19:14:14 +07:00
parent 0236d88b20
commit 73adbb76c7

View File

@@ -263,6 +263,21 @@ function visibleFields(group: ProductGroup) {
} }
const selectedGroup = computed(() => productGroups.value.find((group) => group.key === props.productTypeSlug) ?? null); const selectedGroup = computed(() => productGroups.value.find((group) => group.key === props.productTypeSlug) ?? null);
const currentGroupIndex = computed(() => productGroups.value.findIndex((group) => group.key === props.productTypeSlug));
const previousGroup = computed(() => {
if (currentGroupIndex.value <= 0) {
return null;
}
return productGroups.value[currentGroupIndex.value - 1] ?? null;
});
const nextGroup = computed(() => {
if (currentGroupIndex.value < 0 || currentGroupIndex.value >= productGroups.value.length - 1) {
return null;
}
return productGroups.value[currentGroupIndex.value + 1] ?? null;
});
function requiredKeys(group: ProductGroup) { function requiredKeys(group: ProductGroup) {
return visibleFields(group).map((field) => field.key); return visibleFields(group).map((field) => field.key);
@@ -564,62 +579,88 @@ function decrementSelected(group: ProductGroup) {
decrementProduct(product.id); decrementProduct(product.id);
} }
} }
function productDetailPath(group: ProductGroup) {
return `/products/${group.key}`;
}
</script> </script>
<template> <template>
<section class="space-y-5"> <section class="space-y-5">
<div class="flex items-center gap-3">
<NuxtLink to="/products" class="btn btn-ghost rounded-full px-3">
Назад к каталогу
</NuxtLink>
</div>
<div v-if="loading" class="alert surface-card border-0">Загрузка каталога...</div> <div v-if="loading" class="alert surface-card border-0">Загрузка каталога...</div>
<div v-else-if="error" class="alert alert-error">{{ error.message }}</div> <div v-else-if="error" class="alert alert-error">{{ error.message }}</div>
<div v-else-if="selectedGroup" class="space-y-4"> <div v-else-if="selectedGroup" class="space-y-4">
<article class="surface-card rounded-3xl p-4 md:p-5"> <NuxtLink
<div class="grid gap-4 xl:grid-cols-6 xl:items-start"> v-if="previousGroup"
<div class="p-3 xl:col-span-1"> :to="productDetailPath(previousGroup)"
<img class="fixed left-6 top-1/2 z-10 hidden h-12 w-12 -translate-y-1/2 items-center justify-center rounded-full border border-[#dce9e1] bg-white text-xl text-[#163624] shadow-[0_12px_28px_rgba(18,56,36,0.08)] 2xl:flex"
:src="createProductCover(selectedGroup.typeLabel, selectedGroup.key)" aria-label="Предыдущий товар"
:alt="`Превью группы ${selectedGroup.typeLabel}`"
class="aspect-square w-full rounded-[24px] object-cover"
loading="lazy"
> >
</NuxtLink>
<NuxtLink
v-if="nextGroup"
:to="productDetailPath(nextGroup)"
class="fixed right-6 top-1/2 z-10 hidden h-12 w-12 -translate-y-1/2 items-center justify-center rounded-full border border-[#dce9e1] bg-white text-xl text-[#163624] shadow-[0_12px_28px_rgba(18,56,36,0.08)] 2xl:flex"
aria-label="Следующий товар"
>
</NuxtLink>
<div class="flex items-center justify-between gap-3">
<NuxtLink to="/products" class="btn btn-ghost rounded-full px-3 text-[#163624]">
Назад
</NuxtLink>
<div class="flex items-center gap-2">
<NuxtLink
v-if="previousGroup"
:to="productDetailPath(previousGroup)"
class="btn btn-ghost btn-circle"
aria-label="Предыдущий товар"
>
</NuxtLink>
<NuxtLink
v-if="nextGroup"
:to="productDetailPath(nextGroup)"
class="btn btn-ghost btn-circle"
aria-label="Следующий товар"
>
</NuxtLink>
</div>
</div> </div>
<div class="p-4 md:p-5 xl:col-span-4"> <div class="space-y-4">
<div class="mb-4"> <div class="rounded-[28px] border border-[#e6efe9] bg-white p-5 md:p-7">
<h2 class="text-2xl font-bold text-[#163624]">{{ selectedGroup.typeLabel }}</h2> <h1 class="text-3xl font-bold text-[#163624]">{{ selectedGroup.typeLabel }}</h1>
<p class="mt-2 max-w-3xl text-sm leading-6 text-[#5d7468]">
Выберите параметры внутри карточки, сразу посмотрите ограничения и ниже изучите полный список вариантов по этой позиции.
</p>
<div v-if="customizationDetails(selectedGroup).length" class="mt-4 rounded-[24px] bg-[#eef8f1] p-4"> <div v-if="customizationDetails(selectedGroup).length" class="mt-4 flex flex-wrap gap-2">
<p class="text-xs font-bold uppercase tracking-[0.12em] text-[#15613d]">Под заказ</p>
<div class="mt-3 flex flex-wrap gap-2">
<span <span
v-for="note in customizationDetails(selectedGroup)" v-for="note in customizationDetails(selectedGroup)"
:key="`${selectedGroup.key}-${note}`" :key="`${selectedGroup.key}-${note}`"
class="rounded-full bg-white px-3 py-1 text-xs font-semibold text-[#15613d]" class="rounded-full border border-[#dce9e1] bg-white px-3 py-1 text-xs font-semibold text-[#355947]"
> >
{{ note }} {{ note }}
</span> </span>
</div> </div>
</div> </div>
</div>
<div class="space-y-3"> <div class="space-y-3">
<div <article
v-for="field in visibleFields(selectedGroup)" v-for="field in visibleFields(selectedGroup)"
:key="`${selectedGroup.key}-${field.key}`" :key="`${selectedGroup.key}-${field.key}`"
class="rounded-[22px] border border-base-200 bg-[#fbfcfb] p-4" class="rounded-[24px] border border-[#e6efe9] bg-white p-5"
> >
<div class="grid gap-4 xl:grid-cols-[minmax(0,1fr)_260px] xl:items-start"> <div class="space-y-4">
<div> <div class="space-y-2">
<p class="text-sm font-semibold text-[#163624]">{{ field.label }}</p> <h2 class="text-lg font-semibold text-[#163624]">{{ field.label }}</h2>
<p class="text-sm leading-6 text-[#5d7468]">{{ fieldHelperText(selectedGroup, field.key) }}</p>
</div>
<div class="mt-3 flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<label <label
v-for="option in getAllFieldOptions(selectedGroup, field.key)" v-for="option in getAllFieldOptions(selectedGroup, field.key)"
:key="`${selectedGroup.key}-${field.key}-${option}`" :key="`${selectedGroup.key}-${field.key}-${option}`"
@@ -644,34 +685,27 @@ function decrementSelected(group: ProductGroup) {
</label> </label>
</div> </div>
</div> </div>
</article>
<div class="rounded-[18px] bg-white p-3 text-sm leading-6 text-[#5d7468]"> <article class="rounded-[24px] border border-[#e6efe9] bg-white p-5">
{{ fieldHelperText(selectedGroup, field.key) }} <div class="space-y-4">
</div> <div class="space-y-1">
</div> <h2 class="text-lg font-semibold text-[#163624]">В корзину</h2>
</div> <p class="text-sm leading-6 text-[#5d7468]">
</div>
</div>
<aside class="p-4 md:p-5 xl:col-span-1">
<div class="flex h-full flex-col justify-between gap-4">
<div />
<div class="space-y-3">
<div class="rounded-[22px] bg-[#f5faf7] px-4 py-3 text-center text-sm font-medium text-[#4c6a5a]">
Артикул: <span class="font-semibold text-[#163624]">{{ articleLabel(selectedGroup) }}</span> Артикул: <span class="font-semibold text-[#163624]">{{ articleLabel(selectedGroup) }}</span>
</p>
</div> </div>
<button <button
v-if="selectedQty(selectedGroup) === 0" v-if="selectedQty(selectedGroup) === 0"
class="btn h-11 w-full rounded-full border-0 bg-[#139957] text-sm font-semibold text-white hover:bg-[#0d854a]" class="btn h-11 rounded-full border-0 bg-[#139957] px-6 text-sm font-semibold text-white hover:bg-[#0d854a]"
:disabled="!selectedProduct(selectedGroup)" :disabled="!selectedProduct(selectedGroup)"
@click="incrementSelected(selectedGroup)" @click="incrementSelected(selectedGroup)"
> >
В корзину В корзину
</button> </button>
<div v-else class="rounded-[22px] border border-base-300 bg-base-100 px-2 py-1"> <div v-else class="inline-flex rounded-[22px] border border-base-300 bg-base-100 px-2 py-1">
<div class="flex items-center justify-between gap-2"> <div class="flex items-center justify-between gap-2">
<button <button
class="btn btn-square btn-sm" class="btn btn-square btn-sm"
@@ -687,11 +721,18 @@ function decrementSelected(group: ProductGroup) {
</div> </div>
</div> </div>
</div> </div>
</div> </article>
</aside>
<article class="rounded-[24px] border border-[#e6efe9] bg-white p-5">
<div class="space-y-4">
<div class="space-y-1">
<h2 class="text-lg font-semibold text-[#163624]">Товар в наличии</h2>
<p class="text-sm leading-6 text-[#5d7468]">
Все доступные варианты по этому типу товара.
</p>
</div> </div>
<div class="mt-4 overflow-x-auto rounded-[28px] bg-white"> <div class="overflow-x-auto rounded-[20px] border border-[#edf4ef] bg-white">
<table class="table border-separate border-spacing-0 bg-white [&_tbody_tr:hover]:bg-white [&_tbody_tr]:bg-white [&_td]:bg-white [&_th]:bg-white [&_thead_tr]:bg-white"> <table class="table border-separate border-spacing-0 bg-white [&_tbody_tr:hover]:bg-white [&_tbody_tr]:bg-white [&_td]:bg-white [&_th]:bg-white [&_thead_tr]:bg-white">
<thead> <thead>
<tr> <tr>
@@ -734,8 +775,11 @@ function decrementSelected(group: ProductGroup) {
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
</article> </article>
</div> </div>
</div>
</div>
<div v-else class="alert surface-card border-0">Такой тип товара не найден.</div> <div v-else class="alert surface-card border-0">Такой тип товара не найден.</div>
</section> </section>
</template> </template>