升级商城逻辑
This commit is contained in:
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user