Redesign client cabinet UI with capsule nav and card layouts

This commit is contained in:
Ruslan Bakiev
2026-04-01 18:59:20 +07:00
parent 5b5f6250d3
commit b4537c1483
9 changed files with 598 additions and 111 deletions

View File

@@ -3,24 +3,73 @@ import { useQuery } from '@vue/apollo-composable';
import { ClientProductsDocument } from '~/composables/graphql/generated';
const { result, loading, error } = useQuery(ClientProductsDocument);
const coverPresets = [
['#e9fbe5', '#acfcd5', '#7be9aa'],
['#f5fff7', '#d9f5e6', '#8bd8b0'],
['#fef4ed', '#ffe5d8', '#ffd1b8'],
];
function createProductCover(name: string, sku: string) {
const seed = `${name}${sku}`.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
const [start, middle, finish] = coverPresets[seed % coverPresets.length];
const firstLetter = name.trim().charAt(0).toUpperCase() || 'P';
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="${start}" />
<stop offset="56%" stop-color="${middle}" />
<stop offset="100%" stop-color="${finish}" />
</linearGradient>
</defs>
<rect width="640" height="480" fill="url(#g)" rx="38" />
<g opacity="0.18">
<circle cx="520" cy="66" r="100" fill="#0d854a" />
<circle cx="80" cy="440" r="100" fill="#0d854a" />
</g>
<text x="50%" y="53%" text-anchor="middle" fill="#0f2f20" font-family="Manrope, sans-serif" font-size="186" font-weight="700">${firstLetter}</text>
</svg>
`.trim();
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
}
</script>
<template>
<section class="space-y-4">
<h1 class="text-2xl font-bold">Витрина товаров</h1>
<div v-if="loading" class="alert">Загрузка...</div>
<section class="space-y-5">
<div>
<h1 class="text-3xl font-extrabold text-[#0f2f20]">Каталог</h1>
<p class="mt-1 text-sm text-[#28543f]/80">Витрина в формате карточек: только визуал и название позиции.</p>
</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 class="grid gap-4 lg:grid-cols-2">
<article v-for="product in result?.clientProducts ?? []" :key="product.id" class="card bg-base-100 border border-base-300">
<div class="card-body gap-2">
<h2 class="card-title">{{ product.name }}</h2>
<p class="text-sm opacity-80">{{ product.description }}</p>
<p class="text-xs">SKU: {{ product.sku }}</p>
<ul class="text-sm space-y-1">
<li v-for="stock in product.availableInWarehouses" :key="stock.warehouse.id">{{ stock.warehouse.name }}: {{ stock.availableQty }}</li>
</ul>
<div v-else-if="(result?.clientProducts?.length ?? 0) > 0" class="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
<article
v-for="(product, index) in result?.clientProducts ?? []"
:key="product.id"
class="surface-card product-card-anim overflow-hidden rounded-3xl p-3"
:style="{ animationDelay: `${index * 55}ms` }"
>
<figure class="overflow-hidden rounded-2xl">
<img
:src="createProductCover(product.name, product.sku)"
:alt="`Изображение товара ${product.name}`"
class="h-48 w-full object-cover transition duration-300 hover:scale-105"
loading="lazy"
>
</figure>
<div class="px-1 pb-2 pt-3">
<h2 class="text-lg font-bold text-[#133826]">{{ product.name }}</h2>
</div>
</article>
</div>
<div v-else class="alert surface-card border-0">
В каталоге пока нет активных товаров.
</div>
</section>
</template>