Django 从 0 到 1 打造完整电商平台:我的订单列表与订单详情

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在公众号、今日头条持续发布最新文章,助你少走弯路。


前面几篇我们完成了从下单到支付的完整闭环,支付结果也已能准确处理。但用户付完钱之后,总得有个地方能看到自己所有订单------哪些待付款、哪些待发货、哪些已收货,一目了然。今天我们就来实现 "我的订单"列表页增强版订单详情页,让用户对自己买过的东西心里有数。

订单列表不只是简单的数据展示,还要按状态分 Tab、支持分页、显示关键信息(订单号、金额、时间、状态标签)。订单详情则要展示地址、商品明细、金额构成、时间线(创建、支付、发货等节点)。全程基于已有模型,几乎没有新字段,重点在视图和模板的组织。


一、需求分析

订单列表页需求:

  1. 按订单状态分为多个 Tab:全部、待支付、待发货、待收货、已完成、已取消。

  2. 每个 Tab 下显示对应状态的订单列表,按创建时间倒序。

  3. 每条订单显示:订单号、下单时间、订单金额、状态标签、商品缩略图(第一件商品)。

  4. 支持分页,每页 10 条。

  5. 点击可进入订单详情页。

订单详情页需求:

  1. 订单状态横幅(显示当前状态 + 操作按钮:去支付、查询支付、确认收货)。

  2. 收货地址信息(从快照读取)。

  3. 商品明细表格(图片、名称、规格、单价、数量、小计)。

  4. 金额汇总(商品总额 + 运费 = 实付总额)。

  5. 订单时间线(创建时间、支付时间、发货时间、完成时间)。

  6. 订单号、支付方式等辅助信息。


二、配置 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 }}" />
                        <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 }}" />
                                    <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 查看订单列表

  1. 登录已购买过商品的用户(确保数据库中有至少两三个订单,可用 Admin 或直接走几次下单流程)。

  2. 点击导航栏用户名 → 我的订单,或直接访问 /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 篇。

相关推荐
渐儿15 小时前
第 05 章 · SQL 写法
后端
invicinble15 小时前
对于spring的bean应该有哪些领域的认识
java·后端·spring
Amazing530715 小时前
docker compose 漏一个参数全失效
后端·代码规范
ZengLiangYi15 小时前
从零实现 Embedding 服务:文本转向量
人工智能·后端
星栈15 小时前
订单状态机别写散:我在 Rust CRM 里把 6 个状态收进领域模型
后端·rust·全栈
韩小兔修媛史16 小时前
SpringBoot面试八股文(持续更新)
spring boot·后端·面试
码上出头16 小时前
地理围栏从0到1:我是怎么把轮询接口从每分钟2000次干到0次的
后端
神奇小汤圆16 小时前
搞懂数据库索引:它到底帮了什么忙,又埋了什么坑?
后端
浮游本尊16 小时前
Java学习第38天 - 企业级 REST API 设计、OpenAPI 契约与接口可靠性
后端
苍何16 小时前
分享最近高频用 Agent 提效的 4 大场景
后端