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

/**
* 菜品控制器
*
* 功能:处理菜品和菜品分类的业务逻辑
*/
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
};