From 626ee6ecb01ed3eab46d8367c2d8261702a4526d Mon Sep 17 00:00:00 2001 From: Nikita Kiselev Date: Tue, 22 Jul 2025 00:27:40 +0300 Subject: [PATCH] WIP: cart --- .../controller/extension/tgshop/handle.php | 9 +++ .../src/Adapters/OcCartAdapter.php | 8 +++ .../src/Handlers/CheckoutHandler.php | 68 +++++++++++++++++++ .../upload/oc_telegram_shop/src/routes.php | 4 ++ spa/package-lock.json | 7 ++ spa/package.json | 1 + spa/src/stores/CartStore.js | 58 +++++++++++----- spa/src/utils/ftch.js | 4 +- spa/src/views/Cart.vue | 27 ++++---- spa/src/views/Product.vue | 16 ++--- 10 files changed, 165 insertions(+), 37 deletions(-) create mode 100644 module/oc_telegram_shop/upload/oc_telegram_shop/src/Adapters/OcCartAdapter.php create mode 100644 module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/CheckoutHandler.php diff --git a/module/oc_telegram_shop/upload/catalog/controller/extension/tgshop/handle.php b/module/oc_telegram_shop/upload/catalog/controller/extension/tgshop/handle.php index 2e273b9..231bf83 100755 --- a/module/oc_telegram_shop/upload/catalog/controller/extension/tgshop/handle.php +++ b/module/oc_telegram_shop/upload/catalog/controller/extension/tgshop/handle.php @@ -2,6 +2,7 @@ use App\Adapters\OcModelCatalogProductAdapter; use App\ApplicationFactory; +use Cart\Cart; use Cart\Currency; use Cart\Tax; use Openguru\OpenCartFramework\ImageTool\ImageTool; @@ -60,6 +61,14 @@ class Controllerextensiontgshophandle extends Controller return new ImageTool(DIR_IMAGE, HTTPS_SERVER); }); + $app->bind(\Cart\Cart::class, function () { + return $this->cart; + }); + + $app->bind(DB::class, function () { + return $this->db; + }); + $this->load->model('checkout/order'); $app->bind('model_checkout_order', function () { diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/src/Adapters/OcCartAdapter.php b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Adapters/OcCartAdapter.php new file mode 100644 index 0000000..5f85717 --- /dev/null +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/src/Adapters/OcCartAdapter.php @@ -0,0 +1,8 @@ +cart = $cart; + $this->database = $database; + } + + public function addToCart(Request $request): JsonResponse + { + $item = $request->json(); + + $options = []; + + foreach ($item['options'] as $option) { + if (! empty($option['value']) && ! empty($option['value']['product_option_value_id'])) { + $options[$option['product_option_id']] = $option['value']['product_option_value_id']; + } + } + + $this->cart->add( + $item['productId'], + $item['quantity'], + $options, + ); + + return new JsonResponse([ + 'data' => $this->cart->getProducts(), + ]); + } + + public function checkout(Request $request): JsonResponse + { + $items = $request->json(); + + foreach ($items as $item) { + $options = []; + + foreach ($item['options'] as $option) { + if (! empty($option['value']) && ! empty($option['value']['product_option_value_id'])) { + $options[$option['product_option_id']] = $option['value']['product_option_value_id']; + } + } + + $this->cart->add( + $item['productId'], + $item['quantity'], + $options, + ); + } + + return new JsonResponse([ + 'data' => $items, + ]); + } +} diff --git a/module/oc_telegram_shop/upload/oc_telegram_shop/src/routes.php b/module/oc_telegram_shop/upload/oc_telegram_shop/src/routes.php index 6c334e1..bfb26ed 100755 --- a/module/oc_telegram_shop/upload/oc_telegram_shop/src/routes.php +++ b/module/oc_telegram_shop/upload/oc_telegram_shop/src/routes.php @@ -1,6 +1,7 @@ [OrderCreateHandler::class, 'handle'], 'categoriesList' => [CategoriesHandler::class, 'index'], + + 'checkout' => [CheckoutHandler::class, 'checkout'], + 'addToCart' => [CheckoutHandler::class, 'addToCart'], ]; diff --git a/spa/package-lock.json b/spa/package-lock.json index b809f09..2eba130 100644 --- a/spa/package-lock.json +++ b/spa/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@heroicons/vue": "^2.2.0", "@tailwindcss/vite": "^4.1.11", + "crypto-js": "^4.2.0", "ofetch": "^1.4.1", "pinia": "^3.0.3", "swiper": "^11.2.10", @@ -1364,6 +1365,12 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", diff --git a/spa/package.json b/spa/package.json index e852fd5..165abcb 100644 --- a/spa/package.json +++ b/spa/package.json @@ -11,6 +11,7 @@ "dependencies": { "@heroicons/vue": "^2.2.0", "@tailwindcss/vite": "^4.1.11", + "crypto-js": "^4.2.0", "ofetch": "^1.4.1", "pinia": "^3.0.3", "swiper": "^11.2.10", diff --git a/spa/src/stores/CartStore.js b/spa/src/stores/CartStore.js index bf45d7c..33f7de4 100644 --- a/spa/src/stores/CartStore.js +++ b/spa/src/stores/CartStore.js @@ -1,43 +1,69 @@ import {defineStore} from "pinia"; +import md5 from 'crypto-js/md5'; +import ftch from "@/utils/ftch.js"; export const useCartStore = defineStore('cart', { state: () => ({ items: [], + isLoading: false, }), actions: { - getProduct(productId) { - return this.items.find(item => parseInt(item.productId) === parseInt(productId)) ?? null; + getItem(rowId) { + return this.items.find(item => item.rowId === rowId) ?? null; }, - hasProduct(productId) { - return this.getProduct(productId) !== null; + hasItem(rowId) { + return this.getItem(rowId) !== null; }, - addProduct(productId, productName, price, quantity = 1, options = []) { - this.items.push({ + async addProduct(productId, productName, price, quantity = 1, options = []) { + const rowId = this.generateRowId(productId, options); + + const item = { + rowId: rowId, productId: productId, productName: productName, price: price, quantity: quantity, - options: options, - }); + options: JSON.parse(JSON.stringify(options)), // ← 💥 глубокая копия! + }; + + this.items.push(item); + + return rowId; }, - removeProduct(productId) { - this.items.splice(this.items.indexOf(productId), 1); + removeItem(rowId) { + this.items.splice(this.items.indexOf(rowId), 1); }, - getQuantity(productId) { - if (this.hasProduct(productId)) { - return this.getProduct(productId).quantity; + getQuantity(rowId) { + if (this.hasItem(rowId)) { + return this.getItem(rowId).quantity; } return 0; }, - setQuantity(productId, quantity) { - this.getProduct(productId).quantity = quantity; - } + setQuantity(rowId, quantity) { + this.getItem(rowId).quantity = quantity; + }, + + generateRowId(productId, options) { + return md5(productId + JSON.stringify(options)).toString(); + }, + + async checkout() { + try { + this.isLoading = true; + const {data} = await ftch('checkout', null, this.items); + this.items = data; + } catch (e) { + console.error(e); + } finally { + this.isLoading = false; + } + }, }, }); \ No newline at end of file diff --git a/spa/src/utils/ftch.js b/spa/src/utils/ftch.js index e7f78ee..828969a 100644 --- a/spa/src/utils/ftch.js +++ b/spa/src/utils/ftch.js @@ -2,8 +2,10 @@ import {$fetch} from "ofetch"; const BASE_URL = '/'; -export default async function (action, query) { +export default async function (action, query = null, json = null) { return await $fetch(`${BASE_URL}index.php?route=extension/tgshop/handle&api_action=${action}`, { + method: json ? 'POST' : 'GET', query: query, + body: json, }); }; diff --git a/spa/src/views/Cart.vue b/spa/src/views/Cart.vue index 99b21f4..79d9a20 100644 --- a/spa/src/views/Cart.vue +++ b/spa/src/views/Cart.vue @@ -1,12 +1,20 @@