iT邦幫忙

2024 iThome 鐵人賽

DAY 14
0

購物車會使用localstorage結合store
Components: Shopping Cart

目錄

  • 購物車的功能:
  • ShoppingCartStore
  • Components - ShoppingCart.vue
  • Formatter - productToCartFormatter
  • Others

購物車的功能:

  • 頁面點擊加入購物車按鈕,component購物車會新增產品
  • 顯示列表
  • 刪除列表項目

ShoppingCartStore

購物車功能牽涉的範圍比較廣,也有envolve到結帳功能。
首先,購物車使用localstorage結合pinia,將功能依序完成:

  1. 加載購物車
  2. 儲存購物車變更
  3. 將商品加入購物車
  4. 將商品從購物車移除
  5. 購物車項目總數量
  6. 購物車項目總金額
import { defineStore } from 'pinia'
import { ref, computed, watch } from 'vue'
import type { cartProductModel } from '~/models/viewModel'

const storeName = 'ShoppingCart';
export const useShoppingCartStore = defineStore(storeName, () => {
    // 使用 ref 定義購物車
    const cart: Ref<cartProductModel[]> = ref([]);

    // 從 localStorage 加載購物車數據
    const loadCart = () => {
        const storedCart = localStorage.getItem(storeName);
        
        if (storedCart) {
            cart.value = JSON.parse(storedCart);
        }
    }


    // 保存購物車數據到 localStorage
    const saveCart = () => {
        localStorage.setItem(storeName, JSON.stringify(cart.value));
    }

    // 添加商品到購物車
    const addToCart = (product: cartProductModel) => {
        const item = cart.value.find(i => i.id === product.id);
        if (item) {
            item.quantity = Number(product.quantity) + Number(item.quantity);
        } else {
            cart.value.push({ ...product, quantity: product.quantity });
        }
        saveCart();
    }

    // 從購物車中移除商品
    const removeFromCart = (productId: string) => {
        cart.value = cart.value.filter(item => item.id !== productId);
        saveCart();
    }

    // 清空購物車
    const clearCart = () => {
        cart.value = []
        saveCart();
    }

    const findItemQuantity = (id: string): number => {
        const item = cart.value.find(i => i.id === id);
        if(item) {
            return item.quantity;
        }
        return 0;
    }

    // 計算購物車中商品的總數量
    const totalQuantity = computed(() => {
        return cart.value.reduce((sum, item) => sum + item.quantity, 0)
    });

    // 計算購物車中的總金額
    const totalAmount = computed(() => {
        return cart.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
    });

    return {
        cart,
        addToCart,
        removeFromCart,
        clearCart,
        totalQuantity,
        totalAmount,
        loadCart,
        findItemQuantity
    }
})

Components - ShoppingCart.vue

然後是購物車的component:

<script lang="ts" setup>
import type { cartProductModel } from '~/models/viewModel';
type Props = {
    cartProduct?: cartProductModel[] | null;
}

const props = withDefaults(defineProps<Props>(), {
    cartProduct: () => ([]),
});

const shoppingCartStore = useShoppingCartStore();

const {
    loadCart,
    removeFromCart
} = shoppingCartStore;

onMounted(() => {
    loadCart();
})

</script>
<template>
    <div>
        <h3>shopping cart</h3>
        <div>
            <div v-for="item in cartProduct" :key="item.id" class="flex">
                <img :src="item.thumbnail" 
                    alt=""
                    style="width: 100px; height: 100px;">
                <div>
                    <h5>{{ item.title }}</h5>
                    <p>{{ item.price }} cad X {{ item.quantity }}</p>
                </div>
                <button @click="removeFromCart(item.id)">delete</button>
            </div>
        </div>
        <button>purchase order</button>
    </div>
</template>

<style>
.flex {
    display: flex;
}
</style>

Formatter - productToCartFormatter

這邊目的是將apiModel: ProductDetailModel轉成viewModel: cartProductModel

// ./formatter/index.ts
export const productToCartFormatter = (pModel: ProductDetailModel, quantity: number): cartProductModel => {
    const result: cartProductModel = {...pModel, quantity };
    return result;
}

Others

我將“商品清單”component的加入購物車安鈕,及控制加入數量的input拉出來做成component,因為是重複元件。

// ButtonAddToCart.vue <- 加入購物車
<script lang="ts" setup>
import type { cartProductModel } from '~/models/viewModel';
import { useShoppingCartStore } from '~/stores/useShoppingCartStore';

type Props = {
    cartProduct: cartProductModel;
    isDisabled: boolean;
}

const props = withDefaults(defineProps<Props>(), {
    cartProduct: () => ({}) as cartProductModel,
    isDisabled: false,
});

const shoppingCartStore = useShoppingCartStore();

const { 
    addToCart
} = shoppingCartStore;
</script>
<template>
    <div>
        <button :disabled="isDisabled" @click.stop="addToCart(cartProduct)">
            add to cart
        </button>
    </div>
</template>
// Amount.vue <- 控制加入數量
<script lang="ts" setup>
type Props = {
    max: number;
    isDisabled: boolean;
}

const props = withDefaults(defineProps<Props>(), {
    max: 1,
    isDisabled: false,
});
const emit = defineEmits(['inputValue']);

function emitValue(event: Event) {
    const tg = event.target as HTMLInputElement
    emit('inputValue', Number(tg.value));
}

</script>
<template>
    <div>
        <input @click.stop="" type="number" @input="emitValue" min="0" :max="max">
    </div>
</template>

這是完成圖:
完成圖


上一篇
[Day 19] Product Detail Page
下一篇
[Day 21] Purchase Order Page
系列文
NUXT3xVUE3xPINIA: 從零開始寫電商29
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言