feat: add adding items to cart

This commit is contained in:
Sergey Krylov 2025-01-27 06:06:33 +03:00
parent f67d2d187e
commit 90677c7ee8
10 changed files with 158 additions and 62 deletions

View File

@ -12,7 +12,8 @@
}, },
"dependencies": { "dependencies": {
"axios": "^1.7.9", "axios": "^1.7.9",
"vue": "^3.5.13" "vue": "^3.5.13",
"vue3-toastify": "^0.2.8"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue": "^5.2.1",

View File

@ -1,11 +1,12 @@
<script setup> <script setup>
import { onMounted, provide, reactive, ref, watch } from 'vue' import { computed, onMounted, provide, reactive, ref, watch } from 'vue'
import Header from '@/components/Header.vue' import Header from '@/components/Header.vue'
import CardList from '@/components/CardList.vue' import CardList from '@/components/CardList.vue'
import Drawer from '@/components/Drawer.vue' import Drawer from '@/components/Drawer.vue'
import axios from 'axios' import axios from 'axios'
import Spinner from '@/components/Spinner.vue' import Spinner from '@/components/Spinner.vue'
import { toast } from 'vue3-toastify'
import 'vue3-toastify/dist/index.css';
const showDrawer = ref(false); const showDrawer = ref(false);
const items = ref([]); const items = ref([]);
const isLoading = ref(false); const isLoading = ref(false);
@ -37,25 +38,68 @@ import { onMounted, provide, reactive, ref, watch } from 'vue'
onMounted(fetchItems) onMounted(fetchItems)
watch(filters, fetchItems) watch(filters, fetchItems)
const addItemToFavorite = async (item) => { const toggleToFavorite = async (item) => {
try { try {
item.isFavorite = !item.isFavorite; item.isFavorite = !item.isFavorite;
await axios.patch(`${import.meta.env.VITE_API_URL}/items/${item.id}`, item); await axios.patch(`${import.meta.env.VITE_API_URL}/items/${item.id}`, item);
if (item.isFavorite) {
toast("Добавлено в избранное", {
"type": "success",
"hideProgressBar": true,
"transition": "slide",
"dangerouslyHTMLString": true
})
}
} catch (e) {
console.log('Error', e)
toast("Ошибка при добавлении в избранное", {
"type": "error",
"hideProgressBar": true,
"transition": "slide",
"dangerouslyHTMLString": true
})
}
}
const toggleToCart = async (item) => {
try {
item.isAdded = !item.isAdded;
await axios.patch(`${import.meta.env.VITE_API_URL}/items/${item.id}`, item);
if (item.isAdded) {
toast("Добавлено в корзину", {
"type": "success",
"hideProgressBar": true,
"transition": "slide",
"dangerouslyHTMLString": true
})
}
} catch (e) { } catch (e) {
console.log('Error', e) console.log('Error', e)
} }
} }
provide('openDrawer', () => showDrawer.value = true) const cart = computed(() => items.value.filter(i => i.isAdded));
provide('closeDrawer', () => showDrawer.value = false) const totalPrice = computed(() => cart.value.reduce((res, cur) => {
return res + cur.price
}, 0))
provide('cart', {
cart,
toggleToCart,
totalPrice,
openDrawer: () => showDrawer.value = true,
closeDrawer: () => showDrawer.value = false,
})
</script> </script>
<template> <template>
<Drawer v-if="showDrawer" /> <Drawer v-if="showDrawer" :cart="cart" />
<div <div
class="mt-14 w-4/5 mx-auto bg-white rounded-xl shadow-xl" class="mt-14 w-4/5 mx-auto bg-white rounded-xl shadow-xl"
> >
<Header/> <Header />
<div class="p-10"> <div class="p-10">
<div class="flex justify-between items-center mb-8"> <div class="flex justify-between items-center mb-8">
@ -85,7 +129,8 @@ import { onMounted, provide, reactive, ref, watch } from 'vue'
<CardList <CardList
v-if="items.length > 0" v-if="items.length > 0"
:items="items" :items="items"
@addToFavorite="addItemToFavorite" @toggle-to-favorite="toggleToFavorite"
@toggle-to-cart="toggleToCart"
/> />
<h2 v-else>Пусто</h2> <h2 v-else>Пусто</h2>
</div> </div>

View File

@ -1,16 +1,13 @@
<script setup> <script setup>
import Card from './Card.vue' import Card from './Card.vue'
const onClickAdd = () => {
alert('Добавлено в корзину')
}
defineProps({ defineProps({
items: Array items: Array
}) })
defineEmits([ defineEmits([
'addToFavorite', 'toggleToFavorite',
'toggleToCart',
]) ])
</script> </script>
@ -25,8 +22,8 @@
:price="item.price" :price="item.price"
:is-added="item.isAdded" :is-added="item.isAdded"
:is-favorite="item.isFavorite" :is-favorite="item.isFavorite"
:on-click-add="onClickAdd" :on-click-add="() => $emit('toggleToCart', item)"
:on-click-favorite="() => $emit('addToFavorite', item)" :on-click-favorite="() => $emit('toggleToFavorite', item)"
/> />
</div> </div>
</template> </template>

View File

@ -1,13 +1,25 @@
<script setup>
import { decimalFormatter } from '@/utils/helpers.js'
defineProps({
item: Object,
})
defineEmits([
'onRemove'
])
</script>
<template> <template>
<div class="w-full border border-slate-100 p-4 rounded-xl flex items-center gap-4"> <div class="w-full border border-slate-100 p-4 rounded-xl flex items-center gap-4">
<img class="w-16" src="/sneakers/sneakers-1.jpg" alt="Sneaker"> <img class="w-16" :src="item.imageUrl" alt="Sneaker">
<div class="flex flex-col justify-between"> <div class="flex flex-col justify-between">
<p>Мужские кроссовки Nike Blazer Mid Suede</p> <p>{{item.title}}</p>
<div class="flex justify-between mt-2"> <div class="flex justify-between mt-2">
<b>12 990 руб.</b> <b>{{ decimalFormatter.format(item.price) }} руб.</b>
<img src="/close.svg" alt="Close" class="cursor-pointer transition opacity-40 hover:opacity-100"> <img @click="() => $emit('onRemove', item)" src="/close.svg" alt="Close" class="cursor-pointer transition opacity-40 hover:opacity-100">
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,12 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import CartItem from './CartItem.vue' import CartItem from './CartItem.vue'
import {inject} from 'vue';
defineProps({
items: {
type: Array,
required: true,
},
})
const { toggleToCart } = inject('cart')
</script> </script>
<template> <template>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<CartItem/> <CartItem v-for="item in items" :key="item.id" :item="item" @on-remove="toggleToCart" />
<CartItem/>
<CartItem/>
<CartItem/>
</div> </div>
</template> </template>

View File

@ -2,8 +2,16 @@
import DrawerHead from './DrawerHead.vue' import DrawerHead from './DrawerHead.vue'
import CartItemList from '@/components/CartItemList.vue' import CartItemList from '@/components/CartItemList.vue'
import { inject } from 'vue' import { inject } from 'vue'
import { decimalFormatter } from '@/utils/helpers.js'
const closeDrawer = inject('closeDrawer') const { closeDrawer, totalPrice } = inject('cart')
defineProps({
cart: {
type: Array,
required: true
}
})
</script> </script>
<template> <template>
@ -13,24 +21,37 @@
<div @click.stop class="bg-white w-96 h-full fixed top-0 right-0 z-20 p-8"> <div @click.stop class="bg-white w-96 h-full fixed top-0 right-0 z-20 p-8">
<DrawerHead/> <DrawerHead/>
<CartItemList /> <div v-if="cart.length > 0">
<CartItemList :items="cart" />
<div class="flex flex-col gap-2 my-6"> <div class="flex flex-col gap-2 my-6">
<div class="flex justify-between gap-2"> <div class="flex justify-between gap-2">
<span>Итого:</span> <span>Итого:</span>
<div class="flex-1 border-b border-dashed"></div> <div class="flex-1 border-b border-dashed"></div>
<b>12 990 руб.</b> <b>{{ decimalFormatter.format(totalPrice) }} руб.</b>
</div>
<div class="flex justify-between gap-2">
<span>Налог 5%:</span>
<div class="flex-1 border-b border-dashed"></div>
<b>{{ decimalFormatter.format(totalPrice * 0.05) }} руб.</b>
</div>
<div class="flex justify-between gap-2">
<span>К оплате:</span>
<div class="flex-1 border-b border-dashed"></div>
<b>{{ decimalFormatter.format(totalPrice * 0.95) }} руб.</b>
</div>
<button class="mt-5 bg-lime-500 w-full rounded-xl py-3 text-white transition disabled:bg-slate-300 hover:bg-lime-600 active:bg-lime-700">
Оформить заказ
</button>
</div> </div>
<div class="flex justify-between gap-2">
<span>Налог 5%:</span>
<div class="flex-1 border-b border-dashed"></div>
<b>900 руб.</b>
</div>
<button class="mt-5 bg-lime-500 w-full rounded-xl py-3 text-white transition disabled:bg-slate-300 hover:bg-lime-600 active:bg-lime-700">
Оформить заказ
</button>
</div> </div>
<h2 v-else class="text-lg font-bold text-center mt-10 text-slate-500">
В корзине нет товаров
</h2>
</div> </div>
</template> </template>

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { inject } from 'vue' import { inject } from 'vue'
const closeDrawer = inject('closeDrawer') const { closeDrawer } = inject('cart')
</script> </script>
<template> <template>

View File

@ -1,46 +1,43 @@
<script setup> <script setup>
import { inject } from 'vue' import { inject } from 'vue'
import { decimalFormatter } from '@/utils/helpers.js'
const openDrawer = inject('openDrawer') const { openDrawer } = inject('cart')
const {totalPrice} = inject('cart')
</script> </script>
<template> <template>
<header <header class="flex justify-between border-b border-slate-100 px-11 py-10">
class="flex justify-between border-b border-slate-100 px-11 py-10"
>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<img src="/logo.png" alt="Logo" class="w-10"> <img src="/logo.png" alt="Logo" class="w-10" />
<div> <div>
<h2 class="text-xl font-bold uppercase"> <h2 class="text-xl font-bold uppercase">Vue Sneakers</h2>
Vue Sneakers <p class="text-slate-500">Магазин лучших кроссовок</p>
</h2>
<p class="text-slate-500">
Магазин лучших кроссовок
</p>
</div> </div>
</div> </div>
<ul class="flex items-center gap-8"> <ul class="flex items-center gap-8">
<li @click="openDrawer" class="flex items-center gap-2 text-gray-500 hover:text-black cursor-pointer"> <li
<img src="/cart.svg" alt="Cart"> @click="openDrawer"
<b> class="flex items-center gap-2 text-gray-500 hover:text-black cursor-pointer"
1 205 руб. >
</b> <img src="/cart.svg" alt="Cart" />
<b> {{ decimalFormatter.format(totalPrice)}} руб. </b>
</li> </li>
<li class="flex items-center gap-2 text-gray-500 hover:text-black cursor-pointer"> <li class="flex items-center gap-2 text-gray-500 hover:text-black cursor-pointer">
<img src="/heart.svg" alt="Heart"> <img src="/heart.svg" alt="Heart" />
<span>Закладки</span> <span>Закладки</span>
</li> </li>
<li class="flex items-center gap-2 text-gray-500 hover:text-black cursor-pointer"> <li class="flex items-center gap-2 text-gray-500 hover:text-black cursor-pointer">
<img src="/profile.svg" alt="Profile"> <img src="/profile.svg" alt="Profile" />
<span>Профиль</span> <span>Профиль</span>
</li> </li>
</ul> </ul>
</header> </header>
</template> </template>

View File

@ -1,5 +1,8 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import './assets/main.css' import './assets/main.css'
import App from './App.vue' import App from './App.vue'
import Vue3Toastify from 'vue3-toastify';
createApp(App).mount('#app') const app = createApp(App);
app.use(Vue3Toastify, { autoClose: 1000 });
app.mount('#app')

View File

@ -1290,6 +1290,7 @@ __metadata:
vite: "npm:^6.0.5" vite: "npm:^6.0.5"
vite-plugin-vue-devtools: "npm:^7.6.8" vite-plugin-vue-devtools: "npm:^7.6.8"
vue: "npm:^3.5.13" vue: "npm:^3.5.13"
vue3-toastify: "npm:^0.2.8"
languageName: unknown languageName: unknown
linkType: soft linkType: soft
@ -4110,6 +4111,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"vue3-toastify@npm:^0.2.8":
version: 0.2.8
resolution: "vue3-toastify@npm:0.2.8"
peerDependencies:
vue: ">=3.2.0"
peerDependenciesMeta:
vue:
optional: true
checksum: 10c0/14d7aadd4663cc83dff25da730e6522d1c6735f6427e7d073b1e574a699a9c54d8be9965842a934ab271a127fb3cf850891092e67760773e119caf94c35f67f5
languageName: node
linkType: hard
"vue@npm:^3.5.13": "vue@npm:^3.5.13":
version: 3.5.13 version: 3.5.13
resolution: "vue@npm:3.5.13" resolution: "vue@npm:3.5.13"