Ionic电商应用实战 #

一、项目概述 #

1.1 功能需求 #

text
电商应用功能
    │
    ├── 首页
    │   ├── 轮播图
    │   ├── 商品分类
    │   └── 推荐商品
    │
    ├── 商品
    │   ├── 商品列表
    │   ├── 商品详情
    │   └── 商品评价
    │
    ├── 购物车
    │   ├── 商品管理
    │   ├── 数量调整
    │   └── 结算
    │
    ├── 订单
    │   ├── 订单列表
    │   ├── 订单详情
    │   └── 物流跟踪
    │
    └── 我的
        ├── 个人信息
        ├── 收货地址
        └── 设置

二、数据模型 #

2.1 商品模型 #

typescript
// models/product.model.ts
export interface Product {
  id: string;
  name: string;
  description: string;
  price: number;
  originalPrice: number;
  discount: number;
  images: string[];
  mainImage: string;
  category: Category;
  stock: number;
  sales: number;
  rating: number;
  reviews: Review[];
  specifications: Specification[];
  tags: string[];
}

export interface Category {
  id: string;
  name: string;
  icon: string;
  children?: Category[];
}

export interface Specification {
  name: string;
  value: string;
}

export interface Review {
  id: string;
  userId: string;
  userName: string;
  userAvatar: string;
  rating: number;
  content: string;
  images: string[];
  createTime: Date;
}

2.2 购物车模型 #

typescript
// models/cart.model.ts
export interface CartItem {
  id: string;
  product: Product;
  quantity: number;
  selected: boolean;
  sku?: string;
}

export interface Cart {
  items: CartItem[];
  totalPrice: number;
  totalQuantity: number;
  selectedItems: CartItem[];
}

2.3 订单模型 #

typescript
// models/order.model.ts
export interface Order {
  id: string;
  orderNo: string;
  status: OrderStatus;
  items: OrderItem[];
  totalPrice: number;
  discount: number;
  actualPrice: number;
  address: Address;
  paymentMethod: string;
  paymentTime?: Date;
  deliveryTime?: Date;
  createTime: Date;
  logistics?: Logistics;
}

export enum OrderStatus {
  Pending = 'pending',
  Paid = 'paid',
  Shipped = 'shipped',
  Delivered = 'delivered',
  Cancelled = 'cancelled'
}

export interface OrderItem {
  productId: string;
  productName: string;
  productImage: string;
  price: number;
  quantity: number;
}

export interface Address {
  id: string;
  name: string;
  phone: string;
  province: string;
  city: string;
  district: string;
  detail: string;
  isDefault: boolean;
}

export interface Logistics {
  company: string;
  trackingNo: string;
  traces: LogisticsTrace[];
}

export interface LogisticsTrace {
  time: Date;
  description: string;
}

三、购物车服务 #

3.1 购物车服务 #

typescript
// services/cart.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { Cart, CartItem } from '../models/cart.model';
import { Product } from '../models/product.model';
import { StorageService } from './storage.service';

@Injectable({
  providedIn: 'root'
})
export class CartService {
  private cartSubject = new BehaviorSubject<Cart>(this.getEmptyCart());
  
  cart$ = this.cartSubject.asObservable();
  
  constructor(private storage: StorageService) {
    this.loadCart();
  }
  
  private getEmptyCart(): Cart {
    return {
      items: [],
      totalPrice: 0,
      totalQuantity: 0,
      selectedItems: []
    };
  }
  
  private loadCart() {
    const savedCart = this.storage.get<Cart>('cart');
    if (savedCart) {
      this.cartSubject.next(savedCart);
    }
  }
  
  private saveCart(cart: Cart) {
    this.storage.set('cart', cart);
    this.cartSubject.next(cart);
  }
  
  private calculateTotals(cart: Cart): Cart {
    const selectedItems = cart.items.filter(item => item.selected);
    
    return {
      ...cart,
      totalPrice: selectedItems.reduce((sum, item) => sum + item.product.price * item.quantity, 0),
      totalQuantity: cart.items.reduce((sum, item) => sum + item.quantity, 0),
      selectedItems
    };
  }
  
  addToCart(product: Product, quantity: number = 1) {
    const cart = this.cartSubject.value;
    const existingItem = cart.items.find(item => item.product.id === product.id);
    
    if (existingItem) {
      existingItem.quantity += quantity;
    } else {
      cart.items.push({
        id: Date.now().toString(),
        product,
        quantity,
        selected: true
      });
    }
    
    this.saveCart(this.calculateTotals(cart));
  }
  
  removeFromCart(itemId: string) {
    const cart = this.cartSubject.value;
    cart.items = cart.items.filter(item => item.id !== itemId);
    this.saveCart(this.calculateTotals(cart));
  }
  
  updateQuantity(itemId: string, quantity: number) {
    const cart = this.cartSubject.value;
    const item = cart.items.find(i => i.id === itemId);
    
    if (item) {
      item.quantity = Math.max(1, quantity);
      this.saveCart(this.calculateTotals(cart));
    }
  }
  
  toggleSelection(itemId: string) {
    const cart = this.cartSubject.value;
    const item = cart.items.find(i => i.id === itemId);
    
    if (item) {
      item.selected = !item.selected;
      this.saveCart(this.calculateTotals(cart));
    }
  }
  
  selectAll(selected: boolean) {
    const cart = this.cartSubject.value;
    cart.items.forEach(item => item.selected = selected);
    this.saveCart(this.calculateTotals(cart));
  }
  
  clearCart() {
    this.saveCart(this.getEmptyCart());
  }
  
  clearSelected() {
    const cart = this.cartSubject.value;
    cart.items = cart.items.filter(item => !item.selected);
    this.saveCart(this.calculateTotals(cart));
  }
}

四、购物车页面 #

4.1 购物车页面 #

typescript
// features/cart/cart.page.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Cart, CartItem } from '../../models/cart.model';
import { CartService } from '../../services/cart.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-cart',
  templateUrl: 'cart.page.html',
  styleUrls: ['cart.page.scss']
})
export class CartPage implements OnInit {
  cart$: Observable<Cart>;
  
  constructor(
    private cartService: CartService,
    private router: Router
  ) {
    this.cart$ = this.cartService.cart$;
  }
  
  ngOnInit() {}
  
  increaseQuantity(item: CartItem) {
    this.cartService.updateQuantity(item.id, item.quantity + 1);
  }
  
  decreaseQuantity(item: CartItem) {
    if (item.quantity > 1) {
      this.cartService.updateQuantity(item.id, item.quantity - 1);
    }
  }
  
  removeItem(item: CartItem) {
    this.cartService.removeFromCart(item.id);
  }
  
  toggleSelection(item: CartItem) {
    this.cartService.toggleSelection(item.id);
  }
  
  selectAll(selected: boolean) {
    this.cartService.selectAll(selected);
  }
  
  checkout() {
    this.router.navigate(['/checkout']);
  }
}
html
<!-- features/cart/cart.page.html -->
<ion-header>
  <ion-toolbar>
    <ion-title>购物车</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ng-container *ngIf="cart$ | async as cart">
    <ion-list *ngIf="cart.items.length > 0; else emptyCart">
      <ion-item *ngFor="let item of cart.items">
        <ion-checkbox 
          slot="start"
          [checked]="item.selected"
          (ionChange)="toggleSelection(item)">
        </ion-checkbox>
        
        <ion-thumbnail slot="start">
          <ion-img [src]="item.product.mainImage"></ion-img>
        </ion-thumbnail>
        
        <ion-label>
          <h2>{{ item.product.name }}</h2>
          <p>¥{{ item.product.price }}</p>
          
          <ion-buttons>
            <ion-button (click)="decreaseQuantity(item)">
              <ion-icon name="remove"></ion-icon>
            </ion-button>
            <ion-label>{{ item.quantity }}</ion-label>
            <ion-button (click)="increaseQuantity(item)">
              <ion-icon name="add"></ion-icon>
            </ion-button>
          </ion-buttons>
        </ion-label>
        
        <ion-button slot="end" fill="clear" color="danger" (click)="removeItem(item)">
          <ion-icon name="trash"></ion-icon>
        </ion-button>
      </ion-item>
    </ion-list>
    
    <ng-template #emptyCart>
      <div class="empty-cart">
        <ion-icon name="cart-outline"></ion-icon>
        <p>购物车是空的</p>
        <ion-button routerLink="/tabs/home">去购物</ion-button>
      </div>
    </ng-template>
  </ng-container>
</ion-content>

<ion-footer>
  <ion-toolbar>
    <ion-checkbox slot="start" (ionChange)="selectAll($event.detail.checked)">全选</ion-checkbox>
    
    <ion-title slot="start">
      合计: ¥{{ (cart$ | async)?.totalPrice }}
    </ion-title>
    
    <ion-button slot="end" (click)="checkout()">结算</ion-button>
  </ion-toolbar>
</ion-footer>

五、订单服务 #

5.1 订单服务 #

typescript
// services/order.service.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { ApiService } from './api.service';
import { Order, OrderStatus } from '../models/order.model';

@Injectable({
  providedIn: 'root'
})
export class OrderService {
  
  constructor(private api: ApiService) {}
  
  createOrder(data: any): Observable<Order> {
    return this.api.post<Order>('/orders', data);
  }
  
  getOrders(status?: OrderStatus): Observable<Order[]> {
    const params = status ? { status } : {};
    return this.api.get<Order[]>('/orders', params);
  }
  
  getOrder(id: string): Observable<Order> {
    return this.api.get<Order>(`/orders/${id}`);
  }
  
  cancelOrder(id: string): Observable<void> {
    return this.api.post<void>(`/orders/${id}/cancel`, {});
  }
  
  confirmReceive(id: string): Observable<void> {
    return this.api.post<void>(`/orders/${id}/confirm`, {});
  }
  
  getLogistics(id: string): Observable<any> {
    return this.api.get(`/orders/${id}/logistics`);
  }
}

六、支付集成 #

6.1 支付服务 #

typescript
// services/payment.service.ts
import { Injectable } from '@angular/core';
import { ApiService } from './api.service';

export type PaymentMethod = 'alipay' | 'wechat' | 'balance';

@Injectable({
  providedIn: 'root'
})
export class PaymentService {
  
  constructor(private api: ApiService) {}
  
  async pay(orderId: string, method: PaymentMethod): Promise<boolean> {
    try {
      const result = await this.api.post<any>(`/payment/${method}`, { orderId }).toPromise();
      
      if (method === 'alipay') {
        return this.handleAlipay(result.payUrl);
      } else if (method === 'wechat') {
        return this.handleWechatPay(result);
      }
      
      return true;
    } catch (error) {
      console.error('支付失败:', error);
      return false;
    }
  }
  
  private async handleAlipay(payUrl: string): Promise<boolean> {
    // 调用支付宝支付
    window.location.href = payUrl;
    return true;
  }
  
  private async handleWechatPay(params: any): Promise<boolean> {
    // 调用微信支付
    return true;
  }
}

七、最佳实践 #

7.1 性能优化 #

  • 使用虚拟滚动处理长列表
  • 图片懒加载
  • 路由懒加载
  • 状态管理优化

7.2 安全考虑 #

  • 支付安全
  • 数据加密
  • 接口安全

八、总结 #

8.1 项目要点 #

要点 说明
购物车 本地存储 + 状态管理
订单 API + 状态管理
支付 第三方支付集成
物流 物流跟踪API

8.2 下一步 #

完成了电商应用后,继续学习 社交应用,掌握更多应用开发技巧!

最后更新:2026-03-28