package com.ruoyi.app.mendian; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.system.domain.FoodSpecs; import com.ruoyi.system.domain.FoodSpecsValue; import com.ruoyi.system.service.IFoodSpecsService; import com.ruoyi.system.service.IFoodSpecsValueService; import com.ruoyi.system.utils.Auth; import com.ruoyi.system.utils.JwtUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * 商品规格管理 Controller(规格组 + 级联规格值) * 门店级可复用规格模板 * * @author ruoyi * @date 2026-07-15 */ @RestController @RequestMapping("/chanting/foodSpec") public class FoodSpecController extends BaseController { @Autowired private IFoodSpecsService foodSpecsService; @Autowired private IFoodSpecsValueService foodSpecsValueService; /** * 规格组分页列表(每条带 foodSpecsItems) */ @Anonymous @Auth @GetMapping("/foodSpecPageList") public AjaxResult foodSpecPageList(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam Long mdId, @RequestParam(required = false) String language) { Page page = new Page<>(pageNum, pageSize); LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.eq(FoodSpecs::getMdId, mdId); qw.eq(FoodSpecs::getIsDelete, false); if (language != null && !"".equals(language)) { qw.eq(FoodSpecs::getLanguage, language); } qw.orderByAsc(FoodSpecs::getSort).orderByDesc(FoodSpecs::getId); IPage result = foodSpecsService.page(page, qw); fillItems(result.getRecords(), false); return success(result); } /** * 保存规格组(含级联规格值):id<=0 新增,否则更新 */ @Anonymous @Auth @PostMapping("/saveFoodSpec") @Transactional(rollbackFor = Exception.class) public AjaxResult saveFoodSpec(@RequestBody List list) { if (list == null || list.isEmpty()) { return error(); } for (FoodSpecs s : list) { if (s.getIsDelete() == null) { s.setIsDelete(false); } if (s.getIsOpen() == null) { s.setIsOpen(true); } saveOrUpdateSpecs(s); // 级联规格值(全量替换:先删前端未保留的,再保存/新增) if (s.getObjects() != null) { removeDeletedValues(s); for (FoodSpecsValue v : s.getObjects()) { v.setParentId(s.getId()); if (v.getIsOpen() == null) { v.setIsOpen(true); } saveOrUpdateValue(v); } } } return success(); } private void saveOrUpdateSpecs(FoodSpecs s) { if (s.getId() == null || s.getId() <= 0) { s.setId(null); foodSpecsService.save(s); } else { foodSpecsService.updateById(s); } } private void saveOrUpdateValue(FoodSpecsValue v) { if (v.getId() == null || v.getId() <= 0) { v.setId(null); foodSpecsValueService.save(v); } else { foodSpecsValueService.updateById(v); } } /** * 规格值全量替换:删除该规格组下 DB 有、但前端 objects 未传(id>0 集合外)的规格值(物理删除)。 * 仅对已存在规格组(id>0)生效;新增规格组无历史数据,无需 diff。 */ private void removeDeletedValues(FoodSpecs s) { if (s.getId() == null || s.getId() <= 0) { return; } // 前端保留的规格值 id(id>0 的;id<=0 是新增,不参与 diff) List keepIds = s.getObjects().stream() .map(FoodSpecsValue::getId) .filter(id -> id != null && id > 0) .collect(Collectors.toList()); // DB 中该规格组的全部规格值 id List dbIds = foodSpecsValueService.list( new LambdaQueryWrapper() .eq(FoodSpecsValue::getParentId, s.getId()) .select(FoodSpecsValue::getId) ).stream().map(FoodSpecsValue::getId).collect(Collectors.toList()); // DB 有但前端没传 → 物理删除 List toRemove = dbIds.stream() .filter(id -> !keepIds.contains(id)) .collect(Collectors.toList()); if (!toRemove.isEmpty()) { foodSpecsValueService.removeByIds(toRemove); } } /** * 规格详情(带全部规格值) */ @Anonymous @Auth @GetMapping("/getSpecs") public AjaxResult getSpecs(@RequestParam Long id) { FoodSpecs s = foodSpecsService.getById(id); if (s != null) { List list = new ArrayList<>(); list.add(s); fillItems(list, false); } return success(s); } /** * 软删除规格组(is_delete=1) */ @Anonymous @Auth @GetMapping("/deleteFoodSpec") public AjaxResult deleteFoodSpec(@RequestParam Long id) { FoodSpecs s = new FoodSpecs(); s.setId(id); s.setIsDelete(true); return toAjax(foodSpecsService.updateById(s)); } /** * 商品编辑页:本门店可用规格(is_open=1 & is_delete=0,仅含 is_open=1 的规格值) */ @Anonymous @Auth @GetMapping("/getAvailableSpecsList") public AjaxResult getAvailableSpecsList(@RequestParam Long mdId, @RequestParam(required = false) String language) { LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.eq(FoodSpecs::getMdId, mdId); qw.eq(FoodSpecs::getIsOpen, true); qw.eq(FoodSpecs::getIsDelete, false); if (language != null && !"".equals(language)) { qw.eq(FoodSpecs::getLanguage, language); } qw.orderByAsc(FoodSpecs::getSort).orderByDesc(FoodSpecs::getId); List list = foodSpecsService.list(qw); fillItems(list, true); return success(list); } /** * 启停规格组 */ @Anonymous @Auth @GetMapping("/changeOpen") public AjaxResult changeOpen(@RequestParam Long id, @RequestParam Boolean isOpen) { FoodSpecs s = new FoodSpecs(); s.setId(id); s.setIsOpen(isOpen); return toAjax(foodSpecsService.updateById(s)); } /** * 启停规格值 */ @Anonymous @Auth @GetMapping("/changeSpecValueOpen") public AjaxResult changeSpecValueOpen(@RequestParam Long id, @RequestParam Boolean isOpen) { FoodSpecsValue v = new FoodSpecsValue(); v.setId(id); v.setIsOpen(isOpen); return toAjax(foodSpecsValueService.updateById(v)); } /** * 批量填充规格值到规格组(避免 N+1) * * @param onlyOpen true=只取 is_open=1 的规格值(用于商品可用规格) */ private void fillItems(List list, boolean onlyOpen) { if (list == null || list.isEmpty()) { return; } List ids = list.stream().map(FoodSpecs::getId).collect(Collectors.toList()); LambdaQueryWrapper qw = new LambdaQueryWrapper() .in(FoodSpecsValue::getParentId, ids) .orderByAsc(FoodSpecsValue::getId); if (onlyOpen) { qw.eq(FoodSpecsValue::getIsOpen, true); } List values = foodSpecsValueService.list(qw); Map> map = values.stream() .collect(Collectors.groupingBy(FoodSpecsValue::getParentId)); for (FoodSpecs s : list) { s.setObjects(map.getOrDefault(s.getId(), new ArrayList<>())); } } }