
┌─────────────────────────────────────────────────────────────┐
│ 菜品管理 > 菜品列表 [+新增菜品] │
├─────────────────────────────────────────────────────────────┤
│ [菜品名称] [分类选择▼] [状态▼] [查询] [重置] │
├─────────────────────────────────────────────────────────────┤
│ □ 菜品图片 菜品名称 分类 价格 状态 最后更新时间 操作 │
├─────────────────────────────────────────────────────────────┤
│ □ [图片] 宫保鸡丁 川菜 ¥38 ● 起售 2024-01-15 [修改] [停售] │
│ □ [图片] 麻婆豆腐 川菜 ¥28 ● 起售 2024-01-15 [修改] [停售] │
│ □ [图片] 北京烤鸭 主打菜 ¥88 ○ 停售 2024-01-14 [修改] [起售] │
│ □ [图片] 西湖醋鱼 浙菜 ¥68 ● 起售 2024-01-13 [修改] [停售] │
├─────────────────────────────────────────────────────────────┤
│ < 1 2 3 4 5 ... 10 > │
│ 每页10条 共43条记录 │
└─────────────────────────────────────────────────────────────┘A. 顶部操作区
组件 | 类型 | 作用 |
|---|---|---|
菜品名称 | 输入框 | 模糊搜索 |
分类选择 | 下拉框 | 按菜品分类筛选 |
状态 | 下拉框 | 起售/停售筛选 |
查询按钮 | 按钮 | 触发查询 |
重置按钮 | 按钮 | 清空条件 |
表头设计:
状态标识:
操作按钮:
D分页区
根据页码展示菜品信息,每页展示十条菜品数据,分页查询时可以根据需要根据菜品名称,菜品分类,菜品状态进行查询。
逻辑图示:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 前端 │ │ 后端 │ │ 数据库 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
│ 1. 用户输入查询条件 │ │
│────────────────────>│ │
│ │ │
│ 2. 点击查询按钮 │ │
│────────────────────>│ │
│ │ │
│ 3. 封装请求参数 │ │
│ GET /admin/dish/page│ │
│ ?page=1&pageSize=10 │ │
│ &name=宫保&status=1 │ │
│────────────────────>│ │
│ │ │
│ │ 4. 接收参数 │
│ │ DishPageQueryDTO │
│ │────────────────────>│
│ │ │
│ │ 5. 调用Service │
│ │ pageQuery() │
│ │────────────────────>│
│ │ │
│ │ │ 6. 执行SQL
│ │ │ SELECT...
│ │ │<───┘
│ │ 7. 返回PageResult │
│ │ (total+records) │
│ │<────────────────────│
│ │ │
│ 8. 返回JSON数据 │ │
│ <────────────────────│ │
│ │ │
│ 9. 渲染表格和分页 │ │
│────────────────────>│ │Controller:
pageResult是后端封装分页查询结果的统一对象,它包含了总记录数(total)和当前页数据列表(records)两个核心信息,前端拿到后就能知道总共有多少条数据、当前页显示哪些数据。
@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> page (DishPageQueryDTO dishPageQueryDTO){
log.info("菜品分页查询,#{}",dishPageQueryDTO);
PageResult pageResult= dishService.pageQuery(dishPageQueryDTO);
return Result.success(pageResult);PageHelper.startPagedishMapper.pageQuery() —— 执行查询
new PageResult() —— 结果封装
page.getResult() 返回的就是:
它是连接数据库原始数据和前端展示界面的桥梁,是分页查询中最核心的数据载体。
/**
* 菜品分页查询
* @param dishPageQueryDTO
* @return
*/
public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize());
Page<DishVO> page=dishMapper.pageQuery(dishPageQueryDTO);
return new PageResult(page.getTotal(),page.getResult());为什么不需要手动拼接page和pageSize?
PageHelper的魔法本质:
<select id="pageQuery" resultType="com.sky.vo.DishVO">
select d.*,c.name as categoryName from sky_take_out.dish d left outer join sky_take_out.category c on d.category_id = c.id
<where>
<if test="name!=null">
and d.name like concat('%',#{name},'%')
</if>>
<if test="categoryId!=null">
and d.category_id=#{categoryId}
</if>>
<if test="status!=null">
and d.status=#{status}
</if>>
</where>>
</select>因为我们要查询的是菜品分类的名字而不是ID,所以要查询两张表,
// 2.1 你写的SQL(原始)
SELECT d.*, c.name as categoryName
FROM dish d LEFT JOIN category c ON d.category_id = c.id
WHERE d.name LIKE '%鸡%'
// 2.2 PageHelper拦截后,实际执行了2条SQL
// 第1条SQL:自动执行的COUNT查询
SELECT COUNT(*) FROM (
SELECT d.*, c.name as categoryName
FROM dish d LEFT JOIN category c ON d.category_id = c.id
WHERE d.name LIKE '%鸡%'
) tmp_count;
// 第2条SQL:自动添加LIMIT的分页查询
SELECT d.*, c.name as categoryName
FROM dish d LEFT JOIN category c ON d.category_id = c.id
WHERE d.name LIKE '%鸡%'
LIMIT 0, 10; -- PageHelper自动添加的!
// 第3步:PageHelper把两条结果包装成Page对象返回
// page对象包含了total(43)和list(10条数据)需求分析和设计: 可以一次删除一个菜品,也可以一次删除多个菜品 起售的菜品不能删除 被套餐关联的菜品不能删除 删除菜品后,关联的菜品口味数据也要删除 数据库设计: dish表,dish-flavor表,setmeal-dish表
package com.sky.controller.admin;
/**
* 菜品管理Controller
* 接收前端删除菜品的请求
*/
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品管理")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
/**
* 删除菜品(支持批量)
* @param ids 菜品ID列表,例如:ids=1,2,3
* @return
*/
@DeleteMapping
@ApiOperation("删除菜品")
public Result delete(@RequestParam List<Long> ids) {
log.info("删除菜品,ids:{}", ids);
// 调用Service层执行业务
dishService.deleteBatch(ids);
return Result.success();
}
}/**
* 解析:
* 1. @DeleteMapping:处理HTTP DELETE请求
* 2. @RequestParam List<Long> ids:接收URL参数,如 /admin/dish?ids=1,2,3
* 3. 直接调用Service,Controller不处理业务逻辑
*/package com.sky.service;
public interface DishService {
/**
* 批量删除菜品
* @param ids 菜品ID列表
*/
void deleteBatch(List<Long> ids);
}
/**
* 解析:
* 1. 定义业务接口,明确功能
* 2. 批量删除方法,接收ID列表
*/package com.sky.service.impl;
/**
* 菜品管理Service实现类
* 核心业务逻辑都在这里
*/
@Service
@Slf4j
public class DishServiceImpl implements DishService {
@Autowired
private DishMapper dishMapper;
@Autowired
private DishFlavorMapper dishFlavorMapper;
@Autowired
private SetmealDishMapper setmealDishMapper;
/**
* 批量删除菜品
* @param ids 菜品ID列表
*/
@Override
@Transactional // 事务注解,保证数据一致性
public void deleteBatch(List<Long> ids) {
log.info("批量删除菜品,ids:{}", ids);
// ========== 第1步:参数校验 ==========
if (ids == null || ids.isEmpty()) {
throw new BusinessException("请选择要删除的菜品");
}
// ========== 第2步:检查菜品状态(是否起售) ==========
// 根据ID列表查询所有菜品信息
List<Dish> dishList = dishMapper.selectByIds(ids);
for (Dish dish : dishList) {
if (dish.getStatus() == 1) { // 1表示起售
throw new BusinessException("菜品【" + dish.getName() + "】正在起售中,不能删除");
}
}
// ========== 第3步:检查是否被套餐关联 ==========
// 根据菜品ID查询关联的套餐ID
List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids);
if (setmealIds != null && setmealIds.size() > 0) {
// 查询套餐名称用于提示
List<Setmeal> setmeals = setmealMapper.selectByIds(setmealIds);
String setmealNames = setmeals.stream()
.map(Setmeal::getName)
.collect(Collectors.joining("、"));
throw new BusinessException("菜品已被套餐【" + setmealNames + "】关联,不能删除");
}
// ========== 第4步:删除关联的口味数据 ==========
dishFlavorMapper.deleteByDishIds(ids);
log.info("删除菜品关联的口味数据,dishIds:{}", ids);
// ========== 第5步:删除菜品本身 ==========
dishMapper.deleteByIds(ids);
log.info("删除菜品成功,ids:{}", ids);
}
}package com.sky.mapper;
@Mapper
public interface DishMapper {
/**
* 根据ID列表批量查询菜品
* @param ids 菜品ID列表
* @return 菜品列表
*/
List<Dish> selectByIds(@Param("ids") List<Long> ids);
/**
* 根据ID列表批量删除菜品
* @param ids 菜品ID列表
*/
void deleteByIds(@Param("ids") List<Long> ids);
}根据主键 id查询菜品的起售状态,主键id返回所有的字段,所以可以调用查询字段的方法
id = 1)
ids = [1,2,3])
场景 | 命名 | 类型 | 示例 | 说明 |
|---|---|---|---|---|
主键ID | id | Long | dish.getId() | 实体类的主键字段 |
外键ID | xxxId | Long | flavor.setDishId() | 指向其他表的外键 |
批量主键 | ids | List<Long> | deleteByIds(ids) | 多个主键的集合 |
批量外键 | xxxIds | List<Long> | deleteByDishIds(dishIds) | 多个外键的集合 |
遍历元素 | item名 | 类型 | foreach中的item | 取决于业务含义 |
对比维度 | id | ids | dishId |
|---|---|---|---|
数据类型 | Long(单个数值) | List<Long>(集合) | Long(单个数值) |
英文含义 | 单数:一个ID | 复数:多个ID | 单数:菜品的ID |
数量 | 1个 | N个(≥1) | 1个 |
所属表 | 当前操作的表 | 当前操作的表 | 通常是外键,指向dish表 |
在SQL中的位置 | WHERE id = #{id} | WHERE id IN (1,2,3) | WHERE dish_id = #{dishId} |
业务含义 | 当前表的主键 | 当前表的主键集合 | 菜品表的外键 |
典型场景 | 删除单个菜品 | 批量删除菜品 | 删除菜品的口味 |
结语:如果对你有帮助,请,点赞,关注,收藏,你的支持就是我最大的动力!!