Отображаем товары в корзине

Давайте начнем с отображения товаров в корзине, чтобы мы могли увидеть добавленные товары сразу же после того, как мы сделаем функционал добавления. Все будет очень просто, обычный цикл. Отредактируйте шаблон компонента Cart.vue вот так:

<template>
    <div class="navbar-item has-dropdown is-hoverable">
        <a class="navbar-link" href="">
            Корзина ({{ $store.state.cartCount }})
        </a>

        <div v-if="$store.state.cart.length > 0" class="navbar-dropdown is-boxed is-right">
            <a v-for="item in $store.state.cart"
                :key="item.id"
                class="navbar-item"
                href=""
            >
                {{ item.title }} x{{ item.quantity }} - ${{ item.totalPrice }}
            </a>

            <a class="navbar-item" href="">
                Итого: ${{ totalPrice }}
            </a>

            <hr class="navbar-divider">

            <a class="navbar-item" href="">
                К оплате
            </a>
        </div>

        <div v-else class="navbar-dropdown is-boxed is-right">
            <a class="navbar-item" href="">
                Корзина пуста
            </a>
        </div>
    </div>
</template>

Опять ничего нового, стандартный код Vue. Конечно же, пока что мы не увидим результатов. Двигаемся дальше.

Добавление товаров в корзину

Чтобы добавить товар в корзину, нам нужно модифицировать состояние cart в хранилище Vuex. Для модификаций состояний используются мутации. Мутации это своего рода сеттеры для состояний. Давайте создадим мутацию для добавления товара в корзину в файле store.js.

let store = {
    state: {
        cart: [],
        cartCount: 0,
    },

    mutations: {
        addToCart(state, item) {
            console.log(item.title);
        }
    }
};

Мутации всегда принимают объект state на входе в качестве первого аргумента. Пока что мы просто выведем название переданного товар товара в консоль браузера. Вернемся в компонент ProductsList.vue. Найдите элемент <td> с текстом Добавить в корзину в шаблоне и замените его на кнопку:

<td>
    <button class="button is-success"
        @click="addToCart(item)">Add to Cart</button>
</td>

Дальше добавьте метод addToCart в раздел script. В методе мы вызовем нашу единственную Vuex мутацию, передав в качестве параметра объект товара.

methods: {
    addToCart(item) {
        this.$store.commit('addToCart', item);
    }
}

Обновите страницу и вы должны увидеть заголовок товара, добавляемого в корзину, в консоли браузера каждый раз, когда вы нажимаете на кнопку "Добавить в корзину". Вернемся к мутации addToCart в файле store.js. Внутри нам нужно всего-то добавить товар в массив cart и увеличить счетчик cartCount на 1.

mutations: {
    addToCart(state, item) {
        state.cart.push(item);

        state.cartCount++;
    }
}

Этого кода достаточно для того, чтобы увидеть, как товары добавляются в корзину и увеличивается их счетчик. Круто, правда? Хотя, сейчас все будет выглядеть не так, как должно. Кроме того, в консоли браузера будут ошибки. Все потому, что мы обращаемся к свойства товара quantity и totalPrice при отображении корзины, однако у наших товаров таких свойств изначально нет. Мы добавим эти свойства на ходу, для этого отредактируйте мутацию вот так:

addToCart(state, item) {
    let found = state.cart.find(product => product.id == item.id);

    if (found) {
        found.quantity ++;
        found.totalPrice = found.quantity * found.price;
    } else {
        state.cart.push(item);

        Vue.set(item, 'quantity', 1);
        Vue.set(item, 'totalPrice', item.price);
    }

    state.cartCount++;
}

Здесь мы проверяем, был ли товар уже добавлен в корзину ранее. Если да - мы просто увеличиваем счетчик товара на 1 и заново рассчитываем суммарную цену для данного наименования. Если нет - мы добавляем товар в массив cart и затем вручную добавляем недостающие свойства товару с помощью Vue.set() - только так эти свойства станут реактивными.

Попробуйте подобавлять товары в корзину снова. Попробуйте добавить один и тот же товар несколько раз. Вы увидите, как растет количество и суммарная цена каждого наименования товара. Остается только рассчитать полную стоимость всей корзины. Для этого мы просто добавим вычисляемое свойство в Cart.vue.

computed: {
    totalPrice() {
        let total = 0;

        for (let item of this.$store.state.cart) {
            total += item.totalPrice;
        }

        return total.toFixed(2);
    }
}

Удаляем товары из корзины

Теперь давайте займемся удалением товаров. Откройте Cart.vue. Давайте добавим кнопку удаления возле каждого товара в шаблоне.

<a v-for="item in $store.state.cart"
    :key="item.id"
    class="navbar-item"
    href=""
>
    <span class="removeBtn"
        title="Удалить из корзины"
        @click.prevent="removeFromCart(item)">X</span>

    {{ item.title }} x{{ item.quantity }} - ${{ item.totalPrice }}
</a>

Добавим немного стилей для кнопки в конец файла компонента.

<style>
.removeBtn {
    margin-right: 1rem;
    color: red;
}
</style>

Наконец добавим метод removeFromCart, который будет вызывать мутацию Vuex.

methods: {
    removeFromCart(item) {
        this.$store.commit('removeFromCart', item);
    }
}

Создадим недостающую мутацию в файле store.js.

removeFromCart(state, item) {
    let index = state.cart.indexOf(item);

    if (index > -1) {
        let product = state.cart[index];
        state.cartCount -= product.quantity;

        state.cart.splice(index, 1);
    }
}

Все очень просто. Если товар найден в корзине, то мы вычитаем количество товара в корзине из общего счетчика товаров, после чего удаляем товар из массива cart.

Сохраняем корзину

Единственная оставшаяся проблема - наша корзина очищается при каждом обновлении страницы или переходе на другую страницу сайта. Мы можем сохранять нашу корзину в локальном хранилище HTML5. Добавьте новую мутацию в файл store.js.

saveCart(state) {
    window.localStorage.setItem('cart', JSON.stringify(state.cart));
    window.localStorage.setItem('cartCount', state.cartCount);
}

Нам нужно вызвать эту мутацию в конце мутаций addToCart и removeFromCart следующим образом:

this.commit('saveCart');

И наконец, нам нужно прочитать данные из локального хранилища при загрузке страницы. Сначала добавьте следующие две строчки в самое начала файла store.js:

let cart = window.localStorage.getItem('cart');
let cartCount = window.localStorage.getItem('cartCount');

Затем замените инициализацию состояний хранилища Vuex на следующее:

state: {
    cart: cart ? JSON.parse(cart) : [],
    cartCount: cartCount ? parseInt(cartCount) : 0,
},

Все! Теперь данные из корзины не будут теряться при перезагрузке страницы и должны сохраняться даже если вы закроете браузер.

Страница оплаты

Вам остается только отправить данные из корзины на сервер (backend) с предполагаемоей страницы оплаты/совершения покупки. Это можно легко сделать с помощью axios, например вот так:

let data = {
    cart: JSON.stringify(this.$store.state.cart)
}

axios.post('/ваш-маршрут', data);

После этого вы можете прочитать данные на стороне Laravel (PHP) следующим образом:

$cart = json_decode(request('cart'));

Однако, не принимайте эти данные как есть, ибо все, что приходит из frontend'а должно всегда дополнительно проверяться в backend'е. Вы можете взять из массива $cart только id и кол-во товаров, а непосредственно информацию о товарах вытащить из БД используя эти id. Таким образом вы будете уверены, что важные данные никто не подменил.