Vue+ElementUI+SpringMVC 实现分页 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
TyCoding
V2EX    Java

Vue+ElementUI+SpringMVC 实现分页

  •  
  •   TyCoding
    TyCoding 2018-09-03 12:37:42 +08:00 4359 次点击
    这是一个创建于 2603 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Vue + ElementUI + SpringMVC 实现分页

    这一段时间写项目用到了 Vue+ElementUI,这里记录一下使用 ElementUI 内置分页插件结合后端 SSM 框架的实现思路和实现过程。

    其中遇到了很多坑,我会尽量把见到的坑都记录下来,希望对你有所帮助。

    首先 让我们看一下最终效果:

    起步

    本博文的主要讲一下 Vue+ElementUI 结合后端 SpringMVC 实现分页的实现思路,基本的 elementUI 用法请自行百度;

    Vue 的常用语法可以看我的 博文

    关于 SSM 的整合教程可以看我的这篇 博文GitHub


    介绍

    本案例中设计到的技术栈:

    准备

    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配合服用。

    • 以上涉及两个函数handleSizeChangehandleCurrentChange,我们要在其触发时自动改变对应的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、根据上面的参数,以及handleSizeChangehandleCurrentChange这两个函数的参数你就应该想到分页的实现其实是pageCode(当前页)和pageSize(每页显示的记录数)和后端进行数据交换的。在前端你需要关心的怎样把pageSizepageCode传给后端进行分页查询;在后端你需要关心的是怎样调用pageHelper插件将分页的记录数据(包括totalPageuser数据等) 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 中定义的请求映射路径,其中需要接收两个参数:pageCodepageSize分别表示当前页、每页显示的记录数;即前端请求这个方法时只需要将pageCodepageSize传进来就行,后端使用pageHelper分页插件将查询到的数据进行分页,并将结果返回给前端。

    对于请求映射中包含多个参数的,应该使用@RequestParam()进行标记,不然可能报错 400 等。


    逻辑思路

    后端

    首先我们需要定义分页实体类:PageBean.java

    public class PageBean() implements Serialization { //当前页 private long total; //当前页记录 private List rows; } 

    因为我们使用了 mybatis 的分页插件:PageHelper,所以PageHelper最终为我们封装在PageBean的数据应该是这个样子的:

    **注意:**需要返回 JSON 格式数据。可以看到里面主要包含两个参数:totalrows

    • 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.有哪些可能被触发的事件和方法?

    • findByPage(pageCode,pageSize) 这个是分页的核心方法,会被多次触发。又因为进入页面就应该理解渲染表格中的数据,所以分页方法应在渲染页面时就执行,所以需要在created声明周期函数中调用findByPage(this.pageConf.pageCode,this.pageConf.pageSize)(传入默认的值)。对应的 HTML 代码:
    findByPage(pageCode, pageSize) {}, 
    • handleSizeChange(val) 这个函数是当 pageSize (每页显示的记录数)改变时被触发,通过 HTML 中的@size-change属性绑定。比如:原来 4 条 /每页改变为 6 条 /每页,就将触发这个函数;其中的参数val表示当前页每页显示几条记录pageSize = val。对应的 HTML 代码:
    handleSizeChange(val) { this.findByPage(this.pageConf.pageCode, val); }, 

    每当 pageSize 改变就需要重新调用findByPage(this.pageConf.pageCode, val)函数重新计算页面需要渲染的数据。

    • handleCurrentChange(val) 这个函数是当 pageCode(当前页)改变时触发的函数,通过 HTML 中的@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 方法请求后端得到了totalrows,就应该分别赋值给this.pageConf.totalPagethis.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函数两者共同完成的分页逻辑计算,其返回的数据主要是在totalrows中封装着。

    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.

    15 条回复    2018-09-03 22:41:45 +08:00
    vansl
        1
    vansl  
       2018-09-03 12:38:39 +08:00 via iPhone
    搁这发博客呢
    TyCoding
        2
    TyCoding  
    OP
       2018-09-03 12:41:33 +08:00
    @vansl 初来匝道,不是很懂,V2 社区不能发布博客文章吗?那 V2 社区可以发布什么呢?
    34
        3
    34  
       2018-09-03 12:42:35 +08:00 via iPhone   1
    这种记录发博客比较好吧,也不是什么创造的东西。
    TyCoding
        4
    TyCoding  
    OP
       2018-09-03 12:44:25 +08:00
    @Taosky 哦哦,谢谢,打扰了
    98jiang
        5
    98jiang  
       2018-09-03 12:45:08 +08:00
    看起来做的还不错的样子,不过发这里太长了吧。。
    TyCoding
        6
    TyCoding  
    OP
       2018-09-03 12:48:10 +08:00
    @Taosky @98jiang V2 上创建的主题不能删除吗?找不到删除按钮
    aiyov
        7
    aiyov  
       2018-09-03 12:49:27 +08:00
    data 里面的东西不是要 return 出去吗?
    TyCoding
        8
    TyCoding  
    OP
       2018-09-03 12:52:28 +08:00
    @aiyov 不使用 return 也是可以的,使用 return 包裹后数据中变量只在当前组件中生效,不会影响其他组件。
    98jiang
        9
    98jiang  
       2018-09-03 13:02:16 +08:00
    @TyCoding 的确没有,不过也不用删啦
    gzlock
        10
    gzlock  
       2018-09-03 13:41:45 +08:00 via Android
    about
    虽然写着不反对原文作者自己全文转载到 v 站,但是因为 v 站的贴不能编辑不能删除,所以如果内容有错误就很容易变黑历史了
    jennifertxwoodma
        11
    jennifertxwoodma  
       2018-09-03 14:36:38 +08:00
    我也觉得这种发 blog 比较好
    luckychenhaha
        12
    luckychenhaha  
       2018-09-03 14:47:05 +08:00
    2017 届大一。。比我强多了。。溜了溜了
    TyCoding
        13
    TyCoding  
    OP
       2018-09-03 15:06:25 +08:00
    @gzlock 谢谢提醒,初来匝道,以后不会干这种傻事了
    current
        14
    current  
       2018-09-03 20:21:04 +08:00
    很莫名的被 @了。。
    johnnie502
        15
    johnnie502  
       2018-09-03 22:41:45 +08:00
    写这么一大堆,不如用 buefy
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2557 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 11:23 PVG 19:23 LAX 04:23 JFK 07:23
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86