forked from p4b8lshcr/ChefTronic
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
421 lines
9.7 KiB
421 lines
9.7 KiB
/**
|
|
* 菜品控制器
|
|
*
|
|
* 功能:处理菜品和菜品分类的业务逻辑
|
|
*/
|
|
|
|
const Dish = require('../models/Dish');
|
|
const DishCategory = require('../models/DishCategory');
|
|
const { validateRequired, validateBatch } = require('../utils/validator');
|
|
const { success } = require('../utils/response');
|
|
const { BusinessError, ValidationError } = require('../middleware/errorHandler');
|
|
const { getFileUrl, deleteUploadedFile, getFilenameFromUrl } = require('../middleware/upload');
|
|
|
|
// ========================================
|
|
// 菜品分类相关
|
|
// ========================================
|
|
|
|
/**
|
|
* 获取所有分类
|
|
* GET /api/dishes/categories
|
|
*/
|
|
const getCategories = async (req, res, next) => {
|
|
try {
|
|
const categories = await DishCategory.findAll();
|
|
res.json(success(categories, '获取分类列表成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 创建分类
|
|
* POST /api/dishes/categories
|
|
* 需要管理员权限
|
|
*/
|
|
const createCategory = async (req, res, next) => {
|
|
try {
|
|
const { name, description, sort_order } = req.body;
|
|
|
|
// 数据验证
|
|
const validation = validateBatch([
|
|
validateRequired(name, '分类名称')
|
|
]);
|
|
|
|
if (!validation.valid) {
|
|
throw new ValidationError('数据验证失败', validation.errors);
|
|
}
|
|
|
|
// 检查分类名称是否已存在
|
|
const nameExists = await DishCategory.isNameExists(name);
|
|
if (nameExists) {
|
|
throw new BusinessError('分类名称已存在', 400);
|
|
}
|
|
|
|
const category = await DishCategory.create({
|
|
name,
|
|
description,
|
|
sort_order
|
|
});
|
|
|
|
res.status(201).json(success(category, '创建分类成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 更新分类
|
|
* PUT /api/dishes/categories/:id
|
|
* 需要管理员权限
|
|
*/
|
|
const updateCategory = async (req, res, next) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { name, description, sort_order } = req.body;
|
|
|
|
// 检查分类是否存在
|
|
const category = await DishCategory.findById(id);
|
|
if (!category) {
|
|
throw new BusinessError('分类不存在', 404);
|
|
}
|
|
|
|
// 如果修改了名称,检查新名称是否已存在
|
|
if (name && name !== category.name) {
|
|
const nameExists = await DishCategory.isNameExists(name, id);
|
|
if (nameExists) {
|
|
throw new BusinessError('分类名称已存在', 400);
|
|
}
|
|
}
|
|
|
|
const updatedCategory = await DishCategory.update(id, {
|
|
name,
|
|
description,
|
|
sort_order
|
|
});
|
|
|
|
res.json(success(updatedCategory, '更新分类成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 删除分类
|
|
* DELETE /api/dishes/categories/:id
|
|
* 需要管理员权限
|
|
*/
|
|
const deleteCategory = async (req, res, next) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
// 检查分类是否存在
|
|
const category = await DishCategory.findById(id);
|
|
if (!category) {
|
|
throw new BusinessError('分类不存在', 404);
|
|
}
|
|
|
|
await DishCategory.delete(id);
|
|
|
|
res.json(success(null, '删除分类成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
// ========================================
|
|
// 菜品相关
|
|
// ========================================
|
|
|
|
/**
|
|
* 获取菜品列表(分页、筛选、搜索)
|
|
* GET /api/dishes
|
|
*/
|
|
const getDishes = async (req, res, next) => {
|
|
try {
|
|
const {
|
|
page = 1,
|
|
pageSize = 10,
|
|
category_id,
|
|
is_available,
|
|
status,
|
|
search
|
|
} = req.query;
|
|
|
|
const result = await Dish.findAll({
|
|
page: parseInt(page),
|
|
pageSize: parseInt(pageSize),
|
|
category_id: category_id ? parseInt(category_id) : undefined,
|
|
is_available: is_available !== undefined ? parseInt(is_available) : undefined,
|
|
status: status !== undefined ? parseInt(status) : undefined,
|
|
search
|
|
});
|
|
|
|
res.json(success({
|
|
list: result.list,
|
|
pagination: {
|
|
total: result.total,
|
|
page: result.page,
|
|
pageSize: result.pageSize,
|
|
totalPages: Math.ceil(result.total / result.pageSize)
|
|
}
|
|
}, '获取菜品列表成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 获取单个菜品详情
|
|
* GET /api/dishes/:id
|
|
*/
|
|
const getDishById = async (req, res, next) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const dish = await Dish.findById(id);
|
|
if (!dish) {
|
|
throw new BusinessError('菜品不存在', 404);
|
|
}
|
|
|
|
res.json(success(dish, '获取菜品详情成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 创建菜品
|
|
* POST /api/dishes
|
|
* 需要管理员权限
|
|
*/
|
|
const createDish = async (req, res, next) => {
|
|
try {
|
|
const {
|
|
category_id,
|
|
name,
|
|
price,
|
|
description,
|
|
taste,
|
|
ingredients,
|
|
cooking_time,
|
|
is_available
|
|
} = req.body;
|
|
|
|
// 数据验证
|
|
const validation = validateBatch([
|
|
validateRequired(category_id, '分类'),
|
|
validateRequired(name, '菜品名称'),
|
|
validateRequired(price, '价格')
|
|
]);
|
|
|
|
if (!validation.valid) {
|
|
throw new ValidationError('数据验证失败', validation.errors);
|
|
}
|
|
|
|
// 验证价格
|
|
if (parseFloat(price) <= 0) {
|
|
throw new ValidationError('价格必须大于0');
|
|
}
|
|
|
|
// 检查分类是否存在
|
|
const category = await DishCategory.findById(category_id);
|
|
if (!category) {
|
|
throw new BusinessError('分类不存在', 404);
|
|
}
|
|
|
|
// 处理图片(如果有上传)
|
|
let imageUrl = null;
|
|
if (req.file) {
|
|
imageUrl = getFileUrl(req.file.filename);
|
|
}
|
|
|
|
const dish = await Dish.create({
|
|
category_id,
|
|
name,
|
|
price: parseFloat(price),
|
|
image: imageUrl,
|
|
description,
|
|
taste,
|
|
ingredients,
|
|
cooking_time: cooking_time ? parseInt(cooking_time) : null,
|
|
is_available: is_available !== undefined ? parseInt(is_available) : 1
|
|
});
|
|
|
|
res.status(201).json(success(dish, '创建菜品成功'));
|
|
} catch (err) {
|
|
// 如果创建失败且上传了图片,删除图片
|
|
if (req.file) {
|
|
deleteUploadedFile(req.file.filename);
|
|
}
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 更新菜品
|
|
* PUT /api/dishes/:id
|
|
* 需要管理员权限
|
|
*/
|
|
const updateDish = async (req, res, next) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const {
|
|
category_id,
|
|
name,
|
|
price,
|
|
description,
|
|
taste,
|
|
ingredients,
|
|
cooking_time,
|
|
is_available,
|
|
status
|
|
} = req.body;
|
|
|
|
// 检查菜品是否存在
|
|
const dish = await Dish.findById(id);
|
|
if (!dish) {
|
|
throw new BusinessError('菜品不存在', 404);
|
|
}
|
|
|
|
// 如果修改了分类,检查新分类是否存在
|
|
if (category_id && category_id !== dish.category_id) {
|
|
const category = await DishCategory.findById(category_id);
|
|
if (!category) {
|
|
throw new BusinessError('分类不存在', 404);
|
|
}
|
|
}
|
|
|
|
// 验证价格
|
|
if (price !== undefined && parseFloat(price) <= 0) {
|
|
throw new ValidationError('价格必须大于0');
|
|
}
|
|
|
|
// 处理图片上传
|
|
let imageUrl = dish.image; // 保留原图片
|
|
if (req.file) {
|
|
// 删除旧图片
|
|
if (dish.image) {
|
|
const oldFilename = getFilenameFromUrl(dish.image);
|
|
deleteUploadedFile(oldFilename);
|
|
}
|
|
// 使用新图片
|
|
imageUrl = getFileUrl(req.file.filename);
|
|
}
|
|
|
|
const updatedDish = await Dish.update(id, {
|
|
category_id,
|
|
name,
|
|
price: price ? parseFloat(price) : undefined,
|
|
image: imageUrl,
|
|
description,
|
|
taste,
|
|
ingredients,
|
|
cooking_time: cooking_time ? parseInt(cooking_time) : undefined,
|
|
is_available: is_available !== undefined ? parseInt(is_available) : undefined,
|
|
status: status !== undefined ? parseInt(status) : undefined
|
|
});
|
|
|
|
res.json(success(updatedDish, '更新菜品成功'));
|
|
} catch (err) {
|
|
// 如果更新失败且上传了新图片,删除新图片
|
|
if (req.file) {
|
|
deleteUploadedFile(req.file.filename);
|
|
}
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 删除菜品
|
|
* DELETE /api/dishes/:id
|
|
* 需要管理员权限
|
|
*/
|
|
const deleteDish = async (req, res, next) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
// 检查菜品是否存在
|
|
const dish = await Dish.findById(id);
|
|
if (!dish) {
|
|
throw new BusinessError('菜品不存在', 404);
|
|
}
|
|
|
|
// 删除菜品
|
|
await Dish.delete(id);
|
|
|
|
// 删除关联的图片文件
|
|
if (dish.image) {
|
|
const filename = getFilenameFromUrl(dish.image);
|
|
deleteUploadedFile(filename);
|
|
}
|
|
|
|
res.json(success(null, '删除菜品成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 更新菜品可售状态
|
|
* PUT /api/dishes/:id/availability
|
|
* 需要管理员或服务员权限
|
|
*/
|
|
const updateDishAvailability = async (req, res, next) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { is_available } = req.body;
|
|
|
|
// 验证is_available值
|
|
if (is_available !== 0 && is_available !== 1) {
|
|
throw new ValidationError('可售状态值必须为0或1');
|
|
}
|
|
|
|
// 检查菜品是否存在
|
|
const dish = await Dish.findById(id);
|
|
if (!dish) {
|
|
throw new BusinessError('菜品不存在', 404);
|
|
}
|
|
|
|
await Dish.updateAvailability(id, is_available);
|
|
|
|
res.json(success(null, is_available === 1 ? '菜品已上架' : '菜品已下架'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 获取热门菜品
|
|
* GET /api/dishes/popular
|
|
*/
|
|
const getPopularDishes = async (req, res, next) => {
|
|
try {
|
|
const { limit = 10 } = req.query;
|
|
|
|
const dishes = await Dish.findPopular(parseInt(limit));
|
|
|
|
res.json(success(dishes, '获取热门菜品成功'));
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
// 导出所有控制器函数
|
|
module.exports = {
|
|
// 分类相关
|
|
getCategories,
|
|
createCategory,
|
|
updateCategory,
|
|
deleteCategory,
|
|
|
|
// 菜品相关
|
|
getDishes,
|
|
getDishById,
|
|
createDish,
|
|
updateDish,
|
|
deleteDish,
|
|
updateDishAvailability,
|
|
getPopularDishes
|
|
};
|