Vue基础教程(206)网上购物商城开发实战之系统功能模块设计与实现中的购买模块:Vue购物车剁手指南:从“加入购物车”到“买买买”的奇幻漂流

作为一个当代互联网野生哲学家,我始终坚信一个真理:人类的本质不是复读机,而是购物车——总在“清空”和“塞满”之间反复横跳。今天,我们就用Vue这把神奇的手术刀,解剖网上商城最让人欲罢不能的“购买模块”。放心,不写教科书式八股文,咱们直接进入“剁手实验室”!


一、购买模块:商城的“冲动引擎”

想象一下这个场景:深夜刷手机,看到一双限量版球鞋,你的大脑还没反应,手指已经戳了“加入购物车”——这就是购买模块的魔力。它本质上是一个精密的情感计算器,用技术把“一时冲动”变成“已下单”。

在Vue开发的商城中,购买模块通常包含三大战区:

  1. 商品展示区(让你心动)
  2. 购物车缓冲区(让你纠结)
  3. 下单支付区(让你肉疼)

接下来,我们像拆解乐高一样,把每个零件摊开看看。


二、商品展示:心动的起点

商品页面的核心任务就一条:用最少的信息让你最大程度地上头。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 促进凑单消费
  • 支付成功后的购物车清空:给你“重新开始”的错觉

五、购买模块的隐藏彩蛋

一个好的购买模块不止是功能完整,还要有些人性化的小心思

  1. 本地持久化:关掉浏览器再打开,购物车还在
// 在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))
    })
  }
]
  1. 购买历史:帮你记住每次“为什么买这个”
  2. 猜你喜欢:基于购物车商品推荐相关商品,促进二次消费

结语:购买模块的本质

说到底,购买模块的技术实现并不复杂,但心理博弈却很精妙。Vue给我们提供了响应式数据绑定、组件化开发、状态管理等利器,让我们能够把“一时冲动”顺畅地转化为“已下单”。

下次当你忍不住“加入购物车”时,不妨想想背后这套精密的Vue机器——它理解你的冲动,包容你的纠结,最终温柔地帮你完成那个“买买买”的梦想。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值