IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我也会在其它平台发布最新文章,助你少走弯路。
前面几篇我们完成了从下单到支付的完整闭环,支付结果也已能准确处理。但用户付完钱之后,总得有个地方能看到自己所有订单------哪些待付款、哪些待发货、哪些已收货,一目了然。今天我们就来实现 "我的订单"列表页 和 增强版订单详情页,让用户对自己买过的东西心里有数。
订单列表不只是简单的数据展示,还要按状态分 Tab、支持分页、显示关键信息(订单号、金额、时间、状态标签)。订单详情则要展示地址、商品明细、金额构成、时间线(创建、支付、发货等节点)。全程基于已有模型,几乎没有新字段,重点在视图和模板的组织。
一、需求分析
订单列表页需求:
-
按订单状态分为多个 Tab:全部、待支付、待发货、待收货、已完成、已取消。
-
每个 Tab 下显示对应状态的订单列表,按创建时间倒序。
-
每条订单显示:订单号、下单时间、订单金额、状态标签、商品缩略图(第一件商品)。
-
支持分页,每页 10 条。
-
点击可进入订单详情页。
订单详情页需求:
-
订单状态横幅(显示当前状态 + 操作按钮:去支付、查询支付、确认收货)。
-
收货地址信息(从快照读取)。
-
商品明细表格(图片、名称、规格、单价、数量、小计)。
-
金额汇总(商品总额 + 运费 = 实付总额)。
-
订单时间线(创建时间、支付时间、发货时间、完成时间)。
-
订单号、支付方式等辅助信息。
二、配置 URL 路由
编辑 apps/orders/urls.py,加入列表路由(已有的 order_detail 保留):
bash
from django.urls import path
from . import views
app_name = 'orders'
urlpatterns = [
path('my/', views.order_list, name='order_list'), # 我的订单列表
path('<int:pk>/', views.order_detail, name='order_detail'), # 订单详情
path('confirm/', views.order_confirm, name='order_confirm'),
path('submit/', views.order_submit, name='order_submit'),
]
三、订单列表视图
编辑 apps/orders/views.py,新增 order_list 视图:
bash
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.db.models import Q
@login_required(login_url='users:login')
def order_list(request):
"""我的订单列表,支持按状态筛选"""
user = request.user
status_filter = request.GET.get('status', 'all')
orders = Order.objects.filter(user=user).prefetch_related('items__sku__images')
# 按状态筛选
if status_filter != 'all':
try:
status_code = int(status_filter)
orders = orders.filter(status=status_code)
except ValueError:
status_code = None
else:
status_code = None
orders = orders.order_by('-create_time')
# 分页:每页 10 条
paginator = Paginator(orders, 10)
page_number = request.GET.get('page', 1)
try:
page_obj = paginator.page(page_number)
except PageNotAnInteger:
page_obj = paginator.page(1)
except EmptyPage:
page_obj = paginator.page(paginator.num_pages)
# 统计各状态数量,供 Tab 显示
status_counts = {
'all': Order.objects.filter(user=user).count(),
0: Order.objects.filter(user=user, status=0).count(),
1: Order.objects.filter(user=user, status=1).count(),
2: Order.objects.filter(user=user, status=2).count(),
3: Order.objects.filter(user=user, status=3).count(),
4: Order.objects.filter(user=user, status=4).count(),
}
return render(request, 'orders/order_list.html', {
'page_obj': page_obj,
'status_filter': status_filter,
'status_counts': status_counts,
})
说明:
-
status_filter从 URL 参数获取,默认为all。 -
使用
prefetch_related减少查询次数。 -
分页复用
Paginator,每页 10 条。 -
status_counts字典用于模板中显示各 Tab 的数量。
四、订单列表模板
创建 apps/orders/templates/orders/order_list.html:
bash
{% extends 'base.html' %}
{% load static %}
{% block title %}我的订单{% endblock %}
{% block content %}
<h3 class="mb-3">📋 我的订单</h3>
<!-- 状态 Tab 导航 -->
<ul class="nav nav-tabs mb-4">
<li class="nav-item">
<a class="nav-link {% if status_filter == 'all' %}active{% endif %}"
href="?status=all">全部 <span class="badge bg-secondary">{{ status_counts.all }}</span></a>
</li>
<li class="nav-item">
<a class="nav-link {% if status_filter == '0' %}active{% endif %}"
href="?status=0">待支付 <span class="badge bg-warning text-dark">{{ status_counts.0 }}</span></a>
</li>
<li class="nav-item">
<a class="nav-link {% if status_filter == '1' %}active{% endif %}"
href="?status=1">待发货 <span class="badge bg-info">{{ status_counts.1 }}</span></a>
</li>
<li class="nav-item">
<a class="nav-link {% if status_filter == '2' %}active{% endif %}"
href="?status=2">待收货 <span class="badge bg-primary">{{ status_counts.2 }}</span></a>
</li>
<li class="nav-item">
<a class="nav-link {% if status_filter == '3' %}active{% endif %}"
href="?status=3">已完成 <span class="badge bg-success">{{ status_counts.3 }}</span></a>
</li>
<li class="nav-item">
<a class="nav-link {% if status_filter == '4' %}active{% endif %}"
href="?status=4">已取消 <span class="badge bg-secondary">{{ status_counts.4 }}</span></a>
</li>
</ul>
<!-- 订单列表 -->
{% if page_obj %}
{% for order in page_obj %}
<div class="card shadow-sm mb-3">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<span>
<strong>订单号:{{ order.order_no }}</strong>
<small class="text-muted ms-2">{{ order.create_time|date:"Y-m-d H:i" }}</small>
</span>
<span class="badge
{% if order.status == 0 %}bg-warning text-dark
{% elif order.status == 1 %}bg-info text-dark
{% elif order.status == 2 %}bg-primary
{% elif order.status == 3 %}bg-success
{% else %}bg-secondary
{% endif %}">
{{ order.get_status_display }}
</span>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-8">
{% for item in order.items.all|slice:":3" %}
<div class="d-flex align-items-center mb-2">
<img src="{{ item.sku.main_image_url }}" alt="{{ item.sku_name }}"
style="width: 50px; height: 50px; object-fit: cover;" class="me-3 rounded">
<div>
<div>{{ item.sku_name }}</div>
<small class="text-muted">¥{{ item.price }} × {{ item.quantity }}</small>
</div>
</div>
{% endfor %}
{% if order.items.count > 3 %}
<small class="text-muted">...还有 {{ order.items.count|add:"-3" }} 件商品</small>
{% endif %}
</div>
<div class="col-md-4 text-end">
<h5 class="text-danger">¥{{ order.total_amount|floatformat:2 }}</h5>
<a href="{% url 'orders:order_detail' order.pk %}" class="btn btn-sm btn-outline-primary mt-2">查看详情</a>
{% if order.status == 0 %}
<a href="{% url 'payment:payment_go' order.pk %}" class="btn btn-sm btn-warning mt-2">去支付</a>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
<!-- 分页 -->
{% if page_obj.has_other_pages %}
<nav>
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item"><a class="page-link" href="?status={{ status_filter }}&page={{ page_obj.previous_page_number }}"><< 上一页</a></li>
{% else %}
<li class="page-item disabled"><span class="page-link"><< 上一页</span></li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if num == page_obj.number %}
<li class="page-item active"><span class="page-link">{{ num }}</span></li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item"><a class="page-link" href="?status={{ status_filter }}&page={{ num }}">{{ num }}</a></li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item"><a class="page-link" href="?status={{ status_filter }}&page={{ page_obj.next_page_number }}">下一页 >></a></li>
{% else %}
<li class="page-item disabled"><span class="page-link">下一页 >></span></li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<div class="alert alert-info text-center">暂无订单</div>
{% endif %}
{% endblock %}
模板要点:
-
Tab 导航使用 Bootstrap
nav-tabs,每个 Tab 带数量徽标。 -
订单卡片展示前 3 个商品缩略图,超过显示省略。
-
待支付订单显示"去支付"按钮。
-
分页链接保留
status参数。
五、增强订单详情视图
修改 apps/orders/views.py 中的 order_detail 视图,增加时间线构造:
bash
@login_required(login_url='users:login')
def order_detail(request, pk):
order = get_object_or_404(
Order.objects.prefetch_related('items__sku__images', 'payment'),
pk=pk,
user=request.user
)
# 构造时间线
timeline = [
{'label': '创建订单', 'time': order.create_time},
]
if order.pay_time:
timeline.append({'label': '支付成功', 'time': order.pay_time})
# 发货时间和完成时间(后续功能补充,先预留)
if order.status >= 2:
timeline.append({'label': '商品已发出', 'time': order.update_time}) # 临时用 update_time 占位
if order.status >= 3:
timeline.append({'label': '交易完成', 'time': order.update_time})
return render(request, 'orders/order_detail.html', {
'order': order,
'timeline': timeline,
})
六、订单详情模板增强
编辑 apps/orders/templates/orders/order_detail.html:
bash
{% extends 'base.html' %}
{% block title %}订单详情{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-8">
<!-- 状态横幅 -->
<div class="card shadow-sm mb-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<h4>
<span class="badge
{% if order.status == 0 %}bg-warning text-dark
{% elif order.status == 1 %}bg-info text-dark
{% elif order.status == 2 %}bg-primary
{% elif order.status == 3 %}bg-success
{% else %}bg-secondary
{% endif %} fs-6">
{{ order.get_status_display }}
</span>
</h4>
<div>
{% if order.status == 0 %}
<a href="{% url 'payment:payment_go' order.pk %}" class="btn btn-warning btn-sm">去支付</a>
<a href="{% url 'payment:payment_query' order.pk %}" class="btn btn-outline-info btn-sm ms-2">查询支付状态</a>
{% elif order.status == 2 %}
<form method="post" action="#" style="display:inline;"> {# 确认收货功能后续实现 #}
{% csrf_token %}
<button class="btn btn-success btn-sm">确认收货</button>
</form>
{% endif %}
</div>
</div>
</div>
</div>
<!-- 商品明细 -->
<div class="card shadow-sm mb-4">
<div class="card-header bg-light"><h5 class="mb-0">📦 商品明细</h5></div>
<div class="card-body p-0">
<table class="table mb-0">
<thead class="table-light">
<tr><th>商品</th><th>单价</th><th>数量</th><th>小计</th></tr>
</thead>
<tbody>
{% for item in order.items.all %}
<tr>
<td>
<div class="d-flex align-items-center">
<img src="{{ item.sku.main_image_url }}" alt="{{ item.sku_name }}"
style="width: 50px; height: 50px; object-fit: cover;" class="me-3 rounded">
<div>
<div>{{ item.sku_name }}</div>
</div>
</div>
</td>
<td>¥{{ item.price }}</td>
<td>{{ item.quantity }}</td>
<td>¥{{ item.price|floatformat:2 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- 右侧信息栏 -->
<div class="col-lg-4">
<!-- 收货信息 -->
<div class="card shadow-sm mb-3">
<div class="card-header bg-light"><h5 class="mb-0">📍 收货信息</h5></div>
<div class="card-body">
<p><strong>{{ order.address_snapshot.receiver }}</strong> {{ order.address_snapshot.phone }}</p>
<p class="text-muted">{{ order.address_snapshot.province }} {{ order.address_snapshot.city }} {{ order.address_snapshot.district }} {{ order.address_snapshot.detail }}</p>
</div>
</div>
<!-- 金额汇总 -->
<div class="card shadow-sm mb-3">
<div class="card-header bg-light"><h5 class="mb-0">💰 金额明细</h5></div>
<div class="card-body">
<div class="d-flex justify-content-between"><span>商品总额</span><span>¥{{ order.total_amount|floatformat:2 }}</span></div>
<div class="d-flex justify-content-between"><span>运费</span><span>¥{{ order.freight|floatformat:2 }}</span></div>
<hr>
<div class="d-flex justify-content-between"><strong>实付总额</strong><strong class="text-danger">¥{{ order.total_amount|floatformat:2 }}</strong></div>
</div>
</div>
<!-- 订单信息 -->
<div class="card shadow-sm mb-3">
<div class="card-header bg-light"><h5 class="mb-0">📋 订单信息</h5></div>
<div class="card-body">
<p><strong>订单号:</strong>{{ order.order_no }}</p>
<p><strong>支付方式:</strong>{{ order.pay_method|default:"支付宝" }}</p>
{% if order.remark %}
<p><strong>备注:</strong>{{ order.remark }}</p>
{% endif %}
</div>
</div>
<!-- 时间线 -->
<div class="card shadow-sm">
<div class="card-header bg-light"><h5 class="mb-0">⏱️ 订单进度</h5></div>
<div class="card-body">
<ul class="list-unstyled">
{% for event in timeline %}
<li class="mb-2 {% if forloop.last %}text-success{% endif %}">
<small class="text-muted">{{ event.time|date:"Y-m-d H:i" }}</small>
<span class="ms-2">{{ event.label }}</span>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
{% endblock %}
详情页亮点:
-
状态横幅带操作按钮(去支付、查询支付、确认收货)。
-
左右两栏布局:左侧商品明细,右侧收货信息、金额、订单信息、时间线。
-
时间线展示订单从创建到完成的关键节点。
七、更新导航栏和侧边栏入口
在 templates/base.html 的导航栏下拉菜单中,确保"我的订单"链接正确:
bash
<li><a class="dropdown-item" href="{% url 'orders:order_list' %}">我的订单</a></li>
同时更新第 8 篇创建的个人中心侧边栏(center.html),把"我的订单"链接也指向 {% url 'orders:order_list' %}。
八、测试完整流程
启动服务器:
bash
python manage.py runserver
8.1 查看订单列表
-
登录已购买过商品的用户(确保数据库中有至少两三个订单,可用 Admin 或直接走几次下单流程)。
-
点击导航栏用户名 → 我的订单,或直接访问
/orders/my/。
页面展示:
-
Tab 栏显示全部、待支付、待发货等标签,数量徽标统计正确。
-
下方每个订单卡片显示订单号、时间、状态、前几个商品缩略图、金额、操作按钮。
终端输出:
bash
[28/May/2026 09:30:15] "GET /orders/my/ HTTP/1.1" 200 11234
8.2 切换状态 Tab
点击"待支付"Tab,URL 变为 /orders/my/?status=0,只显示待支付订单。
终端输出:
bash
[28/May/2026 09:31:00] "GET /orders/my/?status=0 HTTP/1.1" 200 8976
点击"已取消",若无对应订单,列表为空,显示"暂无订单"。
8.3 分页测试
如果订单数量超过 10,底部出现分页导航。点击第 2 页,URL 带 ?page=2,分页链接同时保留 status 参数。
8.4 查看订单详情
点击任意订单的"查看详情",进入 /orders/1/(或对应 pk)。页面展示:
-
状态横幅(如"待发货"),右侧有"去支付"或"查询支付状态"按钮。
-
商品明细表格,含图片、价格。
-
右侧收货信息、金额明细、订单号、时间线。
终端输出:
bash
[28/May/2026 09:33:20] "GET /orders/1/ HTTP/1.1" 200 12456
8.5 待支付订单操作
在待支付订单详情页,点击"去支付"跳转支付宝收银台;点击"查询支付状态"手动触发支付结果查询(终端可见查询日志)。
九、总结与下集预告
今天我们为买家打造了一个清晰实用的订单中心:
-
订单列表按状态分 Tab 展示,带数量统计和分页;
-
每个订单卡片缩略展示前几件商品,直观易懂;
-
订单详情页汇聚了收货信息、商品明细、金额构成、时间线,操作按钮齐全;
-
整个订单模块(下单→支付→列表→详情)形成完整闭环。
现在项目已经具备了电商的基本能力。但还有提升空间:注册验证码邮件、订单发货通知等还是模拟的。第 23 篇 ,我们将引入 Celery 异步任务,实现真实场景中的异步发送邮件和短信,让项目的异步处理能力上一个台阶。
想了解更多也可以去其它平台搜索「IT策士」,一起升级 IT 思维 !
本文为《Django 从 0 到 1 打造完整电商平台》系列第 22 篇。