PageHelperは、複数条件のページングクエリを実装し、ページ番号ボタンの生成ロジックをカプセル化し、バックエンドインターフェイスをドッキングします

エフェクト画像

難易度分析

PageHelperの紹介と使用については、ここではあまり説明しません。PageHelperを使用すると、バックエンドのページングクエリは非常に簡単です。

複数の条件を持つページクエリを実現するには、フロントエンドインターフェイスとバックエンドインターフェイスをどのように組み合わせますか?コードを開始する前に、まず次の質問を順番に考えて分析し、次にアイデアを整理して、最後にコードを確認します。

質問1:フロントエンドがバックエンドに渡す必要があるパラメータは何ですか?

(1)ページ:現在のページ。フロントエンドからバックエンドに渡す必要がありますが、フロントエンドのパラメーターが実際の範囲を超える可能性があります(たとえば、合計10ページしかなく、ユーザーがパラメーター11を渡すと、バックエンドは空のデータの代わりに10ページのデータを返します)。これはバックエンドで修正する必要があり、ページ番号に対応するデータをクエリします。次に、バックエンドは修正された現在のページをフロントエンドに返し、フロントエンドがページ番号ボタングループをレンダリングできるようにします。そうでない場合、バックエンドはデフォルト値の1を返します。

(2)カウント:フロントエンドからバックエンドに渡される、各ページに表示されるレコードの数。そうでない場合は、バックエンドで定義されているデフォルト値を使用します。

(3)さまざまな条件パラメータ。上記のレンダリングを例にとると、4つの条件パラメーターをバックエンドに渡す必要があります。①商品名キーワード:prodName;②商品タイプ:cate;③最低価格:minPrice;④最高価格:maxPrice。

質問2:条件はどのようにバックエンドに送信されますか?        

  フロントエンドがバックエンドインターフェイスにアクセスする状況は3つあります。分析する例として、効果図を取り上げます。

(1)初めてページを開くときは、条件なしでGetメソッドを使用します

(2)「照会」ボタンをクリックして、郵送でフォームを送信します。現時点では、複数の条件が適用され、一部の送信が空になる場合があります。

(3)ページ番号ボタンをクリックしてページを切り替えると、タグをジャンプするため、メソッドはGetになります。ジャンプするとき、複数の条件を運ぶことが可能です。

   したがって、インターフェースは@RequestMappingアノテーションを使用する必要があります

質問3:バックエンドはどのデータをフロントエンドに返す必要がありますか?

(1)現在のページのデータを一覧表示します。例:List <Product>

(2)ページ:バックエンドによって修正された後の現在のページ。ページングボタンを生成するために使用されます。

(3)合計:レコードの総数。ページングボタンを生成するために使用されます。

(4)カウント:各ページに表示されるレコードの数は、バックエンドによって修正される場合もあります。ページングボタンを生成するために使用されます。

(5)urlParamsStr:ページ番号ボタンがクリックされたときにタグをジャンプするため、複数の条件を運ぶ必要がある場合があり、urlパラメーターの形式でのみ運ぶことができます。したがって、バックエンドは複数の条件を文字列に連結し、それらをフロントエンドに返して、フロントエンドがページングボタンを生成しやすくする必要があります。

質問4:ページ番号ボタンを生成するにはどのようなパラメーターが必要ですか?ページ番号ボタンのロジックをカプセル化する方法は?

(1)ページ:バックエンドによって修正された後の現在のページ。ページングボタンを生成するために使用されます。

(2)合計:レコードの総数。ページングボタンを生成するために使用されます。

(3)カウント:各ページに表示されるレコードの数は、バックエンドによって修正される場合もあります。ページングボタンを生成するために使用されます。

ページ番号ボタンを生成するには、上記の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";
	}
}

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

サービスレイヤーにはキーコードがないため、ここではSQLステートメントを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>

フロントエンドコード

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

パッケージングページ番号生成ロジック

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

ページ番号ボタンの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

おすすめ

転載: blog.csdn.net/qq_43290318/article/details/111828676