feat(products): show correct product prices
This commit is contained in:
19
spa/src/components/ProductNotFound.vue
Normal file
19
spa/src/components/ProductNotFound.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center text-center py-16">
|
||||
<span class="text-5xl mb-4">😔</span>
|
||||
<h2 class="text-xl font-semibold mb-2">Товар не найден</h2>
|
||||
<p class="text-sm mb-4">К сожалению, запрошенный товар недоступен или был удалён.</p>
|
||||
<button class="btn btn-primary" @click="goBack">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
Назад
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
const goBack = () => router.back();
|
||||
</script>
|
||||
@@ -3,7 +3,7 @@
|
||||
<OptionTemplate :name="model.name" :required="model.required">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<label
|
||||
v-for="value in model.values"
|
||||
v-for="value in model.product_option_value"
|
||||
class="group relative flex items-center justify-center btn btn-soft btn-secondary btn-sm"
|
||||
:class="value.selected ? 'btn-active' : ''"
|
||||
>
|
||||
@@ -31,13 +31,13 @@ const model = defineModel();
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
function select(toggledValue) {
|
||||
model.value.values.forEach(value => {
|
||||
model.value.product_option_value.forEach(value => {
|
||||
if (value === toggledValue) {
|
||||
value.selected = !value.selected;
|
||||
}
|
||||
});
|
||||
|
||||
model.value.value = model.value.values.filter(item => item.selected === true);
|
||||
model.value.value = model.value.product_option_value.filter(item => item.selected === true);
|
||||
|
||||
emit('update:modelValue', model.value);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<OptionTemplate :name="model.name" :required="model.required">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<label
|
||||
v-for="value in model.values"
|
||||
v-for="value in model.product_option_value"
|
||||
class="group relative flex items-center justify-center btn btn-soft btn-secondary btn-sm"
|
||||
:class="value.selected ? 'btn-active' : ''"
|
||||
>
|
||||
@@ -31,7 +31,7 @@ const model = defineModel();
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
function select(selectedValue) {
|
||||
model.value.values.forEach(value => {
|
||||
model.value.product_option_value.forEach(value => {
|
||||
value.selected = (value === selectedValue);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
>
|
||||
<option value="" disabled selected>Выберите значение</option>
|
||||
<option
|
||||
v-for="value in model.values"
|
||||
v-for="value in model.product_option_value"
|
||||
:key="value.product_option_value_id"
|
||||
:value="value.product_option_value_id"
|
||||
:selected="value.selected"
|
||||
@@ -27,11 +27,11 @@ const emit = defineEmits(['update:modelValue']);
|
||||
function onChange(event) {
|
||||
const selectedId = Number(event.target.value);
|
||||
|
||||
model.value.values.forEach(value => {
|
||||
model.value.product_option_value.forEach(value => {
|
||||
value.selected = (value.product_option_value_id === selectedId);
|
||||
});
|
||||
|
||||
model.value.value = model.value.values.find(value => value.product_option_value_id === selectedId);
|
||||
model.value.value = model.value.product_option_value.find(value => value.product_option_value_id === selectedId);
|
||||
|
||||
emit('update:modelValue', model.value);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,13 @@
|
||||
>
|
||||
<ProductImageSwiper :images="product.images"/>
|
||||
<h3 class="product-title mt-4 text-sm">{{ product.name }}</h3>
|
||||
<p class="mt-1 text-lg font-medium">{{ product.price }}</p>
|
||||
|
||||
<div v-if="product.special" class="mt-1">
|
||||
<p class="text-xs line-through mr-2">{{ product.price }}</p>
|
||||
<p class="text-lg font-medium">{{ product.special }}</p>
|
||||
</div>
|
||||
<p v-else class="mt-1 text-lg font-medium">{{ product.price }}</p>
|
||||
|
||||
</RouterLink>
|
||||
<div ref="bottom" style="height: 1px;"></div>
|
||||
</div>
|
||||
|
||||
@@ -22,21 +22,33 @@
|
||||
/>
|
||||
|
||||
<!-- Product info -->
|
||||
<div
|
||||
class="mx-auto max-w-2xl px-4 pt-3 pb-24 sm:px-6 rounded-t-lg">
|
||||
<div class="mx-auto max-w-2xl px-4 pt-3 pb-32 sm:px-6 rounded-t-lg">
|
||||
<div class="lg:col-span-2 lg:border-r lg:pr-8">
|
||||
<h1 class="text-2xl font-bold tracking-tight sm:text-3xl">{{ product.name }}</h1>
|
||||
<h1 class="font-bold tracking-tight text-3xl">{{ product.name }}</h1>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-medium">{{ product.manufacturer }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 lg:row-span-3 lg:mt-0">
|
||||
<p class="text-3xl tracking-tight">{{ product.price }}</p>
|
||||
<div v-if="product.special" class="flex items-center">
|
||||
<p class="text-2xl tracking-tight mr-3">{{ product.special }}</p>
|
||||
<p class="text-base-400 line-through">{{ product.price }}</p>
|
||||
</div>
|
||||
<p v-else class="text-3xl tracking-tight">{{ product.price }}</p>
|
||||
|
||||
<p v-if="product.tax" class="text-sm">Без НДС: {{ product.tax }}</p>
|
||||
<p v-if="product.points && product.points > 0" class="text-sm">Бонусные баллы: {{ product.points }}</p>
|
||||
<p v-for="discount in product.discounts" class="text-sm">
|
||||
{{ discount.quantity }} или больше {{ discount.price }}
|
||||
</p>
|
||||
|
||||
<p v-if="false" class="text-xs">Кол-во на складе: {{ product.quantity }} шт.</p>
|
||||
<p v-if="product.minimum && product.minimum > 1">Минимальное кол-ва для заказа: {{ product.minimum }}</p>
|
||||
<p v-if="product.minimum && product.minimum > 1">Минимальное кол-во для заказа: {{ product.minimum }}</p>
|
||||
</div>
|
||||
|
||||
<div class="badge badge-primary">{{ product.stock }}</div>
|
||||
|
||||
<div v-if="product.options && product.options.length" class="mt-4">
|
||||
<ProductOptions v-model="product.options"/>
|
||||
</div>
|
||||
@@ -51,14 +63,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="product.attributes && product.attributes.length > 0" class="mt-3">
|
||||
<div v-if="product.attribute_groups && product.attribute_groups.length > 0" class="mt-3">
|
||||
<h3 class="font-bold mb-2">Характеристики</h3>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-xs">
|
||||
<tbody>
|
||||
<template v-for="attrGroup in product.attributes" :key="attrGroup.attribute_group_id">
|
||||
<template v-for="attrGroup in product.attribute_groups" :key="attrGroup.attribute_group_id">
|
||||
<tr class="bg-base-200 font-semibold">
|
||||
<td colspan="2">{{ attrGroup.name }}</td>
|
||||
</tr>
|
||||
@@ -76,7 +88,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="product.id" class="fixed px-4 pb-10 pt-4 bottom-0 left-0 w-full bg-base-200 z-50 flex flex-col gap-2 border-t-1 border-t-base-300">
|
||||
<div v-if="product.product_id" class="fixed px-4 pb-10 pt-4 bottom-0 left-0 w-full bg-base-200 z-50 flex flex-col gap-2 border-t-1 border-t-base-300">
|
||||
<div class="text-error">
|
||||
{{ error }}
|
||||
</div>
|
||||
@@ -107,6 +119,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ProductNotFound v-else/>
|
||||
|
||||
<FullScreenImageViewer
|
||||
v-if="isFullScreen"
|
||||
:images="product.images"
|
||||
@@ -127,6 +141,7 @@ import {SUPPORTED_OPTION_TYPES} from "@/constants/options.js";
|
||||
import {apiFetch} from "@/utils/ftch.js";
|
||||
import FullScreenImageViewer from "@/components/FullScreenImageViewer.vue";
|
||||
import LoadingFullScreen from "@/components/LoadingFullScreen.vue";
|
||||
import ProductNotFound from "@/components/ProductNotFound.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const productId = computed(() => route.params.id);
|
||||
|
||||
Reference in New Issue
Block a user