搜索页面渲染
搜索分析
搜索页面要显示的内容主要分为3块。
1)搜索的数据结果
2)筛选出的数据搜索条件
3)用户已经勾选的数据条件
搜索实现
搜索的业务流程如上图,用户每次搜索的时候,先经过搜索业务工程,搜索业务工程调用搜索微服务工程。
搜索工程搭建
(1)引入依赖
在changgou-service_search工程中的pom.xml中引入如下依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
(2)静态资源导入
将资源中的页面资源/所有内容
拷贝到工程的resources
目录下如下图:
(3) 更改配置文件,在spring下添加内容
thymeleaf: cache: false
基础数据渲染
(1)更新SearchController,定义跳转搜索结果页面方法
代码如下:
//搜索页面 http://localhost:9009/search/list?keywords=手机&brand=三星&spec_颜色=粉色& //入参:Map //返回值 Map //由于页面是thymeleaf 完成的 属于服务器内页面渲染 跳转页面 @GetMapping("/list") public String search(@RequestParam Map<String, String> searchMap, Model model) throws Exception { //特殊符号处理 handlerSearchMap(searchMap); //执行查询返回值 Map<String, Object> resultMap = searchService.search(searchMap); model.addAttribute("searchMap", searchMap); model.addAttribute("result", resultMap); return "search"; } }
(2) 搜索结果页面渲染
(2.1)用户选择条件回显
<div class="bread"> <ul class="fl sui-breadcrumb"> <li> <a href="#">全部结果</a> </li> <li class="active"> <span th:text="${searchMap.keywords}"></span> </li> </ul> <ul class="fl sui-tag"> <!-- 品牌--> <li class="with-x" th:if="${#maps.containsKey(searchMap,'brand')}"> 品牌:<span th:text="${searchMap.brand}"></span> <i>×</i> </li> <!-- 价格--> <li class="with-x" th:if="${#maps.containsKey(searchMap,'price')}"> 价格:<span th:text="${searchMap.price}"></span> <i>×</i> </li> <!-- 规格--> <li class="with-x" th:each="sm:${searchMap}" th:if="${#strings.startsWith(sm.key,'spec_')}"> <span th:text="${#strings.replace(sm.key,'spec_','')}"></span>:<span th:text="${#strings.replace(sm.value,'%2B','+')}"></span> <i>×</i> </li> </ul> <form class="fl sui-form form-dark"> <div class="input-control control-right"> <input type="text" /> <i class="sui-icon icon-touch-magnifier"></i> </div> </form> </div>
(2.2)商品属性及规格显示
更新搜索业务层实现
public Map<String, Set<String>> formartSpec(List<String> specList){ Map<String,Set<String>> resultMap = new HashMap<>(); if (specList!=null && specList.size()>0){ for (String specJsonString : specList) { //"{'颜色': '黑色', '尺码': '250度'}" //将获取到的json转换为map Map<String,String> specMap = JSON.parseObject(specJsonString, Map.class); for (String specKey : specMap.keySet()) { Set<String> specSet = resultMap.get(specKey); if (specSet == null){ specSet = new HashSet<String>(); } //将规格信息存入set中 specSet.add(specMap.get(specKey)); //将set存入map resultMap.put(specKey,specSet); } } } return resultMap; }
更新页面
<div class="clearfix selector"> <div class="type-wrap logo" th:unless="${#maps.containsKey(searchMap,'brand')}"> <div class="fl key brand">品牌</div> <div class="value logos"> <ul class="logo-list"> <li th:each="brand,brandStat:${result.brandList}"> <a th:text="${brand}"></a> </li> </ul> </div> <div class="ext"> <a href="javascript:void(0);" class="sui-btn">多选</a> <a href="javascript:void(0);">更多</a> </div> </div> <div class="type-wrap" th:each="spec,specStat:${result.specList}" th:unless="${#maps.containsKey(searchMap,'spec_'+spec.key)}"> <div class="fl key" th:text="${spec.key}"></div> <div class="fl value"> <ul class="type-list"> <li th:each="op,opStat:${spec.value}"> <a th:text="${op}"></a> </li> </ul> </div> <div class="fl ext"></div> </div> <div class="type-wrap" th:unless="${#maps.containsKey(searchMap,'price')}"> <div class="fl key">价格</div> <div class="fl value"> <ul class="type-list"> <li> <a th:text="0-500元"></a> </li> <li> <a th:text="500-1000元"></a> </li> <li> <a th:text="1000-1500元"></a> </li> <li> <a th:text="1500-2000元"></a> </li> <li> <a th:text="2000-3000元"></a> </li> <li> <a th:text="3000元以上"></a> </li> </ul> </div> <div class="fl ext"> </div> </div> <div class="type-wrap"> <div class="fl key">更多筛选项</div> <div class="fl value"> <ul class="type-list"> <li> <a>特点</a> </li> <li> <a>系统</a> </li> <li> <a>手机内存 </a> </li> <li> <a>单卡双卡</a> </li> <li> <a>其他</a> </li> </ul> </div> <div class="fl ext"> </div> </div> </div>
(2.3)商品列表显示
<div class="goods-list"> <ul class="yui3-g"> <li class="yui3-u-1-5" th:each="sku,skuStat:${result.rows}"> <div class="list-wrap"> <div class="p-img"> <a href="item.html" target="_blank"><img src="/img/_/mobile01.png" /></a> </div> <div class="price"> <strong> <em>¥</em> <i th:text="${sku.price}"></i> </strong> </div> <div class="attr"> <a target="_blank" href="item.html" th:title="${sku.spec}" th:utext="${sku.name}">Apple苹果iPhone 6s (A1699)Apple苹果iPhone 6s (A1699)Apple苹果iPhone 6s (A1699)Apple苹果iPhone 6s (A1699)</a> </div> <div class="commit"> <i class="command">已有<span>2000</span>人评价</i> </div> <div class="operate"> <a href="success-cart.html" target="_blank" class="sui-btn btn-bordered btn-danger">加入购物车</a> <a href="javascript:void(0);" class="sui-btn btn-bordered">收藏</a> </div> </div> </li> </ul> </div>
关键字搜索
修改search.html
<form th:action="@{/search/list}" class="sui-form form-inline"> <!--searchAutoComplete--> <div class="input-append"> <input type="text" id="autocomplete" name="keywords" th:value="${searchMap.keywords}" class="input-error input-xxlarge" /> <button class="sui-btn btn-xlarge btn-danger" th:type="submit">搜索</button> </div> </form>
测试
搜索华为
关键字,效果如下:
条件搜索实现
用户每次点击搜索的时候,其实在上次搜索的基础之上加上了新的搜索条件,也就是在上一次请求的URL后面追加了新的搜索条件,我们可以在后台每次拼接组装出上次搜索的URL,然后每次将URL存入到Model中,页面每次点击不同条件的时候,从Model中取出上次请求的URL,然后再加上新点击的条件参数实现跳转即可。
(1)后台记录搜索URL
修改SkuController,添加组装URL的方法,并将组装好的URL存储起来,代码如下:
//拼装url StringBuilder url = new StringBuilder("/search/list"); if (searchMap != null && searchMap.size()>0){ //是由查询条件 url.append("?"); for (String paramKey : searchMap.keySet()) { if (!"sortRule".equals(paramKey) && !"sortField".equals(paramKey) && !"pageNum".equals(paramKey)){ url.append(paramKey).append("=").append(searchMap.get(paramKey)).append("&"); } } //http://localhost:9009/search/list?keywords=手机&spec_网络制式=4G& String urlString = url.toString(); //去除路径上的最后一个& urlString=urlString.substring(0,urlString.length()-1); model.addAttribute("url",urlString); }else{ model.addAttribute("url",url); }
(2)页面搜索对接
<div class="clearfix selector"> <div class="type-wrap logo" th:unless="${#maps.containsKey(searchMap,'brand')}"> <div class="fl key brand">品牌</div> <div class="value logos"> <ul class="logo-list"> <li th:each="brand,brandSate:${result.brandList}"> <a th:text="${brand}" th:href="@{${url}(brand=${brand})}"></a> </li> </ul> </div> <div class="ext"> <a href="javascript:void(0);" class="sui-btn">多选</a> <a href="javascript:void(0);">更多</a> </div> </div> <div class="type-wrap" th:each="spec,specStat:${result.specList}" th:unless="${#maps.containsKey(searchMap,'spec_'+spec.key)}"> <div class="fl key" th:text="${spec.key}"> </div> <div class="fl value"> <ul class="type-list"> <li th:each="op,opstat:${spec.value}"> <a th:text="${op}" th:href="@{${url}('spec_'+${spec.key}=${op})}"></a> </li> </ul> </div> <div class="fl ext"></div> </div> <div class="type-wrap" th:unless="${#maps.containsKey(searchMap,'price')}"> <div class="fl key">价格</div> <div class="fl value"> <ul class="type-list"> <li> <a th:text="0-500元" th:href="@{${url}(price='0-500')}"></a> </li> <li> <a th:text="500-1000元" th:href="@{${url}(price='500-1000')}"></a> </li> <li> <a th:text="1000-1500元" th:href="@{${url}(price='1000-1500')}"></a> </li> <li> <a th:text="1500-2000元" th:href="@{${url}(price='1500-2000')}"></a> </li> <li> <a th:text="2000-3000元" th:href="@{${url}(price='2000-3000')}"></a> </li> <li> <a th:text="3000元以上" th:href="@{${url}(price='3000')}"></a> </li> </ul> </div> <div class="fl ext"> </div> </div> <div class="type-wrap"> <div class="fl key">更多筛选项</div> <div class="fl value"> <ul class="type-list"> <li> <a>特点</a> </li> <li> <a>系统</a> </li> <li> <a>手机内存 </a> </li> <li> <a>单卡双卡</a> </li> <li> <a>其他</a> </li> </ul> </div> <div class="fl ext"> </div> </div> </div>
移除搜索条件
如上图,用户点击条件搜索后,要将选中的条件显示出来,并提供移除条件的x
按钮,显示条件我们可以从searchMap中获取,移除其实就是将之前的请求地址中的指定条件删除即可。
修改search.html,移除分类、品牌、价格、规格搜索条件,代码如下:
<ul class="fl sui-tag"> <li class="with-x" th:if="${#maps.containsKey(searchMap,'brand')}"> 品牌:<span th:text="${searchMap.brand}"></span> <a th:href="@{${#strings.replace(url,'&brand='+searchMap.brand,'')}}">×</a> </li> <li class="with-x" th:if="${#maps.containsKey(searchMap,'price')}"> 价格:<span th:text="${searchMap.price}"></span> <a th:href="@{${#strings.replace(url,'&price='+searchMap.price,'')}}">×</a> </li> <!--规格--> <li class="with-x" th:each="sm:${searchMap}" th:if="${#strings.startsWith(sm.key,'spec_')}"> <span th:text="${#strings.replace(sm.key,'spec_','')}"></span> : <span th:text="${#strings.replace(sm.value,'%2B','+')}"></span> <a th:href="@{${#strings.replace(url,'&'+sm.key+'='+sm.value,'')}}">×</a> </li> </ul>
排序
修改search.html,实现排序,代码如下:
<li> <a th:href="@{${url}(sortRule='ASC',sortField='price')}">价格↑</a> </li> <li> <a th:href="@{${url}(sortRule='DESC',sortField='price')}">价格↓</a> </li>
分页
真实的分页应该像百度那样,如下图:
(1)分页工具类定义
在comm工程中添加Page分页对象,代码如下:
package com.changgou.entity; import java.io.Serializable; import java.util.List; /** * 分页对象 * @param <T> */ public class Page <T> implements Serializable{ //当前默认为第一页 public static final Integer pageNum = 1; //默认每页显示条件 public static final Integer pageSize = 20; //判断当前页是否为空或是小于1 public static Integer cpn(Integer pageNum){ if(null == pageNum || pageNum < 1){ pageNum = 1; } return pageNum; } // 页数(第几页) private long currentpage; // 查询数据库里面对应的数据有多少条 private long total;// 从数据库查处的总记录数 // 每页查5条 private int size; // 下页 private int next; private List<T> list; // 最后一页 private int last; private int lpage; private int rpage; //从哪条开始查 private long start; //全局偏移量 public int offsize = 2; public Page() { super(); } /**** * * @param currentpage * @param total * @param pagesize */ public void setCurrentpage(long currentpage,long total,long pagesize) { //可以整除的情况下 long pagecount = total/pagesize; //如果整除表示正好分N页,如果不能整除在N页的基础上+1页 int totalPages = (int) (total%pagesize==0? total/pagesize : (total/pagesize)+1); //总页数 this.last = totalPages; //判断当前页是否越界,如果越界,我们就查最后一页 if(currentpage>totalPages){ this.currentpage = totalPages; }else{ this.currentpage=currentpage; } //计算start this.start = (this.currentpage-1)*pagesize; } //上一页 public long getUpper() { return currentpage>1? currentpage-1: currentpage; } //总共有多少页,即末页 public void setLast(int last) { this.last = (int) (total%size==0? total/size : (total/size)+1); } /**** * 带有偏移量设置的分页 * @param total * @param currentpage * @param pagesize * @param offsize */ public Page(long total,int currentpage,int pagesize,int offsize) { this.offsize = offsize; initPage(total, currentpage, pagesize); } /**** * * @param total 总记录数 * @param currentpage 当前页 * @param pagesize 每页显示多少条 */ public Page(long total,int currentpage,int pagesize) { initPage(total,currentpage,pagesize); } /**** * 初始化分页 * @param total * @param currentpage * @param pagesize */ public void initPage(long total,int currentpage,int pagesize){ //总记录数 this.total = total; //每页显示多少条 this.size=pagesize; //计算当前页和数据库查询起始值以及总页数 setCurrentpage(currentpage, total, pagesize); //分页计算 int leftcount =this.offsize, //需要向上一页执行多少次 rightcount =this.offsize; //起点页 this.lpage =currentpage; //结束页 this.rpage =currentpage; //2点判断 this.lpage = currentpage-leftcount; //正常情况下的起点 this.rpage = currentpage+rightcount; //正常情况下的终点 //页差=总页数和结束页的差 int topdiv = this.last-rpage; //判断是否大于最大页数 /*** * 起点页 * 1、页差<0 起点页=起点页+页差值 * 2、页差>=0 起点和终点判断 */ this.lpage=topdiv<0? this.lpage+topdiv:this.lpage; /*** * 结束页 * 1、起点页<=0 结束页=|起点页|+1 * 2、起点页>0 结束页 */ this.rpage=this.lpage<=0? this.rpage+(this.lpage*-1)+1: this.rpage; /*** * 当起点页<=0 让起点页为第一页 * 否则不管 */ this.lpage=this.lpage<=0? 1:this.lpage; /*** * 如果结束页>总页数 结束页=总页数 * 否则不管 */ this.rpage=this.rpage>last? this.last:this.rpage; } public long getNext() { return currentpage<last? currentpage+1: last; } public void setNext(int next) { this.next = next; } public long getCurrentpage() { return currentpage; } public long getTotal() { return total; } public void setTotal(long total) { this.total = total; } public long getSize() { return size; } public void setSize(int size) { this.size = size; } public long getLast() { return last; } public long getLpage() { return lpage; } public void setLpage(int lpage) { this.lpage = lpage; } public long getRpage() { return rpage; } public void setRpage(int rpage) { this.rpage = rpage; } public long getStart() { return start; } public void setStart(long start) { this.start = start; } public void setCurrentpage(long currentpage) { this.currentpage = currentpage; } /** * @return the list */ public List<T> getList() { return list; } /** * @param list the list to set */ public void setList(List<T> list) { this.list = list; } public static void main(String[] args) { //总记录数 //当前页 //每页显示多少条 int cpage =17; Page page = new Page(1001,cpage,50,7); System.out.println("开始页:"+page.getLpage()+"__当前页:"+page.getCurrentpage()+"__结束页"+page.getRpage()+"____总页数:"+page.getLast()); } }
(2)分页实现
修改SkuController,实现分页信息封装,代码如下:
(3)页面分页实现
修改search.html,实现分页查询,代码如下: