Skip to content

计划编写练习:电商购物车功能

场景描述

你的团队需要实现一个电商购物车功能。产品经理提供了需求文档,你需要使用 writing-plans 技能将其分解为可执行的开发任务。

需求文档

markdown
# 购物车功能需求

## 核心功能

### 1. 添加商品
- 用户可以点击"加入购物车"按钮添加商品
- 如果商品已在购物车中,则增加数量
- 显示添加成功的提示

### 2. 查看购物车
- 显示购物车中所有商品
- 每个商品显示:图片、名称、单价、数量、小计
- 显示商品总数和总金额

### 3. 修改数量
- 用户可以通过 +/- 按钮调整商品数量
- 数量不能小于 1
- 数量修改后实时更新小计和总计

### 4. 删除商品
- 用户可以删除购物车中的商品
- 删除前显示确认对话框

### 5. 数据持久化
- 购物车数据保存在 LocalStorage
- 用户刷新页面后数据保留

## 技术约束

- 使用 Svelte 框架
- 使用 TypeScript
- 遵循 TDD 开发流程

你的任务

使用 writing-plans 技能:

  1. 分析需求:理解每个功能点的具体要求
  2. 设计架构:确定组件结构和数据流
  3. 分解任务:将需求分解为 2-5 分钟的小任务
  4. 编写计划:生成详细的实现计划

计划编写指南

任务分解原则

每个任务应该是:

  • 原子性:一个任务只做一件事
  • 可验证:有明确的完成标准
  • 短小精悍:2-5 分钟内可完成
  • 独立可测:可以独立测试

任务格式

markdown
## Task N: 任务标题

### 文件
- 创建/修改的文件列表

### 操作
1. 具体操作步骤
2. 包含必要的代码片段

### 验证
- 预期结果
- 验证命令

设计思考

在写计划前,思考:

  1. 状态管理:购物车状态如何存储和管理?
  2. 组件结构:需要哪些组件?职责如何划分?
  3. 数据流:数据如何在组件间流动?
  4. 测试策略:如何保证质量?

提示

提示 1:组件结构

建议的组件结构:

text
src/
├── stores/
│   └── cart.ts          # 购物车状态管理
├── lib/
│   ├── Cart.svelte      # 购物车容器
│   ├── CartItem.svelte  # 单个商品
│   ├── CartSummary.svelte # 价格汇总
│   └── AddToCart.svelte # 添加按钮
└── types/
    └── cart.ts          # 类型定义

提示 2:状态管理

使用 Svelte Store:

typescript
// stores/cart.ts
import { writable, derived } from 'svelte/store';
import { browser } from '$app/environment';
import type { CartItem, Product } from '../types/cart';

// 从 LocalStorage 初始化
const stored = browser 
  ? JSON.parse(localStorage.getItem('cart') || '[]')
  : [];

export const cartItems = writable<CartItem[]>(stored);

// 自动保存到 LocalStorage
cartItems.subscribe(items => {
  if (browser) {
    localStorage.setItem('cart', JSON.stringify(items));
  }
});

// 派生数据:总数和总价
export const cartCount = derived(cartItems, 
  items => items.reduce((sum, item) => sum + item.quantity, 0)
);

export const cartTotal = derived(cartItems,
  items => items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);

// 操作方法
export function addToCart(product: Product) {
  // 实现见下文
}
export function updateQuantity(id: string, quantity: number) {
  // 实现见下文
}
export function removeFromCart(id: string) {
  // 实现见下文
}

提示 3:任务分解

从最简单开始:

  1. 首先建立类型定义
  2. 然后实现状态管理
  3. 再实现 UI 组件
  4. 最后实现交互功能

提示 4:TDD 思考

每个任务都应该包含测试:

markdown
Task: 创建 CartItem 类型
验证: TypeScript 编译通过,类型检查正确

Task: 实现 addToCart 方法
验证: 单元测试通过
  - 添加新商品:购物车数量 +1
  - 添加已存在商品:数量 +1

参考答案

markdown
# 购物车功能实现计划

## 设计概述

### 组件结构
- `Cart.svelte` - 购物车主容器
- `CartItem.svelte` - 单个商品行
- `CartSummary.svelte` - 价格汇总
- `AddToCart.svelte` - 添加按钮

### 状态管理
使用 Svelte Store + LocalStorage 持久化

---

## Task 1: 创建类型定义

### 文件
- 创建 `src/types/cart.ts`

### 操作
```typescript
// src/types/cart.ts
export interface Product {
  id: string;
  name: string;
  price: number;
  imageUrl: string;
}

export interface CartItem {
  productId: string;
  name: string;
  price: number;
  imageUrl: string;
  quantity: number;
}

验证

  • TypeScript 编译通过
  • 无类型错误

Task 2: 创建购物车 Store

文件

  • 创建 src/stores/cart.ts

操作

typescript
// src/stores/cart.ts
import { writable, derived } from 'svelte/store';
import { browser } from '$app/environment';
import type { CartItem, Product } from '../types/cart';

const stored = browser 
  ? JSON.parse(localStorage.getItem('cart') || '[]')
  : [];

export const cartItems = writable<CartItem[]>(stored);

cartItems.subscribe(items => {
  if (browser) {
    localStorage.setItem('cart', JSON.stringify(items));
  }
});

export const cartCount = derived(cartItems, 
  items => items.reduce((sum, item) => sum + item.quantity, 0)
);

export const cartTotal = derived(cartItems,
  items => items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);

验证

  • TypeScript 编译通过
  • Store 可正常导入

Task 3: 实现 addToCart 方法

文件

  • 修改 src/stores/cart.ts

操作

typescript
export function addToCart(product: Product) {
  cartItems.update(items => {
    const existing = items.find(item => item.productId === product.id);
    if (existing) {
      return items.map(item =>
        item.productId === product.id
          ? { ...item, quantity: item.quantity + 1 }
          : item
      );
    }
    return [...items, {
      productId: product.id,
      name: product.name,
      price: product.price,
      imageUrl: product.imageUrl,
      quantity: 1
    }];
  });
}

验证

  • 单元测试:添加新商品
  • 单元测试:添加已存在商品

Task 4: 实现 updateQuantity 方法

文件

  • 修改 src/stores/cart.ts

操作

typescript
export function updateQuantity(productId: string, quantity: number) {
  if (quantity < 1) return;
  
  cartItems.update(items =>
    items.map(item =>
      item.productId === productId
        ? { ...item, quantity }
        : item
    )
  );
}

验证

  • 单元测试:数量正确更新
  • 单元测试:数量 < 1 时不更新

Task 5: 实现 removeFromCart 方法

文件

  • 修改 src/stores/cart.ts

操作

typescript
export function removeFromCart(productId: string) {
  cartItems.update(items =>
    items.filter(item => item.productId !== productId)
  );
}

验证

  • 单元测试:商品从购物车移除

Task 6: 创建 CartItem 组件

文件

  • 创建 src/lib/CartItem.svelte

操作

svelte
<script lang="ts">
  import type { CartItem } from '../types/cart';
  import { updateQuantity, removeFromCart } from '../stores/cart';
  
  export let item: CartItem;
</script>

<div class="cart-item">
  <img src={item.imageUrl} alt={item.name} />
  <div class="info">
    <h3>{item.name}</h3>
    <p class="price">${item.price.toFixed(2)}</p>
  </div>
  <div class="quantity">
    <button on:click={() => updateQuantity(item.productId, item.quantity - 1)}>-</button>
    <span>{item.quantity}</span>
    <button on:click={() => updateQuantity(item.productId, item.quantity + 1)}>+</button>
  </div>
  <p class="subtotal">${(item.price * item.quantity).toFixed(2)}</p>
  <button class="remove" on:click={() => removeFromCart(item.productId)}>×</button>
</div>

验证

  • 组件可正常渲染
  • 显示正确的商品信息

Task 7: 创建 CartSummary 组件

文件

  • 创建 src/lib/CartSummary.svelte

操作

svelte
<script lang="ts">
  import { cartCount, cartTotal } from '../stores/cart';
</script>

<div class="cart-summary">
  <p>商品总数:<strong>{$cartCount}</strong></p>
  <p>总计:<strong>${$cartTotal.toFixed(2)}</strong></p>
</div>

验证

  • 显示正确的总数和总价

Task 8: 创建 Cart 主容器

文件

  • 创建 src/lib/Cart.svelte

操作

svelte
<script lang="ts">
  import { cartItems } from '../stores/cart';
  import CartItem from './CartItem.svelte';
  import CartSummary from './CartSummary.svelte';
</script>

<div class="cart">
  <h2>购物车</h2>
  
  {#if $cartItems.length === 0}
    <p class="empty">购物车是空的</p>
  {:else}
    <div class="items">
      {#each $cartItems as item (item.productId)}
        <CartItem {item} />
      {/each}
    </div>
    <CartSummary />
  {/if}
</div>

验证

  • 空购物车显示提示
  • 有商品时显示列表

Task 9: 创建 AddToCart 组件

文件

  • 创建 src/lib/AddToCart.svelte

操作

svelte
<script lang="ts">
  import { addToCart } from '../stores/cart';
  import type { Product } from '../types/cart';
  
  export let product: Product;
  let added = false;
  
  function handleAdd() {
    addToCart(product);
    added = true;
    setTimeout(() => added = false, 2000);
  }
</script>

<button 
  class="add-to-cart" 
  class:added
  on:click={handleAdd}
>
  {added ? '已添加 ✓' : '加入购物车'}
</button>

验证

  • 点击后显示"已添加"
  • 商品出现在购物车中

Task 10: 集成测试

文件

  • 创建 src/routes/+page.svelte(测试页面)

操作

svelte
<script lang="ts">
  import Cart from '../lib/Cart.svelte';
  import AddToCart from '../lib/AddToCart.svelte';
  
  const demoProducts = [
    { id: '1', name: '商品 A', price: 99.99, imageUrl: '/placeholder.jpg' },
    { id: '2', name: '商品 B', price: 149.99, imageUrl: '/placeholder.jpg' },
  ];
</script>

<main>
  <section class="products">
    {#each demoProducts as product}
      <div class="product">
        <h3>{product.name}</h3>
        <p>${product.price}</p>
        <AddToCart {product} />
      </div>
    {/each}
  </section>
  
  <Cart />
</main>

验证

  • 完整流程测试
  • 添加商品 -> 查看购物车 -> 修改数量 -> 删除商品

完成标准

  • [ ] 所有 TypeScript 类型定义完整
  • [ ] Store 方法单元测试通过
  • [ ] 所有组件正常渲染
  • [ ] LocalStorage 持久化正常
  • [ ] 刷新页面后数据保留

## 学习要点

### 1. 任务粒度

好的任务粒度:
- 太大:实现购物车功能(无法验证)
- 太小:创建一个文件(无意义)
- 刚好:实现 addToCart 方法(可验证、2-5分钟)

### 2. 渐进式开发

从核心开始:
1. 类型定义 → 2. 状态管理 → 3. UI 组件 → 4. 交互功能

### 3. 可验证性

每个任务都有明确的验证标准:
- 编译通过
- 测试通过
- 功能正常

### 4. 架构先行

写计划前先思考架构:
- 组件职责划分
- 数据流向
- 状态管理策略

## 常见错误

| 错误 | 正确做法 |
|------|----------|
| 任务太大(实现整个功能) | 分解为可验证的小任务 |
| 缺少验证标准 | 每个任务有明确验证方式 |
| 忽略测试 | 每个功能任务包含测试 |
| 不考虑依赖关系 | 按依赖顺序排列任务 |

## 进阶练习

完成基础练习后,尝试:

1. **添加优惠券功能**:输入优惠码,计算折扣
2. **添加库存检查**:商品有库存限制
3. **添加批量操作**:全选、批量删除

## 相关技能

- [writing-plans](/skills/writing-plans) - 计划编写技能
- [brainstorming](/skills/brainstorming) - 需求探索
- [test-driven-development](/skills/test-driven-development) - TDD 核心技能
- [executing-plans](/skills/executing-plans) - 计划执行