Files
jurong_circle_black/routes/products.js
2025-08-28 09:14:56 +08:00

1111 lines
33 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const { getDB } = require('../database');
const { auth, adminAuth } = require('../middleware/auth');
const router = express.Router();
/**
* @swagger
* tags:
* name: Products
* description: 商品管理API
*/
/**
* @swagger
* components:
* schemas:
* Product:
* type: object
* required:
* - name
* - points_price
* - stock
* properties:
* id:
* type: integer
* description: 商品ID
* name:
* type: string
* description: 商品名称
* category:
* type: string
* description: 商品分类
* points_price:
* type: integer
* description: 积分价格
* stock:
* type: integer
* description: 库存数量
* image_url:
* type: string
* description: 商品图片URL
* description:
* type: string
* description: 商品描述
* status:
* type: string
* description: 商品状态
* enum: [active, inactive]
* created_at:
* type: string
* format: date-time
* description: 创建时间
* updated_at:
* type: string
* format: date-time
* description: 更新时间
*/
/**
* @swagger
* /products:
* get:
* summary: 获取商品列表
* tags: [Products]
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* description: 页码
* - in: query
* name: limit
* schema:
* type: integer
* default: 10
* description: 每页数量
* - in: query
* name: search
* schema:
* type: string
* description: 搜索关键词
* - in: query
* name: category
* schema:
* type: string
* description: 商品分类
* - in: query
* name: status
* schema:
* type: string
* enum: [active, inactive]
* description: 商品状态
* responses:
* 200:
* description: 成功获取商品列表
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* products:
* type: array
* items:
* $ref: '#/components/schemas/Product'
* pagination:
* type: object
* properties:
* page:
* type: integer
* limit:
* type: integer
* total:
* type: integer
* pages:
* type: integer
*/
router.get('/', async (req, res) => {
try {
const { page = 1, limit = 10, search = '', category = '', status = '' } = req.query;
// 确保参数为有效数字
const pageNum = Math.max(1, parseInt(page) || 1);
const limitNum = Math.max(1, Math.min(100, parseInt(limit) || 10)); // 限制最大100条
const offset = Math.max(0, (pageNum - 1) * limitNum);
console.log('分页参数:', { pageNum, limitNum, offset, search, category, status });
let whereClause = 'WHERE 1=1';
const params = [];
if (search) {
whereClause += ' AND name LIKE ?';
params.push(`%${search}%`);
}
if (category) {
whereClause += ' AND category = ?';
params.push(category);
}
if (status) {
whereClause += ' AND status = ?';
params.push(status);
} else {
whereClause += ' AND status = "active"';
}
// 获取总数
const countQuery = `SELECT COUNT(*) as total FROM products ${whereClause}`;
const [countResult] = await getDB().execute(countQuery, params);
const total = countResult[0].total;
// 获取商品列表
const query = `
SELECT id, name, category, points_price as points, stock, image_url as image, description, status, created_at, updated_at
FROM products
${whereClause}
ORDER BY created_at DESC
LIMIT ${limitNum} OFFSET ${offset}
`;
// 确保参数数组正确传递
const queryParams = [...params];
console.log('Query params:', queryParams, 'Query:', query);
const [products] = await getDB().execute(query, queryParams);
res.json({
success: true,
data: {
products,
pagination: {
page: pageNum,
limit: limitNum,
total,
pages: Math.ceil(total / limitNum)
}
}
});
} catch (error) {
console.error('获取商品列表失败:', error);
res.status(500).json({ success: false, message: '获取商品列表失败' });
}
});
/**
* @swagger
* /products/categories:
* get:
* summary: 获取商品分类列表
* tags: [Products]
* responses:
* 200:
* description: 成功获取商品分类列表
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* categories:
* type: array
* items:
* type: string
*/
router.get('/categories', async (req, res) => {
try {
const [categories] = await getDB().execute(
'SELECT DISTINCT category FROM products WHERE status = "active" AND category IS NOT NULL'
);
res.json({
success: true,
data: {
categories: categories.map(item => item.category)
}
});
} catch (error) {
console.error('获取商品分类失败:', error);
res.status(500).json({ success: false, message: '获取商品分类失败' });
}
});
/**
* @swagger
* /products/{id}:
* get:
* summary: 获取单个商品详情
* tags: [Products]
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: 商品ID
* responses:
* 200:
* description: 成功获取商品详情
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* $ref: '#/components/schemas/Product'
* 404:
* description: 商品不存在
*/
router.get('/:id', async (req, res) => {
try {
const { id } = req.params;
const userId = req.user?.id; // 可选的用户ID用于检查收藏状态
const query = `
SELECT id, name, category, price, points_price, rongdou_price, stock,
image_url, images, videos, description, details, shop_name, shop_avatar,
payment_methods, sales, rating, status, created_at, updated_at
FROM products
WHERE id = ? AND status = 'active'
`;
const [products] = await getDB().execute(query, [id]);
if (products.length === 0) {
return res.status(404).json({ success: false, message: '商品不存在' });
}
const product = products[0];
// 获取商品规格
const [specifications] = await getDB().execute(
'SELECT * FROM product_specifications WHERE product_id = ? ORDER BY id',
[id]
);
// 获取商品属性
const [attributes] = await getDB().execute(
'SELECT * FROM product_attributes WHERE product_id = ? ORDER BY sort_order, id',
[id]
);
// 检查用户是否收藏了该商品
let isFavorited = false;
if (userId) {
const [favorites] = await getDB().execute(
'SELECT id FROM product_favorites WHERE user_id = ? AND product_id = ?',
[userId, id]
);
isFavorited = favorites.length > 0;
}
// 构建增强的商品数据
const enhancedProduct = {
...product,
images: product.images ? JSON.parse(product.images) : (product.image_url ? [product.image_url] : []),
videos: product.videos ? JSON.parse(product.videos) : [],
payment_methods: product.payment_methods ? JSON.parse(product.payment_methods) : ['points'],
specifications,
attributes,
isFavorited,
// 保持向后兼容
points: product.points_price,
image: product.image_url,
tags: product.category ? [product.category] : []
};
res.json({
success: true,
data: { product: enhancedProduct }
});
} catch (error) {
console.error('获取商品详情失败:', error);
res.status(500).json({ success: false, message: '获取商品详情失败' });
}
});
// 创建商品(管理员权限)
router.post('/', auth, adminAuth, async (req, res) => {
try {
const {
name, description, price, points_price, rongdou_price = 0, stock, category,
image_url, images = [], videos = [], details, status = 'active',
shop_name, shop_avatar, payment_methods = ['points', 'rongdou', 'points_rongdou'],
specifications = [], attributes = []
} = req.body;
if (!name || !price || (!points_price && !rongdou_price) || stock === undefined) {
return res.status(400).json({ message: '商品名称、原价、积分价格或融豆价格、库存不能为空' });
}
const [result] = await getDB().execute(
`INSERT INTO products (name, description, price, points_price, rongdou_price, stock, category,
image_url, images, videos, details, shop_name, shop_avatar, payment_methods, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
[name, description, price, points_price, rongdou_price, stock, category || null,
image_url, JSON.stringify(images), JSON.stringify(videos), details,
shop_name, shop_avatar, JSON.stringify(payment_methods), status]
);
const productId = result.insertId;
// 添加商品规格
if (specifications && specifications.length > 0) {
for (const spec of specifications) {
await getDB().execute(
`INSERT INTO product_specifications (product_id, spec_name, spec_value, price_adjustment,
points_adjustment, rongdou_adjustment, stock, sku_code)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[productId, spec.name, spec.value, spec.price_adjustment || 0,
spec.points_adjustment || 0, spec.rongdou_adjustment || 0,
spec.stock || 0, spec.sku_code || null]
);
}
}
// 添加商品属性
if (attributes && attributes.length > 0) {
for (const attr of attributes) {
await getDB().execute(
`INSERT INTO product_attributes (product_id, attribute_key, attribute_value, sort_order)
VALUES (?, ?, ?, ?)`,
[productId, attr.key, attr.value, attr.sort_order || 0]
);
}
}
res.status(201).json({
success: true,
message: '商品创建成功',
data: { productId }
});
} catch (error) {
console.error('创建商品错误:', error);
res.status(500).json({ message: '创建商品失败' });
}
});
// 更新商品(管理员权限)
router.put('/:id', auth, adminAuth, async (req, res) => {
try {
const productId = req.params.id;
const {
name, description, price, points_price, rongdou_price, stock, category,
image_url, images, videos, details, status, shop_name, shop_avatar, payment_methods,
specifications, attributes
} = req.body;
// 检查商品是否存在
const [products] = await getDB().execute(
'SELECT id FROM products WHERE id = ?',
[productId]
);
if (products.length === 0) {
return res.status(404).json({ message: '商品不存在' });
}
// 构建更新字段
const updateFields = [];
const updateValues = [];
if (name) {
updateFields.push('name = ?');
updateValues.push(name);
}
if (description !== undefined) {
updateFields.push('description = ?');
updateValues.push(description);
}
if (price !== undefined) {
updateFields.push('price = ?');
updateValues.push(price);
}
if (points_price !== undefined) {
updateFields.push('points_price = ?');
updateValues.push(points_price);
}
if (rongdou_price !== undefined) {
updateFields.push('rongdou_price = ?');
updateValues.push(rongdou_price);
}
if (stock !== undefined) {
updateFields.push('stock = ?');
updateValues.push(stock);
}
if (category !== undefined) {
updateFields.push('category = ?');
updateValues.push(category);
}
if (image_url !== undefined) {
updateFields.push('image_url = ?');
updateValues.push(image_url);
}
if (images !== undefined) {
updateFields.push('images = ?');
updateValues.push(JSON.stringify(images || []));
}
if (videos !== undefined) {
updateFields.push('videos = ?');
updateValues.push(JSON.stringify(videos || []));
}
if (details !== undefined) {
updateFields.push('details = ?');
updateValues.push(details);
}
if (shop_name !== undefined) {
updateFields.push('shop_name = ?');
updateValues.push(shop_name);
}
if (shop_avatar !== undefined) {
updateFields.push('shop_avatar = ?');
updateValues.push(shop_avatar);
}
if (payment_methods !== undefined) {
updateFields.push('payment_methods = ?');
updateValues.push(JSON.stringify(payment_methods || []));
}
if (status) {
updateFields.push('status = ?');
updateValues.push(status);
}
if (updateFields.length === 0) {
return res.status(400).json({ message: '没有要更新的字段' });
}
updateFields.push('updated_at = NOW()');
updateValues.push(productId);
await getDB().execute(
`UPDATE products SET ${updateFields.join(', ')} WHERE id = ?`,
updateValues
);
// 更新商品规格
if (specifications !== undefined) {
// 删除原有规格
await getDB().execute('DELETE FROM product_specifications WHERE product_id = ?', [productId]);
// 添加新规格
if (specifications && specifications.length > 0) {
for (const spec of specifications) {
await getDB().execute(
`INSERT INTO product_specifications (product_id, spec_name, spec_value, price_adjustment,
points_adjustment, rongdou_adjustment, stock, sku_code)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[productId, spec.name, spec.value, spec.price_adjustment || 0,
spec.points_adjustment || 0, spec.rongdou_adjustment || 0,
spec.stock || 0, spec.sku_code || null]
);
}
}
}
// 更新商品属性
if (attributes !== undefined) {
// 删除原有属性
await getDB().execute('DELETE FROM product_attributes WHERE product_id = ?', [productId]);
// 添加新属性
if (attributes && attributes.length > 0) {
for (const attr of attributes) {
await getDB().execute(
`INSERT INTO product_attributes (product_id, attribute_key, attribute_value, sort_order)
VALUES (?, ?, ?, ?)`,
[productId, attr.key, attr.value, attr.sort_order || 0]
);
}
}
}
res.json({
success: true,
message: '商品更新成功'
});
} catch (error) {
console.error('更新商品错误:', error);
res.status(500).json({ message: '更新商品失败' });
}
});
// 删除商品(管理员权限)
router.delete('/:id', auth, adminAuth, async (req, res) => {
try {
const { id } = req.params;
// 检查商品是否存在
const checkQuery = 'SELECT id FROM products WHERE id = ?';
const [existing] = await getDB().execute(checkQuery, [id]);
if (existing.length === 0) {
return res.status(404).json({ success: false, message: '商品不存在' });
}
// 检查是否有相关订单
const orderCheckQuery = 'SELECT id FROM orders WHERE product_id = ? LIMIT 1';
const [orders] = await getDB().execute(orderCheckQuery, [id]);
if (orders.length > 0) {
return res.status(400).json({ success: false, message: '该商品存在相关订单,无法删除' });
}
const query = 'DELETE FROM products WHERE id = ?';
await getDB().execute(query, [id]);
res.json({
success: true,
message: '商品删除成功'
});
} catch (error) {
console.error('删除商品失败:', error);
res.status(500).json({ success: false, message: '删除商品失败' });
}
});
// 获取商品统计信息(管理员权限)
router.get('/stats', auth, adminAuth, async (req, res) => {
try {
// 获取商品总数
const totalQuery = 'SELECT COUNT(*) as total FROM products';
const [totalResult] = await getDB().execute(totalQuery);
const totalProducts = totalResult[0].total;
// 获取活跃商品数
const activeQuery = 'SELECT COUNT(*) as total FROM products WHERE status = "active"';
const [activeResult] = await getDB().execute(activeQuery);
const activeProducts = activeResult[0].total;
// 获取库存不足商品数库存小于10
const lowStockQuery = 'SELECT COUNT(*) as total FROM products WHERE stock < 10';
const [lowStockResult] = await getDB().execute(lowStockQuery);
const lowStockProducts = lowStockResult[0].total;
// 获取本月新增商品数
const monthlyQuery = `
SELECT COUNT(*) as total
FROM products
WHERE YEAR(created_at) = YEAR(CURDATE()) AND MONTH(created_at) = MONTH(CURDATE())
`;
const [monthlyResult] = await getDB().execute(monthlyQuery);
const monthlyProducts = monthlyResult[0].total;
// 计算月增长率
const lastMonthQuery = `
SELECT COUNT(*) as total
FROM products
WHERE YEAR(created_at) = YEAR(DATE_SUB(CURDATE(), INTERVAL 1 MONTH))
AND MONTH(created_at) = MONTH(DATE_SUB(CURDATE(), INTERVAL 1 MONTH))
`;
const [lastMonthResult] = await getDB().execute(lastMonthQuery);
const lastMonthProducts = lastMonthResult[0].total;
const monthlyGrowth = lastMonthProducts > 0
? ((monthlyProducts - lastMonthProducts) / lastMonthProducts * 100).toFixed(1)
: 0;
res.json({
success: true,
data: {
stats: {
totalProducts,
activeProducts,
lowStockProducts,
monthlyProducts,
monthlyGrowth: parseFloat(monthlyGrowth)
}
}
});
} catch (error) {
console.error('获取商品统计失败:', error);
res.status(500).json({ success: false, message: '获取商品统计失败' });
}
});
// 获取商品评论
router.get('/:id/reviews', async (req, res) => {
try {
const { id } = req.params;
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const offset = (page - 1) * limit;
// 获取评论列表
const [reviews] = await getDB().execute(
`SELECT pr.id, pr.rating, pr.comment as content, pr.images, pr.created_at as createdAt,
u.username as user_name, u.avatar as user_avatar
FROM product_reviews pr
JOIN users u ON pr.user_id = u.id
WHERE pr.product_id = ?
ORDER BY pr.created_at DESC
LIMIT ? OFFSET ?`,
[id, limit, offset]
);
// 获取评论总数
const [countResult] = await getDB().execute(
'SELECT COUNT(*) as total FROM product_reviews WHERE product_id = ?',
[id]
);
// 计算平均评分
const [avgResult] = await getDB().execute(
'SELECT AVG(rating) as avg_rating FROM product_reviews WHERE product_id = ?',
[id]
);
// 格式化评论数据
const formattedReviews = reviews.map(review => ({
id: review.id,
user: {
name: review.user_name,
avatar: review.user_avatar
},
rating: review.rating,
content: review.content,
createdAt: review.createdAt,
images: review.images ? JSON.parse(review.images) : null
}));
res.json({
success: true,
data: {
reviews: formattedReviews,
total: countResult[0].total,
averageRating: avgResult[0].avg_rating ? parseFloat(avgResult[0].avg_rating).toFixed(1) : 0,
pagination: {
page,
limit,
total: countResult[0].total,
totalPages: Math.ceil(countResult[0].total / limit)
}
}
});
} catch (error) {
console.error('获取商品评论失败:', error);
res.status(500).json({ success: false, message: '获取商品评论失败' });
}
});
// 获取推荐商品
router.get('/:id/recommended', async (req, res) => {
try {
const id = parseInt(req.params.id);
// 获取同类别的其他商品作为推荐
const query = `
SELECT p2.id, p2.name, p2.category, p2.price, p2.points_price as points,
p2.stock, p2.image_url as image, p2.description
FROM products p1
JOIN products p2 ON p1.category = p2.category
WHERE p1.id = ? AND p2.id != ? AND p2.status = 'active'
ORDER BY RAND()
LIMIT 6
`;
const [recommendedProducts] = await getDB().execute(query, [id, id]);
// 如果同类别商品不足,补充其他热门商品
if (recommendedProducts.length < 6) {
const remainingCount = 6 - recommendedProducts.length;
if (remainingCount > 0) {
const additionalQuery = `
SELECT id, name, category, price, points_price as points,
stock, image_url as image, description
FROM products
WHERE id != ? AND status = 'active'
ORDER BY RAND()
LIMIT ${remainingCount}
`;
const [additionalProducts] = await getDB().execute(
additionalQuery,
[id]
);
recommendedProducts.push(...additionalProducts);
}
}
res.json({
success: true,
data: {
products: recommendedProducts
}
});
} catch (error) {
console.error('获取推荐商品失败:', error);
res.status(500).json({ success: false, message: '获取推荐商品失败' });
}
});
// 收藏商品
router.post('/:id/favorite', auth, async (req, res) => {
try {
const productId = req.params.id;
const userId = req.user.id;
// 检查商品是否存在
const [products] = await getDB().execute('SELECT id FROM products WHERE id = ?', [productId]);
if (products.length === 0) {
return res.status(404).json({ message: '商品不存在' });
}
// 检查是否已收藏
const [existing] = await getDB().execute(
'SELECT id FROM product_favorites WHERE user_id = ? AND product_id = ?',
[userId, productId]
);
if (existing.length > 0) {
return res.status(400).json({ message: '商品已收藏' });
}
await getDB().execute(
'INSERT INTO product_favorites (user_id, product_id, created_at) VALUES (?, ?, NOW())',
[userId, productId]
);
res.json({
success: true,
message: '收藏成功'
});
} catch (error) {
console.error('收藏商品错误:', error);
res.status(500).json({ message: '收藏失败' });
}
});
// 取消收藏商品
router.delete('/:id/favorite', auth, async (req, res) => {
try {
const productId = req.params.id;
const userId = req.user.id;
const [result] = await getDB().execute(
'DELETE FROM product_favorites WHERE user_id = ? AND product_id = ?',
[userId, productId]
);
if (result.affectedRows === 0) {
return res.status(404).json({ message: '未收藏该商品' });
}
res.json({
success: true,
message: '取消收藏成功'
});
} catch (error) {
console.error('取消收藏错误:', error);
res.status(500).json({ message: '取消收藏失败' });
}
});
// 获取用户收藏的商品列表
router.get('/favorites', auth, async (req, res) => {
try {
const userId = req.user.id;
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const offset = (page - 1) * limit;
const [favorites] = await getDB().execute(
`SELECT p.*, pf.created_at as favorite_time
FROM product_favorites pf
JOIN products p ON pf.product_id = p.id
WHERE pf.user_id = ? AND p.status = 'active'
ORDER BY pf.created_at DESC
LIMIT ? OFFSET ?`,
[userId, limit, offset]
);
const [countResult] = await getDB().execute(
`SELECT COUNT(*) as total
FROM product_favorites pf
JOIN products p ON pf.product_id = p.id
WHERE pf.user_id = ? AND p.status = 'active'`,
[userId]
);
res.json({
success: true,
data: {
products: favorites.map(product => ({
...product,
images: product.images ? JSON.parse(product.images) : [],
videos: product.videos ? JSON.parse(product.videos) : [],
payment_methods: product.payment_methods ? JSON.parse(product.payment_methods) : []
})),
pagination: {
page,
limit,
total: countResult[0].total,
totalPages: Math.ceil(countResult[0].total / limit)
}
}
});
} catch (error) {
console.error('获取收藏列表错误:', error);
res.status(500).json({ message: '获取收藏列表失败' });
}
});
// 获取商品规格
router.get('/:id/specifications', async (req, res) => {
try {
const productId = req.params.id;
const [specifications] = await getDB().execute(
'SELECT id, spec_name as name, spec_value as value, price_adjustment, points_adjustment, rongdou_adjustment, stock, sku_code, created_at, updated_at FROM product_specifications WHERE product_id = ? ORDER BY id',
[productId]
);
res.json({
success: true,
data: specifications
});
} catch (error) {
console.error('获取商品规格错误:', error);
res.status(500).json({ message: '获取商品规格失败' });
}
});
// 创建商品规格(管理员权限)
router.post('/:id/specifications', auth, adminAuth, async (req, res) => {
try {
const productId = req.params.id;
const { name, value, price_adjustment = 0, points_adjustment = 0, rongdou_adjustment = 0, stock = 0, sku_code } = req.body;
if (!name || !value) {
return res.status(400).json({ message: '规格名称和规格值不能为空' });
}
// 检查商品是否存在
const [products] = await getDB().execute('SELECT id FROM products WHERE id = ?', [productId]);
if (products.length === 0) {
return res.status(404).json({ message: '商品不存在' });
}
const [result] = await getDB().execute(
`INSERT INTO product_specifications (product_id, spec_name, spec_value, price_adjustment,
points_adjustment, rongdou_adjustment, stock, sku_code, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
[productId, name, value, price_adjustment, points_adjustment, rongdou_adjustment, stock, sku_code || null]
);
res.status(201).json({
success: true,
message: '规格创建成功',
data: { id: result.insertId }
});
} catch (error) {
console.error('创建商品规格错误:', error);
res.status(500).json({ message: '创建商品规格失败' });
}
});
// 更新商品规格(管理员权限)
router.put('/:id/specifications/:specId', auth, adminAuth, async (req, res) => {
try {
const { id: productId, specId } = req.params;
const { name, value, price_adjustment, points_adjustment, rongdou_adjustment, stock, sku_code } = req.body;
// 检查规格是否存在
const [specs] = await getDB().execute(
'SELECT id FROM product_specifications WHERE id = ? AND product_id = ?',
[specId, productId]
);
if (specs.length === 0) {
return res.status(404).json({ message: '规格不存在' });
}
// 构建更新字段
const updateFields = [];
const updateValues = [];
if (name !== undefined) {
updateFields.push('spec_name = ?');
updateValues.push(name);
}
if (value !== undefined) {
updateFields.push('spec_value = ?');
updateValues.push(value);
}
if (price_adjustment !== undefined) {
updateFields.push('price_adjustment = ?');
updateValues.push(price_adjustment);
}
if (points_adjustment !== undefined) {
updateFields.push('points_adjustment = ?');
updateValues.push(points_adjustment);
}
if (rongdou_adjustment !== undefined) {
updateFields.push('rongdou_adjustment = ?');
updateValues.push(rongdou_adjustment);
}
if (stock !== undefined) {
updateFields.push('stock = ?');
updateValues.push(stock);
}
if (sku_code !== undefined) {
updateFields.push('sku_code = ?');
updateValues.push(sku_code);
}
if (updateFields.length === 0) {
return res.status(400).json({ message: '没有提供要更新的字段' });
}
updateFields.push('updated_at = NOW()');
updateValues.push(specId);
await getDB().execute(
`UPDATE product_specifications SET ${updateFields.join(', ')} WHERE id = ?`,
updateValues
);
res.json({
success: true,
message: '规格更新成功'
});
} catch (error) {
console.error('更新商品规格错误:', error);
res.status(500).json({ message: '更新商品规格失败' });
}
});
// 删除商品规格(管理员权限)
router.delete('/:id/specifications/:specId', auth, adminAuth, async (req, res) => {
try {
const { id: productId, specId } = req.params;
// 检查规格是否存在
const [specs] = await getDB().execute(
'SELECT id FROM product_specifications WHERE id = ? AND product_id = ?',
[specId, productId]
);
if (specs.length === 0) {
return res.status(404).json({ message: '规格不存在' });
}
await getDB().execute('DELETE FROM product_specifications WHERE id = ?', [specId]);
res.json({
success: true,
message: '规格删除成功'
});
} catch (error) {
console.error('删除商品规格错误:', error);
res.status(500).json({ message: '删除商品规格失败' });
}
});
// 获取商品属性
router.get('/:id/attributes', async (req, res) => {
try {
const productId = req.params.id;
const [attributes] = await getDB().execute(
'SELECT * FROM product_attributes WHERE product_id = ? ORDER BY sort_order, id',
[productId]
);
res.json({
success: true,
data: attributes
});
} catch (error) {
console.error('获取商品属性错误:', error);
res.status(500).json({ message: '获取商品属性失败' });
}
});
// 创建商品评论
router.post('/:id/reviews', auth, async (req, res) => {
try {
const productId = req.params.id;
const userId = req.user.id;
const { orderId, rating, comment, images = [] } = req.body;
// 验证必填字段
if (!orderId || !rating || rating < 1 || rating > 5) {
return res.status(400).json({ message: '订单ID和评分(1-5)不能为空' });
}
// 检查订单是否存在且属于当前用户
const [orders] = await getDB().execute(
`SELECT o.id FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE o.id = ? AND o.user_id = ? AND oi.product_id = ? AND o.status = 'delivered'`,
[orderId, userId, productId]
);
if (orders.length === 0) {
return res.status(400).json({ message: '只能评价已完成的订单商品' });
}
// 检查是否已经评价过
const [existingReviews] = await getDB().execute(
'SELECT id FROM product_reviews WHERE product_id = ? AND user_id = ? AND order_id = ?',
[productId, userId, orderId]
);
if (existingReviews.length > 0) {
return res.status(400).json({ message: '该商品已评价过' });
}
// 创建评论
const [result] = await getDB().execute(
`INSERT INTO product_reviews (product_id, user_id, order_id, rating, comment, images, created_at)
VALUES (?, ?, ?, ?, ?, ?, NOW())`,
[productId, userId, orderId, rating, comment, JSON.stringify(images)]
);
// 更新商品平均评分
const [avgResult] = await getDB().execute(
'SELECT AVG(rating) as avg_rating FROM product_reviews WHERE product_id = ?',
[productId]
);
await getDB().execute(
'UPDATE products SET rating = ? WHERE id = ?',
[parseFloat(avgResult[0].avg_rating).toFixed(2), productId]
);
res.status(201).json({
success: true,
message: '评价成功',
data: { reviewId: result.insertId }
});
} catch (error) {
console.error('创建商品评论错误:', error);
res.status(500).json({ message: '评价失败' });
}
});
module.exports = router;