iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 20
1
Modern Web

實作小範例入門 Vue & Vuex 2.0系列 第 20

vue & vuex 20 - Shopping cart - II (購物車,推薦商品加購)

今天一樣是實作簡單 Shopping cart 主要練習,數據驅動 UI


我們是一間高級餐廳,每日會提供限量餐點,因為預訂的客戶太多希望可以開發一套餐點預訂系統。

今天目標:

  1. 點擊右上方購物車圖示,切換購物車頁面。
  2. 推薦一個餐點,讓想結帳的客戶可以加購,增加銷售量。
  3. 購物清單上方,顯示計算購物車總價錢。
  4. 購物清單顯示:品項、價錢、餐點名稱、取消預訂按鈕。
  5. 取消預訂按鈕,點擊,從購物車刪除,餐點剩餘數量 +1
  6. 購物清單下方,(左)返回餐點頁面,(右)購買按鈕。

系統規劃

vue & vuex 20 Shopping cart


切換頁面

使用 <router-link :to="{name: 'shop'}"

router-link 可以直接套用 class 所以看起來像 button


推薦餐點

因為 vuex 流程關係,改變 state 才需要重新計算一次,

這段計算可以輕鬆的在 getter 做,

因為要先撈出庫存單點,(總不能推薦的餐點已經沒有庫存囉)。

然後隨機取一個餐點出來 show 在購物車結帳頁面上面推薦。

如果使用者 加購取消 餐點,會因為 state 改變,而取得新的推薦餐點 (如果沒改只是剛好隨機數一樣)

store/modules/shop.js

getRecommendedProducts: state => {
  // 先取得庫存餐點表
  const inventoryList = state.products.filter(p => p.inventory > 0);
  // 取隨機數
  const random = Math.round(Math.random() * (inventoryList.length - 1));
  // 回傳隨機數的餐點
  return inventoryList[ random ];
}

取消預訂

取消預訂單純操縱產品與購物車陣列,從購物車移除物件,產品庫存加一。

store/modules/shop.js

更多的 ES6 findIndex

[types.CANCEL_CART] (state, title) {
  // 從購物車移除
  // ES6 array findIndex 找到條件成立的物件,所在陣列中的位子。
  const cartIndex = state.shoppingCart.findIndex(item => item.title === title);
  state.shoppingCart.splice(cartIndex, 1);
  // 餐點庫存 +1
  const product = state.products.find(item => item.title === title);
  product.inventory += 1;
},

pages/cart.vue

<template>
  <div class="container">
    <nav class="navbar navbar-default">
      <div class="container-fluid">
        <div class="navbar-header">
          <button type="button" class="collapsed navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-5" aria-expanded="false">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a href="#" class="navbar-brand">Shopping Cart</a>
        </div>
        <div class="collapse navbar-collapse navbar-right">
          <router-link :to="{name: 'cart'}" class="btn navbar-btn">
            <span class="glyphicon glyphicon-shopping-cart" aria-hidden="true"></span>
            <span class="badge">{{ cartTotal }}</span>
          </router-link>
        </div>
      </div>
    </nav>
    <!-- Recommended Products -->
    <div class="recommend">
      <div class="row">
        <div class="col-md-3">
          <img :src="recommend.image" style="width: 100%;">
        </div>
        <div class="col-md-9">
          <div class="recommend-info">
            <h2>{{ recommend.title }}</h2>
            <hr>
            <h3>Just some random text, lorem ipsum text praesent tincidunt ipsum lipsum. Just some random text, Once again, some random text to lorem lorem lorem lorem ipsum text praesent tincidunt ipsum lipsum. 
            </h3>
            <h2>${{ recommend.price }}</h2>
            <button 
              class="btn btn-danger"
              @click="addCart( recommend.title )">
              <span class="glyphicon glyphicon-shopping-cart" aria-hidden="true"></span>
              加購
            </button>
          </div>
        </div>
      </div>
    </div>

    <div class="panel panel-default">
      <div class="panel-heading panel-price">總計: <span>${{ total }}</span> 元</div>
      <table class="table">
        <thead>
          <tr>
            <th>項目</th>
            <th>價錢</th>
            <th>餐點</th>
            <th>取消預訂</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(cart, index) in cartList">
            <td>{{ index + 1 }}</td>
            <td>{{ cart.price }}</td>
            <td>{{ cart.title }}</td>
            <td>
              <button 
                class="btn btn-default btn-xs glyphicon glyphicon-remove"
                @click="cancelCart(cart.title)">
              </button>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
    <div class="row">
      <div class="col-md-6 center-block">
        <router-link :to="{name: 'shop'}" class="btn btn-warning btn-lg btn-block">
          <span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span>
          back to Shop
        </router-link>
      </div>
      <div class="col-md-6 center-block">
        <button class="btn btn-success btn-lg btn-block center-block">
          <span class="glyphicon glyphicon-usd" aria-hidden="true"></span>
          buy now
        </button>
      </div>
    </div>
  </div>
</template>

<script>
import { mapGetters, mapActions } from 'vuex'

export default {
  computed: mapGetters({
    cartTotal: 'getShoppingCartTotal',
    cartList: 'getShoppingCart',
    total: 'getCartPriceTotal',
    recommend: 'getRecommendedProducts'
  }),
  methods: mapActions([
    'cancelCart',
    'addCart'
  ]),
}
</script>

github 完整範例:

實作小範例入門 Vue & Vuex 2.0 - github 完整範例

使用 git checkout 切換每天範例。


上一篇
vue & vuex 19 - Shopping cart - I (v-bind, :class, 數據驅動)
下一篇
vue & vuex 21 - Open Data - I (元件的生命週期、透過 ajax 取得資料)
系列文
實作小範例入門 Vue & Vuex 2.030
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言