Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
  • 实战案例:用 Pinia 实现一个购物车功能

实战案例:用 Pinia 实现一个购物车功能

购物车是电商类应用的常见功能,涉及状态管理、模块交互和动态计算。通过 Pinia 的模块化状态管理,我们可以实现一个简洁、可扩展的购物车。本节将通过实战案例,设计并实现购物车功能,展示如何结合 Pinia 的 Store、Vue 3 的响应式特性和组件化开发,构建一个实用的功能模块。

需求分析

我们需要实现以下功能:

  1. 产品列表:展示可用商品,支持加入购物车。
  2. 购物车管理:添加商品、调整数量、移除商品。
  3. 总价计算:实时计算购物车内商品总价。
  4. 状态持久化:可选功能,将购物车数据保存到本地存储。

项目结构

src/
├── stores/
│   ├── product.js       # 商品模块
│   └── cart.js         # 购物车模块
├── components/
│   ├── ProductList.vue # 商品列表组件
│   └── Cart.vue        # 购物车组件
├── App.vue
└── main.js

实现步骤

1. 商品模块

定义商品状态和数据:

// src/stores/product.js
import { defineStore } from 'pinia';
import { ref } from 'vue';

export const useProductStore = defineStore('product', () => {
  const products = ref([
    { id: 1, name: '苹果', price: 2, stock: 10 },
    { id: 2, name: '香蕉', price: 1, stock: 15 },
    { id: 3, name: '橙子', price: 3, stock: 5 }
  ]);

  return { products };
});

2. 购物车模块

实现购物车逻辑:

// src/stores/cart.js
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { useProductStore } from './product';

export const useCartStore = defineStore('cart', () => {
  const cartItems = ref([]);
  const productStore = useProductStore();

  // 添加商品到购物车
  const addToCart = (productId) => {
    const product = productStore.products.find(p => p.id === productId);
    if (!product || product.stock <= 0) return;

    const cartItem = cartItems.value.find(item => item.id === productId);
    if (cartItem) {
      if (cartItem.quantity < product.stock) {
        cartItem.quantity++;
      }
    } else {
      cartItems.value.push({ ...product, quantity: 1 });
    }
    product.stock--;
  };

  // 调整数量
  const updateQuantity = (productId, quantity) => {
    const item = cartItems.value.find(i => i.id === productId);
    const product = productStore.products.find(p => p.id === productId);
    if (!item || !product) return;

    const diff = quantity - item.quantity;
    if (diff > 0 && product.stock < diff) return; // 库存不足

    item.quantity = quantity;
    product.stock -= diff;
    if (item.quantity <= 0) {
      removeItem(productId);
    }
  };

  // 移除商品
  const removeItem = (productId) => {
    const index = cartItems.value.findIndex(i => i.id === productId);
    if (index !== -1) {
      const item = cartItems.value[index];
      const product = productStore.products.find(p => p.id === productId);
      if (product) product.stock += item.quantity;
      cartItems.value.splice(index, 1);
    }
  };

  // 计算总价
  const totalPrice = computed(() => {
    return cartItems.value.reduce((sum, item) => sum + item.price * item.quantity, 0);
  });

  return { cartItems, addToCart, updateQuantity, removeItem, totalPrice };
});

3. 商品列表组件

<!-- src/components/ProductList.vue -->
<template>
  <div>
    <h2>商品列表</h2>
    <ul>
      <li v-for="product in products" :key="product.id">
        {{ product.name }} - ${{ product.price }} (库存: {{ product.stock }})
        <button @click="addToCart(product.id)" :disabled="product.stock === 0">
          加入购物车
        </button>
      </li>
    </ul>
  </div>
</template>

<script>
import { useProductStore } from '@/stores/product';
import { useCartStore } from '@/stores/cart';

export default {
  setup() {
    const productStore = useProductStore();
    const cartStore = useCartStore();

    return {
      products: productStore.products,
      addToCart: cartStore.addToCart
    };
  }
};
</script>

<style scoped>
li { margin: 5px 0; }
button:disabled { opacity: 0.5; }
</style>

4. 购物车组件

<!-- src/components/Cart.vue -->
<template>
  <div>
    <h2>购物车</h2>
    <ul>
      <li v-for="item in cartItems" :key="item.id">
        {{ item.name }} - ${{ item.price }} x
        <input type="number" v-model.number="item.quantity" @change="updateQuantity(item.id, item.quantity)" min="0" />
        <button @click="removeItem(item.id)">移除</button>
      </li>
    </ul>
    <p>总价:${{ totalPrice }}</p>
  </div>
</template>

<script>
import { useCartStore } from '@/stores/cart';

export default {
  setup() {
    const cartStore = useCartStore();
    return {
      cartItems: cartStore.cartItems,
      updateQuantity: cartStore.updateQuantity,
      removeItem: cartStore.removeItem,
      totalPrice: cartStore.totalPrice
    };
  }
};
</script>

<style scoped>
li { margin: 5px 0; }
input { width: 50px; margin: 0 5px; }
</style>

5. 根组件

<!-- src/App.vue -->
<template>
  <div>
    <ProductList />
    <Cart />
  </div>
</template>

<script>
import ProductList from './components/ProductList.vue';
import Cart from './components/Cart.vue';

export default {
  components: { ProductList, Cart }
};
</script>

扩展:状态持久化

使用 Pinia 的插件实现状态持久化:

npm install pinia-plugin-persistedstate

修改 main.js

// src/main.js
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import App from './App.vue';

const app = createApp(App);
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
app.use(pinia);
app.mount('#app');

更新 Cart Store

// src/stores/cart.js
export const useCartStore = defineStore('cart', () => {
  // ... 现有代码 ...
  return { cartItems, addToCart, updateQuantity, removeItem, totalPrice };
}, {
  persist: true // 启用持久化,默认使用 localStorage
});
  • 效果:页面刷新后,购物车数据保留。

自定义持久化

persist: {
  storage: sessionStorage, // 使用 sessionStorage
  paths: ['cartItems'] // 只持久化 cartItems
}

功能亮点

  1. 模块化:
    • product 和 cart Store 分离职责,清晰可维护。
  2. 响应式:
    • totalPrice 使用 computed 实时计算。
  3. 交互性:
    • 支持动态调整数量和移除,库存同步更新。
  4. 扩展性:
    • 添加持久化插件简单易用。

注意事项

  1. 库存管理:
    • 确保库存不会变成负数,添加边界检查。
  2. 性能:
    • 大量商品时,考虑分页或懒加载。
  3. 错误处理:
    • 可添加提示(如“库存不足”)。

测试与优化

测试

<template>
  <button @click="addToCart(1)">添加苹果</button>
  <p>{{ cartItems.length }} 项,总价:{{ totalPrice }}</p>
</template>

<script>
import { useCartStore } from '@/stores/cart';

export default {
  setup() {
    const store = useCartStore();
    return { cartItems: store.cartItems, totalPrice: store.totalPrice, addToCart: store.addToCart };
  }
};
</script>

优化

  • 防抖:数量输入频繁时添加防抖。
    import { debounce } from 'lodash';
    const updateQuantity = debounce((productId, quantity) => {
      // ... 更新逻辑 ...
    }, 300);
    

总结

通过 Pinia 实现购物车功能,我们展示了模块化状态管理的威力。product 和 cart Store 协作完成数据管理,组件清晰分工,功能完整且易扩展。掌握此案例后,你可以轻松应对类似的状态管理需求。本章结束,下一章将探索路由与导航,带你进入更广阔的 Vue 3 世界!

Last Updated:: 2/24/25, 10:55 AM