keyboard_tabSkip to content
listVue Firebase Masterclass

ทำหน้าตะกร้าสินค้า

สารบัญ

ทำ store cart.js

  • เพิ่ม key ใน state
    • items สำหรับเก็บข้อมูลในตะกร้า
  • สร้าง function สำหรับจัดการตะกร้าสินค้าโดยเพิ่ม function ดังนี้
    • loadCart () โหลดข้อมูลตะกร้าจาก localstorage (จะวางโหลดเอาไว้ในตำแหน่ง App แรกสุด)
    • addToCart (productData) เพิ่มข้อมูลตะกร้าสินค้า (โดยต้องไม่ใส่สินค้าซ้ำ และหากมีสินค้านั้นอยู่แล้ว เป็นการเพิ่มจำนวนไป 1 แทน)
    • updateQuantity (index, quantity) ปรับจำนวนสินค้า
    • removeItemInCart (index) ลบของออกจากตะกร้า
  • save ลง localstorage เพื่อให้สามารถเรียกใช้งานตอน refresh ได้
  • เพิ่ม getters มา 2 ตัวคือ
    • summaryPrice สำหรับแสดงราคารวมในตะกร้าสินค้า
    • quantity สำหรับแสดงจำนวนสินค้าทั้งหมดในตะกร้า (หาก 1 item มี 2 ชิ้น = 2 items)
javascript
import { defineStore } from 'pinia'

export const useUserCartStore = defineStore('user-cart', {
  state: () => ({
    items: []
  }),
  getters: {
    summaryPrice (state) {
      return state.items.reduce((acc, item) => acc + (item.price * item.quantity), 0)
    },
    quantity (state) {
      return state.items.reduce((acc, item) => acc + item.quantity, 0)
    }
  },
  actions: {
    loadCart () {
      const cartItem = localStorage.getItem('cart-item')
      if (cartItem) {
        this.items = JSON.parse(cartItem)
      }
    },
    addToCart (productData) {
      const itemIndex = this.items.findIndex(
        item => item.name === productData.name
      )
      if (itemIndex >= 0) {
        this.updateQuantity(itemIndex, this.items[itemIndex].quantity + 1)
      } else {
        productData.quantity = 1
        this.items.push(productData)
      }
      localStorage.setItem('cart-item', JSON.stringify(this.items))
    },
    updateQuantity (index, quantity) {
      this.items[index].quantity = parseInt(quantity)
      localStorage.setItem('cart-item', JSON.stringify(this.items))
    },
    removeItemInCart (index) {
      this.items.splice(index, 1)
      localStorage.setItem('cart-item', JSON.stringify(this.items))
    }
  }
})

ทำ CartView

  • ทำ style หน้าตะกร้าสินค้า
    • เพิ่ม layout 2 column สำหรับแสดง item ในตะกร้าและสรุปภาพรวม
    • แต่ละแถวเพิ่ม item สำหรับแสดงสินค้าในตะกร้า / dropdown จำนวน และ กากบาทสำหรับลบสินค้าในตะกร้า
  • แสดงข้อมูลตะกร้าสินค้า (โดยจำลองข้อมูลจาก localstorage ก่อน)
  • เพิ่ม function
    • ปรับจำนวน item ในตะกร้าสินค้า (เลือกผ่าน dropdown จำนวน)
    • ลบ item ในตะกร้าสินค้า (กดจาก x ตรง item)
  • แสดงยอดรวมเสมอหลังจากที่มีการปรับตะกร้าสินค้าแล้ว
html
<script setup>
import UserLayout from '@/layouts/UserLayout.vue'

import RightIcon from '@/components/icons/Right.vue'
import CloseIcon from '@/components/icons/Close.vue'

import { computed } from 'vue'
import { RouterLink, useRouter } from 'vue-router'
import { useUserCartStore } from '@/stores/user/cart'

const userCartStore = useUserCartStore()
const router = useRouter()

const changeQuantity = (event, index) => {
  userCartStore.updateQuantity(index, event.target.value)
}

const removeItemInCart = (index) => {
  userCartStore.removeItemInCart(index)
  if (userCartStore.items.length === 0) {
    router.push({ name: 'home' })
  }
}
</script>

<template>
  <UserLayout>
    <div class="container mx-auto my-4">
      <h1 class="text-4xl mb-4">Shopping cart</h1>
      <div class="flex">
        <section class="flex-auto w-64">
          <div v-if="userCartStore.items.length === 0" class="px-8 py-32 bg-base-200">
            Cart is empty
          </div>
          <ul v-else class="px-8 bg-base-200">
            <li v-for="(item, index) in userCartStore.items" class="flex w-full py-10" :key="index">
              <div class="shrink-0">
                <img class="w-48" :src="item.imageUrl">
              </div>
              <div class="flex flex-1 flex-col justify-between pl-4">
                <div class="grid grid-cols-2 gap-6 relative">
                  <div>
                    <div class="text-xl font-bold">{{ item.name }}</div>
                    <div>{{ item.about }}</div>
                    <div>{{ item.price }} ฿</div>
                  </div>
                  <div>
                    <select class="p-1.5" v-model="item.quantity" @change="changeQuantity($event, index)">
                      <option disabled selected>Quantity</option>
                      <option v-for="quantity in [1,2,3,4,5]">{{ quantity }}</option>
                    </select>
                    <div @click="removeItemInCart(index)" class="absolute top-0 right-0 cursor-pointer">
                      <CloseIcon class="w-5"></CloseIcon>
                    </div>
                  </div>
                </div>
                <p class="flex">
                  <RightIcon class="w-5 shrink-0"></RightIcon>
                  <span>In stock</span>
                </p>
              </div>
            </li>
          </ul>
        </section>
        <section class="flex-auto w-32 bg-slate-200 p-8">
          <h2 class="text-2xl">Order summary</h2>
          <div class="mt-4 m-0 divide-y divide-base-200">
            <div class="flex align-middle justify-between mb-2">
              <div class="font-bold">ราคาสินค้าทั้งหมด</div>
              <div>{{ userCartStore.summaryPrice }}</div>
            </div>
            <div class="flex align-middle justify-between mb-2">
              <div class="font-bold">ค่าส่ง</div>
              <div>0</div>
            </div>
            <div class="flex align-middle justify-between mb-2">
              <div class="font-bold">ราคาทั้งสิ้น</div>
              <div>{{ userCartStore.summaryPrice }}</div>
            </div>
            <RouterLink to="/checkout" class="btn btn-primary w-full">
              ชำระเงิน
            </RouterLink>
          </div>
        </section>
      </div>
    </div>
  </UserLayout>
</template>

ลองเทส mock มาหน้าตาจะได้ประมาณนี้

cart-01

เพิ่ม addToCart HomeView และ SearchView

  • ที่ 2 หน้า (HomeView, SearchView) ให้เพิ่ม function addToCart ลงไปยังปุ่ม "Buy Now" และให้ redirect มาหน้าตะกร้า พร้อมกับเพิ่มสินค้านั้นในตะกร้าไป 1 ชิ้น

ที่ HomeView

html
<script setup>
// import router เข้ามาสำหรับทำ redirect
import { useRouter } from 'vue-router'

// import cart store เข้ามา
import { useUserCartStore } from '@/stores/user/cart'

const userCartStore = useUserCartStore()

const router = useRouter()

// เพิ่ม function addToCart
const addToCart = (productData) => {
  userCartStore.addToCart(productData)
  router.push({ name: 'cart' })
}
</script>

<template>
  <UserLayout>
    <!-- code ส่วน hero -->
    <ProductList
      :products="userProductStore.list"
      :addToCart="addToCart"
    > <!-- เพิ่ม addToCart -->
    </ProductList>
  </UserLayout>
</template>

ที่ SearchView

html
<script setup>
// import router เข้ามาสำหรับทำ redirect
import { useRoute, useRouter } from 'vue-router'

// import cart store เข้ามา
import { useUserCartStore } from '@/stores/user/cart'

const userCartStore = useUserCartStore()

// เพิ่ม function addToCart
const addToCart = (productData) => {
  userCartStore.addToCart(productData)
  router.push({ name: 'cart' })
}
</script>

<template>
  <UserLayout>
    <div class="m-10">
      <h1 class="text-3xl">Search: <span class="font-bold">{{ searchText }}</span></h1>
    </div>
    <div v-if="filterProducts.length > 0">
      <ProductList
        :products="filterProducts"
        :addToCart="addToCart"
      >
      </ProductList> <!-- เพิ่ม addToCart -->
    </div>
    <div class="m-10" v-else>
      <div class="text-center text-3xl">Product not found</div>
    </div>
  </UserLayout>
</template>

ผลลัพธ์ (หน้า HomeView และ SearchView เหมือนกัน) cart-03

เพิ่มแสดงจำนวนตะกร้าที่ UserLayout

  • ตรงส่วนด้านบนให้ดึงข้อมูลตะกร้า และราคารวมสินค้าของตะกร้ามาแสดงตรงตำแหน่ง Badge ของตะกร้าสินค้า (ดึงจาก CartStore)
html
<script setup>
import { useUserCartStore } from '@/stores/user/cart'

const userCartStore = useUserCartStore()
</script>
<template>
  <!-- code เฉพาะส่วน dropdown ตะกร้า -->
  <div class="dropdown dropdown-end">
    <label tabindex="0" class="btn btn-ghost btn-circle">
      <div class="indicator">
        <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
        <span class="badge badge-sm indicator-item">{{ userCartStore.quantity }}</span>
      </div>
    </label>
    <div tabindex="0" class="mt-3 z-[1] card card-compact dropdown-content w-52 bg-base-100 shadow">
      <div class="card-body">
        <span class="font-bold text-lg">{{ userCartStore.quantity }} Items</span>
        <span class="text-info">Subtotal: {{ userCartStore.summaryPrice }} ฿</span>
        <div class="card-actions">
          <RouterLink to="/cart" class="btn btn-primary btn-block">
            View cart
          </RouterLink>
        </div>
      </div>
    </div>
  </div>
  <!-- จบ code -->
</template>

ผลลัพธ์ cart-04