feat: product options, speedup home page, themes

This commit is contained in:
Nikita Kiselev
2025-07-21 13:37:09 +03:00
parent 51ce6ed959
commit e3cc0d4b10
18 changed files with 181 additions and 79 deletions

View File

@@ -7,22 +7,13 @@
Каталог
</RouterLink>
<RouterLink v-for="category in categories" class="btn" :to="`/category/${category.id}`">
<RouterLink v-for="category in categoriesStore.topCategories" class="btn" :to="`/category/${category.id}`">
{{ category.name }}
</RouterLink>
</div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import ftch from "../utils/ftch.js";
const categories = ref([]);
onMounted(async () => {
const {data} = await ftch('categoriesList', {
perPage: 7,
});
categories.value = data;
});
import {useCategoriesStore} from "@/stores/CategoriesStore.js";
const categoriesStore = useCategoriesStore();
</script>

View File

@@ -4,8 +4,9 @@
<div class="flex flex-wrap gap-2">
<label
v-for="value in model.values"
class="group relative flex items-center justify-center rounded-md border border-gray-300 bg-white p-2 has-checked:border-indigo-600 has-checked:bg-indigo-600 has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-indigo-600 has-disabled:border-gray-400 has-disabled:bg-gray-200 has-disabled:opacity-25">
class="group relative flex items-center justify-center btn btn-soft btn-secondary btn-sm"
:class="value.selected ? 'btn-active' : ''"
>
<input
type="checkbox"
:value="value.product_option_value_id"
@@ -36,6 +37,8 @@ function select(toggledValue) {
}
});
model.value.value = model.value.values.filter(item => item.selected === true);
emit('update:modelValue', model.value);
}
</script>

View File

@@ -3,7 +3,9 @@
<div class="flex flex-wrap gap-2">
<label
v-for="value in model.values"
class="group relative flex items-center justify-center rounded-md border border-gray-300 bg-base-200 p-2 has-checked:border-indigo-600 has-checked:bg-primary has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-indigo-600 has-disabled:border-gray-400 has-disabled:bg-gray-200 has-disabled:opacity-25">
class="group relative flex items-center justify-center btn btn-soft btn-secondary btn-sm"
:class="value.selected ? 'btn-active' : ''"
>
<input
type="radio"
@@ -14,7 +16,7 @@
class="absolute inset-0 appearance-none focus:outline-none disabled:cursor-not-allowed"
/>
<span class="text-xs font-medium group-has-checked:text-white">
<span class="text-xs font-medium">
{{ value.name }}<span v-if="value.price"> ({{ value.price_prefix }}{{ value.price }})</span>
</span>
</label>
@@ -33,6 +35,8 @@ function select(selectedValue) {
value.selected = (value === selectedValue);
});
model.value.value = selectedValue;
emit('update:modelValue', model);
}
</script>

View File

@@ -5,6 +5,7 @@
class="select"
@change="onChange"
>
<option value="" disabled>Выберите значение</option>
<option
v-for="value in model.values"
:key="value.product_option_value_id"
@@ -30,6 +31,8 @@ function onChange(event) {
value.selected = (value.product_option_value_id === selectedId);
});
model.value.value = model.value.values.find(value => value.product_option_value_id === selectedId);
emit('update:modelValue', model.value);
}
</script>

View File

@@ -0,0 +1,75 @@
<template>
<div class="mx-auto max-w-2xl px-4 py-4 sm:px-6 sm:py-6 lg:max-w-7xl lg:px-8">
<div v-if="isLoading" class="grid grid-cols-2 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
<div v-for="n in 8" :key="n" class="animate-pulse space-y-2">
<div class="aspect-square bg-gray-200 rounded-md"></div>
<div class="h-4 bg-gray-200 rounded w-3/4"></div>
<div class="h-4 bg-gray-200 rounded w-1/2"></div>
</div>
</div>
<template v-else>
<h2 class="text-lg font-bold mb-5 text-center">{{ meta.currentCategoryName }}</h2>
<div v-if="products.length > 0" class="grid grid-cols-2 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
<RouterLink
v-for="product in products"
:key="product.id"
class="group"
:to="`/product/${product.id}`"
@click="haptic"
>
<ProductImageSwiper :images="product.images"/>
<h3 class="mt-4 text-sm">{{ product.name }}</h3>
<p class="mt-1 text-lg font-medium">{{ product.price }}</p>
</RouterLink>
</div>
<NoProducts v-else/>
</template>
</div>
</template>
<script setup>
import {ref} from "vue";
import {useHapticFeedback} from 'vue-tg';
import { useRouter, useRoute } from 'vue-router';
import NoProducts from "../components/NoProducts.vue";
import ProductImageSwiper from "../components/ProductImageSwiper.vue";
const hapticFeedback = useHapticFeedback();
const props = defineProps({
products: {
type: Array,
default: () => [],
},
meta: {
type: Object,
default: () => ({}),
},
isLoading: {
type: Boolean,
default: false,
}
});
function haptic() {
window.Telegram.WebApp.HapticFeedback.selectionChanged();
}
const carouselRef = ref();
let lastScrollLeft = 0;
function onScroll(e) {
const scrollLeft = e.target.scrollLeft;
const delta = Math.abs(scrollLeft - lastScrollLeft);
if (delta > 30) {
hapticFeedback.impactOccurred('soft');
lastScrollLeft = scrollLeft;
}
}
</script>

View File

@@ -1,8 +1,8 @@
<template>
<div class="flex items-center text-center">
<button class="btn btn-lg btn-neutral" @click="inc">-</button>
<div class="w-10 h-10 flex items-center justify-center bg-neutral font-bold">{{ model }}</div>
<button class="btn btn-lg btn-neutral" @click="dec">+</button>
<button class="btn btn-lg" @click="inc">-</button>
<div class="w-10 h-10 flex items-center justify-center font-bold">{{ model }}</div>
<button class="btn btn-lg" @click="dec">+</button>
</div>
</template>