/** * 菜品控制器 * * 功能:处理菜品和菜品分类的业务逻辑 */ 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 && category_id !== '' ? parseInt(category_id) : undefined, is_available: is_available !== undefined && is_available !== '' ? parseInt(is_available) : undefined, status: status !== undefined && status !== '' ? 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 };