Vue+ElementUI+SpringMVC 实现分页

2018-09-03 12:37:42 +08:00
 TyCoding

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 的:绑定):


注意:


会遇到的坑

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

即后端的逻辑比较简单,因为最麻烦的分页逻辑,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) {},
handleSizeChange(val) {
    this.findByPage(this.pageConf.pageCode, val);
},

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

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()是我们定义的分页的核心方法,所有其他分页中触发的方法都会调用这个方法重新和后端交互,获取到最新的数据并返回给页面。其中你需要注意:

第二点

如何将后端交互返回的数据赋值给表格中的绑定的数据、以及分页组件中绑定的数据,并实现 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.

3966 次点击
所在节点    Java
15 条回复
vansl
2018-09-03 12:38:39 +08:00
搁这发博客呢
TyCoding
2018-09-03 12:41:33 +08:00
@vansl 初来匝道,不是很懂,V2 社区不能发布博客文章吗?那 V2 社区可以发布什么呢?
Taosky
2018-09-03 12:42:35 +08:00
这种记录发博客比较好吧,也不是什么创造的东西。
TyCoding
2018-09-03 12:44:25 +08:00
@Taosky 哦哦,谢谢,打扰了
98jiang
2018-09-03 12:45:08 +08:00
看起来做的还不错的样子,不过发这里太长了吧。。
TyCoding
2018-09-03 12:48:10 +08:00
@Taosky @98jiang V2 上创建的主题不能删除吗?找不到删除按钮
aiyov
2018-09-03 12:49:27 +08:00
data 里面的东西不是要 return 出去吗?
TyCoding
2018-09-03 12:52:28 +08:00
@aiyov 不使用 return 也是可以的,使用 return 包裹后数据中变量只在当前组件中生效,不会影响其他组件。
98jiang
2018-09-03 13:02:16 +08:00
@TyCoding 的确没有,不过也不用删啦
gzlock
2018-09-03 13:41:45 +08:00
https://www.v2ex.com/about
虽然写着不反对原文作者自己全文转载到 v 站,但是因为 v 站的贴不能编辑不能删除,所以如果内容有错误就很容易变黑历史了
jennifertxwoodma
2018-09-03 14:36:38 +08:00
我也觉得这种发 blog 比较好
luckychenhaha
2018-09-03 14:47:05 +08:00
2017 届大一。。比我强多了。。溜了溜了
TyCoding
2018-09-03 15:06:25 +08:00
@gzlock 谢谢提醒,初来匝道,以后不会干这种傻事了
current
2018-09-03 20:21:04 +08:00
很莫名的被 @了。。
johnnie502
2018-09-03 22:41:45 +08:00
写这么一大堆,不如用 buefy

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/485600

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX