Django 从 0 到 1 打造完整电商平台:商品搜索

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


上一篇我们让商品详情页的图片展示达到了流畅的电商体验,多图切换、规格联动都已就位。但一个真正的商城,光有浏览还不够------用户得能"搜"。今天我们就来实现 商品搜索功能,让用户输入关键词,快速定位心仪的商品。

我们不会引入 Elasticsearch 这样的重型搜索引擎,而是基于 Django ORM 的 icontainsQ 对象,完成一个简单高效、足够中小型电商使用的搜索方案。配合已有的商品列表与分页,搜索体验浑然一体。


一、需求分析

搜索功能的设计目标:

  1. 全局搜索入口:在顶部导航栏放置一个搜索框,用户输入关键词后提交。

  2. 搜索结果页:展示匹配的 SKU 列表(带分页),关键词高亮可选(我们先用简单方式,后续可美化)。

  3. 搜索逻辑

    • 匹配 SKU 名称(name

    • 匹配所属 SPU 名称(spu__name

    • 匹配品牌(spu__brand

    • 匹配商品描述(spu__desc

  4. 搜索与分类可组合:即用户可以先选分类,再搜关键词,或在搜索结果的上下文中切换分类。

  5. 保留搜索关键词:在搜索框显示已输入的关键词,便于用户调整。

我们将改造已有的 sku_list 视图,增加 q 参数处理,融入搜索逻辑。


二、改造商品列表视图,支持搜索

打开 apps/products/views.py,找到 sku_list 函数。之前它只处理了分类筛选和分页,现在我们增加关键词搜索。

bash 复制代码
from django.db.models import Q

def sku_list(request):
    # 基本查询:只取上架商品
    skus = SKU.objects.filter(is_active=True).select_related('spu__category').prefetch_related('images')

    # 1. 搜索过滤
    query = request.GET.get('q', '').strip()
    if query:
        skus = skus.filter(
            Q(name__icontains=query) |
            Q(spu__name__icontains=query) |
            Q(spu__brand__icontains=query) |
            Q(spu__desc__icontains=query)
        )

    # 2. 分类过滤(保留原有逻辑)
    category_id = request.GET.get('category_id')
    current_category = None
    if category_id:
        try:
            category = Category.objects.get(pk=category_id, is_active=True)
            # 含子分类
            category_ids = [category.id]
            children = Category.objects.filter(parent=category, is_active=True)
            category_ids.extend(children.values_list('id', flat=True))
            skus = skus.filter(spu__category_id__in=category_ids)
            current_category = category
        except Category.DoesNotExist:
            skus = skus.none()

    # 排序(暂时按创建时间倒序,第15篇会扩展)
    skus = skus.order_by('-create_time')

    # 3. 分页
    from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
    paginator = Paginator(skus, 12)
    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)

    # 4. 顶级分类(供分类导航)
    top_categories = Category.objects.filter(parent__isnull=True, is_active=True).order_by('sort')

    context = {
        'page_obj': page_obj,
        'current_category': current_category,
        'top_categories': top_categories,
        'query': query,  # 传递搜索关键词回模板
    }
    return render(request, 'products/sku_list.html', context)

改动要点:

  • request.GET 中获取 q 参数。

  • 使用 Q 对象组合多个字段的 icontains(不区分大小写的包含)条件。

  • query 变量返回给模板,用于在搜索框中回显和"搜索结果"提示。

  • 搜索与分类筛选可同时生效,例如 ?q=iPhone&category_id=1 将搜索电子产品分类下包含"iPhone"的商品。


三、改造商品列表模板,加入搜索框和结果提示

编辑 apps/products/templates/products/sku_list.html,在分类导航条上方加入搜索表单,并根据 query 变量显示搜索状态。

{% block content %} 的开头部分进行修改:

bash 复制代码
{% block content %}
<!-- 搜索栏 -->
<div class="row mb-3">
    <div class="col-md-6 mx-auto">
        <form method="get" action="{% url 'products:sku_list' %}" class="input-group">
            <input type="text" name="q" class="form-control" placeholder="搜索商品名称、品牌..."
                   value="{{ query }}" aria-label="搜索">
            {% if current_category %}
                <input type="hidden" name="category_id" value="{{ current_category.id }}">
            {% endif %}
            <button class="btn btn-primary" type="submit">🔍 搜索</button>
        </form>
    </div>
</div>

<div class="d-flex justify-content-between align-items-center mb-4">
    <h3>
        {% if query %}
            🔎 搜索 "{{ query }}"
        {% elif current_category %}
            📦 {{ current_category.name }}
        {% else %}
            🛍️ 全部商品
        {% endif %}
    </h3>
    <span class="text-muted">共 {{ page_obj.paginator.count }} 件商品</span>
</div>

<!-- 分类导航(保留原有) -->
...

关键点:

  • 搜索框的 value="{{ query }}" 让用户在提交后仍能看到自己输入的关键词。

  • 当存在分类筛选时,通过隐藏字段 <input type="hidden" name="category_id"> 携带分类 ID,保证搜索在分类范围内。

  • 标题根据搜索状态动态变化:搜索时显示 "🔎 搜索 "XXX"",分类时显示分类名,否则显示全部商品。

分页导航部分也需更新,确保页码链接同时携带 qcategory_id 参数。可以在分页链接的 href 中加入:

bash 复制代码
?page={{ num }}{% if query %}&q={{ query }}{% endif %}{% if current_category %}&category_id={{ current_category.id }}{% endif %}

所有出现分页链接的地方统一修改,保持参数完整。例如:

bash 复制代码
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if query %}&q={{ query }}{% endif %}{% if current_category %}&category_id={{ current_category.id }}{% endif %}"><< 上一页</a>

完整代码这里不再全贴,你可以在本地按要求修改,或照之前文章的分页部分对应添加参数。


四、在导航栏添加快速搜索入口(可选)

为了用户能在任何页面发起搜索,可以在 templates/base.html 的导航栏中加入一个小搜索框,但注意不要与列表页的搜索框重复。简单起见,我们仅在商品列表页提供搜索,导航栏保留菜单。如果你希望全局搜索,可以在导航栏加入一个表单,提交到商品列表页。例如在导航栏的 ul 前插入:

bash 复制代码
<form class="d-flex me-3" action="{% url 'products:sku_list' %}" method="get">
    <input class="form-control form-control-sm me-2" type="text" name="q" placeholder="搜索商品" aria-label="搜索">
    <button class="btn btn-sm btn-outline-light" type="submit">🔍</button>
</form>

但这样会导致在非商品页面也能搜索,效果也 OK。这里作为选做,读者可根据需要自行添加。


五、测试流程与输出

启动服务器:

bash 复制代码
python manage.py runserver

5.1 基本搜索

  1. 访问 http://127.0.0.1:8000/products/list/

  2. 在搜索框中输入 iPhone,点击搜索。

  3. 页面刷新,标题变为"🔎 搜索 "iPhone"",共找到 N 件商品(应该是 iPhone 15 的两个 SKU)。

  4. 列表中的商品卡片只显示匹配结果,分页正常。

终端输出:

bash 复制代码
[24/May/2026 14:15:30] "GET /products/list/?q=iPhone HTTP/1.1" 200 8765

5.2 搜索品牌

搜索 Samsung,将匹配到 SPU 品牌为 Samsung 的 SKU。

bash 复制代码
[24/May/2026 14:16:10] "GET /products/list/?q=Samsung HTTP/1.1" 200 7890

5.3 搜索描述中的词汇

如果 SPU 描述中有"智能手机",搜索 智能手机 也能命中。

5.4 组合搜索与分类

先点击分类"手机",再在搜索框输入 128GB,URL 变为 /products/list/?q=128GB&category_id=2(假设手机分类ID=2)。列表将只显示手机分类下名字包含"128GB"的 SKU。

终端输出:

bash 复制代码
[24/May/2026 14:18:45] "GET /products/list/?q=128GB&category_id=2 HTTP/1.1" 200 6543

5.5 无搜索结果

搜索 Xiaomi,如果没有该商品,页面显示"共 0 件商品",并列出空提示(我们在模板中已有 {% empty %} 的处理)。

页面显示:

  • 标题为 "🔎 搜索 "Xiaomi""

  • 数量为 0

  • 内容区域显示"该分类下暂无商品。"(可稍微调整提示,区分是搜索无结果还是分类无商品,这里作为优化点,可以在模板中用 {% if query %} 判断并给不同提示,如"未找到与 "Xiaomi" 相关的商品"。)

终端输出:

bash 复制代码
[24/May/2026 14:20:00] "GET /products/list/?q=Xiaomi HTTP/1.1" 200 5432

六、关于中文搜索的注意事项

SQLite 默认的 icontains 对中文支持没问题,但它是全表扫描,性能会随着数据量下降。对于初期或小规模电商,完全够用。如果你使用的是 MySQL,icontains 会被转换为 LIKE '%keyword%',同样全表扫描。后续我们会在第 26 篇(数据库查询优化与索引)中介绍如何为搜索字段创建索引,以及可选的全文本搜索方案(如 PostgreSQL 的 SearchVector 或 Elasticsearch 的集成)。目前以快速实现业务功能为主。


七、总结与下集预告

今天我们在商品列表模块中无缝融入了搜索功能:

  • 基于 Django ORM 的 icontainsQ 对象,实现了多字段模糊匹配;

  • 搜索与分类筛选可叠加使用,参数在分页链接中完整传递;

  • 前端搜索框友好回显关键词,页面标题动态切换。

现在,用户可以按分类浏览、按关键词搜索,还能组合使用,基本满足商品发现的需求。但默认的排序是按创建时间倒序,比较单一。下一篇我们就来解决这个问题------第 15 篇 将为大家带来 商品排序与浏览量统计,支持按价格、销量、上新时间排序,并在详情页记录每次浏览,为后续热销榜单做准备。

想了解更多还可以去公众号、今日头条搜索「IT策士」,一起升级 IT 思维 !


本文为《Django 从 0 到 1 打造完整电商平台》系列第 14 篇。

相关推荐
前端Hardy1 分钟前
一个时代结束了:npm 终于对 install 脚本下手了
前端·javascript·后端
damaoyou3 分钟前
Cog3DRangeImagePlaneEstimatorTool完全指南
后端
Nturmoils26 分钟前
分页别写太顺手,LIMIT 背后还有排序和边界
数据库·后端
神奇小汤圆30 分钟前
国产版“Codex”初体验,智谱ZCode很强啊!
后端
站大爷IP31 分钟前
Python里的“赋值”到底是什么意思?
后端
鹅城剑仙1 小时前
Spring Boot 微服务架构设计与最佳实践
spring boot·后端·微服务
Full Stack Developme2 小时前
Spring Integration 教程
java·后端·spring
爱勇宝2 小时前
AI 时代,前端工程师的话语权正在下降?
前端·后端
kymjs张涛2 小时前
一个月,纯VibeCoding,全平台云笔记APP
前端·javascript·后端
星辰_mya2 小时前
autowired和resource区别
java·后端·spring·架构·原理