part 1
part 2
part 3
part 4 本页
文章目录
5 套餐管理
5.1 新增套餐
5.2 套餐信息分页查询
5.3 删除套餐
其他小功能都比较简单且类似,不再赘述
5.1 新增套餐
5.1.1 整体分析
- 套餐是由菜品构成的,在添加套餐里面,可以添加菜品(会弹出每个菜品分类下的菜,比如主食下面有米饭)。套餐分类那里之前controller已经写好了,前端传不同的type分别查菜品分类、套餐分类,去修改对于的请求url即可。
- 类似的,添加菜品的时候有多少菜品分类也列出来了,需要写controller处理菜品分类下对于的菜品。
5.1.2 前端分析
- 获取套餐分类的列表
getDishTypeList() {
//通过category中的type = 1/2 查出对于的菜品分类或是套餐分类,显示到前端的下拉框中
//返回值中的data的类型是:List<Category>,赋值给了setMealList属性进行双向绑定
getCategoryList({ type: 2, page: 1, pageSize: 1000 }).then((res) => {
if (res.code === 1) {
this.setMealList = res.data.map((obj) => ({ ...obj, idType: obj.id }))
} else {
this.$message.error(res.msg || '操作失败')
}
})
},
- 添加菜品,注意getDishList()方法!
// 添加菜品,之前有按钮点击事件绑定了这个函数
//<div class="addBut" style="margin-bottom: 20px" @click="openAddDish">+ 添加菜品</div>
openAddDish() {
this.seachKey = ''
this.dialogVisible = true
//搜索条件清空,菜品重新查询,菜品类别选第一个重新查询
this.value = ''
this.keyInd = 0
//每个菜品对应有哪些菜,在dishList中循环显示出来
this.getDishList(this.dishType[0].id)
},
- 通过套餐ID获取菜品列表分类:getDishList (id)
getDishList (id) {
queryDishList({categoryId: id}).then(res => {
if (res.code === 1) {
if (res.data.length == 0) {
this.dishAddList = []
return
}
let newArr = res.data;
newArr.forEach((n) => {
n.dishId = n.id
n.copies = 1
// n.dishCopies = 1
n.dishName = n.name
})
this.dishAddList = newArr
} else {
this.$message.error(res.msg)
}
})
},
其中queryDishList({categoryId: id})
发送ajax请求,根据categoryId获取dish(获取当前套餐分类下的菜)。dish表中有category_id(相当于是副键),也就是说,一个category对应很多的dish。
//提交表单,最主要的代码,把整体数据提交上去。
submitForm(formName, st) {
this.$refs[formName].validate((valid) => {
if (valid) {
let prams = { ...this.ruleForm }
prams.price *= 100
prams.setmealDishes = this.dishTable.map((obj) => ({
copies: obj.copies,
dishId: obj.dishId,
name: obj.name,
price: obj.price,
}))
prams.status = this.ruleForm ? 1 : 0
prams.categoryId = this.ruleForm.idType
if(prams.setmealDishes.length < 1){
this.$message.error('请选择菜品!')
return
}
if(!this.imageUrl){
this.$message.error('请上传套餐图片')
return
}
// delete prams.dishList
if (this.actionType == 'add') {
delete prams.id
//发送请求
addSetmeal(prams)
.then((res) => {
if (res.code === 1) {
this.$message.success('套餐添加成功!')
if (!st) {
this.goBack()
} else {
this.$refs.ruleForm.resetFields()
this.dishList = []
this.dishTable = []
this.ruleForm = {
name: '',
categoryId: '',
price: '',
code: '',
image: '',
description: '',
dishList: [],
status: true,
id: '',
idType: '',
}
this.imageUrl = ''
}
} else {
this.$message.error(res.msg || '操作失败')
}
})
.catch((err) => {
this.$message.error('请求出错了:' + err)
})
} else {
delete prams.updateTime
editSetmeal(prams)
.then((res) => {
if (res.code === 1) {
this.$message.success('套餐修改成功!')
this.goBack()
} else {
this.$message.error(res.msg || '操作失败')
}
})
.catch((err) => {
this.$message.error('请求出错了:' + err)
})
}
} else {
return false
}
})
},
其中调用的方法
// 新增数据接口
const addSetmeal = (params) => {
return $axios({
url: '/setmeal',
method: 'post',
data: { ...params }
})
}
5.1.3 后端分析
- 对应的套餐分类复用了之前的,根据 category中的type = 1/2 查出对于的菜品分类或是套餐分类,显示到前端的下拉框中
- 现在解决点击某个分类,显示该分类下具体菜品。
代码:
/**
* 根据菜品分类查菜品比如: 川菜这个选项一点击,就通过这个controller返回一个list(元素就是各种川菜dish)
* @param dish 参数只有一个categoryId,
* @return
*/
@GetMapping("list/getDishByCategoryId.do")
public RetObj getDishByCategoryId(Dish dish){
LambdaQueryWrapper<Dish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(dish != null,Dish::getCategoryId,dish.getCategoryId())
.orderByDesc(Dish::getSort);
List<Dish> dishList = dishService.list(lambdaQueryWrapper);
return RetObj.success(dishList);
}
2. 对应菜品分类下的菜品查询到并显示到前端后,整体添加页面提交请求给后端,进行插入数据。这里 肯定需要操作来个表,因为新增套餐这个页面提交的数据就涉及来个表的数据,主要是套餐下有很多菜品。
分析前端发送过来的数据
{
"name": "二逼餐饮",
"categoryId": "1641061323236622337",
"price": 12300,
"code": "",
"image": "abfe28a3-35af-493d-a043-295189807847.jpg",
"description": "asdf",
"dishList": [],
"status": 1,
"idType": "1641061323236622337",
"setmealDishes": [
{
"copies": 1,
"dishId": "1645433949618958337",
"name": "二逼2",
"price": 32100
},
{
"copies": 1,
"dishId": "1413384757047271425",
"name": "王老吉",
"price": 500
},
{
"copies": 1,
"dishId": "1413385247889891330",
"name": "米饭",
"price": 200
},
{
"copies": 1,
"dishId": "1397860578738352129",
"name": "白切鸡",
"price": 6600
}
]
}
注意setmealDishes,这里面只有我们选中的菜品的信息,对setmeal_dish这个表进行操作的时候,肯定还需要setmeal_id,就是这个菜现在属于哪个套餐分类中,这个要自己加。
思路和之前类似,写一个MealDto,把meal的信息(名称、价格、图片等等存进去),还要报错对应的dish的信息,一次封装到MealDto
中。
- Dto类如下
@Data
public class SetMealDto extends Setmeal {
//名字一定要和前端一致,否则无法注入
List<SetmealDish> setmealDishes;
}
- service的处理,注意需要给setmeal_dish这个表插入具体菜品的时候,需要setmeal_id这个信息。
List<SetmealDish>
这个前端传来的数据中肯定没有这些菜品对应的套餐的setmeal_id,这个就需要我们重新构造一个list,把前端传来的那个list进行完善,添加setmeal_id的值。
@Transactional
public void saveMealWithFlavor(SetMealDto setMealDto) {
this.save(setMealDto);
List<SetmealDish> setmealDishes = setMealDto.getSetmealDishes();
//setmealDishes中有一个字段:setmeal_id,这个字段在上面那个list中肯定是没有的!
// 只有每个菜品自己的信息,这个setmeal_id就是setMealDto中的id
List<SetmealDish> afterCompleteList = setmealDishes.stream().map(item -> {
SetmealDish setmealDish = new SetmealDish();
BeanUtils.copyProperties(item,setmealDish);
setmealDish.setSetmealId(setMealDto.getId().toString());
return setmealDish;
}).collect(Collectors.toList());
boolean res = setmealDishService.saveBatch(afterCompleteList);
if (res == false){
throw new RuntimeException("套餐中菜品导入失败!");
}
}
- 上面可以优化的点是:在SetMealDish这个类型中直接就有属性id属性,直接给id属性赋值就行(直接把每个List中SetmealDish赋值不就好了,不需要new一个对象,不需要拷贝!)
- controller 简单编写
@PostMapping("/add/setmeal.do")
public RetObj setMealController(@RequestBody SetMealDto setMealDto){
setmealService.saveMealWithFlavor(setMealDto);
return RetObj.success("成功新增套餐!");
}
5.2 套餐信息分页查询
5.2.1 整体分析
- 需求分析
如图,每个套餐都有对应的图片,这些图片展示直接调用下载功能就可以。
和之前的一样,关键点在于套餐分类这一栏的显示,因为在SetMeal表中,对应的套餐信息是categoryId,使用MP插件查出来其他信息都没问题,就是这个套餐分类不能正常显示,应该封装一个categoryId对应的套餐名字name - 交互过程
5.2.2 前端分析
- 批量状态修改操作,还是通过axios请求对数据库操作。点击对应的按钮,进行起售等。
//状态更改
statusHandle (row) {
let params = {}
if (typeof row === 'string' ){
if (this.checkList.length == 0){
this.$message.error('批量操作,请先勾选操作菜品!')
return false
}
params.ids = this.checkList.join(',')
params.status = row
} else {
params.ids = row.id
params.status = row.status ? '0' : '1'
}
this.$confirm('确认更改该套餐状态?', '提示', {
'confirmButtonText': '确定',
'cancelButtonText': '取消',
'type': 'warning'
}).then(() => {
// 起售停售---批量起售停售接口
setmealStatusByStatus(params).then(res => {
if (res.code === 1) {
this.$message.success('套餐状态已经更改成功!')
this.handleQuery()
} else {
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
})
},
如果是多个id,就发送多个id(对应是批量启售)
const setmealStatusByStatus = (params) => {
return $axios({
url: `/setmeal/status/${params.status}`,
method: 'post',
params: { ids: params.ids }
})
}
- 当前端页面发送变化时,双向绑定数据,并调用init()方法
// 全部操作
handleSelectionChange (val){
let checkArr = []
val.forEach((n) => {
checkArr.push(n.id)
})
this.checkList = checkArr
},
handleSizeChange (val) {
this.pageSize = val
this.init()
},
handleCurrentChange (val) {
this.page = val
this.init()
}
- init方法中,分装后端想要的分页数据,如:pageSize,page,name(模糊查询,前端可能会输入)为json,传输给后端,进行分页查询。主要注意后端要给前端返回的数据类型中包含的属性名要保持一致:
this.tableData = res.data.records || [] ;this.counts = res.data.total
async init () {
const params = {
page: this.page,
pageSize: this.pageSize,
name: this.input ? this.input : undefined
}
await getSetmealPage(params).then(res => {
if (String(res.code) === '1') {
this.tableData = res.data.records || []
this.counts = res.data.total
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
},
//图片下载直接调用download的controller方法,注意传过去name,以得到对应的image
getImage (image) {
return `/common/download?name=${image}`
},
handleQuery() {
this.page = 1;
this.init();
},
- 后端返回的数据是放到 record中,record数据放到tableData中,tableData又通过el-table控件的prop,把数据显示到对应的栏目中。
5.2.3 后端分析
- 主要解决套餐分类的问题,把categoryId找到对应的name传给前端去显示,这个之前写过(菜品分类)很类似的。
- 拷贝过程中关键是records,这个不能拷贝,原因是泛型不一样!
@GetMapping("/list/page.do")
public RetObj getSetmealPageController(int page, int pageSize, String name){
LambdaQueryWrapper<Setmeal> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//要注意,如果前端没有给模糊查询的name,那就不应该查询,否则查到是的0条数据!
// select count(*) from setmeal where name=null =>查到0条数据
lambdaQueryWrapper.like(StringUtils.isNotBlank(name),Setmeal::getName,name)
.orderByDesc(Setmeal::getUpdateTime);//根据跟新时间拍个序,你漏掉的
Page<Setmeal> pageInfo = new Page<>(page,pageSize);
setmealService.page(pageInfo,lambdaQueryWrapper);
//解决两个表的问题,setmeal表只有category_id,而没有菜品对应的name,在setmeal_dish中才有。
//解决思路,在SetmealDto中加入Name这个成员变量,多查一次在category这个表中的name,进行赋值
//注意前端那里是categoryName,要的不是name!!
Page<SetMealDto> pageInfoWithName = new Page<>(page,pageSize);
BeanUtils.copyProperties(pageInfo,pageInfoWithName,"records");
List<SetMealDto> recordsWithName = pageInfo.getRecords().stream().map(item -> {
SetMealDto setMealDto = new SetMealDto();
Category category = categoryService.getById(item.getCategoryId());
if (category != null){//没考虑到的,防止出现空指针异常!!
setMealDto.setCategoryName(category.getName());
BeanUtils.copyProperties(item,setMealDto);
}
return setMealDto;
}).collect(Collectors.toList());
pageInfoWithName.setRecords(recordsWithName);
return RetObj.success(pageInfoWithName);
}
5.3 删除套餐
5.3.1 整体分析
5.3.2 前端分析
- 删除操作
// 删除
deleteHandle (type, id) {
if (type === '批量' && id === null) {
if (this.checkList.length === 0) {
return this.$message.error('请选择删除对象')
}
}
this.$confirm('确定删除该套餐, 是否继续?', '确定删除', {
'confirmButtonText': '确定',
'cancelButtonText': '取消',
}).then(() => {
deleteSetmeal(type === '批量' ? this.checkList.join(',') : id).then(res => {
if (res.code === 1) {
this.$message.success('删除成功!')
this.handleQuery()
} else {
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
})
},
上面代码的checkList就是在勾选中对应套餐的时候,触发以下函数,将对应数据压入
handleSelectionChange (val){
let checkArr = []
val.forEach((n) => {
checkArr.push(n.id)
})
this.checkList = checkArr
},
删除操作deleteSetmeal,因为可能是一次删除多个,所以这里用ids,看上面的代码中分装好了多个ids,使用三木运算符判断是删除一个,还是一批。
const deleteSetmeal = (ids) => {
return $axios({
url: '/setmeal',
method: 'delete',
params: { ids }
})
}
5.3.3 后端分析
-
先是接收参数,使用List接收,因为可能是多个ids,注意和名字对应上,使用@RequestParam
-
明确能不能删除(是不是售卖状态),如果不能删除,那就抛出异常
-
sql 设想:
select count(*) from setmeal where id in (id1,id2,id3) and status = 1
-
注意不同表的id,主键和副键,在删除的时候removeByIds的时候多注意。
@Transactional
public void deleteBatchWithDish(@RequestParam List ids){
//查询套餐状态,确定是否可用删除,这个是要第一个确定的!!
LambdaQueryWrapper lambdaQueryWrapper1 = new LambdaQueryWrapper<>();
lambdaQueryWrapper1.eq(Setmeal::getStatus,2);
lambdaQueryWrapper1.in(Setmeal::getId,ids);//这个需要重点学习,就不用遍历ids,比如下面删除dish的代码那样。
//this.remove(lambdaQueryWrapper1); 需要判断!判断能不能删除
if (this.count(lambdaQueryWrapper1) > 0){
//如果不能删除,抛出一个业务异常
throw new CustomException(“套餐正在售卖中,不能删除”);
}
//删除
this.removeBatchByIds(ids);//错误代码!!setmealDishService的id是自己的id,是主键,应该根据副键删除! //setmealDishService.removeBatchByIds(ids); ids.forEach(item -> { LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.eq(ids!= null,SetmealDish::getSetmealId,item); setmealDishService.remove(lambdaQueryWrapper); });
}