修改商城逻辑
This commit is contained in:
@@ -4,7 +4,123 @@ 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;
|
||||
@@ -73,7 +189,30 @@ router.get('/', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 获取商品分类列表
|
||||
/**
|
||||
* @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(
|
||||
@@ -92,15 +231,45 @@ router.get('/categories', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 获取单个商品详情
|
||||
/**
|
||||
* @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 as points, stock, image_url as image, description, details, status, created_at, updated_at
|
||||
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 = ?
|
||||
WHERE id = ? AND status = 'active'
|
||||
`;
|
||||
|
||||
const [products] = await getDB().execute(query, [id]);
|
||||
@@ -109,16 +278,43 @@ router.get('/:id', async (req, res) => {
|
||||
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.image ? [product.image] : ['/imgs/default-product.png'], // 将单个图片转为数组
|
||||
tags: product.category ? [product.category] : [], // 将分类作为标签
|
||||
sales: Math.floor(Math.random() * 1000) + 100, // 模拟销量数据
|
||||
rating: (Math.random() * 2 + 3).toFixed(1), // 模拟评分 3-5分
|
||||
originalPoints: product.points + Math.floor(Math.random() * 100), // 模拟原价
|
||||
discount: Math.floor(Math.random() * 3 + 7) // 模拟折扣 7-9折
|
||||
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({
|
||||
@@ -134,23 +330,57 @@ router.get('/:id', async (req, res) => {
|
||||
// 创建商品(管理员权限)
|
||||
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;
|
||||
|
||||
const { name, description, price, points_price, stock, category, image_url, details, status = 'active' } = req.body;
|
||||
|
||||
if (!name || !price || !points_price || stock === undefined) {
|
||||
return res.status(400).json({ message: '商品名称、原价、积分价格和库存不能为空' });
|
||||
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, stock, category, image_url, details, status, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
|
||||
[name, description, price, points_price, stock, category || null, image_url, details, status]
|
||||
`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: result.insertId }
|
||||
data: { productId }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建商品错误:', error);
|
||||
@@ -161,9 +391,12 @@ router.post('/', auth, adminAuth, async (req, res) => {
|
||||
// 更新商品(管理员权限)
|
||||
router.put('/:id', auth, adminAuth, async (req, res) => {
|
||||
try {
|
||||
|
||||
const productId = req.params.id;
|
||||
const { name, description, price, points_price, stock, category, image_url, details, status } = req.body;
|
||||
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(
|
||||
@@ -199,6 +432,11 @@ router.put('/:id', auth, adminAuth, async (req, res) => {
|
||||
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);
|
||||
@@ -214,13 +452,38 @@ router.put('/:id', auth, adminAuth, async (req, res) => {
|
||||
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 = ?');
|
||||
updateFields.push('status = ?');
|
||||
updateValues.push(status);
|
||||
}
|
||||
|
||||
@@ -236,6 +499,43 @@ router.put('/:id', auth, adminAuth, async (req, res) => {
|
||||
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: '商品更新成功'
|
||||
@@ -343,51 +643,59 @@ router.get('/stats', auth, adminAuth, async (req, res) => {
|
||||
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 mockReviews = [
|
||||
{
|
||||
id: 1,
|
||||
user: {
|
||||
name: '用户1',
|
||||
avatar: null
|
||||
},
|
||||
rating: 5,
|
||||
content: '商品质量很好,非常满意!',
|
||||
createdAt: '2024-01-15 10:30:00',
|
||||
images: null
|
||||
// 获取评论列表
|
||||
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
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
user: {
|
||||
name: '用户2',
|
||||
avatar: null
|
||||
},
|
||||
rating: 4,
|
||||
content: '性价比不错,值得购买。',
|
||||
createdAt: '2024-01-14 15:20:00',
|
||||
images: null
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
user: {
|
||||
name: '用户3',
|
||||
avatar: null
|
||||
},
|
||||
rating: 5,
|
||||
content: '发货速度快,包装精美。',
|
||||
createdAt: '2024-01-13 09:45:00',
|
||||
images: null
|
||||
}
|
||||
];
|
||||
rating: review.rating,
|
||||
content: review.content,
|
||||
createdAt: review.createdAt,
|
||||
images: review.images ? JSON.parse(review.images) : null
|
||||
}));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
reviews: mockReviews,
|
||||
total: mockReviews.length,
|
||||
averageRating: 4.7
|
||||
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) {
|
||||
@@ -448,4 +756,356 @@ router.get('/:id/recommended', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 收藏商品
|
||||
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;
|
||||
Reference in New Issue
Block a user