本人是python业余选手,公司的系统是我业余时间开发的,一直在维护升级。
前段时间更新了一个业务人员下单的功能
上方的是本地开发的页面和数据,实际业务中,订单会很多,每天会增加几十条订单记录,那订单的筛选必不可少,而且是多条件查询,这是刚需。
先对需求做一个整理:
- 多条件交叉查询
- 异步请求
程序采用的是django 2.1.8,跑在本地的windows sever2008上,使用apache2.4部署,前端使用的是django前端模板语言,那么以下几点是可以明确的
- django rest_framework
- 前端使用原生js写请求和数据渲染(jq还未学习,不熟悉)
js原生异步请求思路
数据渲染思路
异步请求有响应后,不管有无数据,都需要清除原来页面的数据以及分页
if(有数据){
清除原有数据
清除原有分页
创建元素 //和原来一模一样的
渲染数据 //使用responseText()方法把后端响应的json数据转换成js对象
}else(无数据){
清除原有数据
清除原有分页
提示:暂无数据
}
前端流程的处理思路
前端:
- 抽象ajax模块,处理请求和响应(按理说,响应部分也需要抽象出来,这里偷懒了,直接写在了ajax模板中)
- 抽象数据过滤模块,使用
addEventListner()
监听多条件点击事件,拼接url,发起异步请求 - 抽象响应后的分页
addEventListner()
点击监听模块,如果存在page
参数,则拼接到url中,发起异步请求 - 分页监听模块,过滤数据后进行调用(如果数据存在)
核心代码展示
- 获取元素,声明变量
// 获取元素,页面上的4个过滤条件
var orderStatus = document.querySelector("#order-status-items").children
var expenseStatus = document.querySelector("#expense-status-items").children
var sales = document.querySelector("#sales-items").children
var designer = document.querySelector("#designer-items").children
// 获取当前的url,后面做url拼接
var host = document.location
// 请求后的分页,分页是在ajax请求后再生成的,所以要在数据过滤模块外进行保存该变量
var pages
// 定义4个过滤条件的url默认值,xx-xx-all代表“全部xx”,如果前端选择的是其他过滤条件,则声明为实际的过滤条件,这个值由前端元素中声明
var orderStatusCode = "order-status-all";
var expenseStatusCode = "expense-status-all";
var salesCode = "sales-status-all";
var designerCode = "designer-status-all";
//异步请求和响应处理模块
var JsonRequest = function(orderStatusCode, expenseStatusCode, salesCode, designerCode, page) {
var osc = orderStatusCode
var esc = expenseStatusCode
var sc = salesCode
var dc = designerCode
// 1.创建XMLHttprequest对象
var xhr = new XMLHttpRequest();
// 2.调用open方法打开URL
// 如果分页参数page存在,即在url后面拼接&page=x,不存在便只拼接过滤的url
if (page) {
xhr.open("get", "filter/?osc=" + osc + "&esc=" + esc + "&sc=" + sc + "&dc=" + dc + "&page=" + page);
} else {
xhr.open("get", "filter/?osc=" + osc + "&esc=" + esc + "&sc=" + sc + "&dc=" + dc);
}
// 3.发送异步请求
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send()
// 4.侦听请求状态码
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
// 获取返回的结果,使用json解析
var res = JSON.parse(xhr.responseText)
var page_obj = res["data"]
//清除当前页面的数据
var tbody = document.getElementById("order-body");
if (tbody) {
tbody.parentNode.removeChild(tbody);
}
// 判断能否筛选到数据 res["flag"] == true
if(res["flag"] == true){
// 有数据则:
// 1.清除当前页面的数据和分页
// 2.写入请求到的数据和分页
// 3.启动分页点击侦听,请求分页数据
//写入过滤后的数据
// 创建table的子元素tbody,并设置id属性为order-body
var tbody = document.createElement("tbody");
tbody.setAttribute("id", "order-body");
var table = document.querySelector("table");
table.appendChild(tbody);
// 清除“哥,暂无结果”的提示,如果存在的话
var h4 = document.querySelector("#nomsg");
if(h4){
h4.parentNode.removeChild(h4);
}
// 创建tbody的子元素tr、子孙元素th和td,创建的规范符合原有的table规范
// 创建tr,数据有几行就创建几个tr,使用循环处理
for (i = 0; i < page_obj.length; i++) {
console.log("遍历获取的数据:", page_obj[i]);
var tr = document.createElement("tr");
// 创建th,即序号
var th = document.createElement("th");
th.setAttribute("scope", "row");
th.innerHTML = i + 1;
tr.appendChild(th);
// 创建td
var tdTitle = document.createElement("td");
tdTitle.innerHTML = "<a href=" + host.origin + "/orders/detail/" + page_obj[i]["id"] + ">" + page_obj[i]["title"] +
"</a>";
tr.appendChild(tdTitle);
var tdCompany = document.createElement("td");
tdCompany.innerHTML = page_obj[i]["company"];
tr.appendChild(tdCompany);
var tdCreateTime = document.createElement("td");
var dateTime = formatDate(page_obj[i]["created_time"]);
tdCreateTime.innerHTML = dateTime;
tr.appendChild(tdCreateTime);
var tdEndTime = document.createElement("td");
var endTime = formatDate(page_obj[i]["end_time"]);
tdEndTime.innerHTML = endTime;
tr.appendChild(tdEndTime);
var tdOrderStatus = document.createElement("td");
tdOrderStatus.innerHTML = page_obj[i]["order_status"];
tr.appendChild(tdOrderStatus);
var tdExpenseStatus = document.createElement("td");
tdExpenseStatus.innerHTML = page_obj[i]["expense_status"];
tr.appendChild(tdExpenseStatus);
var tdDesigner = document.createElement("td");
tdDesigner.innerHTML = page_obj[i]["designer"];
tr.appendChild(tdDesigner);
var tdSales = document.createElement("td");
tdSales.innerHTML = page_obj[i]["sales"];
tr.appendChild(tdSales);
// 把tr添加到tbody中
tbody.appendChild(tr)
}
// 清除分页页面:原有的分页及请求后产生的分页,请求后产生的分页,它们使用一致的id,以便简化代码
var defaultPaginator = document.querySelector("#dj-pagination");
// console.log(defaultPaginator);
if (defaultPaginator) {
defaultPaginator.parentNode.removeChild(defaultPaginator);
}
// 写入分页页面
var Ulpaginator = document.createElement("ul");
// 写入id,实现每次点击请求时都会清除该页面,然后再重写
Ulpaginator.setAttribute("id", "dj-pagination");
// 写入一个自定义属性,以此来区别该ul是异步请求后生成的
Ulpaginator.setAttribute("mark", "true");
// 写入样式,调用bootstrap默认样式
Ulpaginator.classList.add("pagination", "mt-3")
var container = document.querySelector("#use-for-ajax");
container.appendChild(Ulpaginator);
// 写入li,后端传过来的res["page_nums"]是分页后的页码总数,可以此来创建分页
var pageNum = res["page_nums"]
for (var i = 0; i < pageNum; i++) {
var pageLi = document.createElement("li");
pageLi.innerHTML = i + 1;
pageLi.classList.add("page-item", "page-link");
pageLi.style.cursor = "pointer";
// console.log(pageLi);
Ulpaginator.appendChild(pageLi);
}
// 异步请求清除原有分页数据后,才能获取新的分页,并保存在pages中(上面有声明该变量)
pages = document.querySelector("#dj-pagination").children;
// 启动分页点击侦听器
pageListner(pages);
}else{
// 无数据,则:
// 输出无数据的提示
// 依然要清除原有的分页和原有的页面数据
// 清除原有的分页页面
var defaultPaginator = document.querySelector("#dj-pagination");
// console.log(defaultPaginator);
if (defaultPaginator) {
defaultPaginator.parentNode.removeChild(defaultPaginator);
}
// 提示
var h4 = document.querySelector("#nomsg");
if(h4){
return;
}else{
var h4 = document.createElement("h4");
h4.innerText = "哥,暂无数据";
h4.setAttribute("id", "nomsg");
var container = document.querySelector("#use-for-ajax");
container.appendChild(h4);
}
}
}
}
}
//条件过滤点击侦听函数
var vanListner = function(nav) {
for (i = 0; i < nav.length; i++) {
nav[i].addEventListener("click", function() {
for (i = 0; i < nav.length; i++) {
nav[i].className = "";
}
this.className = "order_status_active";
// 获取父元素的id,以此区分当前元素的id值归属于哪部分
// 订单code
if (this.parentNode.getAttribute("id") == "order-status-items") {
tarGetId = this.getAttribute("id");
orderStatusCode = tarGetId;
// 发送异步请求
JsonRequest(orderStatusCode, expenseStatusCode, salesCode, designerCode)
}
// 费用code
if (this.parentNode.getAttribute("id") == "expense-status-items") {
tarGetId = this.getAttribute("id");
expenseStatusCode = tarGetId;
// 发送异步请求
JsonRequest(orderStatusCode, expenseStatusCode, salesCode, designerCode)
}
// 下单人code
if (this.parentNode.getAttribute("id") == "sales-items") {
tarGetId = this.getAttribute("id");
salesCode = tarGetId;
// 发送异步请求
JsonRequest(orderStatusCode, expenseStatusCode, salesCode, designerCode)
}
// 设计code
if (this.parentNode.getAttribute("id") == "designer-items") {
tarGetId = this.getAttribute("id");
designerCode = tarGetId;
// 发送异步请求
JsonRequest(orderStatusCode, expenseStatusCode, salesCode, designerCode)
}
})
}
}
// 分页点击侦听模块,该模块需要在异步请求模块中的过滤有数据后立即调用
var pageListner = function(pages) {
// 分页侦听
// 点击后,获取异步请求后的分页数据
for (var i = 0; i < pages.length; i++) {
pages[i].addEventListener("click", function() {
// 去掉激活样式
for (var i = 0; i < pages.length; i++) {
pages[i].classList.remove("active");
}
this.classList.add("active");
// 获取当前请求参数,发送异步请求
JsonRequest(orderStatusCode, expenseStatusCode, salesCode, designerCode, this.innerText);
})
}
}
以上代码写在一个叫js/order_filter.js
的文件中,在前端页面中引入,并调用过滤条件侦听器
<script src="{% static 'js/order_filter.js' %}" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
// 调用函数,导航点击激活、拼接url和发送异步请求
vanListner(orderStatus);
vanListner(expenseStatus);
vanListner(sales);
vanListner(designer);
</script>
django后端处理逻辑
django中有个叫order
的app,用来处理订单。
- 在order下创建一个序列化器文件:
serializer.py
这个文件的命名不能使用serializers,不然会和serializers
模块冲突
在serializer.py
中创建序列化器
from rest_framework import serializers
from . models import Order
class OrderSerializer(serializers.ModelSerializer):
order_status = serializers.CharField(source='get_order_status_display')
company = serializers.CharField(source='company.company_name')
expense_status = serializers.CharField(source='get_expense_status_display')
sales = serializers.CharField(source='sales.username')
designer = serializers.CharField(source='designer.username')
class Meta:
model = Order
fields = ['id', 'title', 'order_status', 'expense_status', 'sales', 'designer', 'company','items', 'created_time', 'end_time']
对于Forenkey
和 choices
的字段,需要在序列化器中做友好可视化,不然前端只能看到id
或choices的选项。
在views.py
中处理响应
from order.models import Order
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.pagination import PageNumberPagination
from order import serializer
# 重写PageNumberPagination方法,获取页码总数
class DRFPagination(PageNumberPagination):
def paginate_queryset(self, queryset, request, view=None):
"""
Paginate a queryset if required, either returning a
page object, or `None` if pagination is not configured for this view.
"""
page_size = self.get_page_size(request)
if not page_size:
return None
paginator = self.django_paginator_class(queryset, page_size)
page_number = request.query_params.get(self.page_query_param, 1)
if page_number in self.last_page_strings:
page_number = paginator.num_pages
try:
self.page = paginator.page(page_number)
except InvalidPage as exc:
msg = self.invalid_page_message.format(
page_number=page_number, message=str(exc)
)
raise NotFound(msg)
if paginator.num_pages > 1 and self.template is not None:
# The browsable API should display pagination controls.
self.display_page_controls = True
self.request = request
return list(self.page), paginator.num_pages
class OrderSerializerView(APIView):
def post(self, request, *args, **kwargs):
print(request.data)
obj = code_handler(osc=request.data['osc'], esc=request.data['esc'], sc=request.data['sc'], dc=request.data['dc'])
#创建分页对象
page_numb_paginator = PageNumberPagination()
page_obj = page_numb_paginator.paginate_queryset(queryset=obj, request=request, view=self)
json_obj = serializer.OrderSerializer(instance=page_obj, many=True)
return Response(json_obj.data)
def get(self, request, *args, **kwargs):
osc = request.GET.get('osc')
esc = request.GET.get('esc')
sc = request.GET.get('sc')
dc = request.GET.get('dc')
obj = code_handler(osc=osc, esc=esc, sc=sc, dc=dc)
# page_numb_paginator = PageNumberPagination() 原生调用,后来继承重写,增加了page_num的返回
page_numb_paginator = DRFPagination()
page_obj, num = page_numb_paginator.paginate_queryset(queryset=obj, request=request, view=self)
json_obj = serializer.OrderSerializer(instance=page_obj, many=True)
msg = ''
flag = True
drf_data = {}
drf_data['page_nums'] = num
drf_data['data'] = json_obj.data
print(page_obj)
print(len(page_obj))
if len(page_obj) < 1:
msg = '暂无结果,请重新选择'
flag = False
if len(page_obj) >= 1:
msg = '获取成功'
drf_data['msg'] = msg
drf_data['flag'] = flag
return Response(drf_data)
核心代码就这么多,第一次写原生js请求和渲染json数据,过程比较吃力,各种学习才能完整理解其中的原理