作为一个当代互联网野生哲学家,我始终坚信一个真理:人类的本质不是复读机,而是购物车——总在“清空”和“塞满”之间反复横跳。今天,我们就用Vue这把神奇的手术刀,解剖网上商城最让人欲罢不能的“购买模块”。放心,不写教科书式八股文,咱们直接进入“剁手实验室”!
一、购买模块:商城的“冲动引擎”
想象一下这个场景:深夜刷手机,看到一双限量版球鞋,你的大脑还没反应,手指已经戳了“加入购物车”——这就是购买模块的魔力。它本质上是一个精密的情感计算器,用技术把“一时冲动”变成“已下单”。
在Vue开发的商城中,购买模块通常包含三大战区:
- 商品展示区(让你心动)
- 购物车缓冲区(让你纠结)
- 下单支付区(让你肉疼)
接下来,我们像拆解乐高一样,把每个零件摊开看看。
二、商品展示:心动的起点
商品页面的核心任务就一条:用最少的信息让你最大程度地上头。Vue组件的优势在这里淋漓尽致:
<template>
<div class="product">
<!-- 图片轮播:让你360度自我说服 -->
<swiper :images="product.images" @click="zoomImage"/>
<!-- 价格显示:用颜色刺激多巴胺 -->
<div class="price" :class="{ 'sale': product.isOnSale }">
<span class="current">{{ product.price }}</span>
<span v-if="product.isOnSale" class="original">{{ product.originalPrice }}</span>
</div>
<!-- 库存提示:制造稀缺焦虑 -->
<div v-if="product.stock < 10" class="stock-warning">
仅剩{{ product.stock }}件,手慢无!
</div>
<!-- 购买按钮:罪恶的开始 -->
<button
@click="addToCart"
:disabled="product.stock === 0"
class="add-btn"
:class="{ 'disabled': product.stock === 0 }">
{{ product.stock === 0 ? '暂时缺货' : '加入购物车' }}
</button>
</div>
</template>
<script>
export default {
data() {
return {
product: {
id: 1,
name: 'AI写的代码居然不会bug',
price: '¥999',
originalPrice: '¥1299',
isOnSale: true,
stock: 5,
images: ['image1.jpg', 'image2.jpg']
}
}
},
methods: {
addToCart() {
// 这里埋着后续要讲的Vuex
this.$store.dispatch('cart/addItem', this.product)
this.$toast('已加入购物车!') // 即时反馈让你爽一下
}
}
}
</script>
设计心机:
v-if="product.stock < 10":库存警告只在关键时刻出现,多了就不值钱了:disabled="product.stock === 0":缺货时按钮变灰,让你产生“错过一个亿”的失落感- 价格颜色变化:促销价用醒目的红色,刺激购买欲
三、购物车:人类的纠结集中营
购物车是购买模块的情感过山车——在这里,你会经历“这个要不要删?”“那个好像更划算”的灵魂拷问。用Vuex管理这个复杂状态再合适不过:
// store/cart.js
export default {
state: {
items: [], // 存放你的冲动
total: 0 // 存放你的后悔
},
mutations: {
// 添加新冲动
ADD_ITEM(state, product) {
const existing = state.items.find(item => item.id === product.id)
if (existing) {
existing.quantity++ // 同样的冲动重复了
} else {
state.items.push({ ...product, quantity: 1 })
}
this.commit('cart/UPDATE_TOTAL')
},
// 删除某个冲动
REMOVE_ITEM(state, productId) {
state.items = state.items.filter(item => item.id !== productId)
this.commit('cart/UPDATE_TOTAL')
},
// 更新总价(直面现实的时候到了)
UPDATE_TOTAL(state) {
state.total = state.items.reduce((sum, item) => {
return sum + (parseFloat(item.price) * item.quantity)
}, 0)
}
},
actions: {
addItem({ commit }, product) {
commit('ADD_ITEM', product)
},
removeItem({ commit }, productId) {
commit('REMOVE_ITEM', productId)
}
}
}
购物车组件则是你的理性与冲动的战场:
<template>
<div class="cart">
<div v-if="items.length === 0" class="empty">
<!-- 空购物车:人生难得的清醒时刻 -->
<img src="@/assets/empty-cart.png" alt="空空如也">
<p>这里空得就像发工资前的钱包</p>
</div>
<div v-else>
<div v-for="item in items" :key="item.id" class="cart-item">
<!-- 商品信息:提醒你当初为什么心动 -->
<div class="info">
<img :src="item.images[0]" :alt="item.name">
<span class="name">{{ item.name }}</span>
</div>
<!-- 数量控制:微调你的冲动程度 -->
<div class="quantity">
<button @click="decrease(item.id)">-</button>
<span>{{ item.quantity }}</span>
<button @click="increase(item.id)">+</button>
</div>
<!-- 小计:每一次加减都是灵魂拷问 -->
<div class="subtotal">
¥{{ (item.price * item.quantity).toFixed(2) }}
</div>
<!-- 删除:最终的理性胜利 -->
<button @click="remove(item.id)" class="remove-btn">删除</button>
</div>
<!-- 总价:直面现实的时候到了 -->
<div class="total-amount">
总计:<span class="price">¥{{ total }}</span>
</div>
<!-- 结算:从想到买的临门一脚 -->
<button @click="checkout" class="checkout-btn">去结算</button>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
computed: {
...mapState('cart', ['items', 'total'])
},
methods: {
...mapMutations('cart', ['REMOVE_ITEM']),
increase(productId) {
// 找到商品并增加数量
const item = this.items.find(item => item.id === productId)
if (item) item.quantity++
this.$store.commit('cart/UPDATE_TOTAL')
},
decrease(productId) {
const item = this.items.find(item => item.id === productId)
if (item && item.quantity > 1) {
item.quantity--
this.$store.commit('cart/UPDATE_TOTAL')
}
},
remove(productId) {
this.REMOVE_ITEM(productId)
},
checkout() {
if (this.items.length === 0) {
this.$toast('购物车是空的哦~')
return
}
this.$router.push('/checkout')
}
}
}
</script>
设计亮点:
v-if="items.length === 0":空状态用幽默文案缓解尴尬- 数量控制:
+/-按钮让你感觉自己在“精打细算” - 实时计算总价:每次操作都提醒你钱包在哭泣
四、下单支付:冲动变现的最后一步
这是购买模块的终局之战,需要收集用户信息、选择支付方式,然后完成那个神圣的“支付”点击。
<template>
<div class="checkout">
<h2>确认订单</h2>
<!-- 收货地址 -->
<div class="address-section">
<h3>收货地址</h3>
<div v-if="defaultAddress" class="address-card">
<p>{{ defaultAddress.name }} {{ defaultAddress.phone }}</p>
<p>{{ defaultAddress.fullAddress }}</p>
</div>
<button @click="selectAddress">选择地址</button>
</div>
<!-- 商品清单 -->
<div class="order-items">
<h3>商品清单</h3>
<div v-for="item in cartItems" :key="item.id" class="order-item">
<img :src="item.images[0]" :alt="item.name">
<span class="name">{{ item.name }}</span>
<span class="quantity">x{{ item.quantity }}</span>
<span class="price">¥{{ item.price * item.quantity }}</span>
</div>
</div>
<!-- 支付方式 -->
<div class="payment-method">
<h3>支付方式</h3>
<div
v-for="method in paymentMethods"
:key="method.value"
class="method-option"
:class="{ 'selected': selectedMethod === method.value }"
@click="selectedMethod = method.value">
<input type="radio" :value="method.value" v-model="selectedMethod">
<span>{{ method.label }}</span>
</div>
</div>
<!-- 订单汇总 -->
<div class="order-summary">
<div class="summary-item">
<span>商品总价:</span>
<span>¥{{ cartTotal }}</span>
</div>
<div class="summary-item">
<span>运费:</span>
<span>¥{{ shippingFee }}</span>
</div>
<div class="summary-item total">
<span>实付:</span>
<span>¥{{ actualTotal }}</span>
</div>
</div>
<!-- 提交订单:冲动变现的按钮 -->
<button
@click="submitOrder"
:disabled="isSubmitting"
class="submit-order-btn">
{{ isSubmitting ? '支付中...' : `支付 ¥${actualTotal}` }}
</button>
</div>
</template>
<script>
export default {
data() {
return {
selectedMethod: 'alipay',
isSubmitting: false,
paymentMethods: [
{ value: 'alipay', label: '支付宝' },
{ value: 'wechat', label: '微信支付' }
],
defaultAddress: {
name: '张三',
phone: '138****8888',
fullAddress: '北京市朝阳区某某街道某某小区1号楼1单元101'
}
}
},
computed: {
cartItems() {
return this.$store.state.cart.items
},
cartTotal() {
return this.$store.state.cart.total
},
shippingFee() {
return this.cartTotal > 99 ? 0 : 10 // 满99包邮的心机
},
actualTotal() {
return this.cartTotal + this.shippingFee
}
},
methods: {
async submitOrder() {
this.isSubmitting = true
try {
// 模拟API调用
await this.$api.orders.create({
items: this.cartItems,
total: this.actualTotal,
paymentMethod: this.selectedMethod,
address: this.defaultAddress
})
// 清空购物车
this.$store.commit('cart/CLEAR_CART')
// 跳转到支付成功页
this.$router.push('/order/success')
} catch (error) {
this.$toast('支付失败,请重试')
} finally {
this.isSubmitting = false
}
},
selectAddress() {
// 跳转到地址选择页
this.$router.push('/address/select')
}
}
}
</script>
支付环节的精妙之处:
:disabled="isSubmitting":防止重复提交,避免真的“剁手”- 运费计算逻辑:
this.cartTotal > 99 ? 0 : 10促进凑单消费 - 支付成功后的购物车清空:给你“重新开始”的错觉
五、购买模块的隐藏彩蛋
一个好的购买模块不止是功能完整,还要有些人性化的小心思:
- 本地持久化:关掉浏览器再打开,购物车还在
// 在Vuex中加入本地存储
plugins: [
store => {
// 加载时读取本地存储
const saved = localStorage.getItem('vue-mall-cart')
if (saved) {
store.replaceState(JSON.parse(saved))
}
// 状态变化时保存
store.subscribe((mutation, state) => {
localStorage.setItem('vue-mall-cart', JSON.stringify(state))
})
}
]
- 购买历史:帮你记住每次“为什么买这个”
- 猜你喜欢:基于购物车商品推荐相关商品,促进二次消费
结语:购买模块的本质
说到底,购买模块的技术实现并不复杂,但心理博弈却很精妙。Vue给我们提供了响应式数据绑定、组件化开发、状态管理等利器,让我们能够把“一时冲动”顺畅地转化为“已下单”。
下次当你忍不住“加入购物车”时,不妨想想背后这套精密的Vue机器——它理解你的冲动,包容你的纠结,最终温柔地帮你完成那个“买买买”的梦想。

被折叠的 条评论
为什么被折叠?



