赞
踩
PageHelper的引入和使用,这里我就不多说了。有了PageHelper,后端的分页查询就非常简单了。
要实现携带多条件的分页查询,前端要如何跟后端接口对接呢?在开始代码之前,先来依次思考和分析下面的问题,然后再来整理思路,最后看代码。
问题1:前端需要传给后端什么参数?
(1)page:当前页。理应由前端传给后端,但是前端的传参有可能超出实际范围(比如说,一共才有10页,用户传参11,那么后端应该返回第10页的数据,而不应该返回空数据),这就必须交由后端来纠正之后,再查询对应页码的数据。然后后端将纠正后的当前页返回给前端, 以便前端来渲染页码按钮组。如果不传,由后端返回默认值1。
(2)count:每页显示的记录数,由前端传给后端。如果不传,使用后端定义的默认值。
(3)各种条件参数。以上面的效果图为例,需要传给后端 4 个条件参数:① 商品名称关键词:prodName;②商品种类:cate;③最低价格:minPrice;④最高价格:maxPrice。
问题2:条件以什么方式提交给后端?
前端访问后端接口有 3 种情况,以效果图为例分析:
(1)第一次打开页面,以Get方式,此时没有携带任何条件
(2)点击 “查询” 按钮,以Post方式提交表单,此时携带多个条件,且有些提交可能为空
(3)点击页码按钮切换页面时,由于是通过a标签跳转,所以是Get方式。跳转时,有可能携带多个条件。
所以接口需要使用 @RequestMapping 注解
问题3:后端应该返回什么数据给前端?
(1)当前页的列表数据。如:List<Product>
(2)page:被后端纠正之后的当前页。用于生成分页按钮。
(3)total:总记录数。用于生成分页按钮。
(4)count:每一页显示的记录数,也有可能被后端纠正。用于生成分页按钮。
(5)urlParamsStr:由于点击页码按钮时,通过a标签跳转,可能需要携带多个条件,而且只能通过url参数的形式携带过去。所以后端需要将多个条件拼接成字符串,返回给前端,方便前端生成分页按钮。
问题4:生成页码按钮需要什么参数?如何封装页码按钮的逻辑?
(1)page:被后端纠正之后的当前页。用于生成分页按钮。
(2)total:总记录数。用于生成分页按钮。
(3)count:每一页显示的记录数,也有可能被后端纠正。用于生成分页按钮。
要想生成页码按钮,上面3个参数,必不可少!!!至于页码按钮的生成逻辑,参见我的另一篇博客:https://blog.csdn.net/qq_43290318/article/details/111601738。
- package easymall.controller;
-
- import java.util.List;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.Model;
- import org.springframework.util.ObjectUtils;
- import org.springframework.web.bind.annotation.ModelAttribute;
- import org.springframework.web.bind.annotation.RequestMapping;
-
- import com.github.pagehelper.PageHelper;
- import com.github.pagehelper.PageInfo;
-
- import easymall.po.Products;
- import easymall.pojo.ProdListReqParamsVo;
- import easymall.service.ProductsService;
-
- @Controller("productsController")
- public class ProductsController {
-
- @Autowired
- private ProductsService productsService;
-
- /**
- * 该接口必须支持post和get方式访问
- * 因为表单提交是post方式,而第一次打开页面和点击页码切换页面都是get方式
- *
- * @param page 当前页。分页所需参数,如果前端不传,则默认为1
- * @param count 每页显示多少条记录。分页所需参数,如果前端不传,则默认为2
- */
- @RequestMapping("/prodlist")
- public String prodlist(@ModelAttribute("params") ProdListReqParamsVo params,
- Integer page, Integer count, Model model) {
- // 参数检查和纠正
- if (!ObjectUtils.isEmpty(params.getMinPrice()) &&
- !ObjectUtils.isEmpty(params.getMaxPrice())) {
- // 纠正 minPrice为非负数
- if (params.getMinPrice() < 0) {
- params.setMinPrice(0d);
- }
- // 纠正为 minPrice <= maxPrice
- if (params.getMinPrice() > params.getMaxPrice()) {
- double min = params.getMinPrice();
- params.setMinPrice(params.getMaxPrice());
- params.setMaxPrice(min);
- }
- }
- // curPage 是否越界,可不需要判断,PageHelper内部会判断并纠正
- if (ObjectUtils.isEmpty(page)) {
- page = 1;
- }
- if (ObjectUtils.isEmpty(count) || count <= 0) {
- count = 2;
- }
-
- // 查询所有分类
- List<String> cates = productsService.allcategorys();
-
- // 调用PageHelper进行分页
- // 紧跟在这个方法后的第一个MyBatis 查询方法会被进行分页
- PageHelper.startPage(page, count);
-
- // 查询数据
- List<Products> prodList = productsService.getProdListByConds(params);
-
- // 获取各种分页属性
- PageInfo<Products> pageInfo = new PageInfo<>(prodList);
- // 将有效的参数拼接成url字符串,用于拼接到url后面。切换页码时携带
- String urlParamsStr = params.joinUrlParams();
-
- model.addAttribute("cates", cates); // 分类数据
- model.addAttribute("prodList", prodList); // 商品列表数据
- model.addAttribute("page", pageInfo.getPageNum()); // 传给前端被修正后的当前页
- model.addAttribute("count", count); // 每一页显示多少条记录
- model.addAttribute("total", pageInfo.getTotal()); // 总记录数
- model.addAttribute("urlParamsStr", urlParamsStr);
-
- return "prod_list";
- }
- }
- package easymall.pojo;
-
-
- import java.io.UnsupportedEncodingException;
- import java.net.URLEncoder;
-
- import org.springframework.util.ObjectUtils;
-
- /**
- * 商品列表接口的请求参数
- *
- * @author passerbyYSQ
- * @date 2020-11-30 19:49:08
- */
- public class ProdListReqParamsVo {
-
- // 商品名称关键词。可以为空
- private String prodName;
-
- // 分类名字。可以为空,为空时表示所有分类
- private String cate;
-
- // 最低价格。不允许为负数,且 minPrice <= maxPrice
- private Double minPrice;
-
- // 最高价格。不允许为负数,且 minPrice <= maxPrice
- private Double maxPrice;
-
- /**
- * 将有效的参数拼接成url字符串,用于拼接到url后面。切换页码时携带
- */
- public String joinUrlParams() {
- StringBuilder urlParamsStr = new StringBuilder("");
- if (prodName != null) { // 可以为空串
- urlParamsStr.append("&prodName=").append(prodName);
- }
- if (cate != null) { // 可以为空串
- String cateTmp = cate;
- try {
- // 对中文进行url编码
- cateTmp = URLEncoder.encode(cate, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- urlParamsStr.append("&cate=").append(cateTmp);
- }
- if (!ObjectUtils.isEmpty(minPrice)) {
- urlParamsStr.append("&minPrice=").append(minPrice);
- }
- if (!ObjectUtils.isEmpty(maxPrice)) {
- urlParamsStr.append("&maxPrice=").append(maxPrice);
- }
-
- return urlParamsStr.toString();
- }
-
- public String getProdName() {
- return prodName;
- }
-
- public void setProdName(String prodName) {
- this.prodName = prodName;
- }
-
- public String getCate() {
- return cate;
- }
-
- public void setCate(String cate) {
- this.cate = cate;
- }
-
- public Double getMinPrice() {
- return minPrice;
- }
-
- public void setMinPrice(Double minPrice) {
- this.minPrice = minPrice;
- }
-
- public Double getMaxPrice() {
- return maxPrice;
- }
-
- public void setMaxPrice(Double maxPrice) {
- this.maxPrice = maxPrice;
- }
-
- @Override
- public String toString() {
- return "ProdListReqParamsVo [goodsName=" + prodName + ", cate=" + cate + ", minPrice=" + minPrice
- + ", maxPrice=" + maxPrice + "]";
- }
-
- }
由于Service层没有什么关键代码,这里我直接贴出XML中的SQL语句!
- <!-- 根据多条件检索商品 -->
- <select id="selectProdsByConds" parameterType="easymall.pojo.ProdListReqParamsVo" resultType="easymall.po.Products">
-
- select * from products
- <where>
- <if test="prodName!=null and prodName!=''">
- <!-- mybatis提供了<bind>标签来解决不同数据库模糊查询的差异,建议使用bind标签 -->
- <bind name="prod_name" value="'%' + prodName + '%'" />
- and name like #{prod_name}
- </if>
- <if test="cate!=null and cate!=''">
- and category=#{cate}
- </if>
- <if test="minPrice!=null">
- and price>=#{minPrice}
- </if>
- <if test="maxPrice!=null">
- and #{maxPrice} >= price
- </if>
- </where>
-
- </select>
- <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
- <!DOCTYPE HTML>
- <html>
- <head>
- <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
- <link href="${pageContext.request.contextPath }/css/prodList.css" rel="stylesheet" type="text/css">
- <link href="${pageContext.request.contextPath }/css/pageHelper.css" rel="stylesheet" type="text/css">
-
- <style>
- .pagehelper {
- text-align: center;
- }
- </style>
-
- </head>
- <body>
-
- <%@ include file="_head.jsp" %>
-
- <div id="content">
- <div id="search_div">
- <form method="post" action="${pageContext.request.contextPath}/prodlist">
- <span class="input_span">商品名:<input type="text" name="prodName" value="${params.prodName}"/></span>
- <span class="input_span">商品种类:</span>
- <select name="cate">
- <option value="">不限</option>
- <c:forEach items="${cates}" var="cate">
- <option value="${cate}" <c:if test="${cate==params.cate}">selected</c:if>>${cate}</option>
- </c:forEach>
-
- </select>
- <span class="input_span">商品价格区间:</span>
- <input type="text" name="minPrice" value="${params.minPrice}"/>
- - <input type="text" name="maxPrice" value="${params.maxPrice}"/>
- <input type="submit" value="查 询">
- </form>
- </div>
-
- <!-- 放置分页按钮 -->
- <div class="pagehelper">
-
- </div>
-
- <div id="prod_content">
- <c:forEach items="${prodList}" var="prod">
- <div class="prod_div">
- <a href="${pageContext.request.contextPath}/prodInfo?pid=${prod.id}" target="-blank">
- <img src="${pageContext.request.contextPath}${prod.imgurl}" border="0"></img>
- </a>
- <div id="prod_name_div">
- <a href="${pageContext.request.contextPath}/prodInfo?pid=${prod.id}" target="-blank">
- ${prod.name}
- </a>
- </div>
- <div id="prod_price_div">
- ¥${prod.price}元
- </div>
- <div>
- <div id="gotocart_div">
- <a href="${ pageContext.request.contextPath }/cart/addCart?pid=${prod.id}&buyNum=1">加入购物车</a>
- </div>
- <div id="say_div">
- 133人评价
- </div>
- </div>
- </div>
- </c:forEach>
- <div style="clear: both"></div>
- </div>
- </div>
- <%@ include file="_foot.jsp" %>
-
- <script src="${pageContext.request.contextPath }/js/jquery-1.4.2.js"></script>
- <script src="${pageContext.request.contextPath }/js/pageHelper.js"></script>
-
- <script>
- $(function() {
- let API_URL = "${pageContext.request.contextPath}/prodlist";
- let curPage = ${page};
- let total = ${total};
- let count = ${count};
- let sideBtnCount = 2;
- let urlParamsStr = '${urlParamsStr}';
-
- let btnHtml = pageHelper(API_URL, curPage, total, count, sideBtnCount, urlParamsStr);
- $('div.pagehelper').html(btnHtml);
- })
- </script>
- </body>
- </html>
封装页码的生成逻辑
- /**
- * 生成分页按钮的html代码
- * @param curPage 当前页。理应由前端传给后端,但是前端的传参有可能超出实际范围,这就必须交由
- * 后端来纠正之后,再查询对应页码的数据。然后后端将纠正后的当前页返回给前端,
- * 以便前端来渲染页码按钮组。如果不传,由后端返回默认值1
- * @param total 总记录数。实际上,后端可以直接返回总页数就可以了,只不过有一定局限性:假如
- * 前端还需要显示总记录数,凭借总页数和每页记录数,是无法计算出总记录数的。而返
- * 回总记录数,前端可以自行计算总页数,同时还可以额外显示总记录数
- * @param count 每页显示的记录数,由前端传给后端。如果不传,使用后端定义的默认值
- * @param sideBtnCount 当前页按钮的左边有多少个按钮,不需要传给后端
- * @param urlParamsStr 点击页码切换页面时,携带的条件参数的字符串,拼接在url后面。由后端定义并传给
- * 前端。后端接口并负责接收,按照自己定义的规则进行解析,拆解参数。
- * 例子:&name=ysq&age=21。前面的&不能少
- */
- function pageHelper(API_URL, curPage, total, count, sideBtnCount, urlParamsStr) {
- // 计算总页数
- let pageCount = Math.ceil(total / count);
-
- let leftPage, rightPage;
-
- if (pageCount <= 2 * sideBtnCount + 1) {
- leftPage = 1;
- rightPage = pageCount;
- } else {
- // 计算按钮组最左端和最右端的页码
- // 将[1, pageCount]分为3个区间:
- // [1, sideBtnCount],[sideBtnCount+1, pageCount-sideBtnCount],[pageCount-sideBtnCount+1, pageCount]
- if (curPage > sideBtnCount && curPage <= pageCount - sideBtnCount) {
- // [sideBtnCount+1, pageCount-sideBtnCount]
- leftPage = curPage - sideBtnCount;
- rightPage = curPage + sideBtnCount;
-
- } else if (curPage <= sideBtnCount) {
- // [1, sideBtnCount]
- leftPage = 1;
- rightPage = 2 * sideBtnCount + 1;
- // 越界时,修正当前页
- if (curPage < 1) {
- curPage = 1;
- }
-
- } else if (curPage > pageCount - sideBtnCount) {
- // [pageCount-sideBtnCount+1, pageCount]
- leftPage = pageCount - 2 * sideBtnCount;
- rightPage = pageCount;
- // 越界时,修正当前页
- if (curPage > pageCount) {
- curPage = pageCount;
- }
- }
- }
-
- return "<div class='pagination'>" +
- firstBtn('First') +
- preBtn('Pre') +
- numBtn(leftPage, rightPage) +
- nextBtn('Next') +
- lastBtn('Last') +
- "</div>";
-
- /**
- * 返回一个可点击的按钮的html代码
- * @param contentHtml 按钮中的内容
- */
- function clickableBtn(contentHtml, num) {
- //return `<a href='${API_URL}?page=${num}${urlParamsStr}'>${contentHtml}</a>`;
- return "<a href='" + API_URL + "?page=" + num + urlParamsStr + "'>" + contentHtml + "</a>";
- }
-
- /**
- * 返回一个当前页按钮的html代码
- * @param contentHtml
- */
- function currentBtn(contentHtml) {
- //return `<span>${contentHtml}</span>`;
- return "<span>" + contentHtml + "</span>";
- }
-
- /**
- * 返回上一页按钮的html代码
- * @param contentHtml
- */
- function preBtn(contentHtml) {
- if (curPage <= 1) {
- return ''; // 我这里直接返回空,你也可以根据你的喜好,返回禁用点击的按钮
- }
- return clickableBtn(contentHtml, curPage - 1);
- }
-
- /**
- * 返回下一页按钮的html代码
- * @param contentHtml
- */
- function nextBtn(contentHtml) {
- if (curPage >= pageCount) {
- return '';
- }
- return clickableBtn(contentHtml, curPage + 1);
- }
-
- /**
- * 返回首页按钮的html代码
- * @param contentHtml
- */
- function firstBtn(contentHtml) {
- if (leftPage <= 1) {
- // 如果首页(1)已经显示在了按钮组(>=leftPage)当中,则不需要首页按钮,这里我直接返回空
- return '';
- }
- return clickableBtn(contentHtml, 1);
- }
-
- /**
- * 返回末页按钮的html代码
- * @param contentHtml
- */
- function lastBtn(contentHtml) {
- if (pageCount <= rightPage) {
- // 如果末页(pageCount)已经显示在了按钮组(<=rightPage)当中,则不需要首页按钮,这里我直接返回空
- return '';
- }
- return clickableBtn(contentHtml, pageCount);
- }
-
- /**
- * 生成[left, right]区间的按钮的html代码
- * @param left
- * @param right
- */
- function numBtn(left, right) {
- let btnHtml = '';
- for (let i = left; i <= right; i++) {
- if (i === curPage) { // 当前页
- btnHtml += currentBtn(i);
- } else {
- btnHtml += clickableBtn(i, i);
- }
- }
- return btnHtml;
- }
- }
-
- // 获取指定的路径参数,获取不到返回空串
- function getUrlParam(key) {
- // ? 后面的
- let searchStr = window.location.search.substring(1);
- console.log(searchStr);
-
- let paramMap = new Array();
-
- let paramEntrys = searchStr.split('&');
- for(let i=0; i<paramEntrys.length; i++) {
- let entry = paramEntrys[i].split('=');
- paramMap[ entry[0] ] = entry[1];
- }
-
- console.log(paramMap);
-
- return paramMap[key];
- }
自定义页码按钮的css样式
- .pagination {
- margin: 12px;
- }
- .pagination a {
- background:url(../img/core_bg.png) #f8f8f8;
- border-color: #c9c9c9 #bdbdbd #b0b0b0;
- border-image: none;
- border-radius: 3px;
- border-style: solid;
- border-width: 1px;
- color: #666666;
- display: inline-block;
- line-height: 13px;
- margin-right: 3px;
- padding: 6px 10px;
- text-decoration: none;
- vertical-align: top;
- }
- .pagination a:hover {
- background-color: #f8f8f8;
- border-color:#c9c9c9 #bdbdbd #b0b0b0;
- border-image:none;
- border-radius:3px;
- border-style:solid;
- border-width:1px;
- color:#666666;
- display:inline-block;
- line-height:13px;
- margin-right:3px;
- padding:6px 10px;
- text-decoration:none;
- background:#488fcf;
- border-color: #2470b5 #488fcf #488fcf;
- color:#fff;
- }
- .pagination span {
- background-color: #f8f8f8;
- border-color:#c9c9c9 #bdbdbd #b0b0b0;
- border-image:none;
- border-radius:3px;
- border-style:solid;
- border-width:1px;
- color:#666666;
- display:inline-block;
- line-height:13px;
- margin-right:3px;
- padding:6px 10px;
- text-decoration:none;
- background:#488fcf;
- border-color: #2470b5 #488fcf #488fcf;
- color:#fff;
- }
css引用的背景图:core_bg.png
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。