升级商城逻辑

This commit is contained in:
2025-09-02 09:29:20 +08:00
parent 16bfc525c2
commit 49eed40ad0
30 changed files with 22710 additions and 1339 deletions

View File

@@ -100,16 +100,20 @@ const { auth } = require('../middleware/auth');
router.get('/', auth, async (req, res) => {
try {
const userId = req.user.id;
const [addresses] = await getDB().execute(
`SELECT ua.*, al.name as label_name, al.color as label_color
`SELECT ua.*, al.name as label_name, al.color as label_color,
p.name as province_name, c.name as city_name, d.name as district_name
FROM user_addresses ua
LEFT JOIN address_labels al ON ua.label_id = al.id
WHERE ua.user_id = ? AND ua.deleted_at IS NULL
LEFT JOIN address_labels al ON ua.label = al.id
LEFT JOIN china_regions p ON ua.province = p.code
LEFT JOIN china_regions c ON ua.city = c.code
LEFT JOIN china_regions d ON ua.district = d.code
WHERE ua.user_id = ?
ORDER BY ua.is_default DESC, ua.created_at DESC`,
[userId]
);
res.json({
success: true,
data: addresses
@@ -158,7 +162,7 @@ router.get('/:id', auth, async (req, res) => {
try {
const addressId = req.params.id;
const userId = req.user.id;
const [addresses] = await getDB().execute(
`SELECT ua.*, al.name as label_name, al.color as label_color
FROM user_addresses ua
@@ -166,11 +170,11 @@ router.get('/:id', auth, async (req, res) => {
WHERE ua.id = ? AND ua.user_id = ? AND ua.deleted_at IS NULL`,
[addressId, userId]
);
if (addresses.length === 0) {
return res.status(404).json({ message: '收货地址不存在' });
}
res.json({
success: true,
data: addresses[0]
@@ -259,41 +263,36 @@ router.post('/', auth, async (req, res) => {
recipient_name,
phone,
province_code,
province_name,
city_code,
city_name,
district_code,
district_name,
detailed_address,
postal_code,
label_id,
is_default = false
} = req.body;
// 验证必填字段
if (!recipient_name || !phone || !province_code || !city_code || !district_code || !detailed_address) {
return res.status(400).json({ message: '收件人姓名、电话、省市区和详细地址不能为空' });
}
// 如果设置为默认地址,先取消其他默认地址
if (is_default) {
await getDB().execute(
'UPDATE user_addresses SET is_default = false WHERE user_id = ? AND deleted_at IS NULL',
'UPDATE user_addresses SET is_default = false WHERE user_id = ? ',
[userId]
);
}
const [result] = await getDB().execute(
`INSERT INTO user_addresses (
user_id, recipient_name, phone, province_code, province_name, city_code, city_name,
district_code, district_name, detailed_address, postal_code, label_id, is_default, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
user_id, receiver_name, receiver_phone, province, city,
district, detailed_address, is_default, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
[
userId, recipient_name, phone, province_code, province_name, city_code, city_name,
district_code, district_name, detailed_address, postal_code, label_id, is_default
userId, recipient_name, phone, province_code, city_code,
district_code, detailed_address, is_default
]
);
res.status(201).json({
success: true,
message: '收货地址创建成功',
@@ -383,48 +382,45 @@ router.put('/:id', auth, async (req, res) => {
recipient_name,
phone,
province_code,
province_name,
city_code,
city_name,
district_code,
district_name,
detailed_address,
postal_code,
label_id,
is_default
} = req.body;
if (!recipient_name || !phone || !province_code || !city_code || !district_code || !detailed_address) {
return res.status(400).json({ message: '收件人姓名、电话、省市区和详细地址不能为空' });
}
// 检查地址是否存在且属于当前用户
const [existing] = await getDB().execute(
'SELECT id FROM user_addresses WHERE id = ? AND user_id = ? AND deleted_at IS NULL',
'SELECT id FROM user_addresses WHERE id = ? AND user_id = ? ',
[addressId, userId]
);
if (existing.length === 0) {
return res.status(404).json({ message: '收货地址不存在' });
}
// 如果设置为默认地址,先取消其他默认地址
if (is_default) {
await getDB().execute(
'UPDATE user_addresses SET is_default = false WHERE user_id = ? AND id != ? AND deleted_at IS NULL',
'UPDATE user_addresses SET is_default = false WHERE user_id = ? AND id != ? ',
[userId, addressId]
);
}
const [result] = await getDB().execute(
`UPDATE user_addresses SET
recipient_name = ?, phone = ?, province_code = ?, province_name = ?,
city_code = ?, city_name = ?, district_code = ?, district_name = ?,
detailed_address = ?, postal_code = ?, label_id = ?, is_default = ?, updated_at = NOW()
receiver_name = ?, receiver_phone = ?, province = ?, city = ?,
district = ?, detailed_address = ?, is_default = ?, updated_at = NOW()
WHERE id = ? AND user_id = ?`,
[
recipient_name, phone, province_code, province_name, city_code, city_name,
district_code, district_name, detailed_address, postal_code, label_id, is_default,
recipient_name, phone, province_code, city_code,
district_code, detailed_address, is_default,
addressId, userId
]
);
res.json({
success: true,
message: '收货地址更新成功'
@@ -439,7 +435,7 @@ router.put('/:id', auth, async (req, res) => {
* @swagger
* /addresses/{id}:
* delete:
* summary: 删除收货地址(软删除)
* summary: 删除收货地址
* tags: [Addresses]
* security:
* - bearerAuth: []
@@ -475,16 +471,16 @@ router.delete('/:id', auth, async (req, res) => {
try {
const addressId = req.params.id;
const userId = req.user.id;
const [result] = await getDB().execute(
'UPDATE user_addresses SET deleted_at = NOW() WHERE id = ? AND user_id = ? AND deleted_at IS NULL',
'DELETE FROM user_addresses WHERE id = ? AND user_id = ?',
[addressId, userId]
);
if (result.affectedRows === 0) {
return res.status(404).json({ message: '收货地址不存在' });
}
res.json({
success: true,
message: '收货地址删除成功'
@@ -535,29 +531,29 @@ router.put('/:id/default', auth, async (req, res) => {
try {
const addressId = req.params.id;
const userId = req.user.id;
// 检查地址是否存在且属于当前用户
const [existing] = await getDB().execute(
'SELECT id FROM user_addresses WHERE id = ? AND user_id = ? AND deleted_at IS NULL',
[addressId, userId]
);
if (existing.length === 0) {
return res.status(404).json({ message: '收货地址不存在' });
}
// 取消其他默认地址
await getDB().execute(
'UPDATE user_addresses SET is_default = false WHERE user_id = ? AND deleted_at IS NULL',
[userId]
);
// 设置当前地址为默认
await getDB().execute(
'UPDATE user_addresses SET is_default = true WHERE id = ? AND user_id = ?',
[addressId, userId]
);
res.json({
success: true,
message: '默认地址设置成功'

935
routes/cart.js Normal file
View File

@@ -0,0 +1,935 @@
const express = require('express');
const { getDB } = require('../database');
const { auth } = require('../middleware/auth');
const router = express.Router();
/**
* @swagger
* tags:
* name: Cart
* description: 购物车管理相关接口
*/
/**
* @swagger
* components:
* schemas:
* CartItem:
* type: object
* properties:
* id:
* type: integer
* description: 购物车项ID
* user_id:
* type: integer
* description: 用户ID
* product_id:
* type: integer
* description: 商品ID
* quantity:
* type: integer
* description: 商品数量
* spec_combination_id:
* type: integer
* description: 商品规格组合ID
* created_at:
* type: string
* format: date-time
* description: 创建时间
* updated_at:
* type: string
* format: date-time
* description: 更新时间
* product:
* type: object
* properties:
* id:
* type: integer
* name:
* type: string
* price:
* type: integer
* points_price:
* type: integer
* rongdou_price:
* type: integer
* image_url:
* type: string
* stock:
* type: integer
* status:
* type: string
*/
/**
* @swagger
* /api/cart:
* get:
* summary: 获取购物车列表
* tags: [Cart]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 获取购物车成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* items:
* type: array
* items:
* $ref: '#/components/schemas/CartItem'
* total_count:
* type: integer
* description: 购物车商品总数量
* total_amount:
* type: integer
* description: 购物车总金额
* total_points:
* type: integer
* description: 购物车总积分
* total_rongdou:
* type: integer
* description: 购物车总融豆
* 401:
* description: 未授权
* 500:
* description: 服务器错误
*/
router.get('/', auth, async (req, res) => {
try {
const userId = req.user.id;
// 获取购物车商品列表
const query = `
SELECT
c.id, c.user_id, c.product_id, c.quantity, c.specification_id,
c.created_at, c.updated_at,
p.name, p.price, p.points_price, p.rongdou_price, p.image_url,
p.stock, p.status, p.shop_name, p.shop_avatar,
psc.combination_key, psc.price_adjustment,
psc.points_adjustment, psc.rongdou_adjustment, psc.stock as spec_stock,
GROUP_CONCAT(CONCAT(sn.display_name, ':', sv.display_value) ORDER BY sn.sort_order SEPARATOR ' | ') as spec_display
FROM cart_items c
LEFT JOIN products p ON c.product_id = p.id
LEFT JOIN product_spec_combinations psc ON c.specification_id = psc.id
LEFT JOIN JSON_TABLE(psc.spec_values, '$[*]' COLUMNS (spec_value_id INT PATH '$')) jt ON psc.id IS NOT NULL
LEFT JOIN spec_values sv ON jt.spec_value_id = sv.id
LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id
WHERE c.user_id = ? AND p.status = 'active'
GROUP BY c.id
ORDER BY c.created_at DESC
`;
const [cartItems] = await getDB().execute(query, [userId]);
// 计算总计信息
let totalCount = 0;
let totalAmount = 0;
let totalPoints = 0;
let totalRongdou = 0;
const items = cartItems.map(item => {
const finalPrice = item.price + (item.price_adjustment || 0);
const finalPointsPrice = item.points_price + (item.points_adjustment || 0);
const finalRongdouPrice = item.rongdou_price + (item.rongdou_adjustment || 0);
totalCount += item.quantity;
totalAmount += finalPrice * item.quantity;
totalPoints += finalPointsPrice * item.quantity;
totalRongdou += finalRongdouPrice * item.quantity;
return {
id: item.id,
user_id: item.user_id,
product_id: item.product_id,
quantity: item.quantity,
spec_combination_id: item.spec_combination_id,
created_at: item.created_at,
updated_at: item.updated_at,
product: {
id: item.product_id,
name: item.name,
price: finalPrice,
points_price: finalPointsPrice,
rongdou_price: finalRongdouPrice,
image_url: item.image_url,
stock: item.spec_combination_id ? item.spec_stock : item.stock,
status: item.status,
shop_name: item.shop_name,
shop_avatar: item.shop_avatar
},
specification: item.spec_combination_id ? {
id: item.spec_combination_id,
combination_key: item.combination_key,
spec_display: item.spec_display,
price_adjustment: item.price_adjustment,
points_adjustment: item.points_adjustment,
rongdou_adjustment: item.rongdou_adjustment
} : null
};
});
res.json({
success: true,
data: {
items,
total_count: totalCount,
total_amount: totalAmount,
total_points: totalPoints,
total_rongdou: totalRongdou
}
});
} catch (error) {
console.error('获取购物车失败:', error);
res.status(500).json({ success: false, message: '获取购物车失败' });
}
});
/**
* @swagger
* /api/cart:
* post:
* summary: 添加商品到购物车
* tags: [Cart]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* product_id:
* type: integer
* description: 商品ID
* quantity:
* type: integer
* description: 商品数量
* minimum: 1
* spec_combination_id:
* type: integer
* description: 商品规格组合ID可选
* required:
* - product_id
* - quantity
* responses:
* 201:
* description: 添加到购物车成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* data:
* type: object
* properties:
* cart_item_id:
* type: integer
* 400:
* description: 参数错误或库存不足
* 401:
* description: 未授权
* 404:
* description: 商品不存在或已下架
* 500:
* description: 服务器错误
*/
router.post('/add', auth, async (req, res) => {
const db = getDB();
await db.query('START TRANSACTION');
try {
const { productId, quantity, specificationId } = req.body;
const userId = req.user.id;
// 验证必填字段
if (!productId || !quantity || quantity < 1) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '请填写正确的商品信息和数量' });
}
// 检查商品是否存在且有效
const [products] = await db.execute(
'SELECT id, name, stock, status FROM products WHERE id = ?',
[productId]
);
if (products.length === 0 || products[0].status !== 'active') {
await db.query('ROLLBACK');
return res.status(404).json({ success: false, message: '商品不存在或已下架' });
}
const product = products[0];
let availableStock = product.stock;
// 如果指定了规格组合,检查规格组合库存
if (specificationId) {
const [specs] = await db.execute(
'SELECT id, stock, status FROM product_spec_combinations WHERE id = ? AND product_id = ?',
[specificationId, productId]
);
if (specs.length === 0 || specs[0].status !== 'active') {
await db.query('ROLLBACK');
return res.status(404).json({ success: false, message: '商品规格组合不存在或已下架' });
}
availableStock = specs[0].stock;
}
// 检查购物车中是否已存在相同商品和规格组合
const [existingItems] = await db.execute(
'SELECT id, quantity FROM cart_items WHERE user_id = ? AND product_id = ? AND (specification_id = ? OR (specification_id IS NULL AND ? IS NULL))',
[userId, productId, specificationId, specificationId]
);
let finalQuantity = quantity;
if (existingItems.length > 0) {
finalQuantity += existingItems[0].quantity;
}
// 检查库存是否足够
if (availableStock < finalQuantity) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '库存不足' });
}
let cartItemId;
if (existingItems.length > 0) {
// 更新现有购物车项的数量
await db.execute(
'UPDATE cart_items SET quantity = ?, updated_at = NOW() WHERE id = ?',
[finalQuantity, existingItems[0].id]
);
cartItemId = existingItems[0].id;
} else {
// 添加新的购物车项
const [result] = await db.execute(
'INSERT INTO cart_items (user_id, product_id, quantity, specification_id, created_at, updated_at) VALUES (?, ?, ?, ?, NOW(), NOW())',
[userId, productId, quantity, specificationId]
);
cartItemId = result.insertId;
}
await db.query('COMMIT');
res.status(201).json({
success: true,
message: '添加到购物车成功',
data: { cart_item_id: cartItemId }
});
} catch (error) {
await db.query('ROLLBACK');
console.error('添加到购物车失败:', error);
res.status(500).json({ success: false, message: '添加到购物车失败' });
}
});
/**
* @swagger
* /api/cart/{id}:
* put:
* summary: 更新购物车商品数量
* tags: [Cart]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 购物车项ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* quantity:
* type: integer
* description: 新的商品数量
* minimum: 1
* required:
* - quantity
* responses:
* 200:
* description: 更新购物车成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* 400:
* description: 参数错误或库存不足
* 401:
* description: 未授权
* 404:
* description: 购物车项不存在
* 500:
* description: 服务器错误
*/
router.put('/:id', auth, async (req, res) => {
const db = getDB();
await db.query('START TRANSACTION');
try {
const cartItemId = req.params.id;
const { quantity } = req.body;
const userId = req.user.id;
// 验证数量
if (!quantity || quantity < 1) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '商品数量必须大于0' });
}
// 检查购物车项是否存在且属于当前用户
const [cartItems] = await db.execute(
'SELECT id, product_id, specification_id FROM cart_items WHERE id = ? AND user_id = ?',
[cartItemId, userId]
);
console.log(cartItems,'cartItems');
if (cartItems.length === 0) {
await db.query('ROLLBACK');
return res.status(404).json({ success: false, message: '购物车项不存在' });
}
const cartItem = cartItems[0];
// 检查商品库存
const [products] = await db.execute(
'SELECT stock, status FROM products WHERE id = ?',
[cartItem.product_id]
);
if (products.length === 0 || products[0].status !== 'active') {
await db.query('ROLLBACK');
return res.status(404).json({ success: false, message: '商品不存在或已下架' });
}
let availableStock = products[0].stock;
// 如果有规格,检查规格库存
if (cartItem.specification_id) {
const [specs] = await db.execute(
'SELECT stock, status FROM product_spec_combinations WHERE id = ?',
[cartItem.specification_id]
);
if (specs.length === 0 || specs[0].status !== 'active') {
await db.query('ROLLBACK');
return res.status(404).json({ success: false, message: '商品规格不存在或已下架' });
}
availableStock = specs[0].stock;
}
// 检查库存是否足够
if (availableStock < quantity) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '库存不足' });
}
// 更新购物车项数量
await db.execute(
'UPDATE cart_items SET quantity = ?, updated_at = NOW() WHERE id = ?',
[quantity, cartItemId]
);
await db.query('COMMIT');
res.json({
success: true,
message: '更新购物车成功'
});
} catch (error) {
await db.query('ROLLBACK');
console.error('更新购物车失败:', error);
res.status(500).json({ success: false, message: '更新购物车失败' });
}
});
/**
* @swagger
* /api/cart/{id}:
* delete:
* summary: 删除购物车商品
* tags: [Cart]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 购物车项ID
* responses:
* 200:
* description: 删除购物车商品成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* 401:
* description: 未授权
* 404:
* description: 购物车项不存在
* 500:
* description: 服务器错误
*/
router.delete('/:id', auth, async (req, res) => {
try {
const cartItemId = req.params.id;
const userId = req.user.id;
// 检查购物车项是否存在且属于当前用户
const [cartItems] = await getDB().execute(
'SELECT id FROM cart_items WHERE id = ? AND user_id = ?',
[cartItemId, userId]
);
if (cartItems.length === 0) {
return res.status(404).json({ success: false, message: '购物车项不存在' });
}
// 删除购物车项
await getDB().execute(
'DELETE FROM cart_items WHERE id = ?',
[cartItemId]
);
res.json({
success: true,
message: '删除购物车商品成功'
});
} catch (error) {
console.error('删除购物车商品失败:', error);
res.status(500).json({ success: false, message: '删除购物车商品失败' });
}
});
/**
* @swagger
* /api/cart/batch:
* delete:
* summary: 批量删除购物车商品
* tags: [Cart]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* cart_item_ids:
* type: array
* items:
* type: integer
* description: 购物车项ID数组
* required:
* - cart_item_ids
* responses:
* 200:
* description: 批量删除购物车商品成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* data:
* type: object
* properties:
* deleted_count:
* type: integer
* description: 删除的商品数量
* 400:
* description: 参数错误
* 401:
* description: 未授权
* 500:
* description: 服务器错误
*/
router.delete('/batch', auth, async (req, res) => {
try {
const { cart_item_ids } = req.body;
const userId = req.user.id;
// 验证参数
if (!cart_item_ids || !Array.isArray(cart_item_ids) || cart_item_ids.length === 0) {
return res.status(400).json({ success: false, message: '请选择要删除的商品' });
}
// 构建删除条件
const placeholders = cart_item_ids.map(() => '?').join(',');
const query = `DELETE FROM cart_items WHERE id IN (${placeholders}) AND user_id = ?`;
const params = [...cart_item_ids, userId];
const [result] = await getDB().execute(query, params);
res.json({
success: true,
message: '批量删除购物车商品成功',
data: {
deleted_count: result.affectedRows
}
});
} catch (error) {
console.error('批量删除购物车商品失败:', error);
res.status(500).json({ success: false, message: '批量删除购物车商品失败' });
}
});
/**
* @swagger
* /api/cart/clear:
* delete:
* summary: 清空购物车
* tags: [Cart]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 清空购物车成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* 401:
* description: 未授权
* 500:
* description: 服务器错误
*/
router.delete('/clear', auth, async (req, res) => {
try {
const userId = req.user.id;
// 清空用户购物车
await getDB().execute(
'DELETE FROM cart_items WHERE user_id = ?',
[userId]
);
res.json({
success: true,
message: '清空购物车成功'
});
} catch (error) {
console.error('清空购物车失败:', error);
res.status(500).json({ success: false, message: '清空购物车失败' });
}
});
/**
* @swagger
* /api/cart/count:
* get:
* summary: 获取购物车商品数量
* tags: [Cart]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 获取购物车商品数量成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* count:
* type: integer
* description: 购物车商品总数量
* 401:
* description: 未授权
* 500:
* description: 服务器错误
*/
router.get('/count', auth, async (req, res) => {
try {
const userId = req.user.id;
// 获取购物车商品总数量
const [result] = await getDB().execute(
'SELECT SUM(quantity) as count FROM cart_items WHERE user_id = ?',
[userId]
);
const count = result[0].count || 0;
res.json({
success: true,
data: { count }
});
} catch (error) {
console.error('获取购物车商品数量失败:', error);
res.status(500).json({ success: false, message: '获取购物车商品数量失败' });
}
});
/**
* @swagger
* /api/cart/checkout:
* post:
* summary: 购物车结账
* tags: [Cart]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* cart_item_ids:
* type: array
* items:
* type: integer
* description: 要结账的购物车项ID数组
* shipping_address:
* type: string
* description: 收货地址
* required:
* - cart_item_ids
* - shipping_address
* responses:
* 201:
* description: 结账成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* data:
* type: object
* properties:
* order_id:
* type: integer
* order_no:
* type: string
* total_amount:
* type: integer
* total_points:
* type: integer
* total_rongdou:
* type: integer
* 400:
* description: 参数错误或库存不足
* 401:
* description: 未授权
* 500:
* description: 服务器错误
*/
router.post('/checkout', auth, async (req, res) => {
const db = getDB();
await db.query('START TRANSACTION');
try {
const { cart_item_ids, shipping_address } = req.body;
const userId = req.user.id;
// 验证参数
if (!cart_item_ids || !Array.isArray(cart_item_ids) || cart_item_ids.length === 0) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '请选择要结账的商品' });
}
if (!shipping_address) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '请填写收货地址' });
}
// 获取购物车商品信息
const placeholders = cart_item_ids.map(() => '?').join(',');
const cartQuery = `
SELECT
c.id, c.product_id, c.quantity, c.spec_combination_id,
p.name, p.price, p.points_price, p.rongdou_price, p.stock, p.status,
psc.price_adjustment, psc.points_adjustment, psc.rongdou_adjustment, psc.stock as spec_stock
FROM cart_items c
LEFT JOIN products p ON c.product_id = p.id
LEFT JOIN product_spec_combinations psc ON c.spec_combination_id = psc.id
WHERE c.id IN (${placeholders}) AND c.user_id = ?
`;
const [cartItems] = await db.execute(cartQuery, [...cart_item_ids, userId]);
if (cartItems.length === 0) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '购物车商品不存在' });
}
// 验证商品状态和库存
let totalAmount = 0;
let totalPoints = 0;
let totalRongdou = 0;
for (const item of cartItems) {
if (item.status !== 'active') {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: `商品 ${item.name} 已下架` });
}
const availableStock = item.spec_combination_id ? item.spec_stock : item.stock;
if (availableStock < item.quantity) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: `商品 ${item.name} 库存不足` });
}
const finalPrice = item.price + (item.price_adjustment || 0);
const finalPointsPrice = item.points_price + (item.points_adjustment || 0);
const finalRongdouPrice = item.rongdou_price + (item.rongdou_adjustment || 0);
totalAmount += finalPrice * item.quantity;
totalPoints += finalPointsPrice * item.quantity;
totalRongdou += finalRongdouPrice * item.quantity;
}
// 检查用户积分和融豆是否足够
const [users] = await db.execute(
'SELECT points, rongdou FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
await db.query('ROLLBACK');
return res.status(404).json({ success: false, message: '用户不存在' });
}
const user = users[0];
if (user.points < totalPoints) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '积分不足' });
}
if (user.rongdou < totalRongdou) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '融豆不足' });
}
// 生成订单号
const orderNo = 'ORD' + Date.now() + Math.random().toString(36).substr(2, 5).toUpperCase();
// 创建订单
const [orderResult] = await db.execute(
`INSERT INTO orders (order_no, user_id, total_amount, total_points, total_rongdou,
status, shipping_address, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, 'pending', ?, NOW(), NOW())`,
[orderNo, userId, totalAmount, totalPoints, totalRongdou, shipping_address]
);
const orderId = orderResult.insertId;
// 创建订单项
for (const item of cartItems) {
const finalPrice = item.price + (item.price_adjustment || 0);
const finalPointsPrice = item.points_price + (item.points_adjustment || 0);
const finalRongdouPrice = item.rongdou_price + (item.rongdou_adjustment || 0);
await db.execute(
`INSERT INTO order_items (order_id, product_id, spec_combination_id, quantity,
price, points_price, rongdou_price, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, NOW())`,
[orderId, item.product_id, item.spec_combination_id, item.quantity,
finalPrice, finalPointsPrice, finalRongdouPrice]
);
// 更新库存
if (item.spec_combination_id) {
await db.execute(
'UPDATE product_spec_combinations SET stock = stock - ? WHERE id = ?',
[item.quantity, item.spec_combination_id]
);
} else {
await db.execute(
'UPDATE products SET stock = stock - ? WHERE id = ?',
[item.quantity, item.product_id]
);
}
}
// 扣除用户积分和融豆
await db.execute(
'UPDATE users SET points = points - ?, rongdou = rongdou - ? WHERE id = ?',
[totalPoints, totalRongdou, userId]
);
// 删除已结账的购物车项
const deletePlaceholders = cart_item_ids.map(() => '?').join(',');
await db.execute(
`DELETE FROM cart_items WHERE id IN (${deletePlaceholders}) AND user_id = ?`,
[...cart_item_ids, userId]
);
await db.query('COMMIT');
res.status(201).json({
success: true,
message: '结账成功',
data: {
order_id: orderId,
order_no: orderNo,
total_amount: totalAmount,
total_points: totalPoints,
total_rongdou: totalRongdou
}
});
} catch (error) {
await db.query('ROLLBACK');
console.error('购物车结账失败:', error);
res.status(500).json({ success: false, message: '结账失败' });
}
});
module.exports = router;

File diff suppressed because it is too large Load Diff

View File

@@ -4,123 +4,7 @@ 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;
@@ -159,7 +43,7 @@ router.get('/', async (req, res) => {
// 获取商品列表
const query = `
SELECT id, name, category, points_price as points, stock, image_url as image, description, status, created_at, updated_at
SELECT id, name, rongdou_price, category, points_price as points, stock, image_url as image, description, status, payment_methods, created_at, updated_at
FROM products
${whereClause}
ORDER BY created_at DESC
@@ -170,7 +54,10 @@ router.get('/', async (req, res) => {
const queryParams = [...params];
console.log('Query params:', queryParams, 'Query:', query);
const [products] = await getDB().execute(query, queryParams);
products.forEach(item=>{
item.payment_methods = JSON.parse(item.payment_methods)
})
console.log('查询结果:', products);
res.json({
success: true,
data: {
@@ -189,30 +76,7 @@ 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(
@@ -231,11 +95,111 @@ router.get('/categories', async (req, res) => {
}
});
// 获取热销商品
router.get('/hot', async (req, res) => {
try {
// 从活跃商品中随机获取2个商品
const [products] = await getDB().execute(
`SELECT id, name, category, price, points_price, rongdou_price, stock,
image_url, images, description, shop_name, shop_avatar,
payment_methods, sales, rating, status, created_at, updated_at
FROM products
WHERE status = 'active' AND stock > 0
ORDER BY RAND()
LIMIT 2`
);
// 格式化商品数据
const formattedProducts = products.map(product => ({
...product,
images: product.images ? JSON.parse(product.images) : (product.image_url ? [product.image_url] : []),
payment_methods: product.payment_methods ? JSON.parse(product.payment_methods) : ['points'],
// 保持向后兼容
points: product.points_price,
image: product.image_url
}));
res.json({
success: true,
data: {
products: formattedProducts
}
});
} catch (error) {
console.error('获取热销商品失败:', error);
res.status(500).json({ success: false, message: '获取热销商品失败' });
}
});
/**
* @swagger
* /products/flash-sale:
* get:
* summary: 获取秒杀商品
* tags: [Products]
* 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'
*/
router.get('/cheap', async (req, res) => {
try {
// 从活跃商品中随机获取2个商品作为秒杀商品
const [products] = await getDB().execute(
`SELECT id, name, category, price, points_price, rongdou_price, stock,
image_url, images, description, shop_name, shop_avatar,
payment_methods, sales, rating, status, created_at, updated_at
FROM products
WHERE status = 'active' AND stock > 0
ORDER BY RAND()
LIMIT 2`
);
// 格式化商品数据,为秒杀商品添加特殊标识
const formattedProducts = products.map(product => ({
...product,
images: product.images ? JSON.parse(product.images) : (product.image_url ? [product.image_url] : []),
payment_methods: product.payment_methods ? JSON.parse(product.payment_methods) : ['points'],
// 秒杀商品特殊处理价格打8折
flash_sale_price: Math.floor(product.price * 0.8),
flash_sale_points: Math.floor(product.points_price * 0.8),
flash_sale_rongdou: Math.floor(product.rongdou_price * 0.8),
is_flash_sale: true,
// 保持向后兼容
points: product.points_price,
image: product.image_url
}));
res.json({
success: true,
data: {
products: formattedProducts
}
});
} catch (error) {
console.error('获取秒杀商品失败:', error);
res.status(500).json({ success: false, message: '获取秒杀商品失败' });
}
});
/**
* @swagger
* /products/{id}:
* get:
* summary: 获取单个商品详情
* summary: 获取单个商品详情(包含增强规格信息)
* tags: [Products]
* parameters:
* - in: path
@@ -246,7 +210,7 @@ router.get('/categories', async (req, res) => {
* description: 商品ID
* responses:
* 200:
* description: 成功获取商品详情
* description: 成功获取商品详情,包含完整的规格信息
* content:
* application/json:
* schema:
@@ -254,8 +218,116 @@ router.get('/categories', async (req, res) => {
* properties:
* success:
* type: boolean
* example: true
* data:
* $ref: '#/components/schemas/Product'
* type: object
* properties:
* product:
* type: object
* properties:
* id:
* type: integer
* name:
* type: string
* category:
* type: string
* price:
* type: number
* points_price:
* type: number
* rongdou_price:
* type: number
* stock:
* type: integer
* specifications:
* type: array
* description: 商品规格组合列表(笛卡尔积规格系统)
* items:
* type: object
* properties:
* id:
* type: integer
* description: 规格组合ID
* combination_key:
* type: string
* description: 规格组合键1-3-5
* spec_display:
* type: string
* description: 规格显示文本(如:颜色:红色 | 尺寸:XL
* spec_details:
* type: array
* description: 规格详细信息
* items:
* type: object
* properties:
* id:
* type: integer
* spec_name:
* type: string
* description: 规格名称
* spec_display_name:
* type: string
* description: 规格显示名称
* value:
* type: string
* description: 规格值
* display_value:
* type: string
* description: 规格显示值
* color_code:
* type: string
* description: 颜色代码
* image_url:
* type: string
* description: 规格图片
* price_adjustment:
* type: number
* description: 价格调整
* points_adjustment:
* type: number
* description: 积分调整
* rongdou_adjustment:
* type: number
* description: 融豆调整
* stock:
* type: integer
* description: 规格库存
* sku_code:
* type: string
* description: SKU编码
* barcode:
* type: string
* description: 条形码
* weight:
* type: number
* description: 重量
* volume:
* type: number
* description: 体积
* actual_price:
* type: number
* description: 实际价格(基础价格+调整)
* actual_points_price:
* type: number
* description: 实际积分价格
* actual_rongdou_price:
* type: number
* description: 实际融豆价格
* is_available:
* type: boolean
* description: 是否有库存
* specification_count:
* type: integer
* description: 规格总数
* available_specifications:
* type: integer
* description: 有库存的规格数量
* attributes:
* type: array
* description: 商品属性
* isFavorited:
* type: boolean
* description: 是否已收藏
* 404:
* description: 商品不存在
*/
@@ -280,12 +352,92 @@ router.get('/:id', async (req, res) => {
const product = products[0];
// 获取商品规格
const [specifications] = await getDB().execute(
'SELECT * FROM product_specifications WHERE product_id = ? ORDER BY id',
// 获取商品规格组合(新的笛卡尔积规格系统)
const [specCombinations] = await getDB().execute(
`SELECT psc.*,
GROUP_CONCAT(CONCAT(sn.display_name, ':', sv.display_value) ORDER BY sn.sort_order SEPARATOR ' | ') as spec_display
FROM product_spec_combinations psc
LEFT JOIN JSON_TABLE(psc.spec_values, '$[*]' COLUMNS (spec_value_id INT PATH '$')) jt ON TRUE
LEFT JOIN spec_values sv ON jt.spec_value_id = sv.id
LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id
WHERE psc.product_id = ? AND psc.status = 'active'
GROUP BY psc.id
ORDER BY psc.combination_key`,
[id]
);
// 为每个规格组合获取详细的规格值信息
const enhancedSpecifications = [];
for (const combination of specCombinations) {
// 智能解析 spec_values 字段,兼容多种数据格式
let specValueIds = [];
try {
if (combination.spec_values) {
// 如果是 Buffer 对象,先转换为字符串
let specValuesStr = combination.spec_values;
if (Buffer.isBuffer(specValuesStr)) {
specValuesStr = specValuesStr.toString('utf8');
}
// 尝试 JSON 解析
if (typeof specValuesStr === 'string') {
specValuesStr = specValuesStr.trim();
if (specValuesStr.startsWith('[') && specValuesStr.endsWith(']')) {
// JSON 数组格式
specValueIds = JSON.parse(specValuesStr);
} else if (specValuesStr.includes(',')) {
// 逗号分隔的字符串格式
specValueIds = specValuesStr.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id));
} else if (specValuesStr && !isNaN(parseInt(specValuesStr))) {
// 单个数字
specValueIds = [parseInt(specValuesStr)];
}
} else if (Array.isArray(specValuesStr)) {
// 已经是数组
specValueIds = specValuesStr;
}
}
} catch (parseError) {
console.warn(`解析规格值失败 (combination_id: ${combination.id}):`, parseError.message);
specValueIds = [];
}
// 获取规格值详情
if (specValueIds && specValueIds.length > 0) {
const placeholders = specValueIds.map(() => '?').join(',');
const [specDetails] = await getDB().execute(
`SELECT sv.*, sn.name as spec_name, sn.display_name as spec_display_name
FROM spec_values sv
LEFT JOIN spec_names sn ON sv.spec_name_id = sn.id
WHERE sv.id IN (${placeholders})
ORDER BY sn.sort_order, sv.sort_order`,
specValueIds
);
enhancedSpecifications.push({
id: combination.id,
combination_key: combination.combination_key,
spec_display: combination.spec_display,
spec_details: specDetails,
price_adjustment: combination.price_adjustment || 0,
points_adjustment: combination.points_adjustment || 0,
rongdou_adjustment: combination.rongdou_adjustment || 0,
stock: combination.stock,
sku_code: combination.sku_code,
barcode: combination.barcode,
weight: combination.weight,
volume: combination.volume,
actual_price: product.price + (combination.price_adjustment || 0),
actual_points_price: product.points_price + (combination.points_adjustment || 0),
actual_rongdou_price: product.rongdou_price + (combination.rongdou_adjustment || 0),
is_available: combination.stock > 0,
status: combination.status,
created_at: combination.created_at,
updated_at: combination.updated_at
});
}
}
// 获取商品属性
const [attributes] = await getDB().execute(
'SELECT * FROM product_attributes WHERE product_id = ? ORDER BY sort_order, id',
@@ -305,12 +457,72 @@ router.get('/:id', async (req, res) => {
// 构建增强的商品数据
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,
images: (() => {
try {
if (product.images) {
let imagesStr = product.images;
if (Buffer.isBuffer(imagesStr)) {
imagesStr = imagesStr.toString('utf8');
}
if (typeof imagesStr === 'string') {
imagesStr = imagesStr.trim();
if (imagesStr.startsWith('[') && imagesStr.endsWith(']')) {
return JSON.parse(imagesStr);
}
}
}
return product.image_url ? [product.image_url] : [];
} catch (e) {
console.warn('解析商品图片失败:', e.message);
return product.image_url ? [product.image_url] : [];
}
})(),
videos: (() => {
try {
if (product.videos) {
let videosStr = product.videos;
if (Buffer.isBuffer(videosStr)) {
videosStr = videosStr.toString('utf8');
}
if (typeof videosStr === 'string') {
videosStr = videosStr.trim();
if (videosStr.startsWith('[') && videosStr.endsWith(']')) {
return JSON.parse(videosStr);
}
}
}
return [];
} catch (e) {
console.warn('解析商品视频失败:', e.message);
return [];
}
})(),
payment_methods: (() => {
try {
if (product.payment_methods) {
let methodsStr = product.payment_methods;
if (Buffer.isBuffer(methodsStr)) {
methodsStr = methodsStr.toString('utf8');
}
if (typeof methodsStr === 'string') {
methodsStr = methodsStr.trim();
if (methodsStr.startsWith('[') && methodsStr.endsWith(']')) {
return JSON.parse(methodsStr);
}
}
}
return ['points'];
} catch (e) {
console.warn('解析支付方式失败:', e.message);
return ['points'];
}
})(),
specifications: enhancedSpecifications,
attributes,
isFavorited,
// 规格统计信息
specification_count: enhancedSpecifications.length,
available_specifications: enhancedSpecifications.filter(spec => spec.is_available).length,
// 保持向后兼容
points: product.points_price,
image: product.image_url,
@@ -352,19 +564,7 @@ router.post('/', auth, adminAuth, async (req, res) => {
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) {
@@ -499,25 +699,7 @@ 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) {
@@ -655,8 +837,8 @@ router.get('/:id/reviews', async (req, res) => {
JOIN users u ON pr.user_id = u.id
WHERE pr.product_id = ?
ORDER BY pr.created_at DESC
LIMIT ? OFFSET ?`,
[id, limit, offset]
LIMIT ${limit} OFFSET ${offset}`,
[id]
);
// 获取评论总数
@@ -832,8 +1014,8 @@ router.get('/favorites', auth, async (req, res) => {
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]
LIMIT ${limit} OFFSET ${offset}`,
[userId]
);
const [countResult] = await getDB().execute(
@@ -867,163 +1049,13 @@ router.get('/favorites', auth, async (req, res) => {
}
});
// 获取商品规格
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) => {

View File

@@ -120,12 +120,37 @@ router.get('/zhejiang', async (req, res) => {
*/
router.get('/provinces', async (req, res) => {
try {
// 递归获取子区域的函数
async function getChildrenRecursively(parentCode, level) {
const [children] = await getDB().execute(
`SELECT code, name as label, level FROM china_regions
WHERE parent_code = ? AND level = ?
ORDER BY code`,
[parentCode, level]
);
// 为每个子区域递归获取其子区域
for (let child of children) {
if (level < 3) { // 最多到区县级别level 3
child.children = await getChildrenRecursively(child.code, level + 1);
}
}
return children;
}
// 获取所有省份
const [provinces] = await getDB().execute(
`SELECT code, name FROM china_regions
`SELECT code, name as label, level FROM china_regions
WHERE level = 1
ORDER BY code`
);
// 为每个省份递归获取城市和区县
for (let province of provinces) {
province.children = await getChildrenRecursively(province.code, 2);
}
res.json({
success: true,
data: provinces

1096
routes/specifications.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -821,8 +821,8 @@ router.get('/user/:userId', authenticateToken, async (req, res) => {
LEFT JOIN users to_user ON t.to_user_id = to_user.id
${whereClause}
ORDER BY t.created_at DESC
LIMIT ? OFFSET ?
`, listParams);
LIMIT ${limitNum} OFFSET ${offset}
`, countParams);
const [countResult] = await db.execute(`
SELECT COUNT(*) as total FROM transfers t ${whereClause}
@@ -1556,10 +1556,11 @@ router.get('/daily-stats',
u.balance,
COALESCE(yesterday_out.amount, 0) as yesterday_out_amount,
COALESCE(today_in.amount, 0) as today_in_amount,
COALESCE(confirmed_from.confirmed_amount, 0) as confirmed_from_amount,
CASE
WHEN (COALESCE(yesterday_out.amount, 0) - COALESCE(today_in.amount, 0)) > ABS(u.balance)
WHEN (COALESCE(u.balance, 0) +COALESCE(confirmed_from.confirmed_amount, 0) ) > ABS(u.balance)
THEN ABS(u.balance)
ELSE (COALESCE(yesterday_out.amount, 0) - COALESCE(today_in.amount, 0))
ELSE (COALESCE(u.balance, 0)+ COALESCE(confirmed_from.confirmed_amount, 0) )
END as balance_needed
FROM users u
LEFT JOIN (
@@ -1580,13 +1581,29 @@ router.get('/daily-stats',
AND status IN ('confirmed', 'received')
GROUP BY to_user_id
) today_in ON u.id = today_in.to_user_id
left join (
select
from_user_id,
sum(amount) as confirmed_amount
from
transfers
where
status = 'received'
and created_at >= ?
and created_at <= ?
group by
from_user_id
) as confirmed_from on u.id = confirmed_from.from_user_id
WHERE u.role != 'admin'
AND u.is_system_account != 1
AND yesterday_out.amount > 0
AND u.balance < 0
ORDER BY balance_needed DESC, yesterday_out_amount DESC
`, [yesterdayStartStr, yesterdayEndStr, todayStartStr, todayEndStr]);
userStats = userStats.filter(item=>item.balance_needed >= 100)
`, [yesterdayStartStr, yesterdayEndStr, todayStartStr, todayEndStr, todayStartStr, todayEndStr]);
// userStats = userStats.filter(item=>item.balance_needed >= 100)
userStats.forEach(item=>{
item.balance_needed = Math.abs(item.balance_needed)
})
res.json({
success: true,
data: {

File diff suppressed because it is too large Load Diff