美多商城项目-06

热销排行

list.html
  ↓
Vue mounted()
  ↓
axios GET /hot/<category_id>/
  ↓
HotView
  ↓
按 sales 排序查 SKU
  ↓
返回 JSON
  ↓
Vue v-for 渲染

代码实现

后端:添加HotView

apps/goods/views.py

# ...

from django import http
from utils.response_code import RETCODE

class HotView(View):

    def get(self, request, category_id):
        try:
            category = GoodsCategory.objects.get(id=category_id)
        except GoodsCategory.DoesNotExist:
            return http.JsonResponse({
                'code': RETCODE.NODATAERR,
                'errmsg': '暂无此分类'
            })

        skus = SKU.objects.filter(
            category=category,
            is_launched=True
        ).order_by('-sales')[:2]

        hot_skus = []
        for sku in skus:
            hot_skus.append({
                'id': sku.id,
                'name': sku.name,
                'price': sku.price,
                'default_image_url': sku.default_image.url,
                'url': f'/detail/{sku.id}/'
            })

        return http.JsonResponse({
            'code': RETCODE.OK,
            'errmsg': 'ok',
            'hot_skus': hot_skus
        })

代码讲解:

  1. category_id
  2. sales DESC
  3. 取前 N 条
  4. 返回 JSON

路由 apps/goods/urls.py

from .views import HotView

urlpatterns = [
    ...
    path('hot/<int:category_id>/', HotView.as_view(), name='hot'),
]

补齐前端

templates/list.html

<div class="main_wrap clearfix">
    <!-- 左侧 -->
    <div class="l_wrap fl clearfix">

        <!-- 热销排行 -->
        <div class="new_goods" v-cloak>
            <h3>热销排行</h3>
            <ul>
                <li v-for="sku in hots">
                    <a :href="'/detail/' + sku.id + '/'">
                        <img :src="sku.default_image_url">
                    </a>
                    <h4>[[ sku.name ]]</h4>
                    <div class="price">¥[[ sku.price ]]</div>
                </li>
            </ul>
        </div>

    </div>

    <!-- 右侧商品列表 -->
    <div class="r_wrap fr clearfix">
        ...
    </div>
</div>

// 在 list.html 底部(</body> 前)加这一段

<script>
var vm = new Vue({
    el: '#app',
    delimiters: ['[[', ']]'],
    data: {
        hots: []
    },
    mounted() {
        this.get_hot_skus();
    },
    methods: {
        get_hot_skus() {
            axios.get('/hot/' + category_id + '/')
                .then(response => {
                    if (response.data.code === '0') {
                        this.hots = response.data.hot_skus;
                    }
                })
                .catch(error => {
                    console.log(error);
                });
        }
    }
});
</script>

templates/list.html 修改后为:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>美多商城-商品列表</title>

    <!-- CSS -->
    <link rel="stylesheet" href="{{ static('css/jquery.pagination.css') }}">
    <link rel="stylesheet" href="{{ static('css/reset.css') }}">
    <link rel="stylesheet" href="{{ static('css/main.css') }}">

    <!-- JS 库(必须在前) -->
    <script src="{{ static('js/jquery-1.12.4.min.js') }}"></script>
    <script src="{{ static('js/vue-2.5.16.js') }}"></script>
    <script src="{{ static('js/axios-0.18.0.min.js') }}"></script>
    <script src="{{ static('js/host.js') }}"></script>

    <!-- ⭐ 全局变量(只能定义一次) -->
    <script>
        var category_id = "{{ category.id }}";
    </script>
</head>

<body>
<div id="app" v-cloak>

<!-- ========== 顶部 ========== -->
<div class="header_con">
    <div class="header">
        <div class="welcome fl">欢迎来到美多商城!</div>
        <div class="fr">
            <div class="login_btn fl" v-if="username">
                欢迎您:<em>[[ username ]]</em>
                <span>|</span>
                <a href="/logout/">退出</a>
            </div>
            <div class="login_btn fl" v-else>
                <a href="/login/">登录</a>
                <span>|</span>
                <a href="/register/">注册</a>
            </div>

            <div class="user_link fl">
                <span>|</span>
                <a href="/info/">用户中心</a>
                <span>|</span>
                <a href="/carts/">我的购物车</a>
                <span>|</span>
                <a href="/orders/">我的订单</a>
            </div>
        </div>
    </div>
</div>

<!-- ========== 搜索 ========== -->
<div class="search_bar clearfix">
    <a href="/" class="logo fl">
        <img src="{{ static('images/logo.png') }}">
    </a>

    <div class="search_wrap fl">
        <form method="get" action="/search/" class="search_con">
            <input type="text" class="input_text fl" name="q" placeholder="搜索商品">
            <input type="submit" class="input_btn fr" value="搜索">
        </form>
    </div>
</div>

<!-- ========== 分类导航 ========== -->
<div class="navbar_con">
    <div class="navbar">
        <div class="sub_menu_con fl">
            <h1 class="fl">商品分类</h1>
            <ul class="sub_menu">
                {% for group in categories.values() %}
                <li>
                    <div class="level1">
                        {% for channel in group.channels %}
                        <a href="{{ channel.url }}">{{ channel.name }}</a>
                        {% endfor %}
                    </div>
                    <div class="level2">
                        {% for cat2 in group.sub_cats %}
                        <div class="list_group">
                            <div class="group_name fl">{{ cat2.name }} &gt;</div>
                            <div class="group_detail fl">
                                {% for cat3 in cat2.sub_cats %}
                                <a href="/list/{{ cat3.id }}/1/">{{ cat3.name }}</a>
                                {% endfor %}
                            </div>
                        </div>
                        {% endfor %}
                    </div>
                </li>
                {% endfor %}
            </ul>
        </div>

        <ul class="navlist fl">
            <li><a href="/">首页</a></li>
            <li class="interval">|</li>
            <li><a href="#">真划算</a></li>
            <li class="interval">|</li>
            <li><a href="#">抽奖</a></li>
        </ul>
    </div>
</div>

<!-- ========== 面包屑 ========== -->
<div class="breadcrumb">
    <a href="javascript:;">{{ breadcrumb.cat1.name }}</a>
    <span>></span>
    <a href="javascript:;">{{ breadcrumb.cat2.name }}</a>
    <span>></span>
    <a href="javascript:;">{{ breadcrumb.cat3.name }}</a>
</div>

<!-- ========== 商品列表 ========== -->
<div class="main_wrap clearfix">

    <!-- 左侧:热销排行 -->
    <div class="l_wrap fl clearfix">
        <div class="new_goods">
            <h3>热销排行</h3>
            <ul>
                <li v-for="sku in hots" :key="sku.id">
                    <a :href="'/detail/' + sku.id + '/'">
                        <img :src="sku.default_image_url">
                    </a>
                    <h4>[[ sku.name ]]</h4>
                    <div class="price">¥[[ sku.price ]]</div>
                </li>
            </ul>
        </div>
    </div>

    <!-- 右侧商品 -->
    <div class="r_wrap fr clearfix">

        <div class="sort_bar">
            <a href="/list/{{ category.id }}/{{ page_num }}/?sort=default"
               {% if sort == 'default' %}class="active"{% endif %}>默认</a>
            <a href="/list/{{ category.id }}/{{ page_num }}/?sort=price"
               {% if sort == 'price' %}class="active"{% endif %}>价格</a>
            <a href="/list/{{ category.id }}/{{ page_num }}/?sort=hot"
               {% if sort == 'hot' %}class="active"{% endif %}>人气</a>
        </div>

        <ul class="goods_type_list clearfix">
            {% for sku in page_skus %}
            <li>
                <a href="/detail/{{ sku.id }}/">
                    <img src="{{ sku.default_image.url }}">
                </a>
                <h4>{{ sku.name }}</h4>
                <div class="operate">
                    <span class="price">¥{{ sku.price }}</span>
                    <span class="unit">台</span>
                </div>
            </li>
            {% endfor %}
        </ul>

        <div class="pagenation">
            <div id="pagination"></div>
        </div>
    </div>
</div>

<script src="{{ static('js/jquery.pagination.min.js') }}"></script>

<script>
    $('#pagination').pagination({
        currentPage: {{ page_num }},
        totalPage: {{ total_page }},
        callback: function (current) {
            location.href = '/list/{{ category.id }}/' + current + '/?sort={{ sort }}';
        }
    });
</script>

<!-- ⭐ 最关键:Vue 逻辑最后加载 -->
<script src="{{ static('js/list.js') }}"></script>

</body>
</html>

static/js/list.js 里确认有热销逻辑

var vm = new Vue({
    el: '#app',
    delimiters: ['[[', ']]'],
    data: {
        hots: []
    },
    mounted() {
        this.get_hots();
    },
    methods: {
        // 获取热销商品数据
        get_hot_goods(){
            var url = this.host + '/hot/' + this.category_id + '/';
            axios.get(url, {
                responseType: 'json'
            })
                .then(response => {
                    this.hots = response.data.hot_skus;
                    for (var i = 0; i < this.hots.length; i++) {
                        this.hots[i].url = '/goods/' + this.hots[i].id + '.html';
                    }
                })
                .catch(error => {
                    console.log(error.response);
                })
        }
    }
});

测试

美多商城项目-06

商品搜索

简介

Elasticsearch(ES) 是一个 基于 Lucene 的分布式搜索与分析引擎

Elasticsearch = 专门用来“搜东西”的数据库,在海量数据中,极快地完成“全文搜索 + 多条件过滤 + 排序 + 统计聚合”

核心优势

1、搜索特别快(比 MySQL 快很多)

  • MySQL 用 LIKE %关键词%,慢
  • ES 用搜索引擎级别索引,秒出结果

2、支持“人说的话”搜索(中文分词)

比如用户输入:“索尼 微单 相机”

ES 能自动拆成:

  • 索尼
  • 微单
  • 相机

3、搜索结果更“聪明”

谁更相关,谁排前面

可以按下面字段排序:

  • 销量
  • 价格
  • 新品

4、搜索 + 筛选一起做

一次搜索就能完成:

  • 关键词搜索
  • 分类筛选
  • 品牌筛选
  • 价格区间
  • 规格筛选

5、数据多了也不怕

  • 商品 1 万、10 万、100 万
  • ES 都能扛住
  • 天生支持分布式

Django 商城中,ES 的典型用法

1️⃣ 用户输入关键词
2️⃣ Django 调 Elasticsearch 搜索
3️⃣ ES 返回商品 ID 列表
4️⃣ Django 再查 MySQL 拿完整商品数据

例如:用户搜索:“iphone 15”

ES 做的事:

  • 搜商品名、描述
  • 排序(销量高的在前)
  • 返回 SKU id:
    [101, 205, 330]

MySQL 做的事:

  • 根据这些 id
  • 查价格、库存、图片
  • 返回给前端
用户
 ↓
Django View
 ↓
Elasticsearch(搜索)
 ↓
返回 SKU ID 列表
 ↓
MySQL(补充价格、库存)

Django 项目如何引入 Elasticsearch?

  • 第 1 步:Docker 启动 Elasticsearch
  • 第 2 步:Django 接入 ES
  • 第 3 步:为 goods.SKU 建搜索索引
  • 第 4 步:写一个最简单搜索接口
  • 第 5 步:Celery 同步 ES/商品上下架自动更新索引/搜索排序 / 筛选

全文检索 Elasticsearch 接入

第 1 步:用 Docker 跑 Elasticsearch

docker-compose/ 下新增文件:docker-compose.es.yml

version: "3.8"

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3
    container_name: meiduo-es
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - xpack.security.http.ssl.enabled=false
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
    ports:
      - "9200:9200"
    volumes:
      - esdata:/usr/share/elasticsearch/data

volumes:
  esdata:

启动:

cd docker-compose
docker compose -f docker-compose.es.yml up -d

验证:

curl http://127.0.0.1:9200
美多商城项目-06

能返回集群信息就 OK。

第 2 步:安装 Django 侧依赖

虚拟环境里执行:

pip install "elasticsearch>=8,<9" "django-elasticsearch-dsl>=8,<9"

django-elasticsearch-dsl 会依赖 elasticsearch-dsl

第 3 步:改 settings.py(只加几行)

INSTALLED_APPS 加两项

INSTALLED_APPS = [
    # ...
    "django_elasticsearch_dsl",
    "django_elasticsearch_dsl.registries",
    # ...
]

增加 ES 连接配置(建议放 settings.py 底部)

ELASTICSEARCH_DSL = {
    "default": {"hosts": "http://127.0.0.1:9200"}
}

# 索引自动刷新频率(开发环境)
ELASTICSEARCH_DSL_AUTOSYNC = False  # 先关掉,后面我们用命令重建/手动触发更可控

先把 autosync 关掉:避免你调试阶段改一个 SKU 就触发信号导致“莫名其妙 ES 报错”。

第 4 步:在 goods 里新增 documents.py(索引定义)

新建:apps/goods/documents.py

from django_elasticsearch_dsl import Document, Index, fields
from django_elasticsearch_dsl.registries import registry

from .models import SKU

# 索引名(建议带环境前缀)
sku_index = Index("meiduo_skus")

# 可选:索引级设置(先极简)
sku_index.settings(number_of_shards=1, number_of_replicas=0)


@registry.register_document
class SKUDocument(Document):
    # ======================
    # 关联字段(用于搜索 / 过滤 / 展示)
    # ======================

    # SPU 名称(用于搜索)
    spu_name = fields.TextField(attr="spu.name")

    # 品牌
    brand_id = fields.IntegerField(attr="spu.brand_id")
    brand_name = fields.TextField(attr="spu.brand.name")

    # 分类
    category_id = fields.IntegerField(attr="category_id")

    # 图片(⚠️ 不能直接用 attr="default_image",要转成字符串)
    default_image = fields.KeywordField()

    class Index:
        name = "meiduo_skus"

    class Django:
        model = SKU
        # 直接从 SKU 模型中读取的字段(基础类型,ES 可序列化)
        fields = [
            "id",
            "name",
            "caption",
            "price",
            "sales",
            "is_launched",
            "create_time",
        ]

    # ======================
    # 只索引“已上架”的商品
    # ======================
    def get_queryset(self):
        return super().get_queryset().filter(is_launched=True)

    # ======================
    # 自定义字段序列化
    # ======================
    def prepare_default_image(self, instance):
        """
        Elasticsearch 里只能存 JSON 基础类型
        ImageFieldFile 必须转成字符串路径
        """
        if instance.default_image:
            return str(instance.default_image)
        return ""

第 5 步:生成索引 + 灌数据(最关键的跑通点)

django-elasticsearch-dsl库已安装,调用库执行:

python manage.py search_index --rebuild

如果你 MySQL 里有 SKU 数据,这一步会:

  • 找到你项目中注册的 apps/goods/documents.py
  • 删除旧索引
  • 按 Document 定义重新创建索引
  • 从 Django ORM 批量导入数据,从 MySQL 读 SKU,序列化成 dict,把上架 SKU 全部写入 ES
美多商城项目-06

验证一下 ES 有数据:

curl "http://127.0.0.1:9200/meiduo_skus/_count?pretty"
美多商城项目-06

简单搜索测试

http://127.0.0.1:9200/meiduo_skus/_search?q=Apple&pretty
美多商城项目-06

第 6 步:写一个最简单的搜索接口

apps/goods/views.py 末尾加

from django.views import View
from django.shortcuts import render
from elasticsearch_dsl import Search

from .documents import SKUDocument
class SKUSimpleVO:
    """
    SKU 展示用 View Object(不依赖 ORM,不依赖 ES)
    """
    def __init__(self, *, id, name, price, default_image):
        self.id = id
        self.name = name
        self.price = price
        self.default_image = default_image


from django.conf import settings

class SearchView(View):
    """
    商品搜索页
    GET /search/?q=xxx&page=1
    """

    def get(self, request):
        keyword = request.GET.get("q", "").strip()
        page = int(request.GET.get("page", 1))
        page_size = 6

        skus = []
        total = 0

        if keyword:
            s = Search(index=SKUDocument.Index.name)
            s = s.query(
                "multi_match",
                query=keyword,
                fields=["name^3", "caption^2", "spu_name", "brand_name"],
            )

            start = (page - 1) * page_size
            s = s[start:start + page_size]

            response = s.execute()
            total = response.hits.total.value

            for hit in response:
                skus.append(
                    SKUSimpleVO(
                        id=hit.id,
                        name=hit.name,
                        price=hit.price,
                        default_image=settings.MEDIA_URL + str(hit.default_image),
                    )
                )

        context = {
            "keyword": keyword,
            "skus": skus,
            "total": total,
            "page": page,
        }

        return render(request, "search.html", context)

apps/goods/urls.py 加路由

from django.urls import path
from .views import ListView, SearchView

urlpatterns = [
    path('list/<int:category_id>/<int:page_num>/', views.ListView.as_view(), name='list'),
    ...
    path("search/", SearchView.as_view(), name='search'),  # 新增
]

第 7 步:让 templates/search.html 页面真正用上搜索

templates/search.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>美多商城-商品搜索</title>
    <link rel="stylesheet" type="text/css" href="../static/css/jquery.pagination.css">
	<link rel="stylesheet" type="text/css" href="../static/css/reset.css">
    <link rel="stylesheet" type="text/css" href="../static/css/main.css">
    <script type="text/javascript" src="../static/js/host.js"></script>
    <script type="text/javascript" src="../static/js/jquery-1.12.4.min.js"></script>
</head>
<body>
    <div id="app" v-cloak>
	<div class="header_con">
		<div class="header">
			<div class="welcome fl">欢迎来到美多商城!</div>
			<div class="fr">

                {% if request.user.is_authenticated %}
                    <div class="login_btn fl">
                        欢迎您:<em>{{ request.user.username }}</em>
                        <span>|</span>
                        <a href="/logout/" class="quit">退出</a>
                    </div>
                {% else %}
                    <div class="login_btn fl">
                        <a href="../login.html">登录</a>
                        <span>|</span>
                        <a href="../register.html">注册</a>
                    </div>
                {% endif %}

                <div class="user_link fl">
                    <span>|</span>
                    <a href="/info/">用户中心</a>
                    <span>|</span>
                    <a href="/carts/">我的购物车</a>
                    <span>|</span>
                    <a href="/orders/">我的订单</a>
                </div>

            </div>
		</div>
	</div>

	<div class="search_bar clearfix">
		<a href="/static" class="logo fl"><img src="../static/images/logo.png"></a>
		<div class="search_wrap fl">
			<form method="get" action="/search/" class="search_con">
                <input type="text" class="input_text fl" name="q" value="{{ keyword }}" placeholder="搜索商品">
                <input type="submit" class="input_btn fr" value="搜索">
            </form>
			<ul class="search_suggest fl">
				<li><a href="#">索尼微单</a></li>
				<li><a href="#">优惠15元</a></li>
				<li><a href="#">美妆个护</a></li>
				<li><a href="#">买2免1</a></li>
			</ul>
		</div>
	</div>

    <div class="main_wrap clearfix">
        <div class="clearfix">
            <ul class="goods_type_list clearfix">
                {% for sku in skus %}
                <li>
                    <a href="/detail/{{ sku.id }}/">
                        <!-- 关键:SearchView 已经把 sku.default_image 拼成完整 URL,这里直接用 -->
                        <img src="{{ sku.default_image }}">
                    </a>
                    <h4>
                        <a href="/detail/{{ sku.id }}/">{{ sku.name }}</a>
                    </h4>
                    <div class="operate">
                        <span class="price">¥{{ sku.price }}</span>
                        <span class="unit">台</span>
                        <a href="#" class="add_goods" title="加入购物车"></a>
                    </div>
                </li>
                {% endfor %}

                {% if not skus %}
                <li style="padding:20px;">没有搜索到相关商品</li>
                {% endif %}
            </ul>

            <div class="pagenation">
                <div id="pagination" class="page"></div>
            </div>
        </div>
    </div>

	<div class="footer">
		<div class="foot_link">
			<a href="#">关于我们</a>
			<span>|</span>
			<a href="#">联系我们</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情链接</a>
		</div>
		<p>CopyRight © 2016 北京美多商业股份有限公司 All Rights Reserved</p>
		<p>电话:010-****888    京ICP备*******8号</p>
	</div>
    </div>

    <script type="text/javascript" src="../static/js/common.js"></script>
    <script type="text/javascript" src="../static/js/jquery.pagination.min.js"></script>
    <script>
        $(function () {
            // ✅ 用 Jinja2 更安全的兜底写法(即使 total/page 没传也不会炸)
            var total = {{ total if total is not none else 0 }};
            var pageSize = 6;
            var totalPage = Math.ceil(total / pageSize);
            var currentPage = {{ page if page is not none else 1 }};
            var keyword = "{{ keyword }}";

            if (totalPage > 1) {
                $('#pagination').pagination({
                    currentPage: currentPage,
                    totalPage: totalPage,
                    callback: function (current) {
                        location.href = '/search/?q=' + encodeURIComponent(keyword) + '&page=' + current;
                    }
                });
            }
        });
    </script>
</body>
</html>

测试:

浏览器直接访问:

http://127.0.0.1:8000/search/?q=Apple
美多商城项目-06

搜索框测试,打开 http://127.0.0.1:8000/search/ ,输入关键字比如:MacBook

美多商城项目-06

无结果测试:

美多商城项目-06

分页测试

美多商城项目-06

高亮优化(可选)

现在的 SearchView 是这样的结构

s = Search(index=SKUDocument.Index.name)
s = s.query("multi_match", ...)
response = s.execute()

for hit in response:
    skus.append(
        SKUSimpleVO(
            id=hit.id,
            name=hit.name,
            ...
        )
    )

目前:

  • hit.name 是 普通字符串
  • ES 并没有返回高亮字段

第一步:在 ES 查询中开启

apps/goods/views.pySearchView 中,在 s = s.query(...) 后面,加上高亮配置

s = Search(index=SKUDocument.Index.name)
s = s.query(
    "multi_match",
    query=keyword,
    fields=["name^3", "caption^2", "spu_name", "brand_name"],
)

# 高亮配置(新增)
s = s.highlight(
    'name',
    'caption',
    pre_tags=['<span style="color:red">'],
    post_tags=['</span>']
).highlight_options(require_field_match=False)

第二步:优先使用 ES 返回的高亮字段

修改 SearchView 中构造 SKUSimpleVO 的地方

skus.append(
    SKUSimpleVO(
        id=hit.id,
        name=hit.name,
        price=hit.price,
        default_image=settings.MEDIA_URL + str(hit.default_image),
    )
)

改成:

# 优先用高亮结果
if hasattr(hit.meta, "highlight") and "name" in hit.meta.highlight:
    name = hit.meta.highlight.name[0]
else:
    name = hit.name

skus.append(
    SKUSimpleVO(
        id=hit.id,
        name=name,
        price=hit.price,
        default_image=settings.MEDIA_URL + str(hit.default_image),
    )
)

第三步:模板中允许 HTML 生效

现在模板里是:

<a href="/detail/{{ sku.id }}/">{{ sku.name }}</a>

<span> 会被当成普通文本,改成

<a href="/detail/{{ sku.id }}/">{{ sku.name | safe }}</a>

加到 static/css/main.css 最底部

/* ===============================
   搜索关键词高亮(Elasticsearch)
   =============================== */
.hl {
    color: red;
    font-weight: bold;
}

测试:

美多商城项目-06

统计分类商品访问量

触发“访问统计”

访问“分类列表页”就算一次访问

apps/goods/views.py

ListView.get() 里,开头将分类、面包屑与新增的访问统计结合,改为这一段

from django.utils import timezone
from django.db.models import F

# 一、分类 & 访问统计 & 面包屑
try:
    category = GoodsCategory.objects.get(id=category_id)
except GoodsCategory.DoesNotExist:
    return render(request, "list.html", {"errmsg": "分类不存在"})

today = timezone.localdate()

obj, created = GoodsVisitCount.objects.get_or_create(
    category=category,
    date=today,
    defaults={'count': 1}  # 首次插入只能是确定值
)

if not created:
    GoodsVisitCount.objects.filter(
        category=category,
        date=today
    ).update(count=F('count') + 1)

breadcrumb = get_breadcrumb(category)

打开一个分类列表页:http://127.0.0.1:8000/list/115/1/

美多商城项目-06

数据库验证

SELECT * FROM tb_goods_visit ORDER BY date DESC;

刷新页面 → count +1

美多商城项目-06

发布者:LJH,转发请注明出处:https://www.ljh.cool/44616.html

(0)
LJH的头像LJH
上一篇 2026年1月11日 下午2:32
下一篇 2020年10月30日 下午6:06

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注