乐优商城学习笔记十六-搜索过滤(一)

0.学习目标

  • 了解过滤功能的基本思路
  • 独立实现分类和品牌展示
  • 了解规格参数展示
  • 实现过滤条件筛选
  • 实现已选过滤项回显
  • 实现取消选择过滤项

1.过滤功能分析

首先看下页面要实现的效果:

1526725119663

整个过滤部分有3块:

  • 顶部的导航,已经选择的过滤条件展示:
    • 商品分类面包屑,根据用户选择的商品分类变化
    • 其它已选择过滤参数
  • 过滤条件展示,又包含3部分
    • 商品分类展示
    • 品牌展示
    • 其它规格参数
  • 展开或收起的过滤条件的按钮

顶部导航要展示的内容跟用户选择的过滤条件有关。

  • 比如用户选择了某个商品分类,则面包屑中才会展示具体的分类
  • 比如用户选择了某个品牌,列表中才会有品牌信息。

所以,这部分需要依赖第二部分:过滤条件的展示和选择。因此我们先不着急去做。

展开或收起的按钮是否显示,取决于过滤条件现在有多少,如果有很多,那么就没必要展示。所以也是跟第二部分的过滤条件有关。

这样分析来看,我们必须先做第二部分:过滤条件展示。

2.生成分类和品牌过滤

先来看分类和品牌。在我们的数据库中已经有所有的分类和品牌信息。在这个位置,是不是把所有的分类和品牌信息都展示出来呢?

显然不是,用户搜索的条件会对商品进行过滤,而在搜索结果中,不一定包含所有的分类和品牌,直接展示出所有商品分类,让用户选择显然是不合适的。

无论是分类信息,还是品牌信息,都应该从搜索的结果商品中进行聚合得到。

2.1.扩展返回的结果

原来,我们返回的结果是PageResult对象,里面只有total、totalPage、items3个属性。但是现在要对商品分类和品牌进行聚合,数据显然不够用,我们需要对返回的结果进行扩展,添加分类和品牌的数据。

那么问题来了:以什么格式返回呢?

看页面:

1526738120021

分类:页面显示了分类名称,但背后肯定要保存id信息。所以至少要有id和name

品牌:页面展示的有logo,有文字,当然肯定有id,基本上是品牌的完整数据

我们新建一个类,继承PageResult,然后扩展两个新的属性:分类集合和品牌集合:
我们新建一个类,继承PageResult,然后扩展两个新的属性:分类集合和品牌集合:

public class SearchResult extends PageResult<Goods>{

    private List<Category> categories;

    private List<Brand> brands;

    public SearchResult(Long total, Integer totalPage, List<Goods> items, List<Category> categories, List<Brand> brands) {
        super(total, totalPage, items);
        this.categories = categories;
        this.brands = brands;
    }
}

2.2.聚合商品分类和品牌

我们修改搜索的业务逻辑,对分类和品牌聚合。

因为索引库中只有id,所以我们根据id聚合,然后再根据id去查询完整数据。

所以,商品微服务需要提供一个接口:根据品牌id集合,批量查询品牌。

2.2.1.提供查询品牌接口

BrandApi

@RequestMapping("brand")
public interface BrandApi {

    @GetMapping("list")
    List<Brand> queryBrandByIds(@RequestParam("ids") List<Long> ids);
}

BrandController

/**
     * 根据多个id查询品牌
     * @param ids
     * @return
     */
@GetMapping("list")
public ResponseEntity<List<Brand>> queryBrandByIds(@RequestParam("ids") List<Long> ids){
    List<Brand> list = this.brandService.queryBrandByIds(ids);
    if(list == null){
        new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
    return ResponseEntity.ok(list);
}

BrandService

public List<Brand> queryBrandByIds(List<Long> ids) {
    return this.brandMapper.selectByIdList(ids);
}

BrandMapper

继承通用mapper的 SelectByIdListMapper即可

public interface BrandMapper extends Mapper<Brand>, SelectByIdListMapper<Brand,Long> {}

2.2.2.搜索功能改造

添加BrandClient

@FeignClient("item-service")
public interface BrandClient extends BrandApi {
}

修改SearchService:

@Service
public class SearchService {
    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private BrandClient brandClient;

    @Autowired
    private GoodClient goodClient;

    @Autowired
    private SpecificationClient specificationClient;

    @Autowired
    private GoodsRepository repository;

    @Autowired
    private ElasticsearchTemplate template;
}
 public PageResult<Goods> search(SearchRequest request) {
        Integer page = request.getPage() - 1;
        Integer size = request.getSize();
        //创建查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","skus","subTitle"}, null));
        //分页
        queryBuilder.withPageable(PageRequest.of(page,size));
        //过滤
        //queryBuilder.withQuery(QueryBuilders.matchQuery("all",request.getKey()));

        //排序
        String sortBy = request.getSortBy();
        Boolean desc = request.getDescending();
        if (StringUtils.isNotBlank(sortBy)){
            queryBuilder.withSort(SortBuilders.fieldSort(sortBy).order(desc? SortOrder.DESC :SortOrder.ASC));
        }
        //聚合分类和品牌
        //聚合分类
        String categoryAggName = "category_agg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        //聚合品牌
        String brandAggName = "brand_agg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));
        AggregatedPage<Goods> pageInfo = (AggregatedPage<Goods>) this.repository.search(queryBuilder.build());

        //查询
        //Page<Goods> result = repository.search(queryBuilder.build());
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(),Goods.class);
        //解析结果
        //解析分页结果
        long total = result.getTotalElements();
        Integer totalPage =result.getTotalPages();
        List<Goods> goodsList = result.getContent();
       // return new PageResult<>(total, totalPage, goodsList);
        //解析聚合结果
        Aggregations aggs = result.getAggregations();
      //List<Category> categories =parseBrandAgg(pageInfo.getAggregation(categoryAggName));
         List<Category> categories =parseCategoryAgg(aggs.get(categoryAggName));
        List<Brand> brands = parseBrandAgg(aggs.get(brandAggName));
        return new SearchResult(total,totalPage,goodsList,categories,brands);
    }

    private List<Brand> parseBrandAgg(LongTerms terms) {
        try {
            List<Long> ids = terms.getBuckets()
                    .stream().map(b -> b.getKeyAsNumber()
                            .longValue()).collect(Collectors.toList());
            List<Brand> brands = brandClient.queryBrandByIds(ids);
            return brands;
        } catch (Exception e) {
            return null;
        }
    }

    private List<Category> parseCategoryAgg(LongTerms terms) {
        try {
            List<Long> ids = terms.getBuckets()
                    .stream().map(b -> b.getKeyAsNumber()
                            .longValue()).collect(Collectors.toList());
            List<Category> categories = categoryClient.queryCategoryByIds(ids);
            return categories;
        } catch (Exception e) {
            return null;
        }
    }

测试:

1526741462666

2.3.页面渲染数据

2.3.1.过滤参数数据结构

来看下页面的展示效果:

1526742664217

虽然分类、品牌内容都不太一样,但是结构相似,都是key和value的结构。

而且页面结构也极为类似:

1526742817804

所以,我们可以把所有的过滤条件放入一个数组中,然后在页面利用v-for遍历一次生成。

其基本结构是这样的:

[
    {
        k:"过滤字段名",
        options:[{/*过滤字段值对象*/},{/*过滤字段值对象*/}]
    }
]

我们先在data中定义数组,等待组装过滤参数:

1526803067480

然后在查询搜索结果的回调函数中,对过滤参数进行封装:

1530442270378

然后刷新页面,通过浏览器工具,查看封装的结果:

1530442355920

2.3.2.页面渲染数据

首先看页面原来的代码:

1526803362517

我们注意到,虽然页面元素是一样的,但是品牌会比其它搜搜条件多出一些样式,因为品牌是以图片展示。需要进行特殊处理。数据展示是一致的,我们采用v-for处理:

<div v-for="(f,i) in filters" :key="i" class="type-wrap" v-if="f.k !== 'brandId'">
    <div class="fl key">{{f.k === 'cid3' ? '分类' : f.k}}</div>
    <div class="fl value">
        <ul class="type-list">
            <li v-for="(option,j) in f.options" :key="j">
                <a href="javascript:void(0);">{{option.name}}</a>
            </li>
        </ul>
    </div>
    <div class="fl ext">
        <a href="javascript:void(0);" class="sui-btn">多选</a>
    </div>
</div>
<div class="type-wrap logo" v-else>
    <div class="fl key brand">{{f.k}}</div>
    <div class="value logos">
        <ul class="logo-list">
            <li v-for="(option,j) in f.options" :key="j" v-if="option.image">
                <img :src="option.image"/>
            </li>
            <li style="text-align: center" v-else>
                <a style="line-height: 30px; font-size: 12px" href="#">{{option.name}}</a>
            </li>
        </ul>
    </div>
    <div class="fl ext">
        <a href="javascript:void(0);" class="sui-btn">多选</a>
    </div>
</div>

结果:

1526804398051

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值