这一段时间写项目用到了 Vue+ElementUI,这里记录一下使用 ElementUI 内置分页插件结合后端 SSM 框架的实现思路和实现过程。
其中遇到了很多坑,我会尽量把见到的坑都记录下来,希望对你有所帮助。
首先 让我们看一下最终效果:
本博文的主要讲一下 Vue+ElementUI 结合后端 SpringMVC 实现分页的实现思路,基本的 elementUI 用法请自行百度;
Vue 的常用语法可以看我的 博文 。
关于 SSM 的整合教程可以看我的这篇 博文; GitHub。
介绍
本案例中设计到的技术栈:
PageHelper: Mybatis 的分页插件
1、SSM 框架的整合教程可以参考我的这篇博文:手摸手带你整合 SSM 框架; GitHub。
2、在后端项目中导入PageHelper.jar
的依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.0.0</version>
</dependency>
***注意 使用 PageHelper 分页插件除了要导入依赖,还需要在 Mybatis 配置文件中进行相关配置,并交给 Spring 进行管理。如下配置即可:
<plugins>
<!-- com.github.pagehelper 为 PageHelper 类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL 六种数据库-->
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
这里还要注意的是 PageHelper5.X 版本和 PageHelper4.X 版本 PageHelper 类所在的包名是不同的。
在 Spring 配置文件中扫描此配置文件即可:
3、在 HTML 中导入vue.js
and element-ui
。
好的,至此,我们把基本的环境已经讲过了,下面看下相关前端代码:
<!-- 列表 -->
<el-table
ref="user"
:data="user"
tooltip-effect="dark"
style="width: 100%">
<el-table-column
prop="id"
sortable
label="编号"
width="80">
</el-table-column>
<el-table-column
prop="username"
sortable
label="联系人"
width="120">
</el-table-column>
<el-table-column
prop="phone"
sortable
label="联系电话"
width="120">
</el-table-column>
<el-table-column
prop="mailbox"
label="电子邮箱"
width="150">
</el-table-column>
<el-table-column
prop="postalCode"
sortable
label="邮政编码"
width="120">
</el-table-column>
<el-table-column
prop="date"
sortable
label="注册时间"
width="200">
</el-table-column>
<el-table-column
prop="address"
label="通讯地址"
width="200"
show-overflow-tooltip>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageConf.pageCode"
:page-sizes="pageConf.pageOption"
:page-size="pageConf.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pageConf.totalPage">
</el-pagination>
</div>
注意我们上面前端 HTML 样式用使用 Vue 绑定的数据:
1、列表数据
//注意这部分代码是在 Vue 实例中的 data 属性中定义的
data() {
//用户信息
//element-ui 的 table 需要的参数必须是 Array 类型的
user: [{
username: '',
phone: '',
mailbox: '',
postalCode: '',
date: '',
address: ''
}],
}
上面 ElementUI 表格中<el-table>
中用 Vue 绑定的:data="user"
就是这个数据,注意:这里的 user 对象中的数据需要是Array类型的,不要问为什么,请去看 ElementUI 源码;
2、分页数据
//注意这部分代码是在 Vue 实例中的 data 属性中定义的
data() {
//定义分页 Config
pageConf: {
//设置一些初始值(会被覆盖)
pageCode: 1, //当前页
pageSize: 4, //每页显示的记录数
totalPage: 12, //总记录数
pageOption: [4, 10, 20], //分页选项
handleCurrentChange: function () {
console.log("页码改变了");
}
},
}
methods: {
//pageSize 改变时触发的函数
handleSizeChange(val) {},
//当前页改变时触发的函数
handleCurrentChange(val) {},
}
上面<el-pagination>
中绑定的数据就来自这个对象:pageConf
,那么下面你需要关注<el-pagination>
中的几个配置参数(方法通过 Vue 的@
绑定,数据通过 Vue 的:
绑定):
@size-change
: 表示每页记录的个数发生变化时触发的函数,如:原来是每页 /3 条,变为每页 /6 条;handleSizeChange
中包含一个参数表示当前是每页显示几条记录。
@current-change
: 表示当前页发生变化时触发的函数,如:点击下一页;handleCurrentChange
中包含一个参数表示当前是第几页。
:current-page
: 当前页,即我们命名的pageCode
,表示当前页面上展示的第几页。
:page-sizes
: 分页选项,即页面提供一个列表让你选择每页显示多少条记录,注意这个参数的第一个值表示当前页是每页 /记录
,你写上即生效。
:page-size
: 表示每页显示的记录数,即我们命名的pageSize
。
:total
: 表示总记录数,即我们这个表格中一共要显示多少条数据。
以上代码可能与截图中样式不符,因为我把这篇博文中不涉及的都删除了。
表格中的数据来自:data
这个绑定的对象数组中,即我们再 Vue 实例 data 中定义的user: [{}]
,前提是你在每一个<el-table-column>
中都定义了prop
并标识了user:[{}]
中定义的变量,不然 element-ui 不知道你想在表格的这一行显示什么,当然这已经比我们常用的表格渲染数据方便很多了。
element-ui 自带的分页插件需要提供数据才能正常显示分页信息,这些数据都应该是动态的,所以我们绑定在pageConf
对象中;因为这些数据应该是后端读取出来的,即通过得到后端传来的分页数据,我们才知道这里的分页信息应该怎样定义。
在 data 中定义的pageConf
是初始化参数,最后会被覆盖掉,但是要注意pageOption
这个参数,一定要和初始的pageSize
配合服用。
以上涉及两个函数handleSizeChange
、handleCurrentChange
,我们要在其触发时自动改变对应的pageOption
参数。
1、<el-table>
中需要渲染的数据仅需要传入:data="user"
即可,但是这个数据user
必须是一个对象数组,一定是数组
2、想要<el-table>
正确渲染你user
中定义的数据,你必须为每个<el-table-column>
定义prop
属性,绑定对应你想展示的数据,不然 ElementUI 不知道你想展示什么。
3、pageOption
分页选项一定要注意,要配合pageSize
的默认值,不要乱定义,比如:pageSize: 2, pageOption: [10,20,30]
,这样你就会发现页码根本不能正确显示,因为你设置pageSize:2
表示你想每页展示 2 条数据,但是你又定义pageOption: [10,20,30]
第一个参数即是默认被选中的,即你又想每页显示 10 条数据,那么 ElementUI 就蒙蔽了,不知道你到底想每页显示几条数据。
3、根据上面的参数,以及handleSizeChange
、handleCurrentChange
这两个函数的参数你就应该想到分页的实现其实是pageCode
(当前页)和pageSize
(每页显示的记录数)和后端进行数据交换的。在前端你需要关心的怎样把pageSize
和pageCode
传给后端进行分页查询;在后端你需要关心的是怎样调用pageHelper
插件将分页的记录数据(包括totalPage
、user
数据等) return 给前端。
定义请求映射路径:findByPage
@RequestMapping("/findByPage")
public PageBean findByPage(@RequestParam("pageCode") int pageCode, @RequestParam("pageSize") int pageSize) {
System.out.println("分页的数据:" + userService.findByPage(pageCode, pageSize));
return userService.findByPage(pageCode, pageSize);
}
如上是我们在 Controller 中定义的请求映射路径,其中需要接收两个参数:pageCode
和pageSize
分别表示当前页、每页显示的记录数;即前端请求这个方法时只需要将pageCode
和pageSize
传进来就行,后端使用pageHelper
分页插件将查询到的数据进行分页,并将结果返回给前端。
对于请求映射中包含多个参数的,应该使用@RequestParam()
进行标记,不然可能报错 400 等。
首先我们需要定义分页实体类:PageBean.java
public class PageBean() implements Serialization {
//当前页
private long total;
//当前页记录
private List rows;
}
因为我们使用了 mybatis 的分页插件:PageHelper
,所以PageHelper
最终为我们封装在PageBean
的数据应该是这个样子的:
**注意:**需要返回 JSON 格式数据。可以看到里面主要包含两个参数:total
、rows
total
表示当前数据的分页得到的总页数,相当于我们前端定义的pageCode
。rows
表示当前查询到数据的集合体。即后端的逻辑比较简单,因为最麻烦的分页逻辑,PageHelper
已经帮我们完成了,我们需要做的:
1、在 Controller 中定义请求映射方法:PageBean findByPage(@RequestParam("pageCode")int pageCode, @RequestParam("pageSize")int pageSize){}
2、Controller 调用 Service,通过PageHelper
分页插件获取到这两个参数 pageCode,pageSize,自动进行分页计算。
3、Service 调用 Dao,指定对应的 SQLSELECT * FROM user
,可以看到这个 SQL 仅仅需要查询所有数据即可,返回的数据类型是com.github.pagehelper.Page
4、Controller 需要返回给前端的数据类型是:PageBean
(我们自定义的),其中有两个参数:com.github.pagehelper.Page.getTotal()
和com.github.pagehelper.Page.getResult()
。
5、综上,我们基本已经获取到了数据,然后通过 SpringMVC 提供的注解:@RsponseBody
(局部标识方法)或@RestController
(全局标识类),自动将返回的数据转换为 JSON 格式,然后再发送给前端。
前端逻辑相对复杂一些,我们主要需要关注两点:
1.进入页面触发的事件方法、以及点击分页相关的按钮怎样和后端交互? 2.如何将后端交互返回的数据赋值给表格中的绑定的数据、以及分页组件中绑定的数据,并实现 HTML 页面的渲染?
进入页面触发的事件方法、以及点击分页相关的按钮怎样和后端交互?
1.有哪些可能被触发的事件和方法?
created
声明周期函数中调用findByPage(this.pageConf.pageCode,this.pageConf.pageSize)
(传入默认的值)。对应的 HTML 代码:findByPage(pageCode, pageSize) {},
@size-change
属性绑定。比如:原来 4 条 /每页改变为 6 条 /每页,就将触发这个函数;其中的参数val
表示当前页每页显示几条记录pageSize = val
。对应的 HTML 代码:handleSizeChange(val) {
this.findByPage(this.pageConf.pageCode, val);
},
每当 pageSize 改变就需要重新调用findByPage(this.pageConf.pageCode, val)
函数重新计算页面需要渲染的数据。
@current-change
属性绑定。比如:点击下一页、上一页,就会触发这个函数;其中的参数val
表示当前是第几页pageCode = val
。对应的 HTML 代码:handleCurrentChange(val) {
this.findByPage(val, this.pageConf.pageSize);
},
每当 pageCode 改变时就需要重新调用findByPage(val, this.pageConf.pageSize)
函数从新计算页面需要渲染的数据。
2.分页相关按钮是什么鬼?
在传统没有每页插件的时候,我们通常会手写分页逻辑,那么就需要为每一个页面绑定一个触发方法,而使用了 element-ui 提供的分页插件,大大简化了分页逻辑,其中点击的下一页、上一页、点击每页显示记录选项、去第几页等这些功能都是 ElementUI 自动帮我们绑定了事件。
3.怎样和后端交互?
和后端实现交互的方法主要是findByPage()
这个核心方法,其相关 JS 代码:
findByPage(pageCode, pageSize) {
this.$http.post('/user/findByPage.do', {pageCode: pageCode, pageSize: pageSize}).then(result => {
this.pageConf.totalPage = result.body.total;
this.user = result.body.rows;
});
},
如上,findByPage()是我们定义的分页的核心方法,所有其他分页中触发的方法都会调用这个方法重新和后端交互,获取到最新的数据并返回给页面。其中你需要注意:
findByPage()中包含两个参数:pageCode、pageSize。
调用 vue-resource 提供的 post 请求方法,其中传入两个参数 pageCode、pageSize ;在then()
回调函数中可获取请求返回的数据。
注意 Controller 返回的数据就在result
这个参数中,但是实际的数据是在result.body
中的,所以你直接result.total
是获取不到数据的。
前面已经看到了,后端主要返回两个封装了数据的参数:total
(总页数)、rows
(核心数据)
findByPage 方法请求后端得到了total
和rows
,就应该分别赋值给this.pageConf.totalPage
和this.user
;根据 Vue 双向绑定的功能,页面新的数据会直接渲染出来。
如何将后端交互返回的数据赋值给表格中的绑定的数据、以及分页组件中绑定的数据,并实现 HTML 页面的渲染?
其实第一点中我们已经讲到了,因为 Vue 有一个双向绑定的功能,即我们请求后端将数据赋值给data:{}
中的对象后,HTML 页面会立即渲染新的data
数据。
如何将后端返回的数据赋值给页面需要展示的数据?
首先是<el-table>
中要渲染的数据,其来自:data="user"
绑定的 user 对象,我们需要将后端返回的数据赋值给这个user
根据双向绑定思想即会更新表格中的数据。
其次就是<el-pagination>
中定义的分页参数,由于 element-ui 分页插件已经帮我们完成了很多逻辑计算,我们需要交互改变的参数只有三个:pageCode
当前页、pageSize
每页显示的记录数、totalPage
总记录条数,而后端返回的数据我们也看过,综上:我们只需要将后端返回的总页数total
赋值给user
对象中的属性totalPage
即可。
主要 JavaScript 代码
findByPage(pageCode, pageSize) {
this.$http.post('/user/findByPage.do', {pageCode: pageCode, pageSize: pageSize}).then(result => {
this.pageConf.totalPage = result.body.total;
this.user = result.body.rows;
});
},
经过上面的分析,其实很多代码已经展示出来了,下面我们看看完整的代码:
实体类
public class PageBean implements Serializable {
//当前页
private long total;
//当前页记录
private List rows;
...
}
public class User implements Serializable {
private Long id; //用户编号
private String username; //用户名
private String password; //密码
private String phone; //联系电话
private String mailbox; //邮箱
private String address; //地址
private String postalCode; //邮政编码
private String date; //注册日期
...
}
Controller
@ResponseBody
@RequestMapping("/findByPage")
public PageBean findByPage(@RequestParam("pageCode") int pageCode, @RequestParam("pageSize") int pageSize) {
return userService.findByPage(pageCode, pageSize);
}
Service
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.instrument.dao.UserDao;
import com.instrument.entity.PageBean;
import com.instrument.entity.User;
...
public PageBean findByPage(int pageCode, int pageSize) {
//使用 Mybatis 分页插件
PageHelper.startPage(pageCode,pageSize);
//调用分页查询方法,其实就是查询所有数据,mybatis 自动帮我们进行分页计算
Page<User> page = userDao.findByPage();
return new PageBean(page.getTotal(),page.getResult());
}
这里 dao 层调用的findByPage()
对应的 SQL 仅仅是SELECT * FROM 表
。而分页是调用的startPage()
和Page
函数两者共同完成的分页逻辑计算,其返回的数据主要是在total
和rows
中封装着。
mapper.xml
<!-- 分页查询 -->
<select id="findByPage" resultType="com.instrument.entity.User">
SELECT * FROM user
</select>
<div id="#app">
<el-table
ref="user"
:data="user"
tooltip-effect="dark"
style="width: 100%">
<el-table-column
prop="id"
sortable
label="编号"
width="80">
</el-table-column>
<el-table-column
prop="username"
sortable
label="联系人"
width="120">
</el-table-column>
<el-table-column
prop="phone"
sortable
label="联系电话"
width="120">
</el-table-column>
<el-table-column
prop="mailbox"
label="电子邮箱"
width="150">
</el-table-column>
<el-table-column
prop="postalCode"
sortable
label="邮政编码"
width="120">
</el-table-column>
<el-table-column
prop="date"
sortable
label="注册时间"
width="200">
</el-table-column>
<el-table-column
prop="address"
label="通讯地址"
width="200"
show-overflow-tooltip>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageConf.pageCode"
:page-sizes="pageConf.pageOption"
:page-size="pageConf.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pageConf.totalPage">
</el-pagination>
</div>
</div>
<script type="text/javascript" src="../vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#app'
data(){
//用户信息
//element-ui 的 table 需要的参数必须是 Array 类型的
user: [{
username: '',
phone: '',
mailbox: '',
postalCode: '',
date: '',
address: ''
}],
//定义分页 Config
pageConf: {
//设置一些初始值(会被覆盖)
pageCode: 1, //当前页
pageSize: 4, //每页显示的记录数
totalPage: 12, //总记录数
pageOption: [4, 10, 20], //分页选项
handleCurrentChange: function () {
console.log("页码改变了");
}
},
},
methods:{
findByPage(pageCode, pageSize) {
this.$http.post('/user/findByPage.do', {pageCode: pageCode, pageSize: pageSize}).then(result => {
this.pageConf.totalPage = result.body.total;
this.user = result.body.rows;
});
},
//pageSize 改变时触发的函数
handleSizeChange(val) {
this.findByPage(this.pageConf.pageCode, val);
},
//当前页改变时触发的函数
handleCurrentChange(val) {
this.findByPage(val, this.pageConf.pageSize);
},
// 获取所有数据
findAll() {
this.$http.post('/user/findAll.do').then(result => {
this.user = result.body;
});
}
},
created(){
this.findAll();
this.findByPage(this.pageConf.pageCode, this.pageConf.pageSize);
}
});
</script>
以上代码我们基本已经解释过了,唯一没有提到的就是findAll()
这个方法,要知道,进入到页面后,首先就是展示所有数据(即使有没有分页);那么就需要在生命周期函数created
中执行findAll()
获取所有数据直接渲染到页面上this.user=result.body
即可。其次又因为我们使用了分页查询功能,进入页面后展示的数据应该是分页查询后的数据(因为我们设置有默认的分页参数值)。
如果大家有兴趣,欢迎大家加入我的 Java 交流群:671017003,一起交流学习 Java 技术。博主目前一直在自学 JAVA 中,技术有限,如果可以,会尽力给大家提供一些帮助,或是一些学习方法,当然群里的大佬都会积极给新手答疑的。所以,别犹豫,快来加入我们吧!
If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.