第一部分part1 链接
Part2 第2部分(本页)
第三部分part3 链接
3 分类管理业务开发
内容:
3.1 公共字段自动填充
3.2 新增分类 (整个过程包括ThreadLocal、拦截器、元对象处理器等值得学习)
3.3 分类信息分页查询
3.4 删除分类 (需要考虑删除分类下有没有菜品,自己写service而不再是完全使用mybatis_plus值得学习,其中还有sql优化重点关注)
3.5 修改分类
3.1 公共字段自动填充
3.1.1 问题分析
- 就像是把一些公共的操作切出来,封装到方法里去。每次insert或者update操作的时候,都自动执行这些方法,给指定的公共字段属性赋值
- 还有一个问题:MetaObjectHandler类中的方法中设置createUser和UpdateUser的时候,需要从session中获取id,但是该类无法获取request,如何做到这个功能呢?-----使用TreadLocal
3.1.2 mybatis_plus 提供的公共字段自动填充
- 简单的说,就是在执行insert(),或者 update()的时候,自动执行MetaObjectHandler的实现类里面的方法,这个类叫元数据对象处理器。这个类中自动会有前端提交过来的元数据metaObject,其中:id、username…等等都有。
- 加注解@TableField,里面参数INSERT就代表在插入的时候,自动填充这个字段的值,具体填充什么值,就是上面那两个方法中处理的了。
3.1.2.1 ThreadLocal
- 同一线程
- 什么是ThreadLocal、作用是什么?
3.1.3 后端代码分析
- 1、基于ThreadLocal封装工具类,用户保存和获取当前登录用户id(session中的)
public class BaseContext {
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setThreadLocal(Long id){
threadLocal.set(id);
}
public static Long getThreadLocal(){
return threadLocal.get();
}
}
- 2、进入拦截器后,给threadLocal绑定session,让后面需要的公共字段自动填充的时候,填充这个updateUser
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
log.info("当前路径:{}", uri);
/**
* HandlerMethod=>Controller中标注@RequestMapping的方法
* 需要配置静态资源不拦截时,添加这块逻辑 => 前后端分离项目
*/
// 是我们的conrtoller中的方法就拦截,如果不是的话,放行,给加载静态资源
if (!(handler instanceof HandlerMethod)) {
log.info("是静态资源或非controller中的方法,放行");
return true;
}
//通过session判断是否登入
if (request.getSession().getAttribute(Contants.SESSION_USERID) == null) {
//这里应该跳转到登入页面,,如何做?
//5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
log.info("用户未登入,通过输出流方式向客户端页面响应数据,打回登入页面");
response.getWriter().write(JSON.toJSONString(RetObj.error("NOTLOGIN")));//与前端request.js中的代码呼应
return false;
} else {
log.info("用户已经登入,id={}", request.getSession().getAttribute(Contants.SESSION_USERID));
//进入拦截器后,给threadLocal绑定session,让后面需要的公共字段自动填充的时候,填充这个updateUser
//每次http请求,会分配一个新的线程来处理
BaseContext.setThreadLocal((Long) request.getSession().getAttribute(Contants.SESSION_USERID));
log.info("拦截器这里设置了ThreadLocal,值为:{}",(Long) request.getSession().getAttribute(Contants.SESSION_USERID));
log.info("当前线程id={}",Thread.currentThread().getId());
return true;
}
}
- 3、为实体类需要的属性添加@Table注解,指定自动填充的策略
/**
* 创建时间,注意类型LocalDateTime
*/
@TableField(fill = FieldFill.INSERT) //执行insert操作的时候,自动填充该字段
private LocalDateTime createTime;
/**
* 更新时间,注意类型LocalDateTime
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 创建人
*/
@TableField(fill= FieldFill.INSERT) //执行insert操作的时候,自动填充该字段
private Long createUser;
/**
* 修改人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)//执行insert和update操作的时候,自动填充该字段
private Long updateUser;
- 4、自定义元数据对象处理器
import java.time.LocalDateTime;
/**
* 自定义元数据对象处理器
*/
@Slf4j
@Component ///少了这个导致出错,去复习该注解作用,为什么要交给spring去管理
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段[insert]自动填充");
log.info(metaObject.toString());
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("createUser",BaseContext.threadLocal.get());
metaObject.setValue("updateUser",BaseContext.threadLocal.get());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段【update】自动填充");
log.info(metaObject.toString());
log.info("当前线程id为:{}",BaseContext.threadLocal.get());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",BaseContext.threadLocal.get());
}
}
- 经过上面四部的配置,就能够实现对应的功能了,主要就是注意@TableField和元数据对象处理器MetaObjectHandler实现类的配合使用。
3.2 新增分类
3.2.1 整体分析
- 整体流程:在点击添加菜品按钮后, 发起了一个http请求,发起这个请求就会分配一个新的线程来处理,这个请求就会访问对应的url地址:
http://localhost:8080/backend/page/category/add.do
之后通过LoginInterceptor
拦截器,如果session不为空就放行,并且设置ThreadLocal的值为session存的值,之后就到了自定义元数据对象处理器:MyMetaObjectHandler
,设置了对应的时间等公共字段metaObject.setValue("createUser",BaseContext.threadLocal.get());
,之后完成了插入操作。 - 类似的,如果出现问题了,全局异常处理会出手:GlobalExceptionHandler类对controller类进行了异常监听,如果是重复的错误能直接提示出来,其他错误(例如非空要求的字段插入了null)直接提示:未知错误!
023-03-29 21:35:59.481 ERROR 14664 --- [nio-8080-exec-1] c.e.u.r.c.e.GlobalExceptionHandler
: Duplicate entry '二逼菜' for key 'category.idx_category_name'
- 前后端对来个进行了复用,通过表中的一个字段type,来区分是菜品分类还是套餐分类。
- 新使用了一张category表,字段如下(注意公共字段的处理策略和前面一样),注意mybatis-plusX自动生成所有东西之后,@Mapper注解等等要去检查!
- 注意总结,既然前端也是以json的格式发送数据给后端,那后端参数接收那里就需要加@RequestBody注解
- 类似的,菜品 这个分类名称 字段也加了唯一性约束,如果重复添加,会自动跳转到全局异常处理器,然后会有弹窗提示,这个复习之前,有详细分析。
3.2.2 前端代码分析
- 点击两个按钮,点击跳转到对应的新增页面,注意传递的参数meal还是class,后面vue会进行处理
<el-button type="primary" class="continue" @click="addClass('class')">+ 新增菜品分类</el-button>
<el-button type="primary" @click="addClass('meal')">+ 新增套餐分类</el-button>
- addClass()方法,如果是class,对应打开新增菜品分类这个小页面
// 添加
addClass(st) {
if (st == 'class') {
this.classData.title = '新增菜品分类'
this.type = '1'
} else {
this.classData.title = '新增套餐分类'
this.type = '2'
}
this.action = 'add'
this.classData.name = ''
this.classData.sort = ''
this.classData.dialogVisible = true
},
// 关闭弹窗方法, dialogVisible是自定义属性classData中的一个变量
// el-dialog(对话框),visible.sync控制弹框的显示,就设置这个dialogVisible为true或者false
handleClose(st) {
this.classData.dialogVisible = false
},
- 将数据通过ajax送到后端,判断结果,得到数据再进行展示
//数据提交
submitForm(st) {
const classData = this.classData
const valid = (classData.name === 0 ||classData.name) && (classData.sort === 0 || classData.sort)
if (this.action === 'add') {
if (valid) {
const reg = /^\d+$/
if (reg.test(classData.sort)) { //正则表达式匹配一下输入是不是数字
//这里就对应转到ajax处理
addCategory({'name': classData.name,'type':this.type, sort: classData.sort}).then(res => {
console.log(res)
if (res.code === 1) {
this.$message.success('分类添加成功!')
if (!st) {
this.classData.dialogVisible = false
} else {
this.classData.name = ''
this.classData.sort = ''
}
this.handleQuery() //回调之前的init函数进行数据显示
} else {
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
} else { //正则表达式判断输入的如果不是数字
this.$message.error('排序只能输入数字类型')
}
} else {
this.$message.error('请输入分类名称或排序')
}
- addCategory()对应的ajax请求
// 新增接口
const addCategory = (params) => {
return $axios({
url: '/backend/page/category/add.do',
method: 'post',
data: { ...params }
})
}
3.2.3 后端代码分析
- 后端代码很简单,和之前的部分也很类似,像公共字段自动填充,使用之前定义的元数据对象处理器,仅仅需要在Category实体类中加上@TableFile(xxxx)对应的属性就可以自动生成
@PostMapping("/backend/page/category/add.do")
public RetObj<String> addController(@RequestBody Category category){
log.info("category = {}",category);
boolean save = categoryService.save(category);
if (save){
return RetObj.success("成功新增分类");
}else {
return RetObj.error("新增分类失败!");
}
}
3.3 分类信息分页查询
3.3.1 整体分析
- 该部分和前面很像。包括后面每个模块都有分页查询,主要区别就是表的区别。
- 在分页查询的controller中,首先分页构造器是肯定要的,之后也需要一个条件构造器。思考需要条件构造器的原因,之前有附加条件,比如通过名字模糊查询。这里别忘了有排序的条件,之前添加的时候都设置了排序sort字段,这里就要使用起来。
3.3.2 前端分析
- getCategoryPage()这个方法就可以看出来,一共就传两个参数过去,page和pageSize,到时候后端对应进行接收处理。
- 分页查询,注意思考前端需要哪些数据,比如Page对象中的recode等数据,那后面写后端代码的时候返回值
RetObj<Page>
就要注意传递的Page类型数据了。具体的说:Page pageInfo = new Page(page,pageSize);
后端在执行完条件查询:empService.page(pageInfo,lambdaQueryWrapper)
之后,直接将查询到的结果封装到pageInfo对象中了。后端解析这个对象转成的json,就能够拿到里面的recods等属性值!recodes就是查询到的每个分类的信息,前端代码赋值给了tableData。counts是下面“共xxx条数据显示的”
methods: {
async init () {
//分页查询,告诉后端需要的page、pageSize
await getCategoryPage({'page': this.page, 'pageSize': this.pageSize}).then(res => {
if (String(res.code) === '1') {
//注意看,既然前端需要拿这种数据,那后端响应回来的对象,也要符合这种要求!
//mybatisPlus中的Page类中就有就有record属性,所以返回的时候:RetObj<Page>
this.tableData = res.data.records
this.counts = Number(res.data.total)
} else {
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
},
// 查询列表接口
const getCategoryPage = (params) => {
return $axios({
url: 'backend/page/category/page.do',
method: 'get',
params
})
}
- 至于如何展示页面的,主要使用到了vue中的element-ui组件,定了包括每页默认条数等信息。还有一些细节比如后端返回的json数据本来对于如果状态只有1和0,但是在页面上显示了正常、禁用,这就是因为前端进行了处理。
<span>{{ scope.row.type == '1' ? '菜品分类': '套餐分类' }}</span>
- tableData: [],属性重点关注
new Vue({
el: '#category-app',
data() {
return {
//下面应该是默认值
action: '',
counts: 0,
page: 1,
pageSize: 10,
//页面展示数据tableData,其他地方取这个数据进行了展示
tableData: [],
type :'',
classData: {
'title': '添加菜品分类',
'dialogVisible': false,
'categoryId': '',
'name': '',
sort: ''
}
}
},
computed: {},
// created -----------vue实例创建之后(钩子函数)
//mounted -----------DOM挂载之后
created() {
this.init() //init方法下面写了
},
mounted() {
},
- 具体展示在整个表格中了,模板插槽(template slot-scope)是Vue.js中一种用于在组件中动态展示不同内容的方式。它允许父组件将内容传递给子组件,并在子组件中渲染这些内容。 "slot-scope"属性用于给插槽提供一些上下文数据。 拿到每一行的数据(row),就可以调出对应类型这个字段
<!--表格开始,就是一开始进入页面自动查询那个地方的表格-->
<el-table :data="tableData" stripe class="tableBox">
<!--下面这个是表格中的字段-->
<el-table-column prop="name" label="分类名称"/></el-table-column>
<el-table-column prop="type" label="分类类型">
<!--
模板插槽(template slot-scope)是Vue.js中一种用于在组件中动态展示不同内容的方式。
它允许父组件将内容传递给子组件,并在子组件中渲染这些内容。
"slot-scope"属性用于给插槽提供一些上下文数据。
拿到每一行的数据(row),就可以调出对应类型这个字段
-->
<template slot-scope="scope">
<span>{{ scope.row.type == '1' ? '菜品分类': '套餐分类' }}</span>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="操作时间">
<template slot-scope="scope">
{{scope.row.updateTime}}
</template>
</el-table-column>
<el-table-column
prop="sort"
label="排序"
/></el-table-column>
<el-table-column label="操作" width="160" align="center">
<template slot-scope="scope">
<!--操作那一栏的按钮,对应的函数editHandle等等-->
<el-button
type="text"
size="small"
class="blueBug"
@click="editHandle(scope.row)"
>
修改
</el-button>
<el-button
type="text"
size="small"
class="delBut non"
@click="deleteHandle(scope.row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
- 分页导航栏
<!--共xx条,每页xx条,前往第几页的那一块-->
<el-pagination
class="pageList"
:page-sizes="[10, 20, 30, 40]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="counts"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></el-pagination>
3.3.3 后端代码分析
- 和之前一样,MP的分页插件(之前配置好了,这里直接用就好)
/**
* MP分页插件的配置
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
- Page类的其中一个有参构造,参数当前页,和当前页的大小
public Page(long current, long size) {
this(current, size, 0L);
}
- controller:放心写,遇到异常直接被全局异常捕获,他监听着这些controller
@GetMapping("backend/page/category/page.do")
public RetObj<Page> pageController(int page, int pageSize){
log.info("前端数据:page={},pageSize={}",page,pageSize);
log.info("当前线程id={}",Thread.currentThread().getId());
Page<Category> pageInfo = new Page<>(page,pageSize);
LambdaQueryWrapper<Category> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.orderByAsc(Category::getSort);
//Page<Category> page1 = categoryService.page(pageInfo, lambdaQueryWrapper);
categoryService.page(pageInfo, lambdaQueryWrapper);//直接将查询到的结果封装到pageInfo对象中了
return RetObj.success(pageInfo);
}
3.4 删除分类
3.4.1 整体思路
- 删除分类不考虑其他就很简单,只要根据F12看url、method、参数类型是json还是其他类型就可以写controller.
- 完善地考虑:某个分类下关联了某个菜品或者套餐,这是不允许删除的!(后续添加具体菜品的时候,会挂到这里的分类中去) 例如:精品热菜这个分类中有水煮肉片这个菜品,那就不可以直接将精品热菜删除,给页面一个提示:当前分类已经关联了菜品,无法删除!
- 具体思路就是利用:菜品管理、套餐管理对应表对应实体类中有categoryId属性。这个思路应该得想到了,就是这样处理的。在CategoryService中自己定义一个方法(之前都是使用mybatis_plus提供的,这里就是要练习的点了!)。
- 查看当前分类下有多少菜品,想法就是CategoryServiceImpl中 引入dishService、setmealService,使用dishService去查询该categoryId下还有多少个菜品,进而判断要不要删除。就是说在CategoryService中自己构造一个想要的删除方法,这个方法需要引入其他Service
上述代码其实可以做sql优化
3.4.2 sql优化
- 根据某一条件从数据库表中查询 『有』与『没有』,只有两种状态,那为什么在写SQL的时候,还要SELECT count(*)?
业务代码中,需要根据一个或多个条件,查询是否存在记录,不关心有多少条记录。普遍的SQL及代码写法如下
SELECT count(*) FROM table WHERE a = 1 AND b = 2
- 对应java代码
int nums = xxDao.countXxxxByXxx(params);
if ( nums > 0 ) {
//当存在时,执行这里的代码
} else {
//当不存在时,执行这里的代码
}
-
分析一下count慢的原因:
innodb为聚簇索引同时支持事物,其在count指令实现上采用实时统计方式。在无可用的二级索引情况下,执行count会使MySQL扫描全表数据,当数据中存在大字段或字段较多时候,其效率非常低下(每个页只能包含较少的数据条数,需要访问的物理页较多)。 -
优化思路:SQL不再使用count,而是改用LIMIT 1,让数据库查询时遇到一条就返回,不用再继续查找还有多少条了。
-
sql和java代码
SELECT 1 FROM table WHERE a = 1 AND b = 2 LIMIT 1
*************************************************
Integer exist = xxDao.existXxxxByXxx(params);
if ( exist != NULL ) {
//当存在时,执行这里的代码
} else {
//当不存在时,执行这里的代码
3.4.3 前端分析
//删除
deleteHandle(id) {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
'confirmButtonText': '确定',
'cancelButtonText': '取消',
'type': 'warning'
}).then(() => {
deleCategory(id).then(res => {
if (res.code === 1) {
this.$message.success('删除成功!')
this.handleQuery()
} else {
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
})
},
- 对应方法
// 删除当前列的接口
const deleCategory = (ids) => {
return $axios({
url: 'backend/page/category/delete.do',
method: 'delete',
params: { ids }
})
}
- 后端发现当前分类绑定了东西后,如何将提示信息返回给前端,让前端提示对应的信息?
- 不管是否出现了异常进行信息捕获,后端都会传给前端这样一个对象:(分析下面代码)
@DeleteMapping("backend/page/category/delete.do")
public RetObj<String> deleteController(Long ids){
categoryService.removeIfNotBind(ids);
//如果出异常了下面的代码执行不到,在异常处理中:return RetObj.error("分类包含套餐/菜品,无法删除!");
return RetObj.success("分类信息删除成功");
}
- 现在关键就是在前端如何处理这个对象,让对象
RetObj.success(Data);
读取该对象底下的data属性,如果是error,那就是读返回对象的msg属性!!
- 打了log看浏览器的响应
console.log(res)
console.log(res.msg)
//删除
deleteHandle(id) {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
'confirmButtonText': '确定',
'cancelButtonText': '取消',
'type': 'warning'
}).then(() => {
deleCategory(id).then(res => {
if (res.code === 1) {
this.$message.success('删除成功!')
this.handleQuery()
} else {
console.log(res)
console.log(res.msg)
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
})
},
分析上面信息,res就是后端返回给浏览器的RetObj对象(传给前端以json的形式),而res.msg就是RetObj对象的msg属性值!!使用this.$message.error(res.msg || '操作失败')
来显示吗,如果有传递信息过去,就显示该消息,没有那就显示 ‘操作失败’。
为什么res是json的形式?----因为GlobalExceptionHandler类中加了注解
@ResponseBody //因为一会儿的写的方法,需要返回一个json数据,所以需要写这个注解(否则返回的就是那个对象!!),RestController直接复合了这个注解
所以在RetObj.error(customException.getMessage());
的时候,返回的是json! 完美
3.4.4 后端代码编写
3.4.4.1异常的处理分析
- 首先记住如何自定义异常(构造方法论)
/**
* Custom adj.定做(制)的; Custom Exception 自定义异常
*/
public class CustomException extends RuntimeException{
public CustomException(String msg){
super(msg);
}
}
- 接着写代码手动抛出异常
throw new CustomException("该分类已经绑定了套餐,无法删除!");
- 全局异常处理中对异常进行捕获,返回对应的信息,注意记住注解@ExceptionHandler以及形参的形式。
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody //因为一会儿的写的方法,需要返回一个json数据,所以需要写这个注解(否则返回的就是那个对象!!),RestController直接复合了这个注解
@Slf4j
/**
* 全局异常处理器,结合前端了解一下是如何将错误信息传送给前端的(对象返回之后,如何取出信息)
*/
public class GlobalExceptionHandler {
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public RetObj<String> exceptionHandler(SQLIntegrityConstraintViolationException exception){
log.error(exception.getMessage());
if (exception.getMessage().contains("Duplicate entry")){
String[] split = exception.getMessage().split(" ");
return RetObj.error("用户名:" + split[2] + "已存在!");
}else {
return RetObj.error("未知错误!");
}
}
/**
* 构成方法重载
* @param customException 注意要传过来的参数
* @return
*/
@ExceptionHandler(CustomException.class)
public RetObj<String> exceptionHandler(CustomException customException){
return RetObj.error(customException.getMessage());
}
4.customException.getMessage()
是如何调用的?
第一:CustomException extends RuntimeException
第二:public class RuntimeException extends Exception {…}
第三:public class Exception extends Throwable {…}
第四:Throwable中:
public String getMessage() {
return detailMessage;
}
**************************************************************
注:
/**
* Specific details about the Throwable. For example, for
* {@code FileNotFoundException}, this contains the name of
* the file that could not be found.
*
* @serial
*/
private String detailMessage;
3.4.4.2 自己完善Service满足需求
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category>
implements CategoryService {
@Resource
DishService dishService;
@Resource
SetmealService setmealService;
/**
* 分类如果绑定了菜品,不能直接删除套餐,应该给出对应的提示
* (具体做法就是抛出自定义异常,全局捕获,捕获后将返回对象RetObj中包含了对应的提示信息)
* private Long categoryId; 这个字段的使用很重要!
* 想法就是:select count(*) dish where categoryId = xxx 如果有数据,说明该categoryId下关联了count条数据
*
* @param id 这个id对应分类下有没有关联菜品
* @return
*/
@Override
public boolean removeIfNotBind(Long id) {
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
//dishService.getOne()
LambdaQueryWrapper<Dish> lambdaQueryWrapperDish = new LambdaQueryWrapper<>();
LambdaQueryWrapper<Setmeal> lambdaQueryWrapperSetmeal = new LambdaQueryWrapper<>();
lambdaQueryWrapperDish.eq(Dish::getCategoryId, id);
long countDish = dishService.count(lambdaQueryWrapperDish);
lambdaQueryWrapperSetmeal.eq(Setmeal::getCategoryId, id);
long countMeal = setmealService.count(lambdaQueryWrapperSetmeal);
if (countDish != 0){
//已经关联了套餐或者分类,抛出异常
//throw new RuntimeException("分类绑定了菜品或套餐,无法删除!");
//自己定义异常类
throw new CustomException("该分类已经绑定了菜品,无法删除!");
}else if(countMeal != 0){
throw new CustomException("该分类已经绑定了套餐,无法删除!");
}else{
this.removeById(id);
return true;
}
}
}
3.4.4.2 简单地完成controller
@DeleteMapping("backend/page/category/delete.do")
public RetObj<String> deleteController(Long ids){
/*
前端:
Request URL: http://localhost:8080/backend/page/category/delete.do?ids=1641066227887951873
后端上面的形参要和ids这个名字保持一致。则无法删除,对应的id是null
*/
log.info("要删除的id:{}",ids);
//categoryService.removeById(ids);
categoryService.removeIfNotBind(ids);
//如果出异常了下面的代码执行不到,在异常处理中:return RetObj.error("分类包含套餐/菜品,无法删除!");
return RetObj.success("分类信息删除成功");
}
3.5 修改分类
3.5.1 整体思路
- 这部分功能实现比较简单,主要要注意的有:回显(复习之前修改员工信息也是类似的)、然后删除。
3.5.2 前端代码分析
- 在没有查询数据库,进行后端处理的时候就发现其实已经有回显的值了,但是这个只是前端的一个效果。
<el-button
type="text"
size="small"
class="blueBug"
@click="editHandle(scope.row)" 并且把当前这条数据传过去
>
修改
</el-button>
- 点击按钮对应@click="editHandle(scope.row)"函数
//editHandle(dat)方法,有参,以为要edit,需要name、sort、id信息
editHandle(dat) {
this.classData.title = '修改分类' //展示标题
this.action = 'edit' //使用的是同一个窗口,动态的修改标题,这里标识一下
this.classData.name = dat.name //classDate,模型数据
this.classData.sort = dat.sort //为一些模型数据赋值,赋值完毕就会回显了(输入框和模型数据进行了绑定)
this.classData.id = dat.id
this.classData.dialogVisible = true
},
// 关闭弹窗方法, dialogVisible是自定义属性classData中的一个变量
// el-dialog(对话框),visible.sync控制弹框的显示,就设置这个dialogVisible为true或者false
handleClose(st) {
this.classData.dialogVisible = false
},