【黑马旅游网】旅游模块

分类数据展示功能

很简单,从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>&yen;</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>&yen;</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中获取商家信息。具体实现见代码。

前端

  1. 使用getParameter()方法获取uri上面的rid
        var rid = getParameter("rid");
  1. 异步调取后端接口
 $.get("route/findOne", {rid: rid}, function (route) {
  1. 填充
            //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);
  1. 复用焦点图效果
    封装成 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操作。

posted @ 2021-02-03 16:34  朱李洛克  阅读(594)  评论(0编辑  收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css