电商应用实战 #
一、项目概述 #
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