PageHelper implementa consultas de paginación de múltiples condiciones, encapsula la lógica de generación de botones de números de página y acopla interfaces de back-end

Imagen de efecto

Análisis de dificultad

La introducción y el uso de PageHelper, no diré mucho aquí. Con PageHelper, la consulta de paginación de back-end es muy simple.

Para realizar una consulta paginada con múltiples condiciones, ¿cómo interactúa el front-end con la interfaz back-end? Antes de iniciar el código, primero piense y analice las siguientes preguntas sucesivamente, luego clasifique las ideas y, finalmente, observe el código.

Pregunta 1: ¿Qué parámetros debe pasar el front-end al back-end?

(1) página: página actual. Se debe pasar del front-end al back-end, pero los parámetros del front-end pueden exceder el rango real (por ejemplo, solo hay 10 páginas en total y el usuario pasa el parámetro 11, luego el back-end debe devolver los datos de la página 10 en lugar de los datos vacíos), que deben ser corregidos por el back-end, y luego consultar los datos correspondientes al número de página. Luego, el back-end devuelve la página actual corregida al front-end, de modo que el front-end puede representar el grupo de botones de número de página. De lo contrario, el backend devolverá el valor predeterminado de 1.

(2) Recuento: el número de registros que se muestran en cada página, que se transmite desde el principio hasta el final. De lo contrario, utilice el valor predeterminado definido por el backend.

(3) Varios parámetros de condición. Tomando las representaciones anteriores como ejemplo, se deben pasar 4 parámetros de condición al backend: ① Palabra clave del nombre del producto: prodName; ② Tipo de producto: cate; ③ Precio más bajo: minPrice; ④ Precio más alto: maxPrice.

Pregunta 2: ¿De qué manera se envía la condición al backend?        

  Hay 3 situaciones en las que el front-end accede a la interfaz del back-end. Tome el diagrama de efectos como ejemplo para analizar:

(1) Cuando abra la página por primera vez, use el método Get, sin ninguna condición

(2) Haga clic en el botón "Consulta" para enviar el formulario por correo postal. En este momento, se cumplen varias condiciones y algunos envíos pueden estar vacíos.

(3) Cuando hace clic en el botón de número de página para cambiar de página, el método es Obtener porque se salta a través de la etiqueta a. Al saltar, es posible llevar múltiples condiciones.

   Entonces, la interfaz necesita usar la anotación @RequestMapping

Pregunta 3: ¿Qué datos debería devolver el back-end al front-end?

(1) Lista de datos de la página actual. Tales como: Lista <Producto>

(2) página: la página actual después de ser corregida por el backend. Se utiliza para generar botones de paginación.

(3) total: número total de registros. Se utiliza para generar botones de paginación.

(4) Recuento: el backend también puede corregir el número de registros que se muestran en cada página. Se utiliza para generar botones de paginación.

(5) urlParamsStr: porque cuando se hace clic en el botón de número de página, se salta a través de la etiqueta a, es posible que deba tener varias condiciones y solo se puede llevar en forma de parámetros de URL. Por lo tanto, el backend necesita concatenar varias condiciones en cadenas y devolverlas al frontend para facilitar que el frontend genere botones de paginación.

Pregunta 4: ¿Qué parámetros se necesitan para generar un botón de número de página? ¿Cómo encapsular la lógica del botón de número de página?

(1) página: la página actual después de ser corregida por el backend. Se utiliza para generar botones de paginación.

(2) total: número total de registros. Se utiliza para generar botones de paginación.

(3) Recuento: el backend también puede corregir el número de registros que se muestran en cada página. Se utiliza para generar botones de paginación.

Para generar un botón de número de página, ¡los 3 parámetros anteriores son esenciales! ! ! En cuanto a la lógica de generación del botón de número de página, consulte mi otro blog: https://blog.csdn.net/qq_43290318/article/details/111601738 .

Código de backend

capa de controlador

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";
	}
}

vo

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 + "]";
	}
	
}

XML

Como no hay un código clave en la capa de servicio, aquí publico directamente la declaración SQL en el XML.

    <!-- 根据多条件检索商品 -->
   <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&gt;=#{minPrice}
  	 	</if>
  	 	<if test="maxPrice!=null">
  	 		and #{maxPrice} &gt;= price
  	 	</if>
  	 </where>
  		
  </select>

Código de front-end

prod_list.jsp

<%@ 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>

pageHelper.js

Lógica de generación de número de página de empaquetado

/**
 * 生成分页按钮的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];
}

pageHelper.css

Personalizar el estilo CSS del botón de número de página

.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;
}

Imagen de fondo citada por css: core_bg.png

Supongo que te gusta

Origin blog.csdn.net/qq_43290318/article/details/111828676
Recomendado
Clasificación