653 lines
22 KiB
JavaScript
653 lines
22 KiB
JavaScript
const express = require('express');
|
||
const router = express.Router();
|
||
const {getDB} = require('../database');
|
||
const {SelectBuilder} = require('../config/dbv2')
|
||
const {db} = require("../server");
|
||
const sql = require("../config/config");
|
||
|
||
|
||
router.get('/names', async (req, res) => {
|
||
try {
|
||
const {status = 'active'} = req.query;
|
||
const {id: created_id} = req.user;
|
||
let query = `SELECT *
|
||
FROM spec_names
|
||
WHERE created_id = ${created_id}`;
|
||
const params = [];
|
||
|
||
if (status) {
|
||
query += ' and status = ?';
|
||
params.push(status);
|
||
}
|
||
|
||
query += ' ORDER BY sort_order, id';
|
||
|
||
const [specNames] = await getDB().execute(query, params);
|
||
|
||
res.json({
|
||
success: true,
|
||
data: specNames
|
||
});
|
||
} catch (error) {
|
||
console.error('获取规格名称失败:', error);
|
||
res.status(500).json({success: false, message: '获取规格名称失败'});
|
||
}
|
||
});
|
||
|
||
|
||
router.post('/names', async (req, res) => {
|
||
const db = getDB();
|
||
try {
|
||
const {name, display_name, sort_order = 0} = req.body;
|
||
const {id: created_id} = req.user;
|
||
if (!name || !display_name) {
|
||
return res.status(400).json({success: false, message: '规格名称和显示名称不能为空'});
|
||
}
|
||
let spec_name = new SelectBuilder().from()
|
||
const [result] = await getDB().execute(
|
||
'INSERT INTO spec_names (name, display_name, sort_order,created_id) VALUES (?, ?, ?,?)',
|
||
[name, display_name, sort_order,created_id]
|
||
);
|
||
|
||
res.status(201).json({
|
||
success: true,
|
||
message: '规格名称创建成功',
|
||
data: {id: result.insertId}
|
||
});
|
||
} catch (error) {
|
||
if (error.code === 'ER_DUP_ENTRY') {
|
||
return res.status(400).json({success: false, message: '规格名称已存在'});
|
||
}
|
||
console.error('创建规格名称失败:', error);
|
||
res.status(500).json({success: false, message: '创建规格名称失败'});
|
||
}
|
||
});
|
||
|
||
|
||
router.delete('/names/:id', async (req, res) => {
|
||
try {
|
||
const {id} = req.params;
|
||
const {id:user_id,role} = req.user
|
||
|
||
let specCountQuery = new SelectBuilder()
|
||
.from('spec_names')
|
||
.select('COUNT(*) as total')
|
||
.where('id=?',id)
|
||
if(role !== 'admin'){
|
||
specCountQuery.where('created_id=?',user_id)
|
||
}
|
||
let [spec] = await specCountQuery.execute(db)
|
||
if (spec.total === 0) {
|
||
return res.status(404).json({success: false, message: '规格名称不存在'});
|
||
}
|
||
|
||
// 检查该规格名称下是否还有规格值
|
||
const [specValues] = await getDB().execute(
|
||
'SELECT COUNT(*) as count FROM spec_values WHERE spec_name_id = ?',
|
||
[id]
|
||
);
|
||
|
||
if (specValues[0].count > 0) {
|
||
return res.status(400).json({success: false, message: '该规格名称下还有规格值,请先删除所有规格值'});
|
||
}
|
||
// 删除规格名称
|
||
await getDB().execute(
|
||
'DELETE FROM spec_names WHERE id = ?',
|
||
[id]
|
||
);
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '规格名称删除成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('删除规格名称失败:', error);
|
||
res.status(500).json({success: false, message: '删除规格名称失败'});
|
||
}
|
||
});
|
||
|
||
|
||
router.get('/values', async (req, res) => {
|
||
try {
|
||
const {spec_name_id, status = 'active'} = req.query;
|
||
|
||
let query = `
|
||
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 1 = 1
|
||
`;
|
||
const params = [];
|
||
|
||
if (spec_name_id) {
|
||
query += ' AND sv.spec_name_id = ?';
|
||
params.push(spec_name_id);
|
||
}
|
||
|
||
if (status) {
|
||
query += ' AND sv.status = ?';
|
||
params.push(status);
|
||
}
|
||
|
||
query += ' ORDER BY sv.spec_name_id, sv.sort_order, sv.id';
|
||
|
||
const [specValues] = await getDB().execute(query, params);
|
||
|
||
res.json({
|
||
success: true,
|
||
data: specValues
|
||
});
|
||
} catch (error) {
|
||
console.error('获取规格值失败:', error);
|
||
res.status(500).json({success: false, message: '获取规格值失败'});
|
||
}
|
||
});
|
||
|
||
|
||
router.post('/values', async (req, res) => {
|
||
try {
|
||
const {spec_name_id, value, display_value, color_code, image_url, sort_order = 0} = req.body;
|
||
|
||
if (!spec_name_id || !value || !display_value) {
|
||
return res.status(400).json({success: false, message: '规格名称ID、规格值和显示值不能为空'});
|
||
}
|
||
|
||
const [result] = await getDB().execute(
|
||
`INSERT INTO spec_values (spec_name_id, value, display_value, color_code, image_url, sort_order)
|
||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||
[spec_name_id, value, display_value, color_code || null, image_url || null, sort_order]
|
||
);
|
||
|
||
res.status(201).json({
|
||
success: true,
|
||
message: '规格值创建成功',
|
||
data: {id: result.insertId}
|
||
});
|
||
} catch (error) {
|
||
if (error.code === 'ER_DUP_ENTRY') {
|
||
return res.status(400).json({success: false, message: '该规格名称下的规格值已存在'});
|
||
}
|
||
console.error('创建规格值失败:', error);
|
||
res.status(500).json({success: false, message: '创建规格值失败'});
|
||
}
|
||
});
|
||
|
||
|
||
router.get('/combinations/:productId', async (req, res) => {
|
||
try {
|
||
const {productId} = req.params;
|
||
const {status = 'active'} = req.query;
|
||
|
||
// 获取商品的规格组合
|
||
let query = `
|
||
SELECT psc.*,
|
||
p.name as product_name,
|
||
p.price as base_price,
|
||
p.points_price as base_points_price,
|
||
p.rongdou_price as base_rongdou_price
|
||
FROM product_spec_combinations psc
|
||
LEFT JOIN products p ON psc.product_id = p.id
|
||
WHERE psc.product_id = ?
|
||
`;
|
||
const params = [productId];
|
||
|
||
if (status) {
|
||
query += ' AND psc.status = ?';
|
||
params.push(status);
|
||
}
|
||
|
||
query += ' ORDER BY psc.combination_key';
|
||
|
||
const [combinations] = await getDB().execute(query, params);
|
||
|
||
// 为每个组合获取详细的规格值信息
|
||
for (let combination of combinations) {
|
||
let specValueIds;
|
||
try {
|
||
// 处理不同的数据格式
|
||
if (!combination.spec_values) {
|
||
specValueIds = [];
|
||
} else if (typeof combination.spec_values === 'string') {
|
||
// 如果是字符串,尝试JSON解析,失败则按逗号分隔处理
|
||
try {
|
||
specValueIds = JSON.parse(combination.spec_values);
|
||
} catch {
|
||
// 按逗号分隔的字符串处理
|
||
specValueIds = combination.spec_values.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id));
|
||
}
|
||
} else if (Buffer.isBuffer(combination.spec_values)) {
|
||
// 如果是Buffer,转换为字符串后处理
|
||
const strValue = combination.spec_values.toString();
|
||
try {
|
||
specValueIds = JSON.parse(strValue);
|
||
} catch {
|
||
specValueIds = strValue.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id));
|
||
}
|
||
} else {
|
||
// 其他情况,尝试直接使用
|
||
specValueIds = Array.isArray(combination.spec_values) ? combination.spec_values : [];
|
||
}
|
||
} catch (error) {
|
||
console.error('解析规格值失败:', combination.spec_values, error);
|
||
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
|
||
);
|
||
|
||
combination.spec_details = specDetails;
|
||
} else {
|
||
combination.spec_details = [];
|
||
}
|
||
|
||
// 计算实际价格
|
||
combination.actual_price = combination.base_price + (combination.price_adjustment || 0);
|
||
combination.actual_points_price = combination.base_points_price + (combination.points_adjustment || 0);
|
||
combination.actual_rongdou_price = combination.base_rongdou_price + (combination.rongdou_adjustment || 0);
|
||
combination.is_available = combination.stock > 0;
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
data: combinations
|
||
});
|
||
} catch (error) {
|
||
console.error('获取规格组合失败:', error);
|
||
res.status(500).json({success: false, message: '获取规格组合失败'});
|
||
}
|
||
});
|
||
|
||
router.get('/combinations/:id', async (req, res) => {
|
||
try {
|
||
const {id} = req.params;
|
||
|
||
// 获取规格组合详情
|
||
const [combinations] = await getDB().execute(
|
||
`SELECT psc.*,
|
||
p.name as product_name,
|
||
p.price as base_price,
|
||
p.points_price as base_points_price,
|
||
p.rongdou_price as base_rongdou_price
|
||
FROM product_spec_combinations psc
|
||
LEFT JOIN products p ON psc.product_id = p.id
|
||
WHERE psc.id = ?`,
|
||
[id]
|
||
);
|
||
|
||
if (combinations.length === 0) {
|
||
return res.status(404).json({success: false, message: '规格组合不存在'});
|
||
}
|
||
|
||
const combination = combinations[0];
|
||
|
||
// 解析规格值并获取详细信息
|
||
let specValueIds;
|
||
try {
|
||
if (!combination.spec_values) {
|
||
specValueIds = [];
|
||
} else if (typeof combination.spec_values === 'string') {
|
||
try {
|
||
specValueIds = JSON.parse(combination.spec_values);
|
||
} catch {
|
||
specValueIds = combination.spec_values.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id));
|
||
}
|
||
} else if (Buffer.isBuffer(combination.spec_values)) {
|
||
const strValue = combination.spec_values.toString();
|
||
try {
|
||
specValueIds = JSON.parse(strValue);
|
||
} catch {
|
||
specValueIds = strValue.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id));
|
||
}
|
||
} else {
|
||
specValueIds = Array.isArray(combination.spec_values) ? combination.spec_values : [];
|
||
}
|
||
} catch (error) {
|
||
console.error('解析规格值失败:', combination.spec_values, error);
|
||
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
|
||
);
|
||
|
||
combination.spec_details = specDetails;
|
||
} else {
|
||
combination.spec_details = [];
|
||
}
|
||
|
||
// 计算实际价格
|
||
combination.actual_price = combination.base_price + (combination.price_adjustment || 0);
|
||
combination.actual_points_price = combination.base_points_price + (combination.points_adjustment || 0);
|
||
combination.actual_rongdou_price = combination.base_rongdou_price + (combination.rongdou_adjustment || 0);
|
||
combination.is_available = combination.stock > 0;
|
||
|
||
res.json({
|
||
success: true,
|
||
data: combination
|
||
});
|
||
} catch (error) {
|
||
console.error('获取规格组合详情失败:', error);
|
||
res.status(500).json({success: false, message: '获取规格组合详情失败'});
|
||
}
|
||
});
|
||
|
||
|
||
router.delete('/combinations/:id', async (req, res) => {
|
||
try {
|
||
const {id} = req.params;
|
||
|
||
// 检查规格组合是否存在
|
||
const [existingCombination] = await getDB().execute(
|
||
'SELECT id FROM product_spec_combinations WHERE id = ?',
|
||
[id]
|
||
);
|
||
|
||
if (existingCombination.length === 0) {
|
||
return res.status(404).json({success: false, message: '规格组合不存在'});
|
||
}
|
||
|
||
// 删除规格组合
|
||
await getDB().execute(
|
||
'DELETE FROM product_spec_combinations WHERE id = ?',
|
||
[id]
|
||
);
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '规格组合删除成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('删除规格组合失败:', error);
|
||
res.status(500).json({success: false, message: '删除规格组合失败'});
|
||
}
|
||
});
|
||
|
||
|
||
router.put('/combinations/:id', async (req, res) => {
|
||
try {
|
||
const {id} = req.params;
|
||
const {
|
||
price_adjustment,
|
||
points_adjustment,
|
||
rongdou_adjustment,
|
||
stock,
|
||
sku_code,
|
||
barcode,
|
||
weight,
|
||
volume,
|
||
status
|
||
} = req.body;
|
||
|
||
// 检查规格组合是否存在
|
||
const [existing] = await getDB().execute(
|
||
'SELECT id FROM product_spec_combinations WHERE id = ?',
|
||
[id]
|
||
);
|
||
|
||
if (existing.length === 0) {
|
||
return res.status(404).json({success: false, message: '规格组合不存在'});
|
||
}
|
||
|
||
// 构建更新字段
|
||
const updateFields = [];
|
||
const updateValues = [];
|
||
|
||
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 (barcode !== undefined) {
|
||
updateFields.push('barcode = ?');
|
||
updateValues.push(barcode);
|
||
}
|
||
|
||
if (weight !== undefined) {
|
||
updateFields.push('weight = ?');
|
||
updateValues.push(weight);
|
||
}
|
||
|
||
if (volume !== undefined) {
|
||
updateFields.push('volume = ?');
|
||
updateValues.push(volume);
|
||
}
|
||
|
||
if (status !== undefined) {
|
||
updateFields.push('status = ?');
|
||
updateValues.push(status);
|
||
}
|
||
|
||
if (updateFields.length === 0) {
|
||
return res.status(400).json({success: false, message: '没有提供要更新的字段'});
|
||
}
|
||
|
||
updateFields.push('updated_at = NOW()');
|
||
updateValues.push(id);
|
||
|
||
const updateQuery = `UPDATE product_spec_combinations
|
||
SET ${updateFields.join(', ')}
|
||
WHERE id = ?`;
|
||
|
||
await getDB().execute(updateQuery, updateValues);
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '规格组合更新成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('更新规格组合失败:', error);
|
||
res.status(500).json({success: false, message: '更新规格组合失败'});
|
||
}
|
||
});
|
||
|
||
|
||
router.post('/combinations', async (req, res) => {
|
||
try {
|
||
const {
|
||
product_id,
|
||
spec_values,
|
||
price_adjustment = 0,
|
||
points_adjustment = 0,
|
||
rongdou_adjustment = 0,
|
||
stock = 0,
|
||
sku_code,
|
||
barcode,
|
||
weight,
|
||
volume
|
||
} = req.body;
|
||
|
||
if (!product_id || !spec_values || !Array.isArray(spec_values) || spec_values.length === 0) {
|
||
return res.status(400).json({success: false, message: '商品ID和规格值数组不能为空'});
|
||
}
|
||
|
||
// 生成组合键
|
||
const sortedSpecValues = [...spec_values].sort((a, b) => a - b);
|
||
const combinationKey = sortedSpecValues.join('-');
|
||
|
||
const [result] = await getDB().execute(
|
||
`INSERT INTO product_spec_combinations
|
||
(product_id, combination_key, spec_values, price_adjustment, points_adjustment,
|
||
rongdou_adjustment, stock, sku_code, barcode, weight, volume)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
[
|
||
product_id,
|
||
combinationKey,
|
||
JSON.stringify(sortedSpecValues),
|
||
price_adjustment,
|
||
points_adjustment,
|
||
rongdou_adjustment,
|
||
stock,
|
||
sku_code,
|
||
barcode,
|
||
weight,
|
||
volume
|
||
]
|
||
);
|
||
|
||
res.status(201).json({
|
||
success: true,
|
||
message: '规格组合创建成功',
|
||
data: {id: result.insertId, combination_key: combinationKey}
|
||
});
|
||
} catch (error) {
|
||
if (error.code === 'ER_DUP_ENTRY') {
|
||
return res.status(400).json({success: false, message: '该规格组合已存在'});
|
||
}
|
||
console.error('创建规格组合失败:', error);
|
||
res.status(500).json({success: false, message: '创建规格组合失败'});
|
||
}
|
||
});
|
||
|
||
|
||
router.post('/generate-combinations', async (req, res) => {
|
||
try {
|
||
const {product_id, spec_name_ids, default_stock = 0} = req.body;
|
||
|
||
if (!product_id || !spec_name_ids || !Array.isArray(spec_name_ids) || spec_name_ids.length === 0) {
|
||
return res.status(400).json({success: false, message: '商品ID和规格名称ID数组不能为空'});
|
||
}
|
||
|
||
// 获取每个规格名称下的所有活跃规格值
|
||
const specValueGroups = [];
|
||
|
||
for (const specNameId of spec_name_ids) {
|
||
const [specValues] = await getDB().execute(
|
||
'SELECT id FROM spec_values WHERE spec_name_id = ? AND status = "active" ORDER BY sort_order, id',
|
||
[specNameId]
|
||
);
|
||
|
||
if (specValues.length === 0) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: `规格名称ID ${specNameId} 下没有活跃的规格值`
|
||
});
|
||
}
|
||
|
||
specValueGroups.push(specValues.map(sv => sv.id));
|
||
}
|
||
|
||
// 生成笛卡尔积
|
||
function cartesianProduct(arrays) {
|
||
return arrays.reduce((acc, curr) => {
|
||
const result = [];
|
||
acc.forEach(a => {
|
||
curr.forEach(c => {
|
||
result.push([...a, c]);
|
||
});
|
||
});
|
||
return result;
|
||
}, [[]]);
|
||
}
|
||
|
||
const combinations = cartesianProduct(specValueGroups);
|
||
|
||
// 生成所有组合键
|
||
const combinationData = combinations.map(combination => {
|
||
const sortedCombination = [...combination].sort((a, b) => a - b);
|
||
const combinationKey = sortedCombination.join('-');
|
||
return {
|
||
combination: sortedCombination,
|
||
key: combinationKey
|
||
};
|
||
});
|
||
|
||
// 批量检查已存在的组合
|
||
const existingKeys = new Set();
|
||
if (combinationData.length > 0) {
|
||
const keys = combinationData.map(item => item.key);
|
||
const placeholders = keys.map(() => '?').join(',');
|
||
|
||
const [existingCombinations] = await getDB().execute(
|
||
`SELECT combination_key
|
||
FROM product_spec_combinations
|
||
WHERE product_id = ?
|
||
AND combination_key IN (${placeholders})`,
|
||
[product_id, ...keys]
|
||
);
|
||
|
||
existingCombinations.forEach(row => {
|
||
existingKeys.add(row.combination_key);
|
||
});
|
||
}
|
||
|
||
// 过滤出需要插入的新组合
|
||
const newCombinations = combinationData.filter(item => !existingKeys.has(item.key));
|
||
|
||
// 批量插入新的规格组合
|
||
let createdCount = 0;
|
||
const skippedCount = combinationData.length - newCombinations.length;
|
||
|
||
if (newCombinations.length > 0) {
|
||
// 使用批量插入提高性能
|
||
const values = [];
|
||
const placeholders = [];
|
||
|
||
newCombinations.forEach(item => {
|
||
values.push(
|
||
product_id,
|
||
item.key,
|
||
JSON.stringify(item.combination),
|
||
default_stock
|
||
);
|
||
placeholders.push('(?, ?, ?, ?)');
|
||
});
|
||
|
||
const sql = `INSERT INTO product_spec_combinations
|
||
(product_id, combination_key, spec_values, stock)
|
||
VALUES ${placeholders.join(', ')}`;
|
||
|
||
const [result] = await getDB().execute(sql, values);
|
||
createdCount = result.affectedRows;
|
||
}
|
||
|
||
res.status(201).json({
|
||
success: true,
|
||
message: '规格组合生成完成',
|
||
data: {
|
||
total_combinations: combinations.length,
|
||
created: createdCount,
|
||
skipped: skippedCount
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('生成规格组合失败:', error);
|
||
res.status(500).json({success: false, message: '生成规格组合失败'});
|
||
}
|
||
});
|
||
|
||
module.exports = router; |