09.新闻项目——新闻前台——首页
关于首页,我们有这样几个地方需要操作一下:
这三部分都需要从后台的数据库中进行获取,我们从简单到难逐级完成。
处理点击排行部分功能
1.点击排行的展示
目前,点击排行都是死代码:
我们要做的就是在News数据库表中查询,根据点击量clicks倒序查询新闻信息。
后端代码
info -> modules -> index -> views.py
.... @index_blue.route('/index') def index(): .... # 2.新闻点击排行展示 # news_clicks = [News,News,News,News,News,News] news_clicks = [] try: news_clicks = News.query.order_by(News.clicks.desc()).limit(6) except Exception as e: current_app.logger.error(e) # 构造渲染模板的上下文数据 context = { 'user': user, 'news_clicks': news_clicks } # 渲染主页 return render_template('news/index.html', context=context)
前端代码
修改点击排行部分的代码
<div class="rank_con fr"> <div class="rank_title"> <h3>点击排行</h3> </div> <ul class="rank_list"> {% for news in context.news_clicks %} <li><span class="first">{{ loop.index }}</span><a href="#">{{ news.title }}</a></li> {% endfor %} {# <li><span class="first">1</span><a href="#">势如破竹!人民币再度连闯四道关口 在岸、离岸双双升破6.42</a></li>#} {# <li><span class="second">2</span><a href="#">凛冬已至,还有多少银行人在假装干银行</a></li>#} {# <li><span class="third">3</span><a href="#">人民日报:部分城市楼市放松限制引关注,楼市调控不会“拉抽屉”</a></li>#} {# <li><span>4</span><a href="#">势如破竹!人民币再度连闯四道关口 在岸、离岸双双升破6.42</a></li>#} {# <li><span>5</span><a href="#">凛冬已至,还有多少银行人在假装干银行</a></li>#} {# <li><span>6</span><a href="#">人民日报:部分城市楼市放松限制引关注,楼市调控不会“拉抽屉”</a></li>#} </ul> </div>
当修改完成之后,我们运行下代码,会看到下面的运行结果:
新闻排序没啥毛病,就是这个和一开始的样式好像不太一样,一开始的样式中,前三个是有些特别的,而现在都是一个样式了,而这个就是我们接下来要解决的问题。
2、自定义过滤器
在前面,我们发现排序的样式有些不同,而通过看前端代码:
我们可以发现,前三个它们有自己的class类,那相对应的应该有设置样式的css。所以现在要做的就是解决这个class问题。
我们在学Jinja2的时候学过一个东西,叫做过滤器,它能够帮助我们解决这个问题。
在项目中,我们经常会需要写一些工具来完成一些功能,所以这里我们写一个过滤器来帮助给不同排行的消息设置不同的class。
我们之前在项目中创建了一个名为utils的文件夹,它用来专门存放工具,我们在这个文件夹中新建一个文件comment.py,专门存放一些公用的工具文件。
在这个工具类中,放入下面的自定义过滤器:
info -> utils -> comment.py
def do_rank(index): """根据index,返回对应的first,second,third""" if index == 1: return 'first' elif index == 2: return 'second' elif index == 3: return 'third' else: return ''
写好这个过滤器之后,还不行,因为我们还需要将它添加到app的过滤器列表当中
info -> __init__.py
.... def create_app(config_name): """创建app的工厂方法 参数:根据参数选择不同的配置类 """ .... # 将自定义的过滤器函数,添加到app的过滤器列表中 # rank : 在模板中使用的别名 app.add_template_filter(do_rank, 'rank') ....
接下来,就是在前端模板中使用这个定义好的过滤器:
info -> templates -> news -> index.html
<li><span class="{{ loop.index | rank }}">{{ loop.index }}</span><a href="#">{{ news.title }}</a></li>
好了,到这里,点击排行的相关逻辑应该就可以了!
新闻列表数据处理
逻辑分析
新闻列表处理是一个比较麻烦的地方,我们需要先来分析一些内容。
新闻中,默认有六个分类,要查询信息,首先我们要搞明白要查询的是哪一类的新闻。所以在请求的时候,这个参数不可或缺。
除了分类信息,还有个东西也需要思考,比如我们有一万个信息,用户去看个首页都发给前端吗?当然不是,我们只给他一部分,所以,这部分需要用到分页的相关操作。也就是说,在请求的时候也要携带一个参数,告诉我们它要看的是第几页的新闻。
而除了告诉服务器要看第几页的,你还得告诉服务器一页要多少,因为不同的场景对新闻数量的需求是不一样的,所以这个我们也需要考虑一下。
参数一开始能分析的就这些,现在还有一个问题,我们要思考一下,用什么请求方式?
没有私密信息,貌似使用get就可以。那么是否要使用ajax呢?
那怎么把这些信息都表现出来呢?
一行搞定!
接口设计
{ "cid": "0", "currentPage": 1, "errmsg": "OK", "errno": "0", "newsList": [ { "clicks": 105, "create_time": "2018-01-17 17:00:41", "digest": "一周前,新京报获悉,滴滴已在杭州成立代号为“黑马”的事业部,主攻共享电单车和电动汽车,并已经在杭州小范围内测。共享电单车的发布也为滴滴在中短途出行版图中填补了空缺。", "id": 1168, "index_image_url": "https://wpimg.wallstcn.com/aaabf95b-610d-4548-afbb-7cb13fa2f85a.jpg", "source": "张超", "status": 0, "title": "滴滴被曝电单车起名“街兔” 需缴押金99元" }, { "clicks": 40, "create_time": "2018-01-17 16:41:46", "digest": "绿光资本四季度买进推特、时代华纳、海上钻井平台运营商ESV,其称推特改善了用户体验,2018收入有望再增。绿光资本去年全年投资回报仅有1.6%,远远落后于同期标普21.8%的涨幅。", "id": 1165, "index_image_url": "https://wpimg.wallstcn.com/d5bd842c-0026-4d69-a620-4ebf6ad7317d.jpg", "source": "陶旖洁", "status": 0, "title": "绿光资本致股东信:今年Twitter会是大赢家" } ], "totalPage": 116 }
代码实现
1.新闻列表数据的查询
后端代码
indo -> modules -> index -> views.py
@index_blue.route('/news_list') def index_news_list(): """提供主页新闻列表数据 1.接受参数(分类id,要看第几页,每页几条数据) 2.校验参数 (判断以上参数是否为数字) 3.根据参数查询用户想看的新闻列表数据 4.构造响应的新闻列表数据 5.响应新闻列表数据 """ # 1.接受参数(分类id,要看第几页,每页几条数据) cid = request.args.get('cid', '1') page = request.args.get('page', '1') per_page = request.args.get('per_page', '10') # 2.校验参数 (判断以上参数是否为数字) try: cid = int(cid) page = int(page) per_page = int(per_page) except Exception as e: current_app.logger.error(e) return jsonify(errno=response_code.RET.PARAMERR, errmsg='参数错误') # 3.根据参数查询用户想看的新闻列表数据 if cid == 1: # 从所有的新闻中,根据时间倒叙,每页取出10条数据 # paginate = [News,News,News,News,News,News,News,News,News,News] # 排序 时间倒序排列 分页 第几页页数 每页几个 默认值 paginate = News.query.order_by(News.create_time.desc()).paginate(page, per_page, False) else: # 从指定的分类中,查询新闻,根据时间倒叙,每页取出10条数据 paginate = News.query.filter(News.category_id==cid).order_by(News.create_time.desc()).paginate(page, per_page, False) # 4.构造响应的新闻列表数据 # news_list = [News,News,News,News,News,News,News,News,News,News] # 取出当前页的所有的模型对象(paginate只是一个分页对象,并不是查询集) news_list = paginate.items # 读取分页的总页数,将来在主页新闻列表上拉刷新时使用的 total_page = paginate.pages # 读取当前是第几页,将来在主页新闻列表上拉刷新时使用的 current_page = paginate.page # # 将模型对象列表转成字典列表,让json在序列化时乐意认识 # news_dict_list = [] # for news in news_list: # news_dict_list.append(news.to_basic_dict()) # 构造响应给客户单的数据 data = { 'news_dict_list': news_list, 'total_page': total_page, 'current_page': current_page } # 5.响应新闻列表数据 return jsonify(errno=response_code.RET.OK, errmsg='OK', data=data)
在视图中加入上面代码之后,我们 使用下面连接测试一下
http://127.0.0.1:8080/news_list?cid=2&page=3&per_page=10
会发现报了下面的错误:
这是因为:
News对象在序列化为json的时候序列化失败,因为json序列化的时候不认识模型类对象列表的数据形式。
json在序列化的时候只认识:字典,列表,字典列表
这个很关键,也就是说,我们需要将这种模型类对象列表转换为可以序列化的json数据。
要解决的话,首先修改视图函数中的内容:
indo -> modules -> index -> views.py
说明:to_basic_dict()是在定义模型类的时候定义的一个函数,用来封装基本的新闻信息。
修改之后,我们再次访问,就可以看到基本的新闻信息了:
将这些数据放到在线的json解析网站,可以看到简单地看到这些数据的构成:
其实这就是前后端交互的方式,无论前端是app,小程序,还是网页,基本都是用这种方式完成数据交换的。
2.修改前端逻辑
目前新闻的渲染都是“死代码”,这明项不行,所以将这部分代码统统删掉,我们要用js来实现新闻的动态刷新:
我们进入渲染主页的index.js当中,完成这部分js的代码。
在这个js中,正好有我们要补充的内容:
在这个代码中补充下面内容:
function updateNewsData() { // TODO 更新新闻数据 var params = { 'cid':currentCid, 'page':cur_page // 每页多少条不用传,默认10条 }; $.get('/news_list', params, function (response) { if (response.errno == '0') { for (var i=0;i<response.data.news_dict_list.length;i++) { var news = response.data.news_dict_list[i] var content = '<li>' content += '<a href="#" class="news_pic fl"><img src="' + news.index_image_url + '?imageView2/1/w/170/h/170"></a>' content += '<a href="#" class="news_title fl">' + news.title + '</a>' content += '<a href="#" class="news_detail fl">' + news.digest + '</a>' content += '<div class="author_info fl">' content += '<div class="source fl">来源:' + news.source + '</div>' content += '<div class="time fl">' + news.create_time + '</div>' content += '</div>' content += '</li>' $(".list_con").append(content) } } else { alert(response.errmsg); } }); }
完成了这个函数之后,还需要完成一个内容,就是调用它。还是在这个index.js中,在代码中增加下面的内容:
3.上拉刷新新闻
而滚动刷新的相关内容,是在js当中完成的,我们再次打开index.js,查看这部分代码,这些就是更新新闻的js了
这些代码的意思是,当滚动条里页面下方还有100像素的时候去更新新闻。
下面是修改后的index.js:
var currentCid = 1; // 当前分类 id var cur_page = 1; // 当前页 var total_page = 1; // 总页数 var data_querying = true; // 是否正在向后台获取数据:如果为ture表示正在加载数据;反之,没有加载数据 $(function () { // 当主页加载完成之后,立即刷新主页的分页数据 // 默认加载第一页 updateNewsData(); // 首页分类切换 $('.menu li').click(function () { var clickCid = $(this).attr('data-cid') $('.menu li').each(function () { $(this).removeClass('active') }) $(this).addClass('active') if (clickCid != currentCid) { // 记录当前分类id currentCid = clickCid; // 重置分页参数 cur_page = 1; total_page = 1; updateNewsData() } }); //页面滚动加载相关 $(window).scroll(function () { // 浏览器窗口高度 var showHeight = $(window).height(); // 整个网页的高度 var pageHeight = $(document).height(); // 页面可以滚动的距离 var canScrollHeight = pageHeight - showHeight; // 页面滚动了多少,这个是随着页面滚动实时变化的 var nowScroll = $(document).scrollTop(); if ((canScrollHeight - nowScroll) < 100) { // TODO 判断页数,去更新新闻数据 if (!data_querying) { // 表示正在加载数据 data_querying = true; // 计算当前在第几页 cur_page += 1; if (cur_page < total_page) { // 加载指定页码的新闻数据 updateNewsData(); } } } }) }); function updateNewsData() { // TODO 更新新闻数据 var params = { 'cid':currentCid, 'page':cur_page // 每页多少条不用传,默认10条 }; $.get('/news_list', params, function (response) { // 得到响应后,表示一次加载数据结束了 data_querying = false; if (response.errno == '0') { // 记录总页数 total_page = response.data.total_page; if (cur_page == 1) { $(".list_con").html(""); } for (var i=0;i<response.data.news_dict_list.length;i++) { var news = response.data.news_dict_list[i] var content = '<li>' content += '<a href="/news/detail/'+news.id+'" class="news_pic fl"><img src="' + news.index_image_url + '?imageView2/1/w/170/h/170"></a>' content += '<a href="/news/detail/'+news.id+'" class="news_title fl">' + news.title + '</a>' content += '<a href="/news/detail/'+news.id+'" class="news_detail fl">' + news.digest + '</a>' content += '<div class="author_info fl">' content += '<div class="source fl">来源:' + news.source + '</div>' content += '<div class="time fl">' + news.create_time + '</div>' content += '</div>' content += '</li>' $(".list_con").append(content); } } else { alert(response.errmsg); } }); }
毕竟前端代码,能看懂多少看多少。
新闻分类处理
关于新闻分类信息,我们专门存放到了一个表中,所以我们要做的就是遍历这个表,然后获取目前的分类信息。
而这部分的内容与主页渲染有关,所以要修改渲染主页的相关视图函数。
indo -> modules -> index -> views.py
.... @index_blue.route('/index') def index(): """主页 处理网页右上角的用户展示数据:当用户已登录展示'用户名 退出';反之,展示'登录 注册' """ .... # 3.新闻分类 # categories = [Category,Category,Category,Category,Category,Category] categories = [] try: categories = Category.query.all() except Exception as e: current_app.logger.error(e) # 构造渲染模板的上下文数据 context = { 'user':user, 'news_clicks':news_clicks, 'categories':categories } # 渲染主页 return render_template('news/index.html', context=context) ....
接下来,修改前端代码,将写死的内容,替换成下面代码:
{% for category in context.categories %} {% if loop.index == 1 %} <li class="active" data-cid="{{ category.id }}"><a href="javascript:;">{{ category.name }}</a></li> {% else %} <li data-cid="{{ category.id }}"><a href="javascript:;">{{ category.name }}</a></li> {% endif %} {% endfor %}
到这里,如果都ok,主页的内容就可以了。