springboot项目:瑞吉外卖 前后端详细分析 part3

您所在的位置:网站首页 瑞吉外卖前端代码 springboot项目:瑞吉外卖 前后端详细分析 part3

springboot项目:瑞吉外卖 前后端详细分析 part3

2024-07-11 19:16| 来源: 网络整理| 查看: 265

part 1 第二部分链接part 2 part 3 本文章 part 4

文章目录 4 菜品管理4.1 文件上传4.1.1 整体思路分析4.1.2 前端分析4.1.3 后端代码分析 4.2 文件下载4.2.1 整体逻辑4.2.2 前端代码分析4.2.3 后端代码分析 4.3 新增菜品4.3.1 整体分析4.3.2 前端思路分析4.3.2.1 选择菜品分类对应的下拉框4.3.2.2 口味管理4.3.2.3 图片上传下载 4.3.3 后端代码 4.4 菜品信息分页查询4.4.1 思路整理4.4.2 前端分析4.2.3 后端代码分析 4.5 修改菜品信息4.5.1 整体分析4.5.2 前端分析4.5.3 后端分析 5 套餐管理(见part4)

4 菜品管理

4.1 文件上传(后端为什么要返回文件名给前端、yml中自定义路径值并在类中取出、文件如何转存到指定位置、用UUID防止文件名称重复造成文件覆盖) 4.2 文件下载 4.3 新增菜品 (设计多表操作,事务保证一致性,DTO的使用,自己编写controller,值得学习) 4.4 菜品信息分页查询 (多表联合操作、Dto进一步使用) 4.5 修改菜品 ()

4.1 文件上传 4.1.1 整体思路分析 知识点介绍

在这里插入图片描述在这里插入图片描述

具体实现介绍 在这里插入图片描述 上面那段代码会动态地改变元素,生成下面这个input标签

在这里插入图片描述

后面要做的就是写一个controller,接收前端发来的请求 4.1.2 前端分析 upload.html文件上传页面 ... new Vue({ el: '#food-add-app', data() { return { imageUrl: '' } }, methods: { handleAvatarSuccess (response, file, fileList) { this.imageUrl = `/common/download?name=${response.data}` }, beforeUpload (file) { if(file){ const suffix = file.name.split('.')[1] const size = file.size / 1024 / 1024 vue 中的ajax与后端交互,将category表中所有的type=1的数据查出来(把所有菜品查出来),并存为一个list(vue中定义为一个list,然后使用v-for去遍历这个list显示到下拉框中),存在RetObj.sucess(List data) 在这里插入图片描述 // 获取菜品分类 getDishList () { //下面这个方法封装到js文件中,在表中,1是菜品分类,2是套餐分类。 //把type传给后端来查询(直接用一个对象接住,就会自动为这个对象的某些属性赋值),后端可以用一个对象来接收,因为以后可能不止一个type,还有其他参数 getCategoryList({ 'type': 1 }).then(res => { if (res.code === 1) { this.dishList = res.data } else { this.$message.error(res.msg || '操作失败') } }) }, getCategoryList() // 获取菜品分类列表 const getCategoryList = (params) => { return $axios({ url: '/category/list', method: 'get', params }) } 4.3.2.2 口味管理

在这里插入图片描述

// 按钮 - 添加口味 addFlavore () { this.dishFlavors.push({'name': '', 'value': [], showOption: false}) // JSON.parse(JSON.stringify(this.dishFlavorsData)) }, // 按钮 - 删除口味 delFlavor (ind) { this.dishFlavors.splice(ind, 1) }, // 按钮 - 删除口味标签 delFlavorLabel (index, ind) { this.dishFlavors[index].value.splice(ind, 1) }, //口味位置记录 flavorPosition (index) { this.index = index }, // 添加口味标签 keyDownHandle (val,index) { console.log('keyDownHandle----val',val) console.log('keyDownHandle----index',index) console.log('keyDownHandle----this.dishFlavors',this.dishFlavors) if (event) { event.cancelBubble = true event.preventDefault() event.stopPropagation() } if (val.target.innerText.trim() != '') { this.dishFlavors[index].value.push(val.target.innerText) val.target.innerText = '' } }, // 获取口味列表 getFlavorListHand () { // flavor flavorData this.dishFlavorsData = [ {'name':'甜味','value':['无糖','少糖','半糖','多糖','全糖']}, {'name':'温度','value':['热饮','常温','去冰','少冰','多冰']}, {'name':'忌口','value':['不要葱','不要蒜','不要香菜','不要辣']}, {'name':'辣度','value':['不辣','微辣','中辣','重辣']} ] }, 4.3.2.3 图片上传下载 这里直接使用之前的图片上传下载功能。 4.3.3 后端代码 获取分类列表 /** * 通过category中的type = 1/0 查出对于的菜品分类或是套餐分类,显示到前端的下拉框中 * * 需求:查出所有菜品分类,并以优先级sort排序、再以updatetime排序 * * 注意,lambdaQueryWrapper.eq(R column, Object val);这里是两个参数,一个是字段,一个是要匹配的值 * lambdaQueryWrapper.orderByDesc(R column) ,只要指定那个列就行了!因为不和上面那样,需要比较 */ @GetMapping("/food/list/getCategory.do") public RetObj getCategoryList(Category category){ LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper(); lambdaQueryWrapper.eq(Category::getType,category.getType()) .orderByAsc(Category::getSort) .orderByDesc(Category::getUpdateTime); List categoryList = categoryService.list(lambdaQueryWrapper); log.info("查询出菜品:{}",categoryList); return RetObj.success(categoryList); } 对数据进行插入,前端提交的表单如下。涉及到两张表的操作。每个数据都是以json的形式提交的,注意使用注解@RequestBody 其中flavors里面的数据是一个一个的数组,每个数组中存的是json0。不可以直接封装到Dish实体类中,因为Dish实体类不包含favors对应的字段。

在这里插入图片描述

使用DTO构建一个类,这个类保护两个表的字段。

Lombok中的@Data : 注在类上,提供类的get、set、equals、hashCode、canEqual、toString方法

要充分理解DishFlavor这个表,每一条数据就是对应的name比如甜味,然后value对应“无糖…”,因此思路就是后端使用List 来将每一条数据封装到一个DishFlavor对象中去,最后构成一个List集合。 package cn.edu.uestc.ruijitakeout.backend.dto; import cn.edu.uestc.ruijitakeout.backend.domain.Dish; import cn.edu.uestc.ruijitakeout.backend.domain.DishFlavor; import lombok.Data; import java.util.ArrayList; import java.util.List; @Data public class DishDto extends Dish { /* flavors中的所有信息都是通过一个数组传给后端,数组中每个元素是json类型 name、value 就是DishFlavor中最主要的信息,当然,还要绑定上对应的分类id, 后面使用注解,就可以将数组中json解析到DishFlavor对象中,因此, [ { "name": "甜味", "value": "[\"无糖\",\"少糖\",\"半糖\",\"多糖\",\"全糖\"]", "showOption": false }, { "name": "温度", "value": "[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]", "showOption": false } ] */ //错误的点:这里属性名要和前端的保持一致,否者无法注入! //private List flavorsList = new ArrayList(); private List flavors = new ArrayList(); private String categoryName; private Integer copies; } 后端接收到前端数据并封装到DishDto类中后,应该编写service,使用DishDto对象同时操作两张表。dish、dish_flavor。其中dish_f表中的字段dish_id,使用DishDto中的dish_id来插入(口味肯定是描述某道菜的口味,相当于主键副键)。另外,前端一次性传过来多条数据,就是多个dish_flavor (表中行数),对应的是一道菜,多个口味选项。 public interface DishService extends IService { //新增菜品,同时插入菜品对应的口味数据,需要操作两张表:dish、dish_flavor public void saveWithFlavor(DishDto dishDto); ... 加入事务控制,保证两张表的一致性 @Service public class DishServiceImpl extends ServiceImpl implements DishService{ @Resource private DishFlavorService dishFlavorService; @Override //这个方法对多张表进行了操作,需要保证数据一致性!,在Application中也要加上对应的注解 @EnableTransactionManagement @Transactional public void saveWithFlavor(DishDto dishDto) { //为dish表添加数据 this.save(dishDto); Long dishId = dishDto.getId(); List flavorsList = dishDto.getFlavorsList(); flavorsList.forEach(data -> data.setDish_id(dishId)); //为dishFlavor表添加数据 dishFlavorService.saveBatch(flavorsList); } } 4.4 菜品信息分页查询 4.4.1 思路整理

在这里插入图片描述

不止涉及一张表。主要是菜品表,要显示菜品名称、售价、售卖状态、最后操作时间、操作等。除此之外,还需要显示图片(之前已经写好了,会自己调用,发送完ajax请求后会自动在发一个请求进行图片下载,注意改下载的请求地址)、菜品分类的名称。解决菜品分类的名称问题:这里菜品分类名称通过dish表中,只有一个category_id,而没有这个id对应的菜品名字),在后端返回的时候return RetObj.success(pageInfo); 这个pageInfo实际上是Page类型里面并没有封装菜品名称这个属性,也就是说这个泛型Dish不满足要求。思路是之前就使用DishDto涵括了:dish(继承了它)、flavor。现在在这个DishDto中再加一个属性categoryName,就满足了。(当然自己写sql多表(dish表和catogory表)联合查询也能直接解决问题,因为mybatis_plus只有单表的 )交互过程。其中name是模糊查询的条件 在这里插入图片描述 4.4.2 前端分析

前端代码平平无奇,和之前的很相似,这一部分难的在后端的处理上。

页面展示都是类似的。以图片下载为例,主要就是使用vue的组件 发送请求的vue代码,page和pageSize都有了初始值。返回值pageInfo下的records,就是封装好的每一条数据 methods: { async init () { //先构造一个对象 const params = { page: this.page, pageSize: this.pageSize, name: this.input ? this.input : undefined } await getDishPage(params).then(res => { if (String(res.code) === '1') { this.tableData = res.data.records || [] this.counts = res.data.total } }).catch(err => { this.$message.error('请求出错了:' + err) }) }, getImage (image) { return `/backend/page/upload/download.do?name=${image}` }, handleQuery() { this.page = 1; this.init(); }, // 查询列表接口 const getDishPage = (params) => { return $axios({ url: 'backend/page/food/list/page.do', method: 'get', params }) } 4.2.3 后端代码分析 解决菜品分类的名称问题:这里菜品分类名称通过dish表中,只有一个category_id,而没有这个id对应的菜品名字),在后端返回的时候return RetObj.success(pageInfo); 这个pageInfo实际上是Page类型里面并没有封装菜品名称这个属性,也就是说这个泛型Dish不满足要求。思路是之前就使用DishDto涵括了:dish(继承了它)、flavor。现在在这个DishDto中再加一个属性categoryName,就满足了。自己写sql、service等:可以自己写sql实现多表联合查询,返回的数据需要严格按照前端要求分装好,比如List records代表的就是每一条数据,名字需要是record,还有total。如果使用MP,由于MP无法进行多表联合的查询,思路: 先使用 dishService执行分页查询,注意,既然是dishService,在pageInfo中就只能把结果封装到Dish实体类中,不能够直接封装给DishDto类。dishService.page(pageInfo,queryWrapper);创建出 Page dishDtoPage = new Page(); ,之后将原来的pageInfo的信息复制给dishDtoPage,但是要排除record,因为record是:List records ,我们希望这个record中的List中的元素类型是DishDto。遍历List records ,在其中new DishDto,把每一条Dish数据复制给DishDto,同时根据categoryId使用category表去查询categoryName,赋值给DishDto,这样我们最需要的字段得到赋值categoryName。把上面的一个个对象dishDto,放入到list中,之后:dishDtoPage.setRecords(list);,就可以完成dishDtoPage的封装。 @GetMapping("list/page.do") public RetObj pageController(int page, int pageSize, String name){ Page pageInfo = new Page(page,pageSize); Page dishDtoPage = new Page(page,pageSize); LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper(); lambdaQueryWrapper.like(StringUtils.isNotBlank(name),Dish::getName, name) .orderByDesc(Dish::getUpdateTime); dishService.page(pageInfo,lambdaQueryWrapper); //pageInfo中的record,里面的list存的类型是Dish,我们要DishDto,所以整个record就不拷贝了 BeanUtils.copyProperties(pageInfo,dishDtoPage,"record"); List records = pageInfo.getRecords(); List list = records.stream().map(item ->{ DishDto dishDto = new DishDto(); BeanUtils.copyProperties(item,dishDto); Category category = categoryService.getById(item.getCategoryId()); //dishDto.setCategoryName(category.getName()); if (category != null){ //很重要 dishDto.setCategoryName(category.getName()); } return dishDto; }).collect(Collectors.toList()); dishDtoPage.setRecords(list); return RetObj.success(dishDtoPage); } 4.5 修改菜品信息 4.5.1 整体分析 和之前的修改是类似的,使用的还是add.html,还是需要回显,回显麻烦的是口味也要回显,又要使用DishDto

在这里插入图片描述

4.5.2 前端分析

把url中的id取出来,只有0和1,对于编辑和添加 (add这个界面是复用的,在修改的时候也是用这个页面,而且进行数据回显) 有id说明是根据id查询数据进行回显,不是添加,而是修改。

this.id = requestUrlParam('id') this.actionType = this.id ? 'edit' : 'add' if (this.id) { this.init() //是修改页面,才执行这个init方法,主要就是去回显用的,看queryDishById方法 } }, mounted() { }, methods: { async init () { queryDishById(this.id).then(res => { console.log(res) if (String(res.code) === '1') { this.ruleForm = { ...res.data } this.ruleForm.price = String(res.data.price/100) this.ruleForm.status = res.data.status == '1' this.dishFlavors = res.data.flavors && res.data.flavors.map(obj => ({ ...obj, value: JSON.parse(obj.value),showOption: false })) console.log('this.dishFlavors',this.dishFlavors) // this.ruleForm.id = res.data.data.categoryId // this.imageUrl = res.data.data.image this.imageUrl = `/common/download?name=${res.data.image}` } else { this.$message.error(res.msg || '操作失败') } }) 注意是一杠一值,后端记得要使用路径变量注解,并且使用括号{name} // 查询详情 const queryDishById = (id) => { return $axios({ url: `/backend/page/food/add/getInfo.do/${id}`, method: 'get' }) } 4.5.3 后端分析 回显:前端传来id,我们要根据id查询到以下这么多信息 在这里插入图片描述 显然,dish表中是没有口味相关信息的,需要把dish和dish_flavor中的信息都查出来,封装到dishDto中,传给前端进行回显用户在前端修改好信息后,返回给后端的也是DishDto,也是要分步来操作:先跟新dish表,为了方便清空所有dishId对应的dishFlavor的记录条数,之后再根据DishDto新的dish_flavor进行插入。

回显的一个错误注意! 一个空格的错误会导致404,不要多空格这个地方 在这里插入图片描述

注意强转问题,可以使用类的复制来代替强制类型转换 在这里插入图片描述

回显代码

/** * 工具前端传过来的id,查询信息进行回显,当然,还要同时查询口味表,把口味表的数据也进行回显 * @param id ,注意这里的id就是dish的id,不是categoryId,也不是flavor的id,而flavor中有dish——id * @return */ @GetMapping("/add/getInfo.do/{id}") public RetObj showBack(@PathVariable Long id){ //查到的信息直接向下转型----为啥不行,而要进行复制? //编译器:拒绝了父类强转为子类 //DishDto dishDto = (DishDto) dishService.getById(id); Dish dish = dishService.getById(id); DishDto dishDto = new DishDto(); BeanUtils.copyProperties(dish,dishDto); //现在还需要把口味信息封装到DishDto中:List,前端自动展示 LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper(); lambdaQueryWrapper.eq(DishFlavor::getDishId,id); List list = dishFlavorService.list(lambdaQueryWrapper); dishDto.setFlavors(list); return RetObj.success(dishDto); } 修改代码,注意考虑事务,测试的时候就发现,口味删除了,其他没修改成功,最好放在service中,加入事务控制! @Transactional @PutMapping("add/edit.do") public RetObj editController(@RequestBody DishDto dishDto){ //Dish dish = (Dish)dishDto; dishService.updateById(dishDto); //多态,dishDto也是dish!!! //对于flavor,思考,还是把原来的全部删掉,再插入新的数据比较好处理。 LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper(); lambdaQueryWrapper.eq(DishFlavor::getDishId,dishDto.getId()); dishFlavorService.remove(lambdaQueryWrapper); //dishService.saveWithFlavor(dishDto);不能用这个的原因是这个是新增,新增是插入数据,不是更新 List flavors = dishDto.getFlavors(); flavors = flavors.stream().map(item -> { item.setDishId(dishDto.getId()); return item; }).collect(Collectors.toList()); dishFlavorService.saveBatch(flavors); return RetObj.success("成功修改菜品!"); } 5 套餐管理(见part4)


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3