254 lines
13 KiB
JavaScript
254 lines
13 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const multer = require('multer');
|
|
const db = require('../db');
|
|
|
|
router.get('/', async (req, res) => {
|
|
try {
|
|
const { category_id, status, keyword } = req.query;
|
|
let query = `
|
|
SELECT p.*, pc.name as category_name, pc2.name as category_level1_name
|
|
FROM products p
|
|
LEFT JOIN product_categories pc ON p.category_id = pc.id
|
|
LEFT JOIN product_categories pc2 ON pc.parent_id = pc2.id
|
|
WHERE 1=1
|
|
`;
|
|
const params = [];
|
|
let paramIndex = 1;
|
|
if (category_id) {
|
|
const catResult = await db.query('SELECT parent_id FROM product_categories WHERE id = $1', [category_id]);
|
|
if (catResult.rows.length > 0 && !catResult.rows[0].parent_id) {
|
|
const childCategories = await db.query('SELECT id FROM product_categories WHERE parent_id = $1', [category_id]);
|
|
if (childCategories.rows.length > 0) {
|
|
const childIds = childCategories.rows.map(row => row.id);
|
|
const placeholders = childIds.map(() => '$' + paramIndex++).join(',');
|
|
query += ` AND p.category_id IN (${placeholders})`;
|
|
params.push(...childIds);
|
|
} else {
|
|
query += ' AND 1=0';
|
|
}
|
|
} else {
|
|
query += ` AND p.category_id = $${paramIndex++}`;
|
|
params.push(category_id);
|
|
}
|
|
}
|
|
if (status) {
|
|
query += ` AND p.status = $${paramIndex++}`;
|
|
params.push(status);
|
|
}
|
|
if (keyword) {
|
|
const searchTerm = `%${keyword}%`;
|
|
query += ` AND (p.name LIKE $${paramIndex} OR p.model LIKE $${paramIndex + 1} OR p.brand LIKE $${paramIndex + 2})`;
|
|
params.push(searchTerm, searchTerm, searchTerm);
|
|
paramIndex += 3;
|
|
}
|
|
query += ' ORDER BY p.created_at DESC';
|
|
const result = await db.query(query, params);
|
|
res.json({ success: true, data: result.rows, count: result.rows.length });
|
|
} catch (error) {
|
|
console.error('获取商品失败:', error);
|
|
res.status(500).json({ success: false, message: '获取商品失败' });
|
|
}
|
|
});
|
|
|
|
router.get('/template', (req, res) => {
|
|
try {
|
|
const XLSX = require('xlsx');
|
|
const templateData = [
|
|
{ '商品名称': 'JKLYJ-35-22kV', '型号': 'Model-001', '一级分类': '电缆电线', '二级分类': '高压电缆', '单位': '米', '成本单价': 12.50, '销售单价': 15.50, '品牌': '云南线缆', '规格参数': '35mm², 22kV', '来源': '中国', '备注': '示例商品' },
|
|
{ '商品名称': 'XP-70', '型号': 'XP-70', '一级分类': '电杆横担', '二级分类': '横担', '单位': '个', '成本单价': 20.00, '销售单价': 25.00, '品牌': '江西电瓷', '规格参数': '70kN', '来源': '老挝', '备注': '' }
|
|
];
|
|
const worksheet = XLSX.utils.json_to_sheet(templateData);
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, '商品导入模板');
|
|
const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' });
|
|
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
res.setHeader('Content-Disposition', 'attachment; filename="product_template.xlsx"');
|
|
res.send(buffer);
|
|
} catch (error) {
|
|
console.error('生成模板失败:', error);
|
|
res.status(500).json({ success: false, message: '生成模板失败' });
|
|
}
|
|
});
|
|
|
|
router.post('/batch-import', multer().single('file'), async (req, res) => {
|
|
try {
|
|
if (!req.file) {
|
|
return res.status(400).json({ success: false, message: '请上传文件' });
|
|
}
|
|
const XLSX = require('xlsx');
|
|
const workbook = XLSX.read(req.file.buffer, { type: 'buffer' });
|
|
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
|
|
const data = XLSX.utils.sheet_to_json(worksheet);
|
|
const imported = [];
|
|
const errors = [];
|
|
for (let i = 0; i < data.length; i++) {
|
|
const row = data[i];
|
|
try {
|
|
let level1Category = null;
|
|
if (row['一级分类']) {
|
|
const catResult = await db.query('SELECT id FROM product_categories WHERE name = $1 AND parent_id IS NULL', [row['一级分类']]);
|
|
if (catResult.rows.length > 0) {
|
|
level1Category = catResult.rows[0].id;
|
|
} else {
|
|
const newCatResult = await db.query('INSERT INTO product_categories (name) VALUES ($1) RETURNING id', [row['一级分类']]);
|
|
level1Category = newCatResult.rows[0].id;
|
|
}
|
|
}
|
|
let categoryId = null;
|
|
if (row['二级分类']) {
|
|
const catResult = await db.query('SELECT id FROM product_categories WHERE name = $1 AND parent_id = $2', [row['二级分类'], level1Category]);
|
|
if (catResult.rows.length > 0) {
|
|
categoryId = catResult.rows[0].id;
|
|
} else {
|
|
const newCatResult = await db.query('INSERT INTO product_categories (name, parent_id) VALUES ($1, $2) RETURNING id', [row['二级分类'], level1Category]);
|
|
categoryId = newCatResult.rows[0].id;
|
|
}
|
|
}
|
|
const result = await db.query(
|
|
`INSERT INTO products (name, model, category_id, unit, cost_price, price, brand, specification, source, remark, stock_quantity) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id`,
|
|
[row['商品名称'] || '', row['型号'] || '', categoryId, row['单位'] || '件', row['成本单价'] || 0, row['销售单价'] || 0, row['品牌'] || '', row['规格参数'] || '', row['来源'] || '老挝', row['备注'] || '', row['库存数量'] || 0]
|
|
);
|
|
imported.push({ row: i + 2, name: row['商品名称'], id: result.rows[0].id });
|
|
} catch (error) {
|
|
errors.push({ row: i + 2, name: row['商品名称'] });
|
|
}
|
|
}
|
|
res.json({ success: true, message: `导入完成,成功 ${imported.length} 条,失败 ${errors.length} 条`, imported, errors });
|
|
} catch (error) {
|
|
console.error('批量导入商品失败:', error);
|
|
res.status(500).json({ success: false, message: '批量导入商品失败' });
|
|
}
|
|
});
|
|
|
|
router.post('/import', multer().single('file'), async (req, res) => {
|
|
try {
|
|
if (!req.file) {
|
|
return res.status(400).json({ success: false, message: '请上传文件' });
|
|
}
|
|
const XLSX = require('xlsx');
|
|
const workbook = XLSX.read(req.file.buffer, { type: 'buffer' });
|
|
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
|
|
const data = XLSX.utils.sheet_to_json(worksheet);
|
|
const imported = [];
|
|
const errors = [];
|
|
for (let i = 0; i < data.length; i++) {
|
|
const row = data[i];
|
|
try {
|
|
let level1Category = null;
|
|
if (row['一级分类']) {
|
|
const catResult = await db.query('SELECT id FROM product_categories WHERE name = $1 AND parent_id IS NULL', [row['一级分类']]);
|
|
if (catResult.rows.length > 0) { level1Category = catResult.rows[0].id; }
|
|
else { const r = await db.query('INSERT INTO product_categories (name) VALUES ($1) RETURNING id', [row['一级分类']]); level1Category = r.rows[0].id; }
|
|
}
|
|
let categoryId = null;
|
|
if (row['二级分类']) {
|
|
const catResult = await db.query('SELECT id FROM product_categories WHERE name = $1 AND parent_id = $2', [row['二级分类'], level1Category]);
|
|
if (catResult.rows.length > 0) { categoryId = catResult.rows[0].id; }
|
|
else { const r = await db.query('INSERT INTO product_categories (name, parent_id) VALUES ($1, $2) RETURNING id', [row['二级分类'], level1Category]); categoryId = r.rows[0].id; }
|
|
}
|
|
const result = await db.query(
|
|
`INSERT INTO products (name, model, category_id, unit, cost_price, price, brand, specification, source, remark, stock_quantity) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id`,
|
|
[row['商品名称'] || '', row['型号'] || '', categoryId, row['单位'] || '件', row['成本单价'] || 0, row['销售单价'] || 0, row['品牌'] || '', row['规格参数'] || '', row['来源'] || '老挝', row['备注'] || '', row['库存数量'] || 0]
|
|
);
|
|
imported.push({ row: i + 2, name: row['商品名称'], id: result.rows[0].id });
|
|
} catch (error) {
|
|
errors.push({ row: i + 2, name: row['商品名称'] });
|
|
}
|
|
}
|
|
res.json({ success: true, message: `导入完成,成功 ${imported.length} 条,失败 ${errors.length} 条`, imported, errors });
|
|
} catch (error) {
|
|
console.error('导入商品失败:', error);
|
|
res.status(500).json({ success: false, message: '导入商品失败' });
|
|
}
|
|
});
|
|
|
|
router.get('/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const result = await db.query(`
|
|
SELECT p.*, pc.name as category_name, pc2.name as category_level1_name
|
|
FROM products p
|
|
LEFT JOIN product_categories pc ON p.category_id = pc.id
|
|
LEFT JOIN product_categories pc2 ON pc.parent_id = pc2.id
|
|
WHERE p.id = $1
|
|
`, [id]);
|
|
if (result.rows.length === 0) { return res.status(404).json({ success: false, message: '商品不存在' }); }
|
|
res.json({ success: true, data: result.rows[0] });
|
|
} catch (error) {
|
|
console.error('获取商品失败:', error);
|
|
res.status(500).json({ success: false, message: '获取商品失败' });
|
|
}
|
|
});
|
|
|
|
router.post('/', async (req, res) => {
|
|
try {
|
|
const { name, model, category_id, unit, cost_price, price, brand, specification, source, remark, stock_quantity, status } = req.body;
|
|
if (!name) { return res.status(400).json({ success: false, message: '商品名称不能为空' }); }
|
|
let categoryName = null;
|
|
if (category_id) {
|
|
const catResult = await db.query('SELECT name FROM product_categories WHERE id = $1', [category_id]);
|
|
if (catResult.rows.length > 0) { categoryName = catResult.rows[0].name; }
|
|
}
|
|
const result = await db.query(
|
|
`INSERT INTO products (name, model, category_id, category_name, unit, cost_price, price, brand, specification, source, remark, stock_quantity, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING *`,
|
|
[name, model || '', category_id || null, categoryName, unit || '件', cost_price || null, price || 0, brand || '', specification || '', source || '老挝', remark || '', stock_quantity || 0, status || 'active']
|
|
);
|
|
res.json({ success: true, data: result.rows[0], message: '创建成功' });
|
|
} catch (error) {
|
|
console.error('创建商品失败:', error);
|
|
res.status(500).json({ success: false, message: '创建商品失败' });
|
|
}
|
|
});
|
|
|
|
router.put('/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { name, model, category_id, unit, cost_price, price, brand, specification, source, remark, stock_quantity, stock_warning, status } = req.body;
|
|
let categoryName = null;
|
|
if (category_id !== undefined && category_id) {
|
|
const catResult = await db.query('SELECT name FROM product_categories WHERE id = $1', [category_id]);
|
|
if (catResult.rows.length > 0) { categoryName = catResult.rows[0].name; }
|
|
}
|
|
const updates = [];
|
|
const params = [];
|
|
let i = 1;
|
|
if (name !== undefined) { updates.push(`name = $${i++}`); params.push(name); }
|
|
if (model !== undefined) { updates.push(`model = $${i++}`); params.push(model || ''); }
|
|
if (category_id !== undefined) { updates.push(`category_id = $${i++}`); params.push(category_id || null); updates.push(`category_name = $${i++}`); params.push(categoryName); }
|
|
if (unit !== undefined) { updates.push(`unit = $${i++}`); params.push(unit || '件'); }
|
|
if (cost_price !== undefined) { updates.push(`cost_price = $${i++}`); params.push(cost_price || null); }
|
|
if (price !== undefined) { updates.push(`price = $${i++}`); params.push(price || 0); }
|
|
if (brand !== undefined) { updates.push(`brand = $${i++}`); params.push(brand || ''); }
|
|
if (specification !== undefined) { updates.push(`specification = $${i++}`); params.push(specification || ''); }
|
|
if (source !== undefined) { updates.push(`source = $${i++}`); params.push(source || '老挝'); }
|
|
if (remark !== undefined) { updates.push(`remark = $${i++}`); params.push(remark || ''); }
|
|
if (stock_quantity !== undefined) { updates.push(`stock_quantity = $${i++}`); params.push(stock_quantity || 0); }
|
|
if (stock_warning !== undefined) { updates.push(`stock_warning = $${i++}`); params.push(stock_warning || 10); }
|
|
if (status !== undefined) { updates.push(`status = $${i++}`); params.push(status || 'active'); }
|
|
if (updates.length === 0) { return res.status(400).json({ success: false, message: '没有提供更新数据' }); }
|
|
updates.push(`updated_at = CURRENT_TIMESTAMP`);
|
|
params.push(id);
|
|
const result = await db.query(`UPDATE products SET ${updates.join(', ')} WHERE id = $${i} RETURNING *`, params);
|
|
if (result.rows.length === 0) { return res.status(404).json({ success: false, message: '商品不存在' }); }
|
|
res.json({ success: true, data: result.rows[0], message: '更新成功' });
|
|
} catch (error) {
|
|
console.error('更新商品失败:', error);
|
|
res.status(500).json({ success: false, message: '更新商品失败' });
|
|
}
|
|
});
|
|
|
|
router.delete('/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const result = await db.query('DELETE FROM products WHERE id = $1 RETURNING id', [id]);
|
|
if (result.rows.length === 0) { return res.status(404).json({ success: false, message: '商品不存在' }); }
|
|
res.json({ success: true, message: '删除成功' });
|
|
} catch (error) {
|
|
console.error('删除商品失败:', error);
|
|
res.status(500).json({ success: false, message: '删除商品失败' });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|