技术栈选择
- 前端: React 18 + Vite + Tailwind CSS v3 + React Router v6 + Axios
- 后端: Node.js + Express + MongoDB + Mongoose + JWT
- 核心功能: 商品展示、购物车、订单管理、用户系统、后台管理
系统架构
- 用户层: 普通用户(浏览/下单)、管理员(后台管理)
- 前端层: 响应式UI、状态管理、API交互
- 后端层: 业务逻辑、数据处理、权限控制
- 数据层: MongoDB文档数据库
核心功能模块
用户模块
- 注册/登录(JWT认证)
- 个人中心(订单查看、信息修改)
- 权限控制(普通用户/管理员)
商品模块
- 商品列表/详情展示
- 分类筛选/搜索
- 库存管理
购物车模块
- 添加/修改/删除商品
- 批量结算
订单模块
- 订单创建/支付(模拟)
- 订单状态跟踪(待支付/已支付/已发货)
- 订单历史查询
后台管理模块
- 商品CRUD操作
- 订单状态更新
- 用户管理
数据库设计
Users集合
{
_id: ObjectId,
username: String,
password: String (bcrypt加密),
email: String,
phone: String,
role: String (user/admin),
createTime: Date
}
Products集合
{
_id: ObjectId,
name: String,
description: String,
price: Number,
stock: Number,
category: String,
images: Array<String>,
createTime: Date
}
Carts集合
{
_id: ObjectId,
userId: ObjectId,
productId: ObjectId,
quantity: Number,
createTime: Date
}
Orders集合
{
_id: ObjectId,
userId: ObjectId,
products: Array<{productId: ObjectId, name: String, price: Number, quantity: Number}>,
totalPrice: Number,
status: String (pending/payed/shipped/completed),
createTime: Date,
payTime: Date
}
后端实现(关键代码)
数据库连接
// db/connect.js
const mongoose = require('mongoose');
require('dotenv').config();
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI);
console.log('MongoDB connected');
} catch (error) {
console.error('MongoDB connection error:', error);
process.exit(1);
}
};
module.exports = connectDB;
用户注册接口
// routes/auth.js
const express = require('express');
const router = express.Router();
const User = require('../models/User');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
router.post('/register', async (req, res) => {
try {
const { username, password, email } = req.body;
const existingUser = await User.findOne({ username });
if (existingUser) return res.status(400).json({ message: '用户名已存在' });
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = new User({ username, password: hashedPassword, email });
await newUser.save();
res.status(201).json({ message: '注册成功' });
} catch (error) {
res.status(500).json({ message: '服务器错误', error });
}
});
商品列表接口
// routes/products.js
const express = require('express');
const router = express.Router();
const Product = require('../models/Product');
router.get('/', async (req, res) => {
try {
const products = await Product.find();
res.json(products);
} catch (error) {
res.status(500).json({ message: '服务器错误', error });
}
});
前端实现(关键组件)
商品详情页
// pages/ProductDetail.js
import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';
import { useCart } from '../contexts/CartContext';
const ProductDetail = () => {
const { id } = useParams();
const [product, setProduct] = useState(null);
const [quantity, setQuantity] = useState(1);
const { addToCart } = useCart();
useEffect(() => {
axios.get(`/api/products/${id}`).then(res => setProduct(res.data));
}, [id]);
if (!product) return <div className="text-center py-10">Loading...</div>;
return (
<div className="container mx-auto px-4 py-8">
<div className="flex flex-col md:flex-row gap-8">
<div className="w-full md:w-1/2">
<img src={product.images[0]} alt={product.name} className="w-full rounded-lg" />
</div>
<div className="w-full md:w-1/2">
<h1 className="text-3xl font-bold mb-4">{product.name}</h1>
<p className="text-gray-600 mb-6">{product.description}</p>
<p className="text-2xl text-red-500 mb-4">¥{product.price.toFixed(2)}</p>
<div className="flex items-center mb-8">
<button onClick={() => setQuantity(q => Math.max(1, q-1))} className="px-4 py-2 border">-</button>
<span className="px-4 py-2 border-t border-b">{quantity}</span>
<button onClick={() => setQuantity(q => Math.min(product.stock, q+1))} className="px-4 py-2 border">+</button>
</div>
<button
onClick={() => addToCart(product._id, quantity)}
className="bg-blue-500 text-white px-6 py-3 rounded-lg mr-4"
>加入购物车</button>
<button className="bg-red-500 text-white px-6 py-3 rounded-lg">立即购买</button>
</div>
</div>
</div>
);
};
export default ProductDetail;
购物车上下文
// contexts/CartContext.js
import { createContext, useReducer, useContext } from 'react';
import axios from 'axios';
const CartContext = createContext();
const initialState = { items: [], totalQuantity: 0, totalPrice: 0 };
function cartReducer(state, action) {
switch (action.type) {
case 'SET_CART':
return {
items: action.payload.items,
totalQuantity: action.payload.totalQuantity,
totalPrice: action.payload.totalPrice
};
default: return state;
}
}
export function CartProvider({ children }) {
const [state, dispatch] = useReducer(cartReducer, initialState);
const fetchCart = async () => {
const res = await axios.get('/api/carts');
const items = res.data;
const totalQuantity = items.reduce((sum, item) => sum + item.quantity, 0);
const totalPrice = items.reduce((sum, item) => sum + item.product.price * item.quantity, 0);
dispatch({ type: 'SET_CART', payload: { items, totalQuantity, totalPrice } });
};
const addToCart = async (productId, quantity) => {
await axios.post('/api/carts', { productId, quantity });
fetchCart();
};
return (
<CartContext.Provider value={{ ...state, fetchCart, addToCart }}>
{children}
</CartContext.Provider>
);
}
export function useCart() { return useContext(CartContext); }
后台管理系统
商品管理页面
// admin/ProductManage.js
import { useState, useEffect } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
const ProductManage = () => {
const [products, setProducts] = useState([]);
useEffect(() => {
axios.get('/api/admin/products').then(res => setProducts(res.data));
}, []);
const deleteProduct = async (id) => {
if (window.confirm('确定删除?')) {
await axios.delete(`/api/admin/products/${id}`);
setProducts(products.filter(p => p._id !== id));
}
};
return (
<div className="container mx-auto px-4 py-8">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold">商品管理</h1>
<Link to="/admin/products/add" className="bg-green-500 text-white px-4 py-2 rounded-lg">添加商品</Link>
</div>
<table className="w-full border-collapse">
<thead>
<tr className="bg-gray-100">
<th className="border p-2">名称</th>
<th className="border p-2">价格</th>
<th className="border p-2">库存</th>
<th className="border p-2">操作</th>
</tr>
</thead>
<tbody>
{products.map(p => (
<tr key={p._id}>
<td className="border p-2">{p.name}</td>
<td className="border p-2">¥{p.price}</td>
<td className="border p-2">{p.stock}</td>
<td className="border p-2">
<Link to={`/admin/products/edit/${p._id}`} className="text-blue-500 mr-2">编辑</Link>
<button onClick={() => deleteProduct(p._id)} className="text-red-500">删除</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default ProductManage;
部署方案
- 前端: 部署到Vercel/GitHub Pages
- 后端: 部署到Heroku/Vercel Serverless
- 数据库: MongoDB Atlas(云数据库)
核心亮点
- 响应式设计: 适配移动端/桌面端
- 完整流程: 从商品浏览到订单支付的闭环
- 安全可靠: 密码加密、JWT认证、权限控制
- 易扩展: 模块化设计,支持后续功能迭代
该方案可直接用于生产环境,或根据需求进行二次开发,如需完整代码库,可提供GitHub仓库链接。





