В этом руководстве мы создаем корзину покупок с помощью Vue 2 (без Vuex) и стилизуем ее с помощью набора инструментов компонента Element UI vue.

В последнем уроке мы создали приложение для корзины покупок, используя Vuejs, Vuex и Bulma для стилизации. В этом уроке мы собираемся создать почти такую ​​же корзину для покупок, но без Vuex. Мы будем использовать набор инструментов Element UI Vue для стилизации вместо Bulma, чтобы опробовать что-то новое.

Код только для Vue.js проще в рассуждении и выполняет те же функции. Vuex имеет свое место, но я написал это руководство, чтобы продемонстрировать, что это не всегда необходимо.

tl; dr - 🐙 исходный код, 🛒 демо

🐊 Альтернативные подходы

Первоначально я видел, что шаблон EventBus популярен для приложений Vue.js, не использующих Vuex. Самый ясный учебник, который я нашел по описанию шины событий, взят из alligator.io: https://alligator.io/vuejs/global-event-bus/

EventBus использует события для трансляции изменений и обновления компонентов. Я собираюсь использовать похожий шаблон, но назову его магазином, потому что он звучит точнее. В магазине будет храниться информация о корзине и список товаров /

🦄 Создать приложение

Запустите инструмент vue-cli, чтобы создать веб-сайт. Мы воспользуемся генератором webpack-simple. Просмотрите параметры настройки и выберите Да, что мы хотим SASS.

$ vue init webpack-simple vue-shopping-cart
$ cd vue-shopping-cart
$ npm i
$ npm install element-ui -S

После установки зависимостей мы можем запустить сервер webpack с npm run dev. Фреймворк Element UI предназначен для стилизации.

Element UI немного усложняет настройку. Вы можете следовать официальной документации или скопировать и вставить файл webpack.config.js, чтобы получить соответствующие загрузчики.

🐂 Создать магазин

Я помещаю этот файл в src / store / store.js

import Vue from 'vue';
import products from '../products'
export const Store = new Vue({
 data() {
     return {
      products,
      cart: []
     };
 },
 computed: {
  totalCost(){
   return this.cart.reduce((accum, product) => {
    return accum + product.details.price * product.quantity
   }, 0)
  }
 },
 methods: {
  addToCart(product){
   const locationInCart = this.cart.findIndex(p => {
    return p.details.sku === product.sku
   })
  if(locationInCart === -1){
     this.cart.push({
        details: product,
            quantity: 1
          })
      } else {
          this.cart[locationInCart].quantity++
      }
  },
  removeFromCart(sku){
     const locationInCart = this.cart.findIndex(p => {
       return p.details.sku === sku
     })
     if(this.cart[locationInCart].quantity <= 1){
        this.cart.splice(locationInCart, 1)
     } else {
        this.cart[locationInCart].quantity--
     }
   }
 }
});

В экземпляре store Vue есть место для хранения наших продуктов и пустой массив для тележки для измельчения. Данные о товарах представляют собой файл src / products.json, который выглядит следующим образом:

[
  { "id": 1, "name": "Tshirt", "price": 3050, "quantity": 2, "sku": 1, "images": ["https://cdn.shopify.com/s/files/1/2415/9707/products/Screen_Shot_2017-10-02_at_10.18.11_AM_grande.png?v=1506964811"] },
  { "id": 2, "name": "Hat", "price": 2000, "quantity": 10, "sku": 2, "images": ["https://cdn.shopify.com/s/files/1/2415/9707/products/Screen_Shot_2017-10-02_at_10.23.01_AM_grande.png"] },
  { "id": 3, "name": "Stickers", "price": 550, "quantity": 5, "sku": 3, "images": ["https://cdn.shopify.com/s/files/1/2415/9707/products/logo_grande.png?v=1506964542"] }
]

Методы магазина добавляют в корзину и обновляют и вычитают количество из продуктов, если продукт этого типа уже находится в корзине. Таким образом, мы можем легко отобразить информацию о продукте и быстро изменить количество.

🦑 Список продуктов

Компонент "Список продуктов" использует встроенные компоненты Element для отображения карточки с изображением для каждого продукта. Есть некоторое дублирование в методах. Здесь пригодится помощник в стиле Vuex, например ...mapActions. Тем не менее, мы экономим много кода, не делая его так, как Vuex, так что есть компромисс.

<template>
 <el-row>
   <el-col :span="12" v-for="(product, index) in products" :key="product.sku">
     <el-card>
      <img :src="product.images[0]" class="image">
         <span>{{ product.name }}</span>
         <span>{{ product.price | currency }}</span>
         <div class="bottom clearfix">
          <el-button type="info" @click='addToCart(product)'>Add to cart</el-button>
         </div>
     </el-card>
   </el-col>
 </el-row>
</template>
<script>
import {Store} from '../store/Store'
export default {
  data() {
    return {
     products: Store.$data.products
    };
  },
  methods: {
   addToCart(product){
    Store.addToCart(product)
   }
  }
}
</script>
<style>
  .image {
    width: 100%;
    display: block;
  } 
</style>

Мы используем$data для доступа к товарам в магазине. В корзине покупок мы должны использовать вычисляемые свойства, чтобы их можно было обновлять в зависимости от действий пользователя. Артикул означает единицу хранения на складе и уникален для каждого продукта.

Мы также напишем фильтр валют, который сохраним в файле src / main.js:

import Vue from 'vue'
import App from './App.vue'
import Element from 'element-ui'
import locale from 'element-ui/lib/locale/lang/en'
import 'element-ui/lib/theme-default/index.css'
import NavBar from './components/NavBar.vue'
import ProductList from './components/ProductList.vue'
import ShoppingCart from './components/ShoppingCart.vue'
// Services
Vue.use(Element, { locale })
// Components
Vue.component('NavBar', NavBar)
Vue.component('ProductList', ProductList)
Vue.component('ShoppingCart', ShoppingCart)
// Filters
Vue.filter('currency', function (value) {
    return '$' + parseFloat(value/100).toFixed(2);
});
new Vue({
  el: '#app',
  render: h => h(App)
})

Как вы можете видеть здесь, мы регистрируем службу Element и заявляем, что говорим на английском языке в качестве нашего языкового стандарта. Информация о ценах хранится в центах, поэтому делим 100.

🐨 Корзина покупок

Тележка для покупок была немного сложной, поскольку использовала компонент Таблица элементов.

<template>
 <el-table
    :data="cart"
    stripe
    style="width: 100%">
     <el-table-column
   prop="details.name"
   label="Item Name"></el-table-column>
  <el-table-column
   label="Price">
   <template scope='scope'>
    {{ scope.row.details.price | currency }}
   </template>
  </el-table-column>
  <el-table-column
   prop="quantity"
   label="Quantity"></el-table-column>
  <el-table-column
      label="">
      <template scope="scope">
       <el-button type="success" icon="plus" @click='addToCart(scope.row.details)' size="mini"></el-button>
       <el-button type="danger" icon="minus" @click='removeFromCart(scope.row.details.sku)' size="mini"></el-button>
      </template>
     </el-table-column>
 </el-table>
</template>
<script>
import {Store} from '../store/Store'
export default {
 computed: {
  cart(){
   return Store.$data.cart
  }
 },
 methods: {
  removeFromCart(sku){
   Store.removeFromCart(sku)
  },
  addToCart(product){
   Store.addToCart(product);
  }
 }
}
</script>

Часть области действия задокументирована в разделе настраиваемый шаблон столбца. Я не стал слишком углубляться в параметр строки сводки и вместо этого перечислил общее количество в файле src / App.vue:

<template>
  <div id="app">
    <nav-bar></nav-bar>
    <el-row :gutter="20">
      <el-col :span="16">
        <h1>Product List</h1>
        <product-list></product-list>
      </el-col>
      <el-col :span="8">
        <h1>Shopping Cart</h1>
        <shopping-cart></shopping-cart>
        <p><b>Total Cost: {{ totalCost | currency }}</b></p>
      </el-col>
    </el-row>
    
  </div>
</template>
<script>
import {Store} from './store/Store'
export default {
  name: 'app',
  computed: {
    totalCost(){
      return Store.totalCost
    }
  }
}
</script>

В файле App.vue мы вызываем вычисляемое свойство в хранилище и другое вычисляемое свойство для отслеживания изменений. Это может быть неэффективно в массовых приложениях, но в целом оно выполняет свою работу и работает для чего-то небольшого, подобного этому. Вызов вычисляемого метода дважды кажется далеко не идеальным. Возможно, именно здесь шаблон EventBus мог бы стать еще лучше.

🦋 Заключение

В целом это касается нашего простого приложения для корзины покупок. Мы создали его, используя только Vue.js, простым и понятным способом, и нам не нужно было включать Vuex. Element UI хорош тем, что использует компоненты Vue, но абстрагирует большую часть реализации. Это может быть хорошо, пока вам не понадобится копаться и вносить изменения в CSS. Я думаю, что в будущем я буду использовать Bulma для основного стиля и добавлять компоненты Element только по мере необходимости. Этот шаблон использования экземпляра Vue в качестве хранилища может быть не лучшим для приложений JS масштаба Facebook, но выполняет свою работу для того, что создают большинство разработчиков. Также здорово, что вы можете создать магазин Vuex прямо рядом с ним и таким образом управлять некоторым своим состоянием, и ничего не сломается.