电商应用实战 #

一、项目概述 #

1.1 功能需求 #

模块 功能
首页 轮播图、分类、推荐商品
商品 商品列表、详情、搜索
购物车 添加、修改、删除商品
订单 下单、支付、订单列表
用户 登录、注册、个人中心

1.2 技术栈 #

  • 前端框架: React + TypeScript
  • 状态管理: Zustand
  • UI组件: Tailwind CSS
  • 原生功能: Capacitor
  • 构建工具: Vite

二、项目初始化 #

2.1 创建项目 #

bash
# 创建Vite项目
npm create vite@latest ecommerce-app -- --template react-ts

cd ecommerce-app

# 安装依赖
npm install

# 安装Capacitor
npm install @capacitor/core @capacitor/cli
npx cap init "电商应用" "com.example.ecommerce"

# 安装Capacitor插件
npm install @capacitor/preferences
npm install @capacitor/camera
npm install @capacitor/network
npm install @capacitor/status-bar
npm install @capacitor/splash-screen
npm install @capacitor/keyboard

# 安装其他依赖
npm install zustand
npm install react-router-dom
npm install axios
npm install tailwindcss postcss autoprefixer

2.2 项目结构 #

text
src/
├── api/                    # API请求
│   ├── index.ts
│   ├── products.ts
│   ├── orders.ts
│   └── auth.ts
├── components/             # 公共组件
│   ├── Header/
│   ├── Footer/
│   ├── ProductCard/
│   └── Loading/
├── hooks/                  # 自定义Hooks
│   ├── useCart.ts
│   ├── useAuth.ts
│   └── useNetwork.ts
├── pages/                  # 页面组件
│   ├── Home/
│   ├── Products/
│   ├── ProductDetail/
│   ├── Cart/
│   ├── Orders/
│   └── Profile/
├── store/                  # 状态管理
│   ├── cartStore.ts
│   ├── authStore.ts
│   └── productStore.ts
├── types/                  # 类型定义
│   └── index.ts
├── utils/                  # 工具函数
│   ├── storage.ts
│   └── format.ts
├── App.tsx
└── main.tsx

三、状态管理 #

3.1 购物车Store #

typescript
// src/store/cartStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { Preferences } from '@capacitor/preferences';

interface CartItem {
    id: string;
    productId: string;
    name: string;
    price: number;
    quantity: number;
    image: string;
}

interface CartStore {
    items: CartItem[];
    addItem: (item: Omit<CartItem, 'id'>) => void;
    removeItem: (id: string) => void;
    updateQuantity: (id: string, quantity: number) => void;
    clearCart: () => void;
    getTotal: () => number;
    getItemCount: () => number;
}

// Capacitor Preferences存储适配器
const capacitorStorage = {
    getItem: async (name: string): Promise<string | null> => {
        const { value } = await Preferences.get({ key: name });
        return value;
    },
    setItem: async (name: string, value: string): Promise<void> => {
        await Preferences.set({ key: name, value });
    },
    removeItem: async (name: string): Promise<void> => {
        await Preferences.remove({ key: name });
    }
};

export const useCartStore = create<CartStore>()(
    persist(
        (set, get) => ({
            items: [],
            
            addItem: (item) => {
                const items = get().items;
                const existingIndex = items.findIndex(i => i.productId === item.productId);
                
                if (existingIndex > -1) {
                    const newItems = [...items];
                    newItems[existingIndex].quantity += item.quantity;
                    set({ items: newItems });
                } else {
                    set({
                        items: [...items, { ...item, id: Date.now().toString() }]
                    });
                }
            },
            
            removeItem: (id) => {
                set({ items: get().items.filter(item => item.id !== id) });
            },
            
            updateQuantity: (id, quantity) => {
                set({
                    items: get().items.map(item =>
                        item.id === id ? { ...item, quantity } : item
                    )
                });
            },
            
            clearCart: () => set({ items: [] }),
            
            getTotal: () => {
                return get().items.reduce(
                    (total, item) => total + item.price * item.quantity,
                    0
                );
            },
            
            getItemCount: () => {
                return get().items.reduce((count, item) => count + item.quantity, 0);
            }
        }),
        {
            name: 'cart-storage',
            storage: createJSONStorage(() => capacitorStorage)
        }
    )
);

3.2 用户认证Store #

typescript
// src/store/authStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { Preferences } from '@capacitor/preferences';

interface User {
    id: string;
    name: string;
    email: string;
    avatar?: string;
}

interface AuthStore {
    user: User | null;
    token: string | null;
    isAuthenticated: boolean;
    login: (user: User, token: string) => void;
    logout: () => void;
    updateUser: (user: Partial<User>) => void;
}

const capacitorStorage = {
    getItem: async (name: string): Promise<string | null> => {
        const { value } = await Preferences.get({ key: name });
        return value;
    },
    setItem: async (name: string, value: string): Promise<void> => {
        await Preferences.set({ key: name, value });
    },
    removeItem: async (name: string): Promise<void> => {
        await Preferences.remove({ key: name });
    }
};

export const useAuthStore = create<AuthStore>()(
    persist(
        (set) => ({
            user: null,
            token: null,
            isAuthenticated: false,
            
            login: (user, token) => {
                set({
                    user,
                    token,
                    isAuthenticated: true
                });
            },
            
            logout: () => {
                set({
                    user: null,
                    token: null,
                    isAuthenticated: false
                });
            },
            
            updateUser: (userData) => {
                set(state => ({
                    user: state.user ? { ...state.user, ...userData } : null
                }));
            }
        }),
        {
            name: 'auth-storage',
            storage: createJSONStorage(() => capacitorStorage)
        }
    )
);

四、核心页面实现 #

4.1 首页 #

tsx
// src/pages/Home/index.tsx
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { api } from '../../api';
import ProductCard from '../../components/ProductCard';

interface Banner {
    id: string;
    image: string;
    link: string;
}

interface Category {
    id: string;
    name: string;
    icon: string;
}

interface Product {
    id: string;
    name: string;
    price: number;
    image: string;
    originalPrice?: number;
}

export default function Home() {
    const [banners, setBanners] = useState<Banner[]>([]);
    const [categories, setCategories] = useState<Category[]>([]);
    const [products, setProducts] = useState<Product[]>([]);
    const [loading, setLoading] = useState(true);
    
    useEffect(() => {
        loadData();
    }, []);
    
    async function loadData() {
        try {
            const [bannersRes, categoriesRes, productsRes] = await Promise.all([
                api.get<Banner[]>('/banners'),
                api.get<Category[]>('/categories'),
                api.get<Product[]>('/products/recommend')
            ]);
            
            setBanners(bannersRes.data);
            setCategories(categoriesRes.data);
            setProducts(productsRes.data);
        } catch (error) {
            console.error('Failed to load home data:', error);
        } finally {
            setLoading(false);
        }
    }
    
    if (loading) {
        return <div className="loading">加载中...</div>;
    }
    
    return (
        <div className="home">
            {/* 轮播图 */}
            <div className="banner-container">
                {banners.map(banner => (
                    <Link key={banner.id} to={banner.link}>
                        <img src={banner.image} alt="" />
                    </Link>
                ))}
            </div>
            
            {/* 分类 */}
            <div className="categories">
                {categories.map(category => (
                    <Link
                        key={category.id}
                        to={`/products?category=${category.id}`}
                        className="category-item"
                    >
                        <span className="icon">{category.icon}</span>
                        <span className="name">{category.name}</span>
                    </Link>
                ))}
            </div>
            
            {/* 推荐商品 */}
            <div className="section">
                <h2>热门推荐</h2>
                <div className="product-grid">
                    {products.map(product => (
                        <ProductCard key={product.id} product={product} />
                    ))}
                </div>
            </div>
        </div>
    );
}

4.2 购物车页面 #

tsx
// src/pages/Cart/index.tsx
import { Link } from 'react-router-dom';
import { useCartStore } from '../../store/cartStore';
import { useAuthStore } from '../../store/authStore';
import { formatPrice } from '../../utils/format';

export default function Cart() {
    const { items, removeItem, updateQuantity, getTotal, clearCart } = useCartStore();
    const { isAuthenticated } = useAuthStore();
    
    async function handleCheckout() {
        if (!isAuthenticated) {
            // 跳转登录
            window.location.href = '/login?redirect=/checkout';
            return;
        }
        
        // 跳转结算页
        window.location.href = '/checkout';
    }
    
    if (items.length === 0) {
        return (
            <div className="cart-empty">
                <p>购物车是空的</p>
                <Link to="/products">去购物</Link>
            </div>
        );
    }
    
    return (
        <div className="cart">
            <div className="cart-items">
                {items.map(item => (
                    <div key={item.id} className="cart-item">
                        <img src={item.image} alt={item.name} />
                        <div className="item-info">
                            <h3>{item.name}</h3>
                            <p className="price">{formatPrice(item.price)}</p>
                            <div className="quantity-control">
                                <button
                                    onClick={() => updateQuantity(item.id, Math.max(1, item.quantity - 1))}
                                >
                                    -
                                </button>
                                <span>{item.quantity}</span>
                                <button
                                    onClick={() => updateQuantity(item.id, item.quantity + 1)}
                                >
                                    +
                                </button>
                            </div>
                        </div>
                        <button
                            className="remove-btn"
                            onClick={() => removeItem(item.id)}
                        >
                            删除
                        </button>
                    </div>
                ))}
            </div>
            
            <div className="cart-footer">
                <div className="total">
                    <span>合计:</span>
                    <span className="amount">{formatPrice(getTotal())}</span>
                </div>
                <button className="checkout-btn" onClick={handleCheckout}>
                    结算
                </button>
            </div>
        </div>
    );
}

4.3 商品详情页 #

tsx
// src/pages/ProductDetail/index.tsx
import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { api } from '../../api';
import { useCartStore } from '../../store/cartStore';
import { formatPrice } from '../../utils/format';

interface Product {
    id: string;
    name: string;
    price: number;
    originalPrice?: number;
    description: string;
    images: string[];
    specs: { name: string; value: string }[];
    stock: number;
}

export default function ProductDetail() {
    const { id } = useParams<{ id: string }>();
    const [product, setProduct] = useState<Product | null>(null);
    const [loading, setLoading] = useState(true);
    const [quantity, setQuantity] = useState(1);
    const [showToast, setShowToast] = useState(false);
    
    const addItem = useCartStore(state => state.addItem);
    
    useEffect(() => {
        loadProduct();
    }, [id]);
    
    async function loadProduct() {
        try {
            const response = await api.get<Product>(`/products/${id}`);
            setProduct(response.data);
        } catch (error) {
            console.error('Failed to load product:', error);
        } finally {
            setLoading(false);
        }
    }
    
    function handleAddToCart() {
        if (!product) return;
        
        addItem({
            productId: product.id,
            name: product.name,
            price: product.price,
            quantity,
            image: product.images[0]
        });
        
        setShowToast(true);
        setTimeout(() => setShowToast(false), 2000);
    }
    
    if (loading) {
        return <div className="loading">加载中...</div>;
    }
    
    if (!product) {
        return <div className="error">商品不存在</div>;
    }
    
    return (
        <div className="product-detail">
            {/* 商品图片 */}
            <div className="product-images">
                <img src={product.images[0]} alt={product.name} />
            </div>
            
            {/* 商品信息 */}
            <div className="product-info">
                <h1>{product.name}</h1>
                <div className="price-row">
                    <span className="price">{formatPrice(product.price)}</span>
                    {product.originalPrice && (
                        <span className="original-price">
                            {formatPrice(product.originalPrice)}
                        </span>
                    )}
                </div>
            </div>
            
            {/* 商品规格 */}
            <div className="product-specs">
                {product.specs.map((spec, index) => (
                    <div key={index} className="spec-item">
                        <span className="name">{spec.name}</span>
                        <span className="value">{spec.value}</span>
                    </div>
                ))}
            </div>
            
            {/* 商品描述 */}
            <div className="product-description">
                <h2>商品详情</h2>
                <p>{product.description}</p>
            </div>
            
            {/* 底部操作栏 */}
            <div className="bottom-bar">
                <div className="quantity-selector">
                    <button onClick={() => setQuantity(Math.max(1, quantity - 1))}>-</button>
                    <span>{quantity}</span>
                    <button onClick={() => setQuantity(Math.min(product.stock, quantity + 1))}>+</button>
                </div>
                <button className="add-cart-btn" onClick={handleAddToCart}>
                    加入购物车
                </button>
                <button className="buy-btn">
                    立即购买
                </button>
            </div>
            
            {/* Toast提示 */}
            {showToast && (
                <div className="toast">已添加到购物车</div>
            )}
        </div>
    );
}

五、原生功能集成 #

5.1 网络状态监听 #

typescript
// src/hooks/useNetwork.ts
import { useState, useEffect } from 'react';
import { Network, ConnectionStatus } from '@capacitor/network';

export function useNetwork() {
    const [status, setStatus] = useState<ConnectionStatus>({
        connected: true,
        connectionType: 'unknown'
    });
    
    useEffect(() => {
        // 获取初始状态
        Network.getStatus().then(setStatus);
        
        // 监听变化
        Network.addListener('networkStatusChange', setStatus);
        
        return () => {
            Network.removeAllListeners();
        };
    }, []);
    
    return {
        isConnected: status.connected,
        connectionType: status.connectionType
    };
}

5.2 应用初始化 #

typescript
// src/App.tsx
import { useEffect } from 'react';
import { StatusBar, Style } from '@capacitor/status-bar';
import { SplashScreen } from '@capacitor/splash-screen';
import { Keyboard } from '@capacitor/keyboard';
import { Capacitor } from '@capacitor/core';

export default function App() {
    useEffect(() => {
        initApp();
    }, []);
    
    async function initApp() {
        if (Capacitor.isNativePlatform()) {
            // 配置状态栏
            await StatusBar.setStyle({ style: Style.Dark });
            await StatusBar.setBackgroundColor({ color: '#ffffff' });
            
            // 隐藏启动画面
            await SplashScreen.hide({
                fadeOutDuration: 500
            });
            
            // 键盘配置
            Keyboard.addListener('keyboardWillShow', () => {
                document.body.classList.add('keyboard-open');
            });
            
            Keyboard.addListener('keyboardWillHide', () => {
                document.body.classList.remove('keyboard-open');
            });
        }
    }
    
    return (
        // 应用内容
    );
}

六、总结 #

6.1 项目要点 #

模块 技术要点
状态管理 Zustand + 持久化
路由导航 React Router
原生功能 Capacitor插件
数据存储 Preferences

6.2 下一步 #

了解电商应用后,让我们学习 社交应用

最后更新:2026-03-28