这一节增加了大章的编辑和删除功能,这样大章的增删改查功能就都有了,但是在增加和修改时应该还要有校验功能。
编辑功能
这一节主要对大章模块增加编辑功能,其次还删除一些多余组件。首先,我们将多余对按钮进行了删除,并且对剩余按钮绑定函数;接着我们将修改功能和增加功能合并,修改了前端的 save 方法和后端对应的类。
修改页面
删除了部分按钮,并把编辑按钮和删除按钮绑定对应函数。
<tbody><tr v-for="chapter in chapters" v-bind:key="chapter"><td>{{chapter.id}}</td><td>{{chapter.name}}</td><td>{{chapter.courseId}}</td><td><div class="hidden-sm hidden-xs btn-group"><button class="btn btn-xs btn-info"><i v-on:click="edit(chapter)" class="ace-icon fa fa-pencil bigger-120"></i></button><button class="btn btn-xs btn-danger"><i v-on:click="del(chapter.id)" class="ace-icon fa fa-trash-o bigger-120"></i></button></div></td></tr></tbody>
修改后页面如下:
前端修改
下面新增对应函数:
edit(chapter) {let _this = this;_this.chapter = $.extend({},chapter);$("#form-modal").modal("show");},del(chapter.id) {}
这里也是点击编辑按钮先弹出修改框,在修改完之后点击 save 按钮后将数据传到后端处理。
这里需要注意的是用到了 extend 关键字,这样可以将当前的 chapter 信息传给 {} ,再绑定到弹出框,这样修改弹出框中的信息时不会把页面中的 chapter 改变。
后端修改
Controller 层没有改变,主要是修改 Service 层的 save 方法。首先判断传入的 chapter 是否有 Id 属性,然后对其进行修改或者新增操作。
public void save(ChapterDto chapterDto){Chapter chapter = CopyUtil.copy(chapterDto,Chapter.class);if(StringUtils.isEmpty(chapter.getId())){this.insert(chapter);}else{this.update(chapter);}}private void insert(Chapter chapter){chapter.setId(UuidUtil.getShortUuid());chapterMapper.insert(chapter);}private void update(Chapter chapter){chapterMapper.updateByPrimaryKey(chapter);}
删除功能
上节已经将删除按钮和 del 函数绑定在一起,这节首先修改前端删除函数,然后在后端新增删除操作。
前端修改
前端删除功能用 delete 请求,将需要删除的章节 id 传给后端。
del(id) {let _this = this;console.log(id); _this.$ajax.delete('http://127.0.0.1:9000/business/admin/chapter/delete/'+id).then((response)=>{console.log("删除大章结果",response);let res = response.data;if(res.success){_this.list(1);}})}
后端控制层先增加 delete 方法 ,id 用 @PathVariable 接收。
@DeleteMapping("/delete/{id}")public ResponseDto delete(@PathVariable String id){chapterService.delete(id);ResponseDto responseDto = new ResponseDto();return responseDto;}
然后增加服务层对应方法。
public void delete(String id){chapterMapper.deleteByPrimaryKey(id);}
前端界面优化
这里页面点击删除就直接删除了,一般应该会有提示是否删除,再确认后才会删除。这里确认框和操作结果提示使用的是 sweetalter2 ;界面的等待框使用的是 blockui。
添加 js
这里采用的方式是在 vue 项目的 public/static 包中添加 js 包,用来存放一些前端组件。
在 js 包中新建一个 toast.js 文件,这个文件存放的是前端操作后提示的 js 组件。
新建一个 confirm.js 文件,这个文件存放的是前端确认提示的 js 组件。
新建一个 loading.js 文件,这个文件存放的是前端等待提示的 js 组件。
toast.js
Toast = {success: function (message) {Swal.fire({position: 'top-end',icon: 'success',title: message,showConfirmButton: false,timer: 3000})},error: function (message) {Swal.fire({position: 'top-end',icon: 'error',title: message,showConfirmButton: false,timer: 3000})},warning: function (message) {Swal.fire({position: 'top-end',icon: 'warning',title: message,showConfirmButton: false,timer: 3000})}
};
confirm.js
Confirm = {show: function (message, callback) {Swal.fire({title: 'Are you sure?',text: message,icon: 'warning',showCancelButton: true,confirmButtonColor: '#3085d6',cancelButtonColor: '#d33',confirmButtonText: 'sure!'}).then((result) => {if (result.value) {if (callback) {callback()}}})}
}
loading.js
Loading = {show: function () {$.blockUI({message: '<img src="/static/image/loading.gif" />',css: {zIndex: "10011",padding: "10px",left: "50%",width: "80px",marginLeft: "-40px",}});},hide: function () {// 本地查询速度太快,loading显示一瞬间,故意做个延迟setTimeout(function () {$.unblockUI();}, 500)}
};
这里的 loading.gif 就是一张动图,在网上找一下就有类似的了
index.html
<!-- 确认框和提示框 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@9"></script>
<script src="<%= BASE_URL %>static/js/toast.js"></script>
<script src="<%= BASE_URL %>static/js/confirm.js"></script><!-- loading等待框 -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery.blockUI/2.70.0-2014.11.23/jquery.blockUI.min.js"></script>
<script src="<%= BASE_URL %>static/js/loading.js"></script>
修改方法
前端组件添加后就应该在各个操作中调用这些提示。
1.在查询之前会显示 Loading 图标,然后查询完后会关闭 Loading 图标。
2.在保存编辑时,会先显示 Loading 图标,发送请求后关闭 Loading 图标,如果成功还会显示保存成功提示。
3.在删除时,先弹出确认提示,如果确认删除会先显示 Loading 图标,接着关闭 Loading 图标。
修改后的方法如下:
list(page) {let _this = this;Loading.show();console.log(page, _this.$refs.pagination.size);_this.$ajax.post('http://127.0.0.1:9000/business/admin/chapter/list',{page: page,size: _this.$refs.pagination.size}).then((response)=>{console.log(response);Loading.hide();let res = response.data;_this.chapters = res.content.list;_this.$refs.pagination.render(page, res.content.total);})
},
save(page) {let _this = this;Loading.show();console.log(page);_this.$ajax.post('http://127.0.0.1:9000/business/admin/chapter/save',_this.chapter).then((response)=>{console.log("保存大章结果",response);let res = response.data;Loading.hide();if(res.success){$("#form-modal").modal("hide");_this.list(1);Toast.success("保存成功");}})
},
del(id) {let _this = this;console.log(id);Confirm.show("删除后不能还原,是否确认删除?",function() {Loading.show();_this.$ajax.delete('http://127.0.0.1:9000/business/admin/chapter/delete/'+id).then((response)=>{console.log("删除大章结果",response);let res = response.data;Loading.hide();if(res.success){Toast.success("删除成功");_this.list(1);}})});
}
校验模块
前端校验
首先在 js 包中添加 tool.js 和 validator.js ,分别是分离出来的处理流程和校验流程,接着在 save 中添加校验。
Validator.js
Validator = {require: function (value, text) {if (Tool.isEmpty(value)) {Toast.warning(text + "不能为空");return false;} else {return true}},length: function (value, text, min, max) {if (Tool.isEmpty(value)) {return true;}if (!Tool.isLength(value, min, max)) {Toast.warning(text + "长度" + min + "~" + max + "位");return false;} else {return true}}
};
tool.js
Tool = {/*** 空校验 null或""都返回true*/isEmpty: function (obj) {if ((typeof obj == 'string')) {return !obj || obj.replace(/\s+/g, "") == ""} else {return (!obj || JSON.stringify(obj) === "{}" || obj.length === 0);}},/*** 非空校验*/isNotEmpty: function (obj) {return !this.isEmpty(obj);},/*** 长度校验*/isLength: function (str, min, max) {return $.trim(str).length >= min && $.trim(str).length <= max;}
}
index.html
<!-- 通用工具类 -->
<script src="<%= BASE_URL %>static/js/tool.js"></script>
<!-- 校验类 -->
<script src="<%= BASE_URL %>static/js/validator.js"></script>
后端校验
首先新增了 validatorException 类,然后在工具类中增加了校验的方法,接着新增了一个处理 validator 异常的类,最后在 save 方法中添加了校验。
validatorException 类继承 RuntimeException,这样可以不用写 try catch。
package com.course.server.exception;public class ValidatorException extends RuntimeException{public ValidatorException(String message) {super(message);}
}
接着在工具类中添加了校验的方法
package com.course.server.util;import com.course.server.exception.ValidatorException;
import org.springframework.util.StringUtils;public class ValidatorUtil {/*** 空校验(null or "")*/public static void require(Object str, String fieldName) {if (StringUtils.isEmpty(str)) {throw new ValidatorException(fieldName + "不能为空");}}/*** 长度校验*/public static void length(String str, String fieldName, int min, int max) {if (StringUtils.isEmpty(str)) {return;}int length = 0;if (!StringUtils.isEmpty(str)) {length = str.length();}if (length < min || length > max) {throw new ValidatorException(fieldName + "长度" + min + "~" + max + "位");}}
}
Controller 层中保存方法在最开始添加校验
// 保存校验
ValidatorUtil.require(chapterDto.getName(), "名称");
ValidatorUtil.require(chapterDto.getCourseId(), "课程ID");
ValidatorUtil.length(chapterDto.getCourseId(), "课程ID", 1, 8);
这时如果校验后不满足会抛出异常,所以添加一个处理 validatorException 异常的方法。
package com.course.business.controller;import com.course.server.dto.ResponseDto;
import com.course.server.exception.ValidatorException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice
public class ControllerExceptionHandler {@ExceptionHandler(value = ValidatorException.class)@ResponseBodypublic ResponseDto validatorExceptionHandler(ValidatorException e){ResponseDto responseDto = new ResponseDto();responseDto.setMessage(e.getMessage());responseDto.setSuccess(false);return responseDto;}
}
这时把前端校验方法注释后可以看到后端的校验正常。