購物車會使用localstorage
結合store
購物車功能牽涉的範圍比較廣,也有envolve到結帳功能。
首先,購物車使用localstorage
結合pinia
,將功能依序完成:
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
}
})
然後是購物車的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>
這邊目的是將apiModel
: ProductDetailModel
轉成viewModel
: cartProductModel
// ./formatter/index.ts
export const productToCartFormatter = (pModel: ProductDetailModel, quantity: number): cartProductModel => {
const result: cartProductModel = {...pModel, quantity };
return result;
}
我將“商品清單”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>
這是完成圖: