安装Node.js 18.0+版本,访问https://nodejs.org下载安装包。安装完成后验证版本:
``` node --version npm --version ```创建项目目录并初始化:
``` mkdir ecommerce-product-page cd ecommerce-product-page npm init -y ```安装项目所需依赖包:
``` npm install express@4.18.2 npm install ejs@3.1.9 npm install axios@1.4.0 npm install multer@1.4.5-lts.1 npm install mysql2@3.5.0 npm install dotenv@16.3.1 ```创建MySQL数据库和商品表:
``` CREATE DATABASE ecommerce_db; USE ecommerce_db; CREATE TABLE products ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, description TEXT NOT NULL, price DECIMAL(10,2) NOT NULL, original_price DECIMAL(10,2), stock INT DEFAULT 0, sku VARCHAR(50) UNIQUE, main_image VARCHAR(255), gallery_images JSON, specifications JSON, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_sku (sku) ); ```在项目根目录创建.env文件:
``` DB_HOST=localhost DB_USER=root DB_PASSWORD=your_password DB_NAME=ecommerce_db PORT=3000 UPLOAD_PATH=./public/uploads ```创建项目目录结构:
``` ecommerce-product-page/ ├── public/ │ ├── css/ │ ├── js/ │ └── uploads/ ├── views/ │ └── product.ejs ├── routes/ │ └── products.js ├── config/ │ └── database.js ├── app.js └── package.json ```创建config/database.js:
``` const mysql = require('mysql2/promise'); require('dotenv').config(); const pool = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, waitForConnections: true, connectionLimit: 10, queueLimit: 0 }); module.exports = pool; ```创建routes/products.js路由文件:
``` const express = require('express'); const router = express.Router(); const pool = require('../config/database'); const multer = require('multer'); const path = require('path'); const storage = multer.diskStorage({ destination: function(req, file, cb) { cb(null, process.env.UPLOAD_PATH); }, filename: function(req, file, cb) { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() 1E9); cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname)); } }); const upload = multer({ storage: storage }); router.post('/upload', upload.array('images', 10), async (req, res) => { try { const { title, description, price, original_price, stock, sku, specifications } = req.body; const mainImage = req.files[0] ? `/uploads/${req.files[0].filename}` : null; const galleryImages = req.files.slice(1).map(file => `/uploads/${file.filename}`); const [result] = await pool.execute( `INSERT INTO products (title, description, price, original_price, stock, sku, main_image, gallery_images, specifications) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [title, description, price, original_price || null, stock, sku, mainImage, JSON.stringify(galleryImages), JSON.stringify(JSON.parse(specifications))] ); res.json({ success: true, productId: result.insertId }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }); ```在products.js中添加:
``` router.get('/:id', async (req, res) => { try { const [rows] = await pool.execute( `SELECT , CASE WHEN original_price IS NOT NULL AND original_price > price THEN ROUND((1 - price/original_price) 100, 0) ELSE NULL END as discount_percentage FROM products WHERE id = ?`, [req.params.id] ); if (rows.length === 0) { return res.status(404).json({ error: 'Product not found' }); } const product = rows[0]; product.gallery_images = JSON.parse(product.gallery_images || '[]'); product.specifications = JSON.parse(product.specifications || '{}'); res.json(product); } catch (error) { res.status(500).json({ error: error.message }); } }); ```创建views/product.ejs:
```创建public/js/product.js:
``` function changeMainImage(imageUrl) { document.getElementById('mainImage').src = imageUrl; } async function addToCart(productId) { try { const response = await fetch('/api/cart/add', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ productId: productId, quantity: 1 }) }); const result = await response.json(); if (result.success) { alert('商品已加入购物车'); } else { alert('加入购物车失败:' + result.error); } } catch (error) { alert('网络错误,请重试'); } } function buyNow(productId) { window.location.href = `/checkout?product=${productId}&quantity=1`; } document.addEventListener('DOMContentLoaded', function() { const stockElement = document.querySelector('.stock-status'); if (stockElement.classList.contains('out-of-stock')) { document.querySelector('.btn-add-cart').disabled = true; document.querySelector('.btn-buy-now').disabled = true; } }); ```创建public/css/product.css:
``` .product-container { display: flex; max-width: 1200px; margin: 0 auto; padding: 20px; gap: 40px; } .product-gallery { flex: 1; } .main-image img { width: 100%; height: auto; border: 1px solid ddd; border-radius: 8px; } .thumbnail-list { display: flex; gap: 10px; margin-top: 15px; overflow-x: auto; } .thumbnail { width: 80px; height: 80px; object-fit: cover; border: 2px solid transparent; border-radius: 4px; cursor: pointer; } .thumbnail:hover { border-color: ff6b6b; } .product-info { flex: 1; } .price-section { margin: 20px 0; } .original-price { text-decoration: line-through; color: 999; margin-right: 10px; } .discount-tag { background-color: ff6b6b; color: white; padding: 2px 8px; border-radius: 4px; font-size: 14px; } .current-price { font-size: 28px; color: e4393c; font-weight: bold; margin-top: 5px; } .sku-section { background-color: f5f5f5; padding: 15px; border-radius: 4px; margin: 20px 0; } .stock-status { display: inline-block; margin-left: 20px; padding: 4px 8px; border-radius: 4px; font-size: 14px; } .in-stock { background-color: d4edda; color: 155724; } .out-of-stock { background-color: f8d7da; color: 721c24; } .specifications ul { list-style: none; padding: 0; } .specifications li { padding: 8px 0; border-bottom: 1px solid eee; } .action-buttons { margin-top: 30px; display: flex; gap: 15px; } .btn-add-cart, .btn-buy-now { padding: 12px 30px; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; flex: 1; } .btn-add-cart { background-color: ff6b6b; color: white; } .btn-buy-now { background-color: e4393c; color: white; } .product-description { max-width: 1200px; margin: 40px auto; padding: 0 20px; } .description-content { line-height: 1.8; color: 333; } ```创建app.js:
``` const express = require('express'); const path = require('path'); require('dotenv').config(); const app = express(); const productRoutes = require('./routes/products'); app.set('view engine', 'ejs'); app.set('views', path.join(__dirname, 'views')); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(express.static(path.join(__dirname, 'public'))); app.use('/api/products', productRoutes); app.get('/product/:id', async (req, res) => { try { const response = await fetch(`http://localhost:${process.env.PORT}/api/products/${req.params.id}`); const product = await response.json(); if (product.error) { return res.status(404).send('商品不存在'); } res.render('product', { product: product }); } catch (error) { res.status(500).send('服务器错误'); } }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`服务器运行在 http://localhost:${PORT}`); }); ```在项目根目录执行:
``` node app.js ```访问 http://localhost:3000/product/1 查看商品详情页。
使用Postman或curl添加测试商品:
``` curl -X POST http://localhost:3000/api/products/upload \ -F "title=测试商品" \ -F "description=这是商品详细描述内容" \ -F "price=99.99" \ -F "original_price=199.99" \ -F "stock=100" \ -F "sku=TEST001" \ -F "specifications={\"颜色\":\"红色\",\"尺寸\":\"M\",\"材质\":\"纯棉\"}" \ -F "images=@/path/to/image1.jpg" \ -F "images=@/path/to/image2.jpg" ```上一篇: 踩过半年电商创业烂泥坑的老司机带普通人啃本地小蛋糕啦
下一篇: 电商大促,别让钱包在购物车里裸奔
易频IT社区是综合性互联网IT技术门户网站,专注分享网络技术、服务器运维、网络安全、编程开发、系统架构、云计算、大数据等行业干货,实时更新IT行业资讯、零基础教程、实战案例,为IT从业者、技术爱好者提供专业的学习交流平台。
Copyright © 2021-2026 易频IT社区. All Rights Reserved. 备案号:闽ICP备2023013482号 网站地图