1.分析
业务处理流程:
- 判断前端传的标签分类id是否为空,是否为整数、是否超过范围
- 判断前端传的当前文章页数是否为空,是否为整数、是否超过范围
请求方法:GET
url定义:/news/list/
传参方式:url字符串传参
参数 | 类型 | 前端是否必须传 | 描述 |
---|---|---|---|
tag_id | 整数 | 是 | 标签分类id |
page | 整数 | 是 | 当前文章页数 |
向前端返回的数据格式为json格式,返回实例如下:
{
"data": {
"total_pages": 61,
"news": [
{
"digest": "在python用import或者from...import或者from...import...as...来导入相应的模块,作用和使用方法与C语言的include头文件类似。其实就是引入...",
"title": "import方法引入模块详解",
"author": "python",
"image_url": "/media/jichujiaochen.jpeg",
"tag_name": "Python基础",
"update_time": "2018年12月17日 14:48"
},
{
"digest": "如果你原来是一个php程序员,你对于php函数非常了解(PS:站长原来就是一个php程序员),但是现在由于工作或者其他原因要学习python,但是p...",
"title": "给曾经是phper的程序员推荐个学习网站",
"author": "python",
"image_url": "/media/jichujiaochen.jpeg",
"tag_name": "Python基础",
"update_time": "2018年12月17日 14:48"
}
]
},
"errno": "200",
"errmsg": ""
}
2.后端视图实现
首先在项目根目录下创建一个media文件夹,用于存放新闻图片以及用户上传的文件(仅供测试使用)
正常会存储在七牛云等平台
# 在settings.py文件中添加如下配置:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# 在全局urls.py文件中添加如下配置:
from django.conf import settings
from django.conf.urls.static import static
from django.urls import path, include
urlpatterns = [
...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# 在xshell中导入测试数据,在xshell中通过rz命令,将tb_news_20181217.sql文件上传到虚拟机
mysql -u 用户名 -p -D 数据库名 < tb_news_20181217.sql
# 在news目录下views.py文件中创建如下类视图:
import logging
from django.views import View
from django.shortcuts import render
from django.core.paginator import Paginator, EmptyPage
from . import models
from . import constants
from utils.res_code.rescode import Code, error_map
from utils.json_translate.json_fun import to_json_data
logger = logging.getLogger('django')
# 新闻列表及分页视图
class NewsListView(View):
"""
1.创建一个类视图 NewsListView
2.明确请求方式
-- 请求类型 ajax get
-- 传参方式 url字符串传参
-- url定义 '/news/list/' ?tag_id=1&?page=1
3.获取前端参数:
-- tag_id = request.GET.get('tag_id')
4.业务处理:
-- 是否需要校验 需要 自定义校验
-- 是否需要储存以及储存方式 不需要
-- 其他:
-- 数据库读取数据
-- 分页处理
5.返回前端数据
-- to_json_data(errno= , errmsg=, data=)
"""
def get(self, request):
# tag_id 参数校验
try:
tag_id = int(request.GET.get('tag_id', 0))
except Exception as e:
tag_id = 0
logger.error("传入tag_id参数错误:\n{}".format(e))
# page 参数校验
try:
page = int(request.GET.get('page', 1))
except Exception as e:
page = 1
logger.error("传入page参数错误:\n{}".format(e))
# 若page为0,会返回最后一页内容,用户体验不好
if page == 0:
page = 1
# 获取新闻
news_queryset = models.News.objects.select_related('tag', 'author').\
only('title', 'digest', 'image_url', 'update_time', 'tag__name', 'author__username')
news = news_queryset.filter(is_delete=False, tag_id=tag_id) or \
news_queryset.filter(is_delete=False)
# 分页处理
paginator = Paginator(news, constants.PER_PAGE_NEWS_COUNT)
try:
news_info = paginator.page(page)
except EmptyPage:
# 若用户访问的页数大于实际页数,则返回最后一页数据
logger.info('用户访问页数大于总页数')
news_info = paginator.page(paginator.num_pages)
# 序列化输出
news_info_list = []
for n in news_info:
news_info_list.append({
'title':n.title,
'digest':n.digest,
'image_url':n.image_url,
'tag_name':n.tag.name,
'author':n.author.username,
'update_time':n.update_time.strftime('%Y年%m月%d日 %H:%M')
})
# 返回前端数据
data = {
'total_pages':paginator.num_pages,
'news':news_info_list,
}
return to_json_data(data=data)
# 在news目录下constants.py中定义如下常量:
# 每页新闻数
PER_PAGE_NEWS_COUNT = 5
# 在news目录下urls.py中定义如下路由:
urlpatterns = [
path('list/', views.NewsListView.as_view(), name='news_list'),
]
3.前端代码实现
<!-- 在templates/news/index.html中 -->
<!-- 清空news-list中的内容 -->
<ul class="news-list">
</ul>
{% block script %}
<script src="{% static 'js/news/index.js' %}"></script>
{% endblock %}
// js/news/index.js
$(function () {
// 新闻列表功能
let $newsLi = $(".news-nav ul li");
let iPage = 1; //默认第1页
let iTotalPage = 1; //默认总页数为1
let sCurrentTagId = 0; //默认分类标签为0
let bIsLoadData = true; // 是否正在向后台加载数据
fn_load_content();
$newsLi.click(function () {
// 点击分类标签,则为点击的标签加上一个class属性为active
// 并移除其它兄弟元素的上的,值为active的class属性
$(this).addClass('active').siblings('li').removeClass('active');
// 获取绑定在当前选中分类上的data-id属性值
let sClickTagId = $(this).children('a').attr('data-id');
if (sClickTagId !== sCurrentTagId) {
sCurrentTagId = sClickTagId; // 记录当前分类id
// 重置分页参数
iPage = 1;
iTotalPage = 1;
fn_load_content()
}
});
//页面滚动加载相关
$(window).scroll(function () {
// 浏览器窗口高度
let showHeight = $(window).height();
// 整个网页的高度
let pageHeight = $(document).height();
// 页面可以滚动的距离
let canScrollHeight = pageHeight - showHeight;
// 页面滚动了多少,这个是随着页面滚动实时变化的
let nowScroll = $(document).scrollTop();
if ((canScrollHeight - nowScroll) < 100) {
// 判断页数,去更新新闻数据
if (!bIsLoadData) {
bIsLoadData = true;
// 如果当前页数据如果小于总页数,那么才去加载数据
if (iPage < iTotalPage) {
iPage += 1;
$(".btn-more").remove(); // 删除标签
// 去加载数据
fn_load_content()
} else {
message.showInfo('已全部加载,没有更多内容!');
$(".btn-more").remove(); // 删除标签
$(".news-list").append($('<a href="javascript:void(0);" class="btn-more">已全部加载,没有更多内容!</a>'))
}
}
}
});
// 定义向后端获取新闻列表数据的请求
function fn_load_content() {
// let sCurrentTagId = $('.active a').attr('data-id');
// 创建请求参数
let sDataParams = {
"tag_id": sCurrentTagId,
"page": iPage
};
// 创建ajax请求
$.ajax({
// 请求地址
url: "/news/list/", // url尾部需要添加/
// 请求方式
type: "GET",
data: sDataParams,
// 响应数据的格式(后端返回给前端的格式)
dataType: "json",
// 若不加异步:false 后面调用这个函数时,会出现 ""/define
})
.done(function (res) {
if (res.errno === "200") {
iTotalPage = res.data.total_pages; // 后端传过来的总页数
if (iPage === 1) {
$(".news-list").html("")
}
res.data.news.forEach(function (one_news) {
let content = `
<li class="news-item">
<a href="https://www.shiguangkey.com/course/2432" class="news-thumbnail" target="_blank">
<img src="${one_news.image_url}" alt="${one_news.title}" title="${one_news.title}">
</a>
<div class="news-content">
<h4 class="news-title"><a href="#">${one_news.title}</a></h4>
<p class="news-details">${one_news.digest}</p>
<div class="news-other">
<span class="news-type">${one_news.tag_name}</span>
<span class="news-time">${one_news.update_time}</span>
<span class="news-author">${one_news.author}</span>
</div>
</div>
</li>`;
$(".news-list").append(content)
});
$(".news-list").append($('<a href="javascript:void(0);" class="btn-more">滚动加载更多</a>'));
// 数据加载完毕,设置正在加载数据的变量为false,表示当前没有在加载数据
bIsLoadData = false;
} else {
// 登录失败,打印错误信息
message.showError(res.errmsg);
}
})
.fail(function () {
message.showError('服务器超时,请重试!');
});
}
});