FoodSpecController.java 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. package com.ruoyi.app.mendian;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import com.baomidou.mybatisplus.core.metadata.IPage;
  4. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  5. import com.ruoyi.common.annotation.Anonymous;
  6. import com.ruoyi.common.core.controller.BaseController;
  7. import com.ruoyi.common.core.domain.AjaxResult;
  8. import com.ruoyi.system.domain.FoodSpecs;
  9. import com.ruoyi.system.domain.FoodSpecsValue;
  10. import com.ruoyi.system.service.IFoodSpecsService;
  11. import com.ruoyi.system.service.IFoodSpecsValueService;
  12. import com.ruoyi.system.utils.Auth;
  13. import com.ruoyi.system.utils.JwtUtil;
  14. import org.springframework.beans.factory.annotation.Autowired;
  15. import org.springframework.transaction.annotation.Transactional;
  16. import org.springframework.web.bind.annotation.*;
  17. import java.util.ArrayList;
  18. import java.util.List;
  19. import java.util.Map;
  20. import java.util.stream.Collectors;
  21. /**
  22. * 商品规格管理 Controller(规格组 + 级联规格值)
  23. * 门店级可复用规格模板
  24. *
  25. * @author ruoyi
  26. * @date 2026-07-15
  27. */
  28. @RestController
  29. @RequestMapping("/chanting/foodSpec")
  30. public class FoodSpecController extends BaseController
  31. {
  32. @Autowired
  33. private IFoodSpecsService foodSpecsService;
  34. @Autowired
  35. private IFoodSpecsValueService foodSpecsValueService;
  36. /**
  37. * 规格组分页列表(每条带 foodSpecsItems)
  38. */
  39. @Anonymous
  40. @Auth
  41. @GetMapping("/foodSpecPageList")
  42. public AjaxResult foodSpecPageList(@RequestParam(defaultValue = "1") Integer pageNum,
  43. @RequestParam(defaultValue = "10") Integer pageSize,
  44. @RequestParam Long mdId,
  45. @RequestParam(required = false) String language) {
  46. Page<FoodSpecs> page = new Page<>(pageNum, pageSize);
  47. LambdaQueryWrapper<FoodSpecs> qw = new LambdaQueryWrapper<>();
  48. qw.eq(FoodSpecs::getMdId, mdId);
  49. qw.eq(FoodSpecs::getIsDelete, false);
  50. if (language != null && !"".equals(language)) {
  51. qw.eq(FoodSpecs::getLanguage, language);
  52. }
  53. qw.orderByAsc(FoodSpecs::getSort).orderByDesc(FoodSpecs::getId);
  54. IPage<FoodSpecs> result = foodSpecsService.page(page, qw);
  55. fillItems(result.getRecords(), false);
  56. return success(result);
  57. }
  58. /**
  59. * 保存规格组(含级联规格值):id<=0 新增,否则更新
  60. */
  61. @Anonymous
  62. @Auth
  63. @PostMapping("/saveFoodSpec")
  64. @Transactional(rollbackFor = Exception.class)
  65. public AjaxResult saveFoodSpec(@RequestBody List<FoodSpecs> list) {
  66. if (list == null || list.isEmpty()) {
  67. return error();
  68. }
  69. for (FoodSpecs s : list) {
  70. if (s.getIsDelete() == null) {
  71. s.setIsDelete(false);
  72. }
  73. if (s.getIsOpen() == null) {
  74. s.setIsOpen(true);
  75. }
  76. saveOrUpdateSpecs(s);
  77. // 级联规格值(全量替换:先删前端未保留的,再保存/新增)
  78. if (s.getObjects() != null) {
  79. removeDeletedValues(s);
  80. for (FoodSpecsValue v : s.getObjects()) {
  81. v.setParentId(s.getId());
  82. if (v.getIsOpen() == null) {
  83. v.setIsOpen(true);
  84. }
  85. saveOrUpdateValue(v);
  86. }
  87. }
  88. }
  89. return success();
  90. }
  91. private void saveOrUpdateSpecs(FoodSpecs s) {
  92. if (s.getId() == null || s.getId() <= 0) {
  93. s.setId(null);
  94. foodSpecsService.save(s);
  95. } else {
  96. foodSpecsService.updateById(s);
  97. }
  98. }
  99. private void saveOrUpdateValue(FoodSpecsValue v) {
  100. if (v.getId() == null || v.getId() <= 0) {
  101. v.setId(null);
  102. foodSpecsValueService.save(v);
  103. } else {
  104. foodSpecsValueService.updateById(v);
  105. }
  106. }
  107. /**
  108. * 规格值全量替换:删除该规格组下 DB 有、但前端 objects 未传(id>0 集合外)的规格值(物理删除)。
  109. * 仅对已存在规格组(id>0)生效;新增规格组无历史数据,无需 diff。
  110. */
  111. private void removeDeletedValues(FoodSpecs s) {
  112. if (s.getId() == null || s.getId() <= 0) {
  113. return;
  114. }
  115. // 前端保留的规格值 id(id>0 的;id<=0 是新增,不参与 diff)
  116. List<Long> keepIds = s.getObjects().stream()
  117. .map(FoodSpecsValue::getId)
  118. .filter(id -> id != null && id > 0)
  119. .collect(Collectors.toList());
  120. // DB 中该规格组的全部规格值 id
  121. List<Long> dbIds = foodSpecsValueService.list(
  122. new LambdaQueryWrapper<FoodSpecsValue>()
  123. .eq(FoodSpecsValue::getParentId, s.getId())
  124. .select(FoodSpecsValue::getId)
  125. ).stream().map(FoodSpecsValue::getId).collect(Collectors.toList());
  126. // DB 有但前端没传 → 物理删除
  127. List<Long> toRemove = dbIds.stream()
  128. .filter(id -> !keepIds.contains(id))
  129. .collect(Collectors.toList());
  130. if (!toRemove.isEmpty()) {
  131. foodSpecsValueService.removeByIds(toRemove);
  132. }
  133. }
  134. /**
  135. * 规格详情(带全部规格值)
  136. */
  137. @Anonymous
  138. @Auth
  139. @GetMapping("/getSpecs")
  140. public AjaxResult getSpecs(@RequestParam Long id) {
  141. FoodSpecs s = foodSpecsService.getById(id);
  142. if (s != null) {
  143. List<FoodSpecs> list = new ArrayList<>();
  144. list.add(s);
  145. fillItems(list, false);
  146. }
  147. return success(s);
  148. }
  149. /**
  150. * 软删除规格组(is_delete=1)
  151. */
  152. @Anonymous
  153. @Auth
  154. @GetMapping("/deleteFoodSpec")
  155. public AjaxResult deleteFoodSpec(@RequestParam Long id) {
  156. FoodSpecs s = new FoodSpecs();
  157. s.setId(id);
  158. s.setIsDelete(true);
  159. return toAjax(foodSpecsService.updateById(s));
  160. }
  161. /**
  162. * 商品编辑页:本门店可用规格(is_open=1 & is_delete=0,仅含 is_open=1 的规格值)
  163. */
  164. @Anonymous
  165. @Auth
  166. @GetMapping("/getAvailableSpecsList")
  167. public AjaxResult getAvailableSpecsList(@RequestParam Long mdId,
  168. @RequestParam(required = false) String language) {
  169. LambdaQueryWrapper<FoodSpecs> qw = new LambdaQueryWrapper<>();
  170. qw.eq(FoodSpecs::getMdId, mdId);
  171. qw.eq(FoodSpecs::getIsOpen, true);
  172. qw.eq(FoodSpecs::getIsDelete, false);
  173. if (language != null && !"".equals(language)) {
  174. qw.eq(FoodSpecs::getLanguage, language);
  175. }
  176. qw.orderByAsc(FoodSpecs::getSort).orderByDesc(FoodSpecs::getId);
  177. List<FoodSpecs> list = foodSpecsService.list(qw);
  178. fillItems(list, true);
  179. return success(list);
  180. }
  181. /**
  182. * 启停规格组
  183. */
  184. @Anonymous
  185. @Auth
  186. @GetMapping("/changeOpen")
  187. public AjaxResult changeOpen(@RequestParam Long id, @RequestParam Boolean isOpen) {
  188. FoodSpecs s = new FoodSpecs();
  189. s.setId(id);
  190. s.setIsOpen(isOpen);
  191. return toAjax(foodSpecsService.updateById(s));
  192. }
  193. /**
  194. * 启停规格值
  195. */
  196. @Anonymous
  197. @Auth
  198. @GetMapping("/changeSpecValueOpen")
  199. public AjaxResult changeSpecValueOpen(@RequestParam Long id, @RequestParam Boolean isOpen) {
  200. FoodSpecsValue v = new FoodSpecsValue();
  201. v.setId(id);
  202. v.setIsOpen(isOpen);
  203. return toAjax(foodSpecsValueService.updateById(v));
  204. }
  205. /**
  206. * 批量填充规格值到规格组(避免 N+1)
  207. *
  208. * @param onlyOpen true=只取 is_open=1 的规格值(用于商品可用规格)
  209. */
  210. private void fillItems(List<FoodSpecs> list, boolean onlyOpen) {
  211. if (list == null || list.isEmpty()) {
  212. return;
  213. }
  214. List<Long> ids = list.stream().map(FoodSpecs::getId).collect(Collectors.toList());
  215. LambdaQueryWrapper<FoodSpecsValue> qw = new LambdaQueryWrapper<FoodSpecsValue>()
  216. .in(FoodSpecsValue::getParentId, ids)
  217. .orderByAsc(FoodSpecsValue::getId);
  218. if (onlyOpen) {
  219. qw.eq(FoodSpecsValue::getIsOpen, true);
  220. }
  221. List<FoodSpecsValue> values = foodSpecsValueService.list(qw);
  222. Map<Long, List<FoodSpecsValue>> map = values.stream()
  223. .collect(Collectors.groupingBy(FoodSpecsValue::getParentId));
  224. for (FoodSpecs s : list) {
  225. s.setObjects(map.getOrDefault(s.getId(), new ArrayList<>()));
  226. }
  227. }
  228. }