033 搭建搜索微服务02----实现基本搜索功能
1.
<script type="text/javascript"> var vm = new Vue({ el: "#searchApp", data: { }, components:{ // 加载页面顶部组件 lyTop: () => import("./js/pages/top.js") } }); </script>
这个Vue实例中,通过import导入的方式,加载了另外一个js:top.js并作为一个局部组件。top其实是页面顶部导航组件,我们暂时不管
(2)发起异步请求
data: {
search:{
key:"", // 搜索页面的关键字
},
goodsList:[]
},
我们通过钩子函数created,在页面加载时获取请求参数,并记录下来。
created(){
// 判断是否有请求参数
if(!location.search){
return;
}
// 将请求参数转为对象
const search = ly.parse(location.search.substring(1));
// 记录在data的search对象中
this.search = search;
// 发起请求,根据条件搜索
this.loadData();
}
在浏览器中进行测试:
然后发起请求,搜索数据。
在leyou-gateway中的CORS配置类中,添加允许信任域名:
并在leyou-gateway工程的Application.yml中添加网关映射:
测试:
2.
-
请求方式:Post
-
请求路径:/search/page,不过前面的/search应该是网关的映射路径,因此真实映射路径page,代表分页查询
-
package lucky.leyou.domain; public class SearchRequest { private String key;// 搜索条件 private Integer page;// 当前页 private static final Integer DEFAULT_SIZE = 20;// 每页大小,不从页面接收,而是固定大小 private static final Integer DEFAULT_PAGE = 1;// 默认页 public String getKey() { return key; } public void setKey(String key) { this.key = key; } public Integer getPage() { if(page == null){ return DEFAULT_PAGE; } // 获取页码时做一些校验,不能小于1 return Math.max(DEFAULT_PAGE, page); } public void setPage(Integer page) { this.page = page; } public Integer getSize() { return DEFAULT_SIZE; } }
返回结果:作为分页结果,一般都两个属性:当前页数据、总条数信息,我们可以使用之前定义的PageResult类
package lucky.leyou.controller; import lucky.leyou.common.domain.PageResult; import lucky.leyou.domain.Goods; import lucky.leyou.domain.SearchRequest; import lucky.leyou.service.SearchService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping public class SearchController { @Autowired private SearchService searchService; /** * 搜索商品 * * @param request * @return */ @PostMapping("page") public ResponseEntity<PageResult<Goods>> search(@RequestBody SearchRequest request) { PageResult<Goods> result = this.searchService.search(request); if (result == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } return ResponseEntity.ok(result); } }
(2)service
@Autowired private GoodsRepository goodsRepository; public PageResult<Goods> search(SearchRequest request) { String key = request.getKey(); // 判断是否有搜索条件,如果没有,直接返回null。不允许搜索全部商品 if (StringUtils.isBlank(key)) { return null; } // 自定义查询构建器,构建查询条件 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); // 1、对key进行全文检索查询 queryBuilder.withQuery(QueryBuilders.matchQuery("all", key).operator(Operator.AND)); // 2、通过sourceFilter设置返回的结果字段,我们只需要id、skus、subTitle queryBuilder.withSourceFilter(new FetchSourceFilter( new String[]{"id","skus","subTitle"}, null)); // 3、分页 // 准备分页参数 int page = request.getPage(); int size = request.getSize(); queryBuilder.withPageable(PageRequest.of(page - 1, size)); // 4、查询,获取结果 Page<Goods> pageInfo = this.goodsRepository.search(queryBuilder.build()); // 封装结果并返回 return new PageResult<>(pageInfo.getTotalElements(), pageInfo.getTotalPages(), pageInfo.getContent()); }
注意点:我们要设置SourceFilter,来选择要返回的结果,否则返回一堆没用的数据,影响查询效率。
(3)测试
重启search微服务,刷新页面测试:
复制响应结果到:
spring: jackson: default-property-inclusion: non_null # 配置json处理时忽略空值
结果:
访问:http://www.leyou.com/search.html?key=手机
第一次可能出现:
再次刷新页面:
将json数据格式化可见:
3.页面渲染
页面已经拿到了结果,接下来就要渲染样式了。
(1)保存搜索结果
首先,在data中定义属性,保存搜索的结果:
methods: { loadData(){ // ly.http.post("/search/page", ly.stringify(this.search)).then(resp=>{ //注意:http在common.js文件定义的,实际上就是axios //resp表示后台响应的数据对象,resp.data为数据 ly.http.post("/search/page", this.search).then(resp=>{ this.goodsList=resp.data.items; }); } },
访问:http://www.leyou.com/search.html?key=手机
再次刷新页面:
(2)
我们删除多余的,只保留一个li
(3)
-
在搜索到数据时,先默认把第一个sku作为被选中的,记录下来
-
我们在查询成功的回调函数中,对goods进行遍历,把skus转化成json对象集合,并添加一个selected属性保存被选中的sku:
methods: {
loadData(){
// ly.http.post("/search/page", ly.stringify(this.search)).then(resp=>{
//注意:http在common.js文件定义的,实际上就是axios
//resp表示后台响应的数据对象,resp.data为数据
ly.http.post("/search/page", this.search).then(resp=>{
this.goodsList=resp.data.items;
//遍历goodsList集合
this.goodsList.forEach(goods=>{
//将skus字段这个json字符串转换为json对象
goods.skus=JSON.parse(goods.skus);
//扩展一个selected属性
goods.selected=goods.skus[0];
})
});
}
},
<3>多图片列表
接下来,我们看看多个sku的图片列表位置:
<div class="p-img"> <a href="item.html" target="_blank"><img src="img/_/mobile01.png" height="200"/></a> <ul class="skus"> <!--若sku的id等于goods.selected选中的id,则去渲染这个样式,@mouseOver鼠标移动事件--> <li :class="{selected: goods.selected.id==sku.id}" v-for="(sku,j) in goods.skus" @mouseOver="goods.selected=sku"><img :src="sku.image"></li> </ul> </div>
-
class样式通过 goods.selected的id是否与当前sku的id一致来判断
-
然而,这一切有一个前提,那就是当你第一次渲染时,对象中有哪些属性,Vue就只监视这些属性,后来添加的属性发生改变,是不会被监视到的。
methods: {
loadData(){
// ly.http.post("/search/page", ly.stringify(this.search)).then(resp=>{
//注意:http在common.js文件定义的,实际上就是axios
//resp表示后台响应的数据对象,resp.data为数据
ly.http.post("/search/page", this.search).then(resp=>{
//遍历goodsList集合
resp.data.items.forEach(goods=>{
//将skus字段这个json字符串转换为json对象
goods.skus=JSON.parse(goods.skus);
//扩展一个selected属性
goods.selected=goods.skus[0];
});
this.goodsList=resp.data.items;
});
}
},
<4>展示sku其他属性
<5>价格显示是分
结果报错:
因为在Vue范围内使用任何变量,都会默认去Vue实例中寻找,我们使用ly,但是Vue实例中没有这个变量。所以解决办法就是把ly记录到Vue实例:
<!--搜索结果展示--> <div class="goods-list"> <ul class="yui3-g"> <!--利用v-for遍历goodsList集合--> <li class="yui3-u-1-5" v-for="(goods,index) in goodsList" :key="index"> <div class="list-wrap"> <div class="p-img"> <a href="item.html" target="_blank"><img :src="goods.selected.image" height="200"/></a> <ul class="skus"> <!--若sku的id等于goods.selected选中的id,则去渲染这个样式,@mouseOver鼠标移动事件--> <li :class="{selected: goods.selected.id==sku.id}" v-for="(sku,j) in goods.skus" @mouseOver="goods.selected=sku"><img :src="sku.image"></li> </ul> </div> <div class="clearfix"></div> <div class="price"> <strong> <em>¥</em> <i>{{ly.formatPrice(goods.selected.price)}}</i> </strong> </div> <div class="attr"> <!--若标题太长就截取前20个字符--> <em>{{goods.selected.title>20 ? goods.selected.title.substring(0,20) :goods.selected.title}}}</em> </div> <div class="cu"> <em>{{goods.subTitle >20 ? goods.selected.subTitle.substring(0,20) :goods.selected.subTitle}}}</em> </div> <div class="commit"> <i class="command">已有2000人评价</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> <a href="javascript:void(0);" class="sui-btn btn-bordered">关注</a> </div> </div> </li> </ul> </div>
最终效果图:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)