【黑马旅游网】旅游模块
分类数据展示功能
很简单,从tab_category查询全部分类,展示成如图所示的列表。
后端代码
非常简单的一套流程,具体见源代码CategoryServlet.findAll()
方法。
前端代码
hader.html加载后,发送ajax请求,请求category/findAll,并展示。主要代码如下:
//查询分类数据
$.get("category/findAll", {}, function (data) {
//[{ cid: 8, cname: "全球自由行" },....]
var lis = '<li class="nav-active"><a href="index.html">首页</a></li>';
//遍历数组,拼接字符串(<li>)
for (var i = 0; i < data.length; i++) {
var li = '<li><a href="route_list.html">' + data[i].cname + '</a></li>';
lis += li;
}
//拼接收藏排行榜
lis += '<li><a href="favoriterank.html">收藏排行榜</a></li>';
//将lis设置到内容里
$("#category").html(lis);
});
分类数据缓存优化
分类的数据在每一次页面加载后都会重新请求数据库来加载,对数据库的压力比较大,而且分类的数据不会经常产生变化,所有可以使用redis来缓存这个数据。
核心就在于在service方法中实现对redis的缓存,代码如下:
public List<Category> findAll() {
//1. 从redis中查询
//1.1 获取jedis客户端
Jedis jedis = JedisUtil.getJedis();
//1.2 可使用sortedset排序查询
// Set<String> categorys = jedis.zrange("category", 0, -1);
//1.3 查询sortedset中的分数(cid)和值(cname)
Set<Tuple> categorys = jedis.zrangeWithScores("category", 0, -1);
//2. 判断查询的集合是否为空
List<Category> categoryList = new ArrayList<>();
if (categorys == null || categorys.size() == 0) {
System.out.println("从数据库查询");
//3. 如果为空,需要从数据库查询,再将数据存入redis
//3.1 从数据库查询
categoryList = categoryDAO.findAll();
//3.2将集合存储到redis中
for (int i = 0; i < categoryList.size(); i++) {
jedis.zadd("category", categoryList.get(i).getCid(), categoryList.get(i).getCname());
}
}else {
System.out.println("从redis查询");
//4. 如果不为空,将set的数据存入List
for (Tuple tuple : categorys) {
Category category = new Category();
category.setCname(tuple.getElement());
category.setCid((int) tuple.getScore());
categoryList.add(category);
}
}
return categoryList;
}
旅游线路的分页展示
点击了不同的分类后,将来看到的旅游线路不一样的。通过分析数据库表结构,发现旅游线路表和分类表是一个多对一的关系。
后端实现很简单,就是从tab_route表中根据cid获取数据,在分页返回。主要复杂的在于前端如果实现分页,以及动态的实现数据的更新。
分析可以发现,后端需要返回一个带分页参数的返回值。前端发起携带当前页码、每页显示条数和cid作为参数的请求。
后端代码
@Override
public PageBean<Route> pageQuery(int cid, int currentPage, int pageSize) {
//封装PageBean
PageBean<Route> pageBean = new PageBean<>();
//设置当前页码
pageBean.setCurrentPage(currentPage);
//设置每页显示条数
pageBean.setPageSize(pageSize);
//设置总记录数
int totalCount = routeDAO.findTotalCount(cid);
pageBean.setTotalCount(totalCount);
//设置当前页现实的数据集合
int start = (currentPage - 1) * pageSize;
List<Route> routes = new ArrayList<>();
routes = routeDAO.findByPage(cid, start, pageSize);
pageBean.setList(routes);
//设置总页数
int totalPage = totalCount % pageSize == 0 ? totalCount / pageSize : (totalCount / pageSize) + 1;
pageBean.setTotalPage(totalPage);
return pageBean;
}
核心代码如上,详细可以查看注释,不做过多解释。下面将分析前端代码。
类别id的传递
首先在header.html中添加传递id的url
var li = '<li><a href="route_list.html?cid=' + data[i].cid + '">' + data[i].cname + '</a></li>';
然后在route_list.html中用location.search获取,在分割获取cid。
$(function () {
var search = location.search;
// alert(search);//?id=5
//切割字符串,拿到第二个值
var cid = search.split("=")[1];
});
根据id查询不同类别的旅游线路数据
第一步:ajax异步发起访问
$.get("route/pageQuery", {cid: cid}, function (pb) {
第二步:展示总页码和总记录数
$("#totalPage").html(pb.totalPage);
$("#totalCount").html(pb.totalCount);
第三步:根据原有的html格式,遍历循环获取分页小工具的html,再赋值
// <li><a href="">首页</a></li>
// <li class="threeword"><a href="#">上一页</a></li>
// <li class="curPage"><a href="#">1</a></li>
// <li><a href="#">2</a></li>
// <li><a href="#">3</a></li>
// <li><a href="#">4</a></li>
// <li><a href="#">5</a></li>
// <li><a href="#">6</a></li>
// <li><a href="#">7</a></li>
// <li><a href="#">8</a></li>
// <li><a href="#">9</a></li>
// <li><a href="#">10</a></li>
// <li class="threeword"><a href="javascript:;">下一页</a></li>
// <li class="threeword"><a href="javascript:;">末页</a></li>
var lis = "";
//首页
lis += '<li><a href=#">首页</a></li>';
//上一页
lis += '<li class="threeword" ><a href="#">上一页</a></li>';
for (var i = 1; i <= pb.totalPage; i++) {
if (i == pb.currentPage) {
//创建一个当前页码的li
var li = '<li class="curPage"><a href="#">' + i + '</a></li>'
} else {
//创建一个页码的li
var li = '<li"><a href="#>' + i + '</a></li>';
}
//拼接
lis += li;
}
//下一页
lis += '<li class="threeword" ><a href="">下一页</a></li>';
lis += '<li class="threeword"><a href="">末页</a></li>';
//将lis设置到ul中
$("#pageNum").html(lis);
第四步:根据原有的html格式,遍历循环获取路线信息的html,再赋值
// <li>
// <div class="img"><img src="images/04-search_03.jpg" alt=""></div>
// <div class="text1">
// <p>【减100元 含除夕/春节出发】广州增城三英温泉度假酒店/自由行套票</p>
// <br/>
// <p>1-2月出发,网付立享¥1099/2人起!爆款位置有限,抢完即止!</p>
// </div>
// <div class="price">
// <p class="price_num">
// <span>¥</span>
// <span>299</span>
// <span>起</span>
// </p>
// <p><a href="route_detail.html">查看详情</a></p>
// </div>
// </li>
var elements = "";
for (var i = 0; i < pb.list.length; i++) {
var route = pb.list[i];
var element = '<li>\n' +
'<div class="img"><img src="' + route.rimage + '" style="width: 299px" alt=""></div>\n' +
'<div class="text1">\n' +
'<p>' + route.rname + '</p>\n' +
'<br/>\n' +
'<p>' + route.routeIntroduce + '</p>\n' +
'</div>\n' +
'<div class="price">\n' +
'<p class="price_num">\n' +
'<span>¥</span>\n' +
'<span>' + route.price + '</span>\n' +
'<span>起</span>\n' +
'</p>\n' +
'<p><a href="route_detail.html">查看详情</a></p>\n' +
'</div>\n' +
'</li>';
elements += element;
}
$("#route").html(elements);
至此基本展示功能都已经实现了,但是分页小工具还很丑陋。让我们继续优化。
第五步:优化分页小工具功能
首先我们要做的就是抽取调用的接口route/pageQuery,这样就可以在上一页下一页的时候异步刷新数据。
//当页面加载完成后,调用load方法,发送ajax请求加载数据
load(cid);
function load(cid, currentPage) {
//发送ajax请求,请求route/pageQuery,传递cid
$.get("route/pageQuery", {cid: cid, currentPage: currentPage}, function (pb) {
然后我们修改分页小插件的语句即可
// 首页
lis += '<li onclick="javascript:load(' + cid + ',' + 1 + ')"><a href="javascript:void()">首页</a></li>';
给首页的li添加点击事件,访问load函数,传递cid和1为参数。而原本的a标签让其无相应,javascript:void()。下一页传递的参数就不是1了,而是(当前页-1),如下:
lis += '<li class="threeword" onclick="javascript:load(' + cid + ',' + pb.currentPage - 1 + ')"><a href="javascript:void()">上一页</a></li>';
但是这样还有问题,如果第一页,点上一页,就会变成0页,显然不合理。所以我们对参数进行编辑。
var beforeNum = pb.currentPage - 1
if (beforeNum < 1) {
beforeNum = 1;
}
lis += '<li class="threeword" onclick="javascript:load(' + cid + ',' + beforeNum + ')"><a href="javascript:void()">上一页</a></li>';
下一页和尾页同理。至此,小插件的全部功能就都实现了。
第六步:优化分页小插件显示
现在我们的分页小插件还是一堆堆在下面,很不合理。让我们看看百度的分页小插件的逻辑,如下:
总结如下:
1.一共展示10个页码,能够达到前5后4的效果
2.如果前边不够5个,后边补齐10个
3.如果后边不足4个,前边补齐10个
如何实现呢?
//定义一个开始位置begin,结束位置end
var begin;
var end;
//1. 要展示10页
if (pb.totalPage <= 10) {
begin = 1;
end = pb.totalPage;
} else {
if (pb.currentPage > pb.totalPage - 4) { // 3.如果后边不足4个,前边补齐10个
end = pb.totalPage;
begin = end - 9;
} else if (pb.currentPage <= 6) { // 2.如果前边不够5个,后边补齐10个
begin = 1;
end = begin + 9;
} else {// 1.一共展示10个页码,能够达到前5后4的效果
begin = pb.currentPage - 5;
end = pb.currentPage + 4;
}
}
for (var i = begin; i <= end; i++) {
if (i == pb.currentPage) {
//创建一个当前页码的li
var li = '<li class="curPage"><a href="javascript:void()">' + i + '</a></li>'
} else {
//创建一个页码的li
var li = '<li onclick="javascript:load(' + cid + ',' + i + ')"><a href="javascript:void()">' + i + '</a></li>';
}
//拼接
lis += li;
}
主要是中间的逻辑,我和视频中的想法是一样的,但是做法不太一样。首先就是不满10个时,从1开始到最大页码,这没问题。然后在10个页码以上,主要处理的就是前后不足的情况,很容易就能想到只有当当前页面是1-6的时候,才是前边不够5个,所以条件就是pb.currentPage <= 6
,然后就是begin = 1; end = begin + 9;
;然后就是后边不足4个,条件也很好判断pb.currentPage > pb.totalPage - 4
,同样的思想处理页码,这次是后面的页码不变了,所以我们固定end,用end-9来确定begin, end = pb.totalPage; begin = end - 9;
;排除上述两种情况,剩下的就都是动态的了,减五加四即可。
第七步:跳转到头部
//定位到页面的顶部
window.scrollTo(0, 0);
至此旅游线路的分页展示功能就全部实现了。
第八步:
笔者这里还做了一个优化,当没有数据的时候,会导致异步请求失败,所以还是展示的原来的假数据,这不合理。这主要是因为后端代码在返回时并没有初始化,导致没有返回值,在前端无法提取,就报错了。优化如下:
List<Route> routes = new ArrayList<>();
routes = routeDAO.findByPage(cid, start, pageSize);
pageBean.setList(routes);
这样返回的就是空对象,而不是null。
旅游线路名称查询功能
第一步:还是传递数据,给搜索绑定点击事件
//给搜索按钮绑定单击事件,获取搜索输入框的内容
$("#search-button").click(function () {
//线路名称
var rname = $("#search_input").val();
var cid = getParameter("cid");
//跳转路径 http://localhost/travel/route_list.html?cid=3,拼接上rname
location.href = "http://localhost/travel/route_list.html?cid=" + cid + "&rname=" + rname;
})
修改route_list.html的uri的处理逻辑
$(function () {
// var search = location.search;
// // alert(search);//?id=5
// //切割字符串,拿到第二个值
// var cid = search.split("=")[1];
var cid = getParameter("cid");
var rname = getParameter("rname");
//如果rname如果不为空或者”“
if (rname) {
//解码
rname = window.decodeURIComponent(rname);
}
//当页面加载完成后,调用load方法,发送ajax请求加载数据
load(cid);
});
第二步:修改后端逻辑,主要部分在于动态的拼接cid和rname的SQL,具体如下:
@Override
public int findTotalCount(int cid, String rname) {
//1. 定义sql模板
String sql = "SELECT count(*) FROM tab_route WHERE 1=1 ";
StringBuilder sb = new StringBuilder(sql);
//参数们
List params = new ArrayList();
//2. 判断参数是否有值
if (cid != 0) {
sb.append(" AND cid = ? ");
params.add(cid);
}
if (rname != null && rname.length() != 0) {
sb.append(" AND rname like ? ");
params.add("%" + rname + "%");
}
sql = sb.toString();
return template.queryForObject(sql, Integer.class, params.toArray());
}
@Override
public List<Route> findByPage(int cid, int start, int pageSize, String rname) {
// String sql = "SELECT * FROM tab_route WHERE cid = ? LIMIT ?,?";
String sql = "SELECT * FROM tab_route WHERE 1=1 ";
StringBuilder sb = new StringBuilder(sql);
//参数们
List params = new ArrayList();
//2. 判断参数是否有值
if (cid != 0) {
sb.append(" AND cid = ? ");
params.add(cid);
}
if (rname != null && rname.length() != 0) {
sb.append(" AND rname like ? ");
params.add("%" + rname + "%");
}
sb.append(" LIMIT ?,? ");
params.add(start);
params.add(pageSize);
sql = sb.toString();
return template.query(sql, new BeanPropertyRowMapper<Route>(Route.class), params.toArray());
}
很简单,相信大家都可以轻松理解
第三步:修改前端代码
首先在load中传入参数,第一次加载currentPage为null即可。
load(cid, null, rname);
传递rname
function load(cid, currentPage, rname) {
//发送ajax请求,请求route/pageQuery,传递cid
$.get("route/pageQuery", {cid: cid, currentPage: currentPage, rname: rname}, function (pb) {
分页小插件也加入参数rname,注意引号的转义。
lis += '<li onclick="javascript:load(' + cid + ',' + 1 + ',\'' + rname + '\')"><a href="javascript:void()">首页</a></li>';
查看旅游页面详情功能
分析图如下:
前端传递数据
在route_list.html中循环遍历生成route对象的时候,更改查看详情的地址,如下:
'<p><a href="route_detail.html?rid='+route.rid+'">查看详情</a></p>\n' +
后端
后端逻辑主要是从tab_route中根据rid获取route,然后再从tab_route_img中根据rid获取缩略图List存放在route对象中,最后再根据route中的sid从tab_seller中获取商家信息。具体实现见代码。
前端
- 使用getParameter()方法获取uri上面的rid
var rid = getParameter("rid");
- 异步调取后端接口
$.get("route/findOne", {rid: rid}, function (route) {
- 填充
//3.解析数据填充html
$("#rname").html(route.rname);
$("#routeIntroduce").html(route.routeIntroduce);
$("#price").html("¥" + route.price);
$("#sname").html(route.seller.sname);
$("#consphone").html(route.seller.consphone);
$("#address").html(route.seller.address);
//图片展示
var ddstr = '<a class="up_img up_img_disable"></a>\n';
//遍历routeImgList
for (var i = 0; i < route.routeImgList.length; i++) {
var astr;
if (i >= 4) {
astr = '<a title="" class="little_img"\n' +
'data-bigpic="' + route.routeImgList[i].bigPic + '"\n' +
'style="display:none;">\n' +
'<img src="' + route.routeImgList[i].smallPic + '">\n' +
'</a>\n';
} else {
astr = '<a title="" class="little_img"\n' +
'data-bigpic="' + route.routeImgList[i].bigPic + '">\n' +
'<img src="' + route.routeImgList[i].smallPic + '">\n' +
'</a>\n';
}
ddstr += astr;
}
ddstr += '<a class="down_img down_img_disable" style="margin-bottom: 0;"></a>';
console.log(ddstr)
$("#dd").html(ddstr);
- 复用焦点图效果
封装成 goImg(),在加载页面和接口响应完成后分别调用。
收藏
判断当前登录用户是否收藏过该线路
当页面加载完成后,发送ajax请求,获取用户是否收藏的标记。根据标记,展示不同的按钮样式。
前端:
$(function () {
var rid = getParameter("rid");
$.get("route/isFavorite", {rid: rid}, function (flag) {
if (flag) {
//用户已经收藏
//设置样式
$("#favorite").addClass("already");
$("#favorite").attr("disabled", "disabled");
//删除按钮的点击事件
$("#favorite").removeAttr("onclick");
} else {
//用户还没有收藏
}
})
});
后端:
核心就是判断tab_favorite表中是否有rid和uid的关联数据,根据结果返回true或者false,详情见代码。
动态展示收藏数量
在之前的查找旅游线路的实体的方法中添加查询收藏总数的方法,如下:
//4. 根据rid查询收藏次数
int count = favoriteDAO.findCountByRid(rid);
route.setCount(count);
然后在前端相应位置替换html。
点击按钮收藏功能
分析如下:
前端:
主要就是按钮触发点击事件,判断是否登录,调用后端接口
function addFavorite() {
var rid = getParameter("rid");
//1. 判断用户是否登录
$.get("user/findOne", {}, function (user) {
if (user) {
//用户登录了
//添加
$.get("route/addFavorite", {rid:rid}, function (user) {
});
}else {
//没有登陆
alert("您尚未登录,请登录")
location.href = "http://localhost/travel/login.html";
}
});
}
后端:
是一个简单的insert操作。