这一节增加了大章的编辑和删除功能,这样大章的增删改查功能就都有了,但是在增加和修改时应该还要有校验功能。
编辑功能
这一节主要对大章模块增加编辑功能,其次还删除一些多余组件。首先,我们将多余对按钮进行了删除,并且对剩余按钮绑定函数;接着我们将修改功能和增加功能合并,修改了前端的 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)
@ResponseBody
public ResponseDto validatorExceptionHandler(ValidatorException e){
ResponseDto responseDto = new ResponseDto();
responseDto.setMessage(e.getMessage());
responseDto.setSuccess(false);
return responseDto;
}
}
这时把前端校验方法注释后可以看到后端的校验正常。