Для начала давайте создадим новый проект на Laravel.

laravel new cart
cd cart

Откройте проект в вашей любимой IDE, затем откройте файл package.json. Давайте удалим некоторые ненужные нам зависимости и добавим новые. Вот как должен выглядеть блок devDependencies после изменений:

"devDependencies": {
    "axios": "^0.18",
    "bulma": "^0.7.1",
    "cross-env": "^5.1",
    "laravel-mix": "^2.0",
    "vue": "^2.5.7",
    "vuex": "^3.0.1"
}

Сохраните файл и выполните команду npm install, чтобы установить все зависимости. Обратите внимание, что я заменил bootstrap на bulma - это всего лишь личное предпочтение, CSS фреймворк не играет значения в данном уроке. Теперь давайте внесем соответствующие изменения в файлы ресурсов. Начнем с sass. Зайдите в папку resources/assets/sass и удалите файл _variables.scss. Затем откройте app.scss и замените его содержимое на это:

@import '~bulma/bulma';

Дальше папка resources/assets/js. Отредактируйте bootstrap.js следующим образом:

import axios from 'axios';
import Vue from 'vue';

window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
let token = document.head.querySelector('meta[name="csrf-token"]');

if (token) {
    window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
    console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}

window.Vue = Vue;

И затем файл app.js:

require('./bootstrap');

Vue.component('example-component', require('./components/ExampleComponent.vue'));

const app = new Vue({
    el: '#app'
});

Как вы заметили, мы оставили только Vue и axios в этих файлах, так как мы удалили все остальные зависимости в package.json.

Наконец давайте отредактируем view resources/views/welcome.blade.php. Это будет нашим основным и единственным шаблоном в этом уроке. Внутри шаблона присутствует стандартная разметка Bulma.

<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>Voerro Tutorial - Shopping Cart w/ Vue.js 2 & Vuex</title>

    <link rel="stylesheet" href="{{ asset('css/app.css') }}">
</head>
<body>
    <div id="app">
        <nav class="navbar is-primary">
            <div class="navbar-brand">
                <a class="navbar-item" href="/">
                    Voerro Shopping Cart Tutorial
                </a>

                <div class="navbar-burger burger">
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
            </div>

            <div id="navbarExampleTransparentExample" class="navbar-menu"> 
                <div class="navbar-end">
                    <div class="navbar-item has-dropdown is-hoverable">
                        <a class="navbar-link" href="">
                            Корзина (0)
                        </a>

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

                            <hr class="navbar-divider">

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

        <div class="section content">
            <h1>Our Products</h1>
        </div>
    </div>

    <script src="{{ asset('js/app.js') }}"></script>
</body>
</html>

Список товаров

Нам нужно отобразить список товаров нашего магазина. Мы могли бы использовать Vue в связке с Laravel для этих целей, но давайте сделаем все проще. К тому же, я обещал, что мы будем работать только (или по большей части) с frontend технологиями.

Начнем с того, что переименуем файл resources/assets/js/components/ExampleComponent.vue в ProductsList.vue. Затем откройте файл resources/assets/js/app.js и замените Vue.component('example-component', require('./components/ExampleComponent.vue')); на Vue.component('products-list', require('./components/ProductsList.vue'));. Вернитесь в файл view и вставьте следующий код под заголовком h1:

<products-list></products-list>

Запустите наблюдатель npm выполнив команду npm run watch или просто скомпилируйте ресурсы единоразово выполнив npm run dev. Перезагрузите страницу браузера и вы должны увидеть стандартный компонент-пример Vue под заголовком. Отлично, теперь мы можем начать работать над ProductsList.vue. Сначала отредактируйте раздел <script> вот так:

<script>
export default {
    data() {
        return {
            items: [
                {
                    id: 1,
                    title: 'Children of Bodom - Hatebreeder',
                    price: 9.99
                },
                {
                    id: 2,
                    title: 'Emperor - Anthems to the Welkin at Dusk',
                    price: 6.66
                },
                {
                    id: 3,
                    title: 'Epica - The Quantum Enigma',
                    price: 15.99
                },
                {
                    id: 4,
                    title: 'Chthonic - Takasago Army',
                    price: 14.00
                },
                {
                    id: 5,
                    title: 'Silencer - Death - Pierce Me',
                    price: 1.20
                },
                {
                    id: 6,
                    title: 'My Dying Bride - 34.788%... Complete',
                    price: 10.00
                },
                {
                    id: 7,
                    title: 'Shape of Despair - Shades of',
                    price: 7.80
                },
                {
                    id: 8,
                    title: 'Ne Obliviscaris - Portal of I',
                    price: 11.30
                },
                {
                    id: 9,
                    title: 'Protest the Hero - Fortress',
                    price: 5.55
                },
                {
                    id: 10,
                    title: 'Dark Lunacy - Devoid',
                    price: 6.00
                },
            ]
        };
    }
}
</script>

Мы просто создали статичный список продуктов, которые в данный момент в наличии в нашем магазине. В реальном проекте вы бы сделали AJAX запрос на сервер, чтобы получить данные из БД, но для простоты примера мы не будем этого делать. Предположительно, наш магазин продает музыкальные компакт-диски. Дальше отредактируем блок <template>, чтобы он выводил список товаров.

<template>
    <table>
        <thead>
            <th>Название</th>
            <th>Цена</th>
            <th></th>
        </thead>

        <tbody>
            <tr v-for="item in items" :key="item.id">
                <td v-text="item.title"></td>

                <td>${{ item.price.toFixed(2) }}</td>

                <td>Добавить в корзину</td>
            </tr>
        </tbody>
    </table>
</template>

Весь код выше это простейший код Vue, поэтому у вас не должно возникнуть по нему вопросов. Перекомпилируйте ресурсы, обновите страницу браузера и вы должны увидеть список товаров в виде обычной таблицы.

Подключаем Vuex

Как вы уже поняли, на данный момент у нас есть отдельный Vue компонент для отображения списка продуктов. Также на странице у нас есть выпадающий список корзины в верхнем меню, который тоже будет отдельным Vue компонентом. Когда мы добавляем товар в корзину из компонента со списком товаров, это должно отобразиться в компоненте корзины. Другими словами, наши компоненты должны как-то общаться друг с другом.

Вы, наверное, слышали, что коммуникацию между компонентами можно сделать с помощью глобальных событий или глобальных переменных. Однако в этот раз мы будем использовать Vuex, который "служит централизованным хранилищем данных для всех компонентов приложения". В нашем уроке мы будем использовать хранилище Vuex для хранения товаров корзины. У каждого компонента сайта будет доступ к этом хранилищу с возможностью редактирования данных. Данные в хранилище Vuex реактивны по-умолчанию.

Для начала давайте импортируем Vuex в файле bootstrap.js:

import Vuex from 'vuex';

...

window.Vuex = Vuex;

Vue.use(Vuex);

Затем сделаем следующие изменения в файле app.js:

import store from './store.js';

new Vue({
    el: '#app',

    store: new Vuex.Store(store)
});

Наше Vuex хранилище будет находиться в отдельном файле, чтобы не загромождать app.js. Создайте файл resources/assets/js/store.js со следующим содержимым:

let store = {

};

export default store;

Как вы видите, хранилище Vuex это простой объект с набором специальных свойств. Давайте начнем со свойства state (состояния). state это что-то вроде аналога свойства data в компонентах Vue. В нашем state мы будем хранить массив товаров, добавленных в корзину, а так же отдельно счетчик количества этих товаров.

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

Я временно установил значение счетчика на 10, чтобы мы могли попробовать прочитать и отобразить это значение в шапке сайта. Давайте создадим новый компонент resources/assets/js/components/Cart.vue. Зарегистрируйте компонет в app.js:

Vue.component('cart-dropdown', require('./components/Cart.vue'));

Мы вырежем кусок HTML из view и вставим его в новый компонент. Итак, вот наш Cart.vue:

<template>
    <div class="navbar-item has-dropdown is-hoverable">
        <a class="navbar-link" href="">
            Корзина (0)
        </a>

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

            <hr class="navbar-divider">

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

<script>
export default {

}
</script>

Откройте welcome.blade.php, найдите кусок кода, который мы вставили в компонент, замените его на <cart-dropdown></cart-dropdown>. После перезагрузки страницы визуально ничего не должно поменяться. В нашем компоненте число товаров в корзине статично и всегда равно "0". Давайте выведем реальное значение счетчика товаров из хранилища Vuex вместо этого нуля. Отредактируйте соответствующий кусок шаблона компонента следующим образом:

<a class="navbar-link" href="">
    Корзина ({{ $store.state.cartCount }})
</a>

Каждый раз, когда мы будем менять значение состояния cartCount в хранилище, интерфейс пользователя будет автоматически обновляться. Неплохо, неправда ли? Теперь давайте вернемся в store.js и зададим счетчику значение 0, каким оно и должно быть. Пока что все. Мы займемся разработкой непосредственно корзины в следующей части урока.