04搜索页面渲染

本文介绍了搜索页面的渲染过程,包括搜索分析和实现。在搜索分析部分,提到了搜索结果、搜索条件和用户已选条件的展示。在搜索实现部分,详细阐述了搜索工程的搭建步骤,如引入依赖、静态资源导入、配置文件修改。此外,还讲解了基础数据的渲染,包括SearchController的更新、业务层代码以及分页工具类的使用。
摘要由CSDN通过智能技术生成

4.搜索页面渲染

4.1搜索分析

在这里插入图片描述

搜索页面要显示的内容主要分为3块。
1)搜索的数据结果
2)筛选出的数据搜索条件
3)用户已经勾选的数据条件

4.2搜索实现

在这里插入图片描述

搜索的业务流程如上图,用户每次搜索的时候,先经过搜索业务工程,搜索业务工程调用搜索微服务工程。

4.2.1搜索工程搭建

(1)引入依赖
在changgou-service_search工程中的pom.xml中引入如下依赖:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

(2)静态资源导入(给我留言可以获取)
将资源中的页面资源/所有内容 拷贝到工程的 resources 目录下如下图:
在这里插入图片描述
(3) 更改配置文件,在spring下添加内容
在这里插入图片描述

4.2.2基础数据渲染

(1)更新SearchController,定义跳转搜索结果页面方法代码如下 :

package com.changgou.search.controller;

import com.changgou.entity.Page;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.Map;
import java.util.Set;

@Controller
@RequestMapping("/search")
public class SearchController {
   

    @Autowired
    private SearchService searchService;

    @GetMapping("/list")
    public String list(@RequestParam Map<String,String> searchMap,Model model){
   

        //特殊符号处理
        this.handleSearchMap(searchMap);

        //获取查询结果
        Map resultMap = searchService.search(searchMap);
        model.addAttribute("result",resultMap);
        model.addAttribute("searchMap",searchMap);

        //封装分页数据并返回
        //1.总记录数
        //2.当前页
        //3.每页显示多少条
        Page<SkuInfo> page = new Page<SkuInfo>(
                Long.parseLong(String.valueOf( resultMap.get("total"))),
                Integer.parseInt(String.valueOf(resultMap.get("pageNum"))),
                Page.pageSize
        );
        model.addAttribute("page",page);

        //拼装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);
        }
        return "search";
    }

    @GetMapping
    @ResponseBody
    public Map search(@RequestParam Map<String,String> searchMap){
   
        //特殊符号处理
        this.handleSearchMap(searchMap);
        Map searchResult = searchService.search(searchMap);
        return searchResult;
    }

    private void handleSearchMap(Map<String, String> searchMap) {
   
        Set<Map.Entry<String, String>> entries = searchMap.entrySet();
        for (Map.Entry<String, String> entry : entries) {
   
            if (entry.getKey().startsWith("spec_")){
   
                searchMap.put(entry.getKey(),entry.getValue().replace("+","%2B"));
            }
        }
    }
}

(2) 业务层代码

package com.changgou.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {
   

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Override
    public Map search(Map<String, String> searchMap) {
   

        Map<String,Object> resultMap = new HashMap<>();

        //构建查询
        if (searchMap != null){
   
            //构建查询条件封装对象
            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

            //按照关键字查询
            if (StringUtils.isNotEmpty(searchMap.get("keywords"))){
   
                boolQuery.must(QueryBuilders.matchQuery("name",searchMap.get("keywords")).operator(Operator.AND));
            }

            //按照品牌进行过滤查询
            if (StringUtils.isNotEmpty(searchMap.get("brand"))){
   
                boolQuery.filter(QueryBuilders.termQuery("brandName",searchMap.get("brand")));
            }

            //按照规格进行过滤查询
            for (String key : searchMap.keySet()) {
   
                if (key.startsWith("spec_")){
   
                    String value = searchMap.get(key).replace("%2B","+");
                    //spec_网络制式
                    boolQuery.filter(QueryBuilders.termQuery(("specMap."+key.substring(5)+".keyword"),value));
                }
            }

            //按照价格进行区间过滤查询
            if (StringUtils.isNotEmpty(searchMap.get("price"))){
   
                String[] prices = searchMap.get("price").split("-");
                // 0-500 500-1000
                if (prices.length == 2){
   
                    boolQuery.filter(QueryBuilders.rangeQuery("price").lte(prices[1]));
                }
                boolQuery.filter(QueryBuilders.rangeQuery("price").gte(prices[0]));
            }
            nativeSearchQueryBuilder.withQuery(boolQuery);

            //按照品牌进行分组(聚合)查询
            String skuBrand="skuBrand";
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));

            //按照规格进行聚合查询
            String skuSpec="skuSpec";
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));

            //开启分页查询
            String pageNum = searchMap.get("pageNum"); //当前页
            String pageSize = searchMap.get("pageSize"); //每页显示多少条
            if (StringUtils.isEmpty(pageNum)){
   
                pageNum ="1";
            }
            if (StringUtils.isEmpty(pageSize)){
   
                pageSize="30";
            }
            //设置分页
            //第一个参数:当前页 是从0开始
            //第二个参数:每页显示多少条
            nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum)-1,Integer.parseInt(pageSize)));

            //按照相关字段进行排序查询
            // 1.当前域 2.当前的排序操作(升序ASC,降序DESC)
            if (StringUtils.isNotEmpty(searchMap.get("sortField")) && StringUtils.isNotEmpty(searchMap.get("sortRule"))){
   
                if ("ASC".equals(searchMap.get("sortRule"))){
   
                    //升序
                    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort((searchMap.get("sortField"))).order(SortOrder.ASC));
                }else{
   
                    //降序
                    nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort((searchMap.get("sortField"))).order(SortOrder.DESC));
                }
            }

            //设置高亮域以及高亮的样式
            HighlightBuilder.Field field = new HighlightBuilder.Field("name")//高亮域
                    .preTags("<span style='color:red'>")//高亮样式的前缀
                    .postTags("</span>");//高亮样式的后缀
            nativeSearchQueryBuilder.withHighlightFields(field);

            //开启查询
            /**
             * 第一个参数: 条件构建对象
             * 第二个参数: 查询操作实体类
             * 第三个参数: 查询结果操作对象
             */
            //封装查询结果
            AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
   
                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
   
                    //查询结果操作
                    List<T> list = new ArrayList<>();

                    //获取查询命中结果数据
                    SearchHits hits = searchResponse.getHits();
                    if (hits != null){
   
                        //有查询结果
                        for (SearchHit hit : hits) {
   
                            //SearchHit转换为skuinfo
                            SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);

                            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                            if (highlightFields != null && highlightFields.size()>0){
   
                                //替换数据
                                skuInfo.setName(highlightFields.get("name").getFragments()[0].toString());
                            }

                            list.add((T) skuInfo);
                        }
                    }
                    return new AggregatedPageImpl<T>(list,pageable,hits.getTotalHits(),searchResponse.getAggregations());
                }
            });

            //封装最终的返回结果
            //总记录数
            resultMap.put("total",resultInfo.getTotalElements());
            //总页数
            resultMap.put("totalPages",resultInfo.getTotalPages());
            //数据集合
            resultMap.put("rows",resultInfo.getContent());

            //封装品牌的分组结果
           StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
           List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("brandList",brandList);

            //封装规格分组结果
            StringTerms specTerms= (StringTerms) resultInfo.getAggregation(skuSpec);
            List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            resultMap.put("specList",this.formartSpec(specList));

            //当前页
            resultMap.put("pageNum",pageNum);
            return resultMap;
        }
        return null;
    }

    /**
     * 原有数据
     *  [
     *         "{'颜色': '黑色', '尺码': '平光防蓝光-无度数电脑手机护目镜'}",
     *         "{'颜色': '红色', '尺码': '150度'}",
     *         "{'颜色': '黑色', '尺码': '150度'}",
     *         "{'颜色': '黑色'}",
     *         "{'颜色': '红色', '尺码': '100度'}",
     *         "{'颜色': '红色', '尺码': '250度'}",
     *         "{'颜色': '红色', '尺码': '350度'}",
     *         "{'颜色': '黑色', '尺码': '200度'}",
     *         "{'颜色': '黑色', '尺码': '250度'}"
     *     ]
     *
     *    需要的数据格式
     *    {
     *        颜色:[黑色,红色],
     *        尺码:[100度,150度]
     *    }
     */
    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) {
   
                //将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;
    }
}

(3)页面渲染

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<head>
	<meta charset="utf-8" />
	<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
	<title>产品列表页</title>
	 <link rel="icon" href="/img/favicon.ico">
	
    <link rel="stylesheet" type="text/css" href="/css/all.css" />
    <link rel="stylesheet" type="text/css" href="/css/pages-list.css" />
</head>

<body>
	<!-- 头部栏位 -->
	<!--页面顶部-->
<div id="nav-bottom">
	<!--顶部-->
	<div class="nav-top">
		<div class="top">
			<div class="py-container">
				<div class="shortcut">
					<ul class="fl">
						<li class="f-item">畅购欢迎您!</li>
						<li class="f-item"><a href="login.html" target="_blank">登录</a> <span><a href="register.html" target="_blank">免费注册</a></span></li>
					</ul>
					<div class="fr typelist">
						<ul class="types">
							<li class="f-item"><span>我的订单</span></li>
							
							<li class="f-item"><span><a href="cart.html" target="_blank">我的购物车</a></span></li>
							<li class="f-item"><span><a href="home.html" target="_blank">我的畅购</a></span></li>			
							<li class="f-item"><span>畅购会员</span></li>						
							<li class="f-item"><span>企业采购</span></li>						
							<li class="f-item"><span>关注畅购</span></li>
							<li class="f-item"><span><a href="cooperation.html" target="_blank">合作招商</a></span></li>
							<li class="f-item"><span><a href="shoplogin.html" target="_blank">商家后台</a></span></li>				
							<li class="f-item"><span>网站导航</li>
						</ul>
					</div>
					
				</div>
			</div>
		</div>

		<!--头部-->
		<div class="header">
			<div class="py-container">
				<div class="yui3-g Logo">
					<div class="yui3-u Left logoArea">
						<a class="logo-bd" title="畅购" href="index.html" target="_blank"></a>
					</div>
					<div class="yui3-u Rit searchArea">
						<div class="search">
							<form  th:action="@{/search/list}" class="sui-form form-inline">
								<div class="input-append">
									<input th: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>
						</div>
					</div>
					
				</div>

			</div>
		</div>
	</div>
</div>

	<!-- 商品分类导航 -->
	<div class="typeNav">
			<div class="py-container">
				<div class="yui3-g NavList">
    <div class="all-sorts-list">
        <div class="yui3-u Left all-sort">
            <h4>全部商品分类</h4>
        </div>
        <div class="sort">
                <div class="all-sort-list2">
                    <div class="item bo">
                        <h3><a href="">图书、音像、数字商品</a></h3>
                        <div class="item-list clearfix">
                            <div class="subitem">
                                <dl class="fore1">
                                    <dt><a href="">电子书</a></dt>
                                    <dd><a href="">免费</a><a href="">小说</a></em><a href="">励志与成功</a><em><a href="">婚恋/两性</a></em><em><a href="">文学</a></em><em><a href="">经管</a></em><em><a href="">畅读VIP</a></em></dd>
                                </dl>
                            </div>
                        </div>
                    </div>
                    <div class="item">
                            <h3><a href="">家用电器</a></h3>
                            <div class="item-list clearfix">
                                <div class="subitem">
                                    <dl class="fore1">
                                        <dt><a href="">电子书1</a></dt>
                                        <dd><em><a href="">免费</a></em><em><a href="">小说</a></em><em><a href="">励志与成功</a></em><em><a href="">婚恋/两性</a></em><em><a href="">文学</a></em><em><a href="">经管</a></em><em><a href="">畅读VIP</a></em></dd>
                                    </dl>
                                    <dl class="fore2">
                                        <dt><a href="">数字音乐</a></dt>
                                        <dd><em><a href="">通俗流行</a></em><em><a href="">古典音乐</a></em><em><a href="">摇滚说唱</a></em><em><a href="">爵士蓝调</a></em><em><a href="">乡村民谣</a></em><em><a href="">有声读物</a></em></dd>
                                    </dl>
                                    <dl class="fore3">
                                        <dt><a href="">音像</a></dt>
                                        <dd><em><a href="">音乐</a></em><em><a href="">影视</a></em><em><a href="">教育音像</a></em><em><a href="">游戏</a></em></dd>
                                    </dl>
                                    <dl class="fore4">
                                        <dt>文艺</dt>
                                        <dd><em><a href="">小说</a></em><em><a href="">文学</a></em><em><a href="">青春文学</a></em><em><a href="">传记</a></em><em><a href="">艺术</a></em></dd>
                                    </dl>
                                    <dl class="fore5">
                                        <dt>人文社科</dt>
                                        <dd><em><a href="">历史</a></em><em><a href="">心理学</a></em><em><a href="">政治/军事</a></em><em><a href="">国学/古籍</a></em><em><a href="">哲学/宗教</a></em><em><a href="">社会科学</a></em></dd>
                                    </dl>
                                    <dl class="fore6">
                                        <dt>经管励志</dt>
                                        <dd><em><a href="">经济</a></em><em><a href="">金融与投资</a></em><em><a href="">管理</a></em><em><a href="">励志与成功</a></em></dd>
                                    </dl>
                                    <dl class="fore7">
                                        <dt>生活</dt>
                                        <dd><em><a href="">家庭与育儿</a></em><em><a href="">旅游/地图</a></em><em><a href="">烹饪/美食</a></em><em><a href="">时尚/美妆</a></em
很抱歉,作为AI语言模型,我无法提供基于Vue3使用vant下拉菜单实现搜索页面渲染效果。不过,我可以为您提供一些实现该功能的代码示例: 1. 引入Vant组件和相关样式库: ```js import { DropdownMenu, DropdownItem } from 'vant'; import 'vant/lib/dropdown-menu/style'; import 'vant/lib/dropdown-item/style'; ``` 2. 在Vue组件中使用下拉菜单: ```html <template> <div> <van-dropdown-menu :disabled="!searchResult || !searchResult.length" v-model="dropdownVisible" :style="{ width: `${dropdownWidth}px` }" @click-overlay="handleDropdownClose" > <template #title> <van-field class="search-field" v-model.trim="searchText" placeholder="Search" @change="handleSearch" /> </template> <van-dropdown-item v-for="(item, index) in searchResult" :key="index" :title="item.name" :options="item.children" @click-item="handleDropdownItemClick" /> </van-dropdown-menu> </div> </template> ``` 3. 实现相关方法: ```js import { reactive } from 'vue'; export default { name: 'SearchDropdown', components: { DropdownMenu, DropdownItem }, setup() { const state = reactive({ searchText: '', // 搜索关键字 searchResult: [], // 搜索结果 dropdownWidth: 0, // 下拉菜单宽度 dropdownVisible: false, // 下拉菜单是否展开 }); const handleSearch = () => { // 根据关键字搜索结果 const result = /* 执行搜索方法 */; state.searchResult = result; } const handleDropdownClose = () => { // 关闭下拉菜单 state.dropdownVisible = false; } const handleDropdownItemClick = (item) => { // 处理下拉菜单点击事件 console.log(item); } return { state, handleSearch, handleDropdownClose, handleDropdownItemClick, }; } } ``` 希望这些代码示例能够帮助您实现基于Vue3使用vant下拉菜单实现搜索。如有不足之处,还请指正。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值