Vue3 + TypeScript + Pinia构建电商购物车实战
引言
在电商网站中,购物车是连接商品与订单的关键环节。一个功能完善、体验流畅的购物车不仅能提升用户转化率,还能增强用户粘性。本文将带你使用Vue3、TypeScript和Pinia这三个现代前端技术栈,从零开始构建一个电商购物车系统,涵盖商品添加、数量调整、价格计算、库存检查等核心功能。
一、技术栈选型与优势
选择Vue3、TypeScript和Pinia作为开发组合,是基于它们各自的优势:
- Vue3:采用Composition API,逻辑复用更灵活,性能更优,对TypeScript的支持也更加友好。
- TypeScript:提供静态类型检查,减少运行时错误,让代码更易维护,特别适合中大型项目。
- Pinia:Vue官方推荐的状态管理库,比Vuex更轻量,API更简洁,完美支持TypeScript。
二、项目初始化与基础配置
首先使用Vite创建Vue3项目,并配置TypeScript:
npm create vite@latest shopping-cart -- --template vue-ts
cd shopping-cart
npm install
安装Pinia:
npm install pinia
在main.ts中初始化Pinia:
import { createApp } from \'vue\'
import { createPinia } from \'pinia\'
import App from \'./App.vue\'
const app = createApp(App)
app.use(createPinia())
app.mount(\'#app\')
三、设计购物车数据模型
在TypeScript中,我们需要定义购物车和商品的数据类型。创建types/cart.ts:
interface Product {
id: number
name: string
price: number
stock: number
image: string
}
interface CartItem {
product: Product
quantity: number
}
interface CartState {
items: CartItem[]
total: number
itemCount: number
}
四、创建Pinia购物车Store
创建stores/cart.ts,实现购物车的核心逻辑:
import { defineStore } from \'pinia\'
import type { CartItem, Product, CartState } from \'../types/cart\'
export const useCartStore = defineStore(\'cart\', {
state: (): CartState => ({
items: [],
total: 0,
itemCount: 0
}),
getters: {
// 计算总价
getTotal: (state) => {
return state.items.reduce((sum, item) => sum + item.product.price * item.quantity, 0)
},
// 计算商品总数
getItemCount: (state) => {
return state.items.reduce((count, item) => count + item.quantity, 0)
},
// 检查商品是否在购物车
isInCart: (state) => (productId: number) => {
return state.items.some(item => item.product.id === productId)
}
},
actions: {
// 添加商品到购物车
addToCart(product: Product, quantity: number = 1) {
const existingItem = this.items.find(item => item.product.id === product.id)
if (existingItem) {
// 检查库存
if (existingItem.quantity + quantity > product.stock) {
alert(\'库存不足\')
return
}
existingItem.quantity += quantity
} else {
this.items.push({ product, quantity })
}
this.updateTotals()
},
// 从购物车移除商品
removeFromCart(productId: number) {
this.items = this.items.filter(item => item.product.id !== productId)
this.updateTotals()
},
// 更新商品数量
updateQuantity(productId: number, quantity: number) {
const item = this.items.find(item => item.product.id === productId)
if (item) {
if (quantity > item.product.stock) {
alert(\'库存不足\')
return
}
item.quantity = quantity
if (quantity <= 0) {
this.removeFromCart(productId)
}
this.updateTotals()
}
},
// 更新总计
updateTotals() {
this.total = this.getTotal
this.itemCount = this.getItemCount
},
// 清空购物车
clearCart() {
this.items = []
this.updateTotals()
}
}
})
五、构建购物车组件
创建components/CartList.vue:
<template>
<div class=\"cart-list\">
<h2>购物车</h2>
<div v-if=\"cartStore.items.length === 0\">
购物车为空
</div>
<div v-else>
<div v-for=\"item in cartStore.items\" :key=\"item.product.id\" class=\"cart-item\">
<img :src=\"item.product.image\" :alt=\"item.product.name\">
<div class=\"item-info\">
<h3>{{ item.product.name }}</h3>
<p>¥{{ item.product.price.toFixed(2) }}</p>
<div class=\"quantity-control\">
<button @click=\"updateQuantity(item.product.id, item.quantity - 1)\">-</button>
<span>{{ item.quantity }}</span>
<button @click=\"updateQuantity(item.product.id, item.quantity + 1)\">+</button>
</div>
</div>
<button class=\"remove-btn\" @click=\"removeFromCart(item.product.id)\">删除</button>
</div>
<div class=\"cart-summary\">
<p>总计: ¥{{ cartStore.total.toFixed(2) }}</p>
<button @click=\"clearCart\">清空购物车</button>
</div>
</div>
</div>
</template>
<script setup lang=\"ts\">
import { useCartStore } from \'../stores/cart\'
import type { Product } from \'../types/cart\'
const cartStore = useCartStore()
const updateQuantity = (productId: number, quantity: number) => {
cartStore.updateQuantity(productId, quantity)
}
const removeFromCart = (productId: number) => {
cartStore.removeFromCart(productId)
}
const clearCart = () => {
cartStore.clearCart()
}
</script>
六、商品列表与购物车联动
创建components/ProductList.vue:
<template>
<div class=\"product-list\">
<h2>商品列表</h2>
<div class=\"products\">
<div v-for=\"product in products\" :key=\"product.id\" class=\"product\">
<img :src=\"product.image\" :alt=\"product.name\">
<div class=\"product-info\">
<h3>{{ product.name }}</h3>
<p>¥{{ product.price.toFixed(2) }}</p>
<p>库存: {{ product.stock }}</p>
<button
@click=\"addToCart(product)\"
:disabled=\"cartStore.isInCart(product.id)\"
>
{{ cartStore.isInCart(product.id) ? \'已在购物车\' : \'加入购物车\' }}
</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang=\"ts\">
import { ref } from \'vue\'
import { useCartStore } from \'../stores/cart\'
import type { Product } from \'../types/cart\'
const cartStore = useCartStore()
// 模拟商品数据
const products = ref<Product[]>([
{ id: 1, name: \'商品A\', price: 99.99, stock: 10, image: \'/product-a.jpg\' },
{ id: 2, name: \'商品B\', price: 199.99, stock: 5, image: \'/product-b.jpg\' },
{ id: 3, name: \'商品C\', price: 299.99, stock: 8, image: \'/product-c.jpg\' }
])
const addToCart = (product: Product) => {
cartStore.addToCart(product)
}
</script>
七、优化与扩展功能
1. **持久化存储**:使用localStorage保存购物车状态,实现刷新不丢失:
// 在stores/cart.ts中添加
import { persist } from \'pinia-plugin-persistedstate\'
export const cartStore = defineStore(\'cart\', {
// ...原有配置
}, {
persist: {
key: \'shopping-cart\',
storage: localStorage,
paths: [\'items\']
}
})
// 在main.ts中注册插件
import { createPinia } from \'pinia\'
const pinia = createPinia()
pinia.use(persist)
2. **购物车动画**:使用Vue Transition实现添加/删除商品的动画效果。
3. **商品推荐**:基于购物车商品推荐相关商品。
4. **优惠计算**:添加优惠券、满减等促销逻辑。
八、总结
通过本文的实践,我们完成了基于Vue3、TypeScript和Pinia的电商购物车系统开发。这个购物车不仅实现了基本的商品管理功能,还考虑了库存检查、数据持久化等实际应用场景。Composition API的灵活使用让代码逻辑更加清晰,TypeScript的类型安全则减少了潜在的运行时错误。Pinia作为状态管理库,提供了简洁的API和优秀的TypeScript支持,让状态管理变得轻松愉快。
在实际项目中,还可以进一步扩展购物车的功能,比如添加商品规格选择、运费计算、多收货地址支持等。但无论功能如何扩展,本文提供的核心架构和实现思路都能作为良好的基础。希望这个实战案例能帮助你更好地理解Vue3生态系统的协同工作方式,并在实际项目中应用这些技术。
