wip: shopping cart, product options
This commit is contained in:
40
spa/src/components/CartButton.vue
Normal file
40
spa/src/components/CartButton.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div v-if="route.name !== 'cart.show'" class="fixed right-2 bottom-30 z-50 opacity-90">
|
||||
<div class="indicator">
|
||||
<span class="indicator-item indicator-top indicator-start badge badge-secondary">{{ cart.productsCount }}</span>
|
||||
<button class="btn btn-primary btn-lg btn-circle" @click="openCart">
|
||||
<span v-if="cart.isLoading" class="loading loading-spinner"></span>
|
||||
<template v-else>
|
||||
<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="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 0 0-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 0 0-16.536-1.84M7.5 14.25 5.106 5.272M6 20.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm12.75 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z" />
|
||||
</svg>
|
||||
</template>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted} from "vue";
|
||||
import {useCartStore} from "@/stores/CartStore.js";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
|
||||
const cart = useCartStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
function openCart() {
|
||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
||||
router.push({name: 'cart.show'});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await cart.getProducts();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
13
spa/src/components/Price.vue
Normal file
13
spa/src/components/Price.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<span>{{ formatPrice(value) }} ₽</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {formatPrice} from "@/helpers.js";
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
18
spa/src/components/ProductOptions/Cart/OptionCheckbox.vue
Normal file
18
spa/src/components/ProductOptions/Cart/OptionCheckbox.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<p>
|
||||
<span class="text-xs font-medium">
|
||||
{{ option.name }}: {{ option.value }} <span v-if="option.price"> ({{ option.price_prefix }}<Price :value="option.price"/>)</span>
|
||||
</span>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Price from "@/components/Price.vue";
|
||||
|
||||
const props = defineProps({
|
||||
option: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
18
spa/src/components/ProductOptions/Cart/OptionRadio.vue
Normal file
18
spa/src/components/ProductOptions/Cart/OptionRadio.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<p>
|
||||
<span class="text-xs font-medium">
|
||||
{{ option.name }}: {{ option.value }} <span v-if="option.price"> ({{ option.price_prefix }}<Price :value="option.price"/>)</span>
|
||||
</span>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Price from "@/components/Price.vue";
|
||||
|
||||
const props = defineProps({
|
||||
option: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
18
spa/src/components/ProductOptions/Cart/OptionText.vue
Normal file
18
spa/src/components/ProductOptions/Cart/OptionText.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<p>
|
||||
<span class="text-xs font-medium">
|
||||
{{ option.name }}: {{ option.value }} <span v-if="option.price"> ({{ option.price_prefix }}<Price :value="option.price"/>)</span>
|
||||
</span>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Price from "@/components/Price.vue";
|
||||
|
||||
const props = defineProps({
|
||||
option: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -1,10 +1,13 @@
|
||||
<template>
|
||||
<div v-for="option in options" :key="option.product_option_id" class="mt-3">
|
||||
<OptionRadio v-if="option.type === 'radio'" :modelValue="option"/>
|
||||
<OptionCheckbox v-else-if="option.type === 'checkbox'" :modelValue="option"/>
|
||||
<OptionText v-else-if="option.type === 'text'" :modelValue="option"/>
|
||||
<OptionTextarea v-else-if="option.type === 'textarea'" :modelValue="option"/>
|
||||
<OptionSelect v-else-if="option.type === 'select'" :modelValue="option"/>
|
||||
<component
|
||||
v-if="SUPPORTED_OPTION_TYPES.includes(option.type) && componentMap[option.type]"
|
||||
:is="componentMap[option.type]"
|
||||
:modelValue="option"
|
||||
/>
|
||||
<div v-else class="text-sm text-error">
|
||||
Тип опции "{{ option.type }}" не поддерживается.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -14,6 +17,15 @@ import OptionCheckbox from "./Types/OptionCheckbox.vue";
|
||||
import OptionText from "./Types/OptionText.vue";
|
||||
import OptionTextarea from "./Types/OptionTextarea.vue";
|
||||
import OptionSelect from "./Types/OptionSelect.vue";
|
||||
import {SUPPORTED_OPTION_TYPES} from "@/constants/options.js";
|
||||
|
||||
const componentMap = {
|
||||
radio: OptionRadio,
|
||||
checkbox: OptionCheckbox,
|
||||
text: OptionText,
|
||||
textarea: OptionTextarea,
|
||||
select: OptionSelect,
|
||||
};
|
||||
|
||||
const options = defineModel();
|
||||
</script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class="select"
|
||||
@change="onChange"
|
||||
>
|
||||
<option value="" disabled>Выберите значение</option>
|
||||
<option value="" disabled selected>Выберите значение</option>
|
||||
<option
|
||||
v-for="value in model.values"
|
||||
:key="value.product_option_value_id"
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
<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";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="flex items-center text-center">
|
||||
<button class="btn" :class="btnClassList" @click="inc">-</button>
|
||||
<button class="btn" :class="btnClassList" @click="dec" :disabled="disabled">-</button>
|
||||
<div class="w-10 h-10 flex items-center justify-center font-bold">{{ model }}</div>
|
||||
<button class="btn" :class="btnClassList" @click="dec">+</button>
|
||||
<button class="btn" :class="btnClassList" @click="inc" :disabled="disabled">+</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -15,6 +15,10 @@ const props = defineProps({
|
||||
size: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -27,12 +31,8 @@ const btnClassList = computed(() => {
|
||||
});
|
||||
|
||||
function inc() {
|
||||
if (model.value - 1 >= 0) {
|
||||
model.value--;
|
||||
}
|
||||
}
|
||||
if (props.disabled) return;
|
||||
|
||||
function dec() {
|
||||
if (props.max && model.value + 1 > props.max) {
|
||||
model.value = props.max;
|
||||
return;
|
||||
@@ -40,4 +40,12 @@ function dec() {
|
||||
|
||||
model.value++;
|
||||
}
|
||||
|
||||
function dec() {
|
||||
if (props.disabled) return;
|
||||
|
||||
if (model.value - 1 >= 1) {
|
||||
model.value--;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user