IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在公众号、今日头条持续发布最新文章,助你少走弯路。
上一篇我们让商品详情页的图片展示达到了流畅的电商体验,多图切换、规格联动都已就位。但一个真正的商城,光有浏览还不够------用户得能"搜"。今天我们就来实现 商品搜索功能,让用户输入关键词,快速定位心仪的商品。
我们不会引入 Elasticsearch 这样的重型搜索引擎,而是基于 Django ORM 的 icontains 和 Q 对象,完成一个简单高效、足够中小型电商使用的搜索方案。配合已有的商品列表与分页,搜索体验浑然一体。
一、需求分析
搜索功能的设计目标:
-
全局搜索入口:在顶部导航栏放置一个搜索框,用户输入关键词后提交。
-
搜索结果页:展示匹配的 SKU 列表(带分页),关键词高亮可选(我们先用简单方式,后续可美化)。
-
搜索逻辑:
-
匹配 SKU 名称(
name) -
匹配所属 SPU 名称(
spu__name) -
匹配品牌(
spu__brand) -
匹配商品描述(
spu__desc)
-
-
搜索与分类可组合:即用户可以先选分类,再搜关键词,或在搜索结果的上下文中切换分类。
-
保留搜索关键词:在搜索框显示已输入的关键词,便于用户调整。
我们将改造已有的 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"",分类时显示分类名,否则显示全部商品。
分页导航部分也需更新,确保页码链接同时携带 q 和 category_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 基本搜索
-
访问
http://127.0.0.1:8000/products/list/ -
在搜索框中输入
iPhone,点击搜索。 -
页面刷新,标题变为"🔎 搜索 "iPhone"",共找到 N 件商品(应该是 iPhone 15 的两个 SKU)。
-
列表中的商品卡片只显示匹配结果,分页正常。
终端输出:
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 的
icontains和Q对象,实现了多字段模糊匹配; -
搜索与分类筛选可叠加使用,参数在分页链接中完整传递;
-
前端搜索框友好回显关键词,页面标题动态切换。
现在,用户可以按分类浏览、按关键词搜索,还能组合使用,基本满足商品发现的需求。但默认的排序是按创建时间倒序,比较单一。下一篇我们就来解决这个问题------第 15 篇 将为大家带来 商品排序与浏览量统计,支持按价格、销量、上新时间排序,并在详情页记录每次浏览,为后续热销榜单做准备。
想了解更多还可以去公众号、今日头条搜索「IT策士」,一起升级 IT 思维 !
本文为《Django 从 0 到 1 打造完整电商平台》系列第 14 篇。