分页
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><!-- 引入样式 --><link rel="stylesheet" href="../../plugins/element-ui/index.css" /><link rel="stylesheet" href="../../styles/common.css" /><link rel="stylesheet" href="../../styles/page.css" /><style>#member-app .notAdmin::after{border: 0 !important;}</style>
</head>
<body><div class="dashboard-container" id="member-app"><div class="container"><div class="tableBar"><el-inputv-model="input"placeholder="请输入员工姓名"style="width: 250px"clearable@keyup.enter.native="handleQuery"><islot="prefix"class="el-input__icon el-icon-search"style="cursor: pointer"@click="handleQuery"></i></el-input><el-buttontype="primary"@click="addMemberHandle('add')">+ 添加员工</el-button></div><el-table:data="tableData"stripeclass="tableBox"><el-table-columnprop="name"label="员工姓名"></el-table-column><el-table-columnprop="username"label="账号"></el-table-column><el-table-columnprop="phone"label="手机号"></el-table-column><el-table-column label="账号状态"><template slot-scope="scope">{{ String(scope.row.status) === '0' ? '已禁用' : '正常' }}</template></el-table-column><el-table-columnlabel="操作"width="160"align="center"><template slot-scope="scope"><el-buttontype="text"size="small"class="blueBug"@click="addMemberHandle(scope.row.id)":class="{notAdmin:user !== 'admin'}">编辑</el-button><el-buttontype="text"size="small"class="delBut non"@click="statusHandle(scope.row)"v-if="user === 'admin'">{{ scope.row.status == '1' ? '禁用' : '启用' }}</el-button></template></el-table-column></el-table><el-paginationclass="pageList":page-sizes="[2]":page-size="pageSize"layout="total, sizes, prev, pager, next, jumper":total="counts":current-page.sync="page"@size-change="handleSizeChange"@current-change="handleCurrentChange"></el-pagination></div></div><!-- 开发环境版本,包含了有帮助的命令行警告 --><script src="../../plugins/vue/vue.js"></script><!-- 引入组件库 --><script src="../../plugins/element-ui/index.js"></script><!-- 引入axios --><script src="../../plugins/axios/axios.min.js"></script><script src="../../js/request.js"></script><script src="../../api/member.js"></script><script>new Vue({el: '#member-app',data() {return {input: '',counts: 0,page: 1,pageSize: 2,tableData : [],id : '',status : '',}},computed: {},created() {this.init()if(localStorage.getItem('userInfo') != null){//获取当前登录员工的账号,并赋值给模型数据userthis.user = JSON.parse(localStorage.getItem('userInfo')).username}},mounted() {},methods: {async init () {const params = {page: this.page,pageSize: this.pageSize,name: this.input ? this.input : undefined}await getMemberList(params).then(res => {if (String(res.code) === '1') {this.tableData = res.data.records || []this.counts = res.data.total}}).catch(err => {this.$message.error('请求出错了:' + err)})},handleQuery() {this.page = 1;this.init();},// 添加addMemberHandle (st) {if (st === 'add'){window.parent.menuHandle({id: '2',url: '/backend/page/member/add.html',name: '添加员工'},true)} else {window.parent.menuHandle({id: '2',url: '/backend/page/member/add.html?id='+st,name: '修改员工'},true)}},//状态修改statusHandle (row) {this.id = row.idthis.status = row.statusthis.$confirm('确认调整该账号的状态?', '提示', {'confirmButtonText': '确定','cancelButtonText': '取消','type': 'warning'}).then(() => {enableOrDisableEmployee({ 'id': this.id, 'status': !this.status ? 1 : 0 }).then(res => {console.log('enableOrDisableEmployee',res)if (String(res.code) === '1') {this.$message.success('账号状态更改成功!')this.handleQuery()}}).catch(err => {this.$message.error('请求出错了:' + err)})})},handleSizeChange (val) {this.pageSize = valthis.init()},handleCurrentChange (val) {this.page = valthis.init()}}})</script>
</body>
</html>
发送get的ajax请求
此时params参数是json格式
而浏览器中不是json格式
因为发送get请求后,又重新组装了
(function (win) {axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'// 创建axios实例const service = axios.create({// axios中请求配置有baseURL选项,表示请求URL公共部分baseURL: '/',// 超时timeout: 1000000})// request拦截器service.interceptors.request.use(config => {// 是否需要设置 token// const isToken = (config.headers || {}).isToken === false// if (getToken() && !isToken) {// config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改// }// get请求映射params参数if (config.method === 'get' && config.params) {let url = config.url + '?';for (const propName of Object.keys(config.params)) {const value = config.params[propName];var part = encodeURIComponent(propName) + "=";if (value !== null && typeof(value) !== "undefined") {if (typeof value === 'object') {for (const key of Object.keys(value)) {let params = propName + '[' + key + ']';var subPart = encodeURIComponent(params) + "=";url += subPart + encodeURIComponent(value[key]) + "&";}} else {url += part + encodeURIComponent(value) + "&";}}}url = url.slice(0, -1);config.params = {};config.url = url;}return config}, error => {console.log(error)Promise.reject(error)})// 响应拦截器service.interceptors.response.use(res => {console.log('---响应拦截器---',res)// 未设置状态码则默认成功状态const code = res.data.code;// 获取错误信息const msg = res.data.msgconsole.log('---code---',code)if (res.data.code === 0 && res.data.msg === 'NOTLOGIN') {// 返回登录页面console.log('---/backend/page/login/login.html---',code)localStorage.removeItem('userInfo')window.top.location.href = '/backend/page/login/login.html'} else {return res.data}},error => {console.log('err' + error)let { message } = error;if (message == "Network Error") {message = "后端接口连接异常";}else if (message.includes("timeout")) {message = "系统接口请求超时";}else if (message.includes("Request failed with status code")) {message = "系统接口" + message.substr(message.length - 3) + "异常";}window.ELEMENT.Message({message: message,type: 'error',duration: 5 * 1000})return Promise.reject(error)})win.$axios = service
})(window);
这是一个全局的拦截器
他拦截get请求后,又对参数进行重新组装
tableData是返回的数据
数据给tableData就可以自动的展示数据
有这些
有员工姓名,账号,手机号,由elementUI自动展示
总结:
代码开发
在开发代码之前,需要梳理一下整个程序的执行过程:
页面发送ajax请求,将分页查询参数(page.pageSize、name)提交到服务端
服务端Controller接收页面提交的数据并调用Service查询数据
Service调用Mapper操作数据库,查询分页数据
Controller将查询到的分页数据响应给页面
页面接收到分页数据并通过ElementUI的Table组件展示到页面上
@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor;}
}
PaginationInnerInterceptor是分页拦截器
methods: {async init () {const params = {page: this.page,pageSize: this.pageSize,name: this.input ? this.input : undefined}await getMemberList(params).then(res => {if (String(res.code) === '1') {this.tableData = res.data.records || []this.counts = res.data.total}}).catch(err => {this.$message.error('请求出错了:' + err)})},handleQuery() {this.page = 1;this.init();},
this.tableData = res.data.records || []
this.counts = res.data.total
data后面由records和total,说明响应的json里应该有records和total字段
public class Page<T> implements IPage<T> {private static final long serialVersionUID = 8545996863226528798L;protected List<T> records;protected long total;protected long size;protected long current;protected List<OrderItem> orders;protected boolean optimizeCountSql;protected boolean isSearchCount;protected boolean hitCount;protected String countId;protected Long maxLimit;
public R<Page> page(int page,int pageSize,String name){}
@GetMapping("/page")public R<Page> page(int page,int pageSize,String name){log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);//构造分页构造器Page pageInfo = new Page(page,pageSize);//构造条件构造器LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();//添加过滤条件queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);//添加排序条件queryWrapper.orderByDesc(Employee::getUpdateTime);//执行查询employeeService.page(pageInfo,queryWrapper);return R.success(pageInfo);}
log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);
{}是占位符,后面传的参数是page,pageSize和name
为什么泛型用Page,因为Page类有total和records字段
//查询数据列表
protected List<T> records = Collections.emptyList();
//总数
protected long total = 0;
StringUtils
StringUtils 方法的操作对象是 Java.lang.String 类型的对象,是 JDK 提供的 String 类型操作方法的补充,并且是 null 安全的(即如果输入参数 String 为 null 则不会抛出 NullPointerException ,而是做了相应处理,例如,如果输入为 null 则返回也是 null 等,具体可以查看源代码)。
除了构造器,StringUtils 中一共有130多个方法,并且都是 static 的,所以我们可以这样调用 StringUtils.xxx(),参考https://blog.csdn.net/weixin_42290280/article/details/82591161?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168104654316800197099173%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=168104654316800197099173&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-82591161-null-null.142^v82^insert_down38,201^v4^add_ask,239^v2^insert_chatgpt&utm_term=StringUtils&spm=1018.2226.3001.4187
新增员工
数据模型
新增员工,其实就是将我们新增页面录入的员工数据插入到employee表。需要注意,employee表中对username字段加入了唯一约束,因为username是员工的登录账号,必须是唯一的
<el-buttontype="primary"@click="addMemberHandle('add')">+ 添加员工</el-button>
@click="addMemberHandle('add')"
当鼠标点击时,会跳转到添加页面
(Request URL: http://localhost:8080/backend/page/member/add.html)
addMemberHandle方法
// 添加addMemberHandle (st) {if (st === 'add'){window.parent.menuHandle({id: '2',url: '/backend/page/member/add.html',name: '添加员工'},true)} else {window.parent.menuHandle({id: '2',url: '/backend/page/member/add.html?id='+st,name: '修改员工'},true)}},
if (st === 'add'){
window.parent.menuHandle({
id: '2',
url: '/backend/page/member/add.html',
name: '添加员工'
},true)
}
这些代码就能跳转到新的页面
add页面
<el-buttontype="primary"@click="submitForm('ruleForm', false)">保存</el-button>
点击就执行submitForm方法
submitForm (formName, st) {this.$refs[formName].validate((valid) => {if (valid) {if (this.actionType === 'add') {const params = { //用const关键字定义常量...this.ruleForm,sex: this.ruleForm.sex === '女' ? '0' : '1'}addEmployee(params).then(res => {if (res.code === 1) {this.$message.success('员工添加成功!')if (!st) {this.goBack()} else {this.ruleForm = {username: '','name': '','phone': '',// 'password': '',// 'rePassword': '',/'sex': '男','idNumber': ''}}} else {this.$message.error(res.msg || '操作失败')}}).catch(err => {this.$message.error('请求出错了:' + err)})} else {const params = {...this.ruleForm,sex: this.ruleForm.sex === '女' ? '0' : '1'}editEmployee(params).then(res => {if (res.code === 1) {this.$message.success('员工信息修改成功!')this.goBack()} else {this.$message.error(res.msg || '操作失败')}}).catch(err => {this.$message.error('请求出错了:' + err)})}} else {console.log('error submit!!')return false}})},
formName是表单对象
校验通过valid就会变成true
Validate 是 jQuery 的一个子插件, 该插件为表单提供了强大的验证功能,让客户端表单验证变得更简单,同时提供了大量的定制选项,满足用户的各种验证需求。该插件捆绑了一套有用的验证方法,同时提供了一个用来编写用户自定义方法的 API。默认使用英语作为错误信息,且已翻译成其他 37 种语言,支持中文错误提示。
@ControllerAdvice(annotations = {RestController.class, Controller.class})
annotations是注解的意思(@XXXXX)
{RestController.class, Controller.class}扫描所有带有@RestController和@Controller的类
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
处理SQLIntegrityConstraintViolationException异常
if(ex.getMessage().contains("Duplicate entry")){String[] split = ex.getMessage().split(" ");String msg=split[2]+"已存在";return R.error(msg);}
如果异常信息含有"Duplicate entry"
String[] split = ex.getMessage().split(" ");String msg=split[2]+"已存在";return R.error(msg);
就会动态的截取出来
代码修复
通过观察控制台输出的SQL发现页面传递过来的员工id的值和数据库中的id值不一致,这是怎么回事呢?
分页查询时服务端响应给页面的数据中id的值为19位数字,类型为long
页面中js处理long型数字只能精确到前16位,所以最终通过ajax请求提交给服务端的时候id就改变了
前面我们已经发现了问题的原因,即js对long型数据进行处理时丢失精度,导致提交的id和数据库中的id不一致。
如何解决这个问题?
我们可以在服务端给页面响应json数据时进行处理,将long型数据统一转为String字符串。
具体实现步骤:
1) 提供对象转换器JacksonobjectMapper,基于Jackson进行Java对象到json数据的转换(资料中已经提供,直接复制到项目中使用)
/*** 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]*/
public class JacksonObjectMapper extends ObjectMapper {public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";public JacksonObjectMapper() {super();//收到未知属性时不报异常this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);//反序列化时,属性不存在的兼容处理this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);SimpleModule simpleModule = new SimpleModule().addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))).addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))).addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))).addSerializer(BigInteger.class, ToStringSerializer.instance).addSerializer(Long.class, ToStringSerializer.instance).addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))).addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))).addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));//注册功能模块 例如,可以添加自定义序列化器和反序列化器this.registerModule(simpleModule);}
}
.addSerializer(Long.class, ToStringSerializer.instance)
对于long型的,将会转换为string
2) 在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到json数据的转换
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {//创建消息转换器MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();//设置对象转换器,底层使用Jackson将Java转换为jsonmessageConverter.setObjectMapper(new JacksonObjectMapper());//将上面的消息转换器对象追加到mvc框架的转换器集合中converters.add(0,messageConverter);super.extendMessageConverters(converters);
}
编辑员工信息
代码开发
在开发代码之前需要梳理一下操作过程和对应的程序的执行流程:
1、点击编辑按钮时,页面跳转到add.html,并在url中携带参数[员工id]
2、在add.html页面获取url中的参数[员工id]
3、发送ajax请求,请求服务端,同时提交员工id参数
4、服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面
5、页面接收服务端响应的json数据,通过VUE的数据绑定进行员工信息回显
6、点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端
7、服务端接收员工信息,并进行处理,完成后给页面响应
8、页面接收到服务端响应信息后进行相应处理
注意:add.html页面为公共页面,新增员工和编辑员工都是在此页面操作
/* 自定义trim */
function trim (str) { //删除左右两端的空格,自定义的trim()方法return str == undefined ? "" : str.replace(/(^\s*)|(\s*$)/g, "")
}//获取url地址上面的参数
function requestUrlParam(argname){var url = location.href //获取完整的请求url路径var arrStr = url.substring(url.indexOf("?")+1).split("&")for(var i =0;i<arrStr.length;i++){var loc = arrStr[i].indexOf(argname+"=")if(loc!=-1){return arrStr[i].replace(argname+"=","").replace("?","")}}return ""
}
var arrStr = url.substring(url.indexOf("?")+1).split("&")截取问号后的id,用来查询员工,而后进行编辑员工信息