1. 导航功能实现

导航数据的存储,必须要找出导航数据的结构,也就是有哪些属性?

导航位置、导航名称、导航链接、导航序号、是否显示、是否外链、添加时间、更新时间、是否删除

E-R图,如下:

项目搭建2 - 图1

1.1 创建模型

项目搭建2 - 图2

home/models.py,代码:

  1. from luffycityapi.utils.models import models, BaseModel
  2. # Create your models here.
  3. class Nav(BaseModel):
  4. """导航菜单"""
  5. # 字段选项
  6. # 模型对象.<字段名> ---> 实际数据
  7. # 模型对象.get_<字段名>_display() --> 文本提示
  8. POSITION_OPTION = (
  9. # (实际数据, "文本提示"),
  10. (0, "顶部导航"),
  11. (1, "脚部导航"),
  12. )
  13. link = models.CharField(max_length=255, verbose_name="导航连接")
  14. is_http = models.BooleanField(default=False, verbose_name="是否是外部链接")
  15. position = models.IntegerField(choices=POSITION_OPTION, default=0)
  16. class Meta:
  17. db_table = "lf_nav"
  18. verbose_name = "导航菜单"
  19. verbose_name_plural = verbose_name

公共模型【抽象模型,不会在数据迁移的时候为它创建表】,保存项目的公共代码库目录下luffycityapi/utils.py文件中。

luffycityapi/utils/models.py,代码:

  1. from django.db import models
  2. class BaseModel(models.Model):
  3. """
  4. 公共模型
  5. 保存项目中的所有模型的公共属性和公共方法的声明
  6. """
  7. name = models.CharField(max_length=255, default="", verbose_name="名称/标题")
  8. is_deleted = models.BooleanField(default=False, verbose_name="是否删除")
  9. orders = models.IntegerField(default=0, verbose_name="序号")
  10. is_show = models.BooleanField(default=True, verbose_name="是否显示")
  11. # auto_now_add=True 当数据被创建时,以当前时间作为默认值写入当前字段
  12. created_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
  13. # auto_now=True 当数据被更新时,以当前时间作为值写入当前字段
  14. updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
  15. class Meta:
  16. # 设置当前模型类并非真正的模型,而是一种保存公共代码的抽象模型类
  17. # 这种模型在数据迁移中不会被当做数据模型来创建数据表
  18. abstract = True

settings/dev.py,中配置导包路径,代码:

  1. # 因为项目中子应用已经换了存储目录,所以需要把apps设置为系统导包路径,方便我们后面开发时可以简写子应用相关的导包路径。
  2. import sys
  3. sys.path.insert(0, str( BASE_DIR / "apps") )
  4. sys.path.insert(0, str( BASE_DIR / "utils") )

home/models.py,代码调整如下:

  1. from models import BaseModel, models
  2. # Create your models here.
  3. class Nav(BaseModel):
  4. """导航菜单"""
  5. # 字段选项
  6. # 模型对象.<字段名> ---> 实际数据
  7. # 模型对象.get_<字段名>_display() --> 文本提示
  8. POSITION_CHOICES = (
  9. # (实际数据, "文本提示"),
  10. (0, "顶部导航"),
  11. (1, "脚部导航"),
  12. )
  13. link = models.CharField(max_length=255, verbose_name="导航连接")
  14. is_http = models.BooleanField(default=False, verbose_name="是否站外连接地址")
  15. position = models.SmallIntegerField(default=0, choices=POSITION_CHOICES, verbose_name="导航位置")
  16. class Meta:
  17. db_table = "fg_nav"
  18. verbose_name = "导航菜单"
  19. verbose_name_plural = verbose_name

实际工作中,有些大厂企业不需要我们使用django的数据迁移操作的,如果公司有DBA的话,那就直接DBA已经提前设计数据表结构了,我们只需要根据表结构声明模型代码,保证模型代码的表名和字段与数据库中的表结构对应上,就可以在django中直接调用ORM模型对象操作数据库中的数据了。当然,我们现在在学习阶段,所以并没有DBA,所以老老实实执行数据迁移命令吧。

  1. cd ~/Desktop/luffycity/luffycityapi
  2. python manage.py makemigrations
  3. python manage.py migrate

刚上面仅仅创建的是数据表结构而已,所以接下来我们如果要实现客户端展示导航功能,则还需要在admin后台手动添加测试数据,或者MySQL交互终端下添加测试数据才可以。

  1. INSERT INTO luffycity.fg_nav (id, name, orders, is_show, is_delete, created_time, updated_time, link, is_http, position) VALUES (1, '免费课', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 0);
  2. INSERT INTO luffycity.fg_nav (id, name, orders, is_show, is_delete, created_time, updated_time, link, is_http, position) VALUES (2, '项目课', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/project', 0, 0);
  3. INSERT INTO luffycity.fg_nav (id, name, orders, is_show, is_delete, created_time, updated_time, link, is_http, position) VALUES (3, '学位课', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/position', 0, 0);
  4. INSERT INTO luffycity.fg_nav (id, name, orders, is_show, is_delete, created_time, updated_time, link, is_http, position) VALUES (4, '习题库', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/exam', 0, 0);
  5. INSERT INTO luffycity.fg_nav (id, name, orders, is_show, is_delete, created_time, updated_time, link, is_http, position) VALUES (5, '路飞学城', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', 'https://www.luffycity.com', 1, 0);
  6. INSERT INTO luffycity.fg_nav (id, name, orders, is_show, is_delete, created_time, updated_time, link, is_http, position) VALUES (6, '企业服务', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 1);
  7. INSERT INTO luffycity.fg_nav (id, name, orders, is_show, is_delete, created_time, updated_time, link, is_http, position) VALUES (7, '关于我们', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 1);
  8. INSERT INTO luffycity.fg_nav (id, name, orders, is_show, is_delete, created_time, updated_time, link, is_http, position) VALUES (8, '联系我们', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 1);
  9. INSERT INTO luffycity.fg_nav (id, name, orders, is_show, is_delete, created_time, updated_time, link, is_http, position) VALUES (9, '商务合作', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 1);
  10. INSERT INTO luffycity.fg_nav (id, name, orders, is_show, is_delete, created_time, updated_time, link, is_http, position) VALUES (10, '帮助中心', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 1);
  11. INSERT INTO luffycity.fg_nav (id, name, orders, is_show, is_delete, created_time, updated_time, link, is_http, position) VALUES (11, '意见反馈', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 1);
  12. INSERT INTO luffycity.fg_nav (id, name, orders, is_show, is_delete, created_time, updated_time, link, is_http, position) VALUES (12, '新手指南', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 1);

1.2 序列化器

home.serializers,代码:

  1. from rest_framework import serializers
  2. from .models import Nav
  3. class NavModelSerializer(serializers.ModelSerializer):
  4. """
  5. 导航菜单的序列化器
  6. """
  7. class Meta:
  8. models = Nav
  9. fields = ["name", "link", "is_http"]

1.3 视图代码

home/views.py

  1. import constants
  2. from rest_framework.generics import ListAPIView
  3. from .models import Nav
  4. from .serializers import NavModelSerializer
  5. class NavHeaderListAPIView(ListAPIView):
  6. """顶部导航视图"""
  7. queryset = Nav.objects.filter(position=constants.NAV_HEADER_POSITION, is_show=True, is_deleted=False).order_by("orders", "-id")[:constants.NAV_HEADER_SIZE]
  8. serializer_class = NavModelSerializer
  9. class NavFooterListAPIView(ListAPIView):
  10. """脚部导航视图"""
  11. queryset = Nav.objects.filter(position=constants.NAV_FOOTER_POSITION, is_show=True, is_deleted=False).order_by("orders", "-id")[:constants.NAV_FOOTER_SIZE]
  12. serializer_class = NavModelSerializer

1.3.1 常量配置

utils/constants.py,代码:

  1. """常量配置文件"""
  2. # 导航的位置 --> 顶部
  3. NAV_HEADER_POSITION = 0
  4. # 导航的位置 --> 脚部
  5. NAV_FOOTER_POSITION = 1
  6. # 顶部导航显示的最大数量
  7. NAV_HEADER_SIZE = 5
  8. # 脚部导航显示的最大数量
  9. NAV_FOOTER_SIZE = 10

1.4 路由代码

home/urls.py

  1. from django.urls import path
  2. from . import views
  3. urlpatterns = [
  4. path("nav/header/", views.NavHeaderListAPIView.as_view()),
  5. path("nav/footer/", views.NavFooterListAPIView.as_view()),
  6. ]

完成了上面操作以后,可以直接访问url地址或者postman来请求测试接口数据是否正确。

项目搭建2 - 图3

提交代码版本

  1. cd ~/Desktop/luffycity
  2. git add .
  3. git commit -m "feature:服务端提供导航api接口"
  4. git push origin develop

1.5 客户端获取导航数据

所有与api服务端进行交互的操作代码,可以单独保存api目录下,根据数据表单独创建js文件,方便将来代码复用。

src/api/nav.js,代码:

  1. import http from "../utils/http"
  2. import {reactive, ref} from "vue"
  3. const nav = reactive({
  4. header_nav_list: [], // 头部导航列表
  5. footer_nav_list: [], // 脚部导航列表
  6. get_header_nav(){
  7. // 获取头部导航
  8. return http.get("/home/nav/header/")
  9. },
  10. get_footer_nav(){
  11. // 获取脚部导航
  12. return http.get("/home/nav/footer/")
  13. },
  14. })
  15. export default nav;

components/Header.vue代码:

  1. <template>
  2. <div class="header-box">
  3. <div class="header">
  4. <div class="content">
  5. <div class="logo">
  6. <router-link to="/"><img src="../assets/logo.svg" alt=""></router-link>
  7. </div>
  8. <ul class="nav">
  9. <li v-for="nav in nav.header_nav_list">
  10. <a :href="nav.link" v-if="nav.is_http">{{nav.name}}</a>
  11. <router-link :to="nav.link" v-else>{{nav.name}}</router-link>
  12. </li>
  13. </ul>
  14. <div class="search-warp">
  15. <div class="search-area">
  16. <input class="search-input" placeholder="请输入关键字..." type="text" autocomplete="off">
  17. <div class="hotTags">
  18. <router-link to="/search/?words=Vue" target="_blank" class="">Vue</router-link>
  19. <router-link to="/search/?words=Python" target="_blank" class="last">Python</router-link>
  20. </div>
  21. </div>
  22. <div class="showhide-search" data-show="no"><img class="imv2-search2" src="../assets/search.svg" /></div>
  23. </div>
  24. <div class="login-bar">
  25. <div class="shop-cart full-left">
  26. <img src="../assets/cart.svg" alt="" />
  27. <span><router-link to="/cart">购物车</router-link></span>
  28. </div>
  29. <div class="login-box full-left">
  30. <span>登录</span>
  31. &nbsp;/&nbsp;
  32. <span>注册</span>
  33. </div>
  34. </div>
  35. </div>
  36. </div>
  37. </div>
  38. </template>
  1. <script setup>
  2. import nav from "../api/nav";
  3. // 请求头部导航列表
  4. nav.get_header_nav().then(response=>{
  5. nav.header_nav_list = response.data
  6. })
  7. </script>

components/Footer.vue代码:

  1. <template>
  2. <div class="footer">
  3. <ul>
  4. <li v-for="nav in nav.footer_nav_list">
  5. <a :href="nav.link" v-if="nav.is_http">{{nav.name}}</a>
  6. <router-link :to="nav.link" v-else>{{nav.name}}</router-link>
  7. </li>
  8. </ul>
  9. <p>Copyright © luffycity.com版权所有 | 京ICP备17072161号-1</p>
  10. </div>
  11. </template>
  1. <script setup>
  2. import nav from "../api/nav";
  3. // 获取脚部导航列表
  4. nav.get_footer_nav().then(response=>{
  5. nav.footer_nav_list = response.data
  6. })
  7. </script>

提交代码版本

  1. cd ~/Desktop/luffycity
  2. git add .
  3. git commit -m "feature:客户端实现导航信息展示"
  4. git push origin develop

2. 轮播图功能实现

Banner.vue,代码:

  1. <template>
  2. <div class="bk"></div>
  3. <div class="bgfff banner-box">
  4. <div class="g-banner pr" @mouseleave="state.current_menu=-1">
  5. <!-- 商品课程分类信息 -->
  6. <div class="submenu" v-if="state.current_menu==0">
  7. <div class="inner-box">
  8. <h2 class="type">前端开发</h2>
  9. <div class="tag clearfix">
  10. </div>
  11. <div class="lore">
  12. <span class="title">知识点:</span>
  13. <p class="lores clearfix"><a target="_blank" href="">Vue.js</a>
  14. <a target="_blank" href="">Typescript</a>
  15. <a target="_blank" href="">React.JS</a>
  16. <a target="_blank" href="">HTML/CSS</a>
  17. <a target="_blank" href="">JavaScript</a>
  18. <a target="_blank" href="">Angular</a>
  19. <a target="_blank" href="">Node.js</a>
  20. <a target="_blank" href="">jQuery</a>
  21. <a target="_blank" href="">Bootstrap</a>
  22. <a target="_blank" href="">Sass/Less</a>
  23. <a target="_blank" href="">WebApp</a>
  24. <a target="_blank" href="">小程序</a>
  25. <a target="_blank" href="">前端工具</a>
  26. <a target="_blank" href="">CSS</a>
  27. <a target="_blank" href="">Html5</a>
  28. <a target="_blank" href="">CSS3</a>
  29. </p>
  30. </div>
  31. </div>
  32. <div class="recomment clearfix">
  33. <a href="" target="_blank" title="" class="recomment-item">
  34. <div class="img" style="background-image: url('/src/assets/60a7779909e3fc1206960344.png'); background-size: 100%; "></div>
  35. <div class="details">
  36. <!--路径单独写-->
  37. <div class="title-box">
  38. <p class="title"> <span class="text">前端工程师2021</span> <span class="tag tixi">体系</span> </p>
  39. </div>
  40. <div class="bottom">
  41. <span class="discount-name">优惠价</span>
  42. <span class="price">¥4599.00</span> &middot;
  43. <span class="difficulty"> 零基础 </span> &middot;
  44. <span class="num"><i class="imv2-set-sns"></i> 19322</span>
  45. </div>
  46. </div> </a>
  47. <a href="" target="_blank" title="前端框架及项目面试 聚焦Vue3/React/Webpack" class="recomment-item">
  48. <div class="img" style="background-image: url('/src/assets/5e3cfea008e9a61b06000338-360-202.jpg')"></div>
  49. <div class="details">
  50. <!--路径单独写-->
  51. <div class="title-box">
  52. <p class="title"> <span class="text">前端框架及项目面试 聚焦Vue3/React/Webpack</span> <span class="tag shizhan">实战</span> </p>
  53. </div>
  54. <div class="bottom">
  55. <span class="price">399.00</span> &middot;
  56. <span class="difficulty"> 中级 </span> &middot;
  57. <span class="num"><i class="imv2-set-sns"></i> 2946</span>
  58. </div>
  59. </div> </a>
  60. <a href="" target="_blank" title="从0打造微前端框架,实战汽车资讯平台,系统掌握微前端架构设计与落地能力" class="recomment-item">
  61. <div class="img" style="background-image: url('/src/assets/60d44ec8084b799712000676-360-202.jpg')"></div>
  62. <div class="details">
  63. <!--路径单独写-->
  64. <div class="title-box">
  65. <p class="title"><span class="text">从0打造微前端框架,实战汽车资讯平台,系统掌握微前端架构设计与落地能力</span><span class="tag shizhan">实战</span> </p>
  66. </div>
  67. <div class="bottom">
  68. <span class="discount-name">限时优惠</span>
  69. <span class="price">¥328.00</span> &middot;
  70. <span class="difficulty"> 高级 </span> &middot;
  71. <span class="num"><i class="imv2-set-sns"></i> 109</span>
  72. </div>
  73. </div> </a>
  74. <a href="" target="_blank" title="" class="recomment-item">
  75. <div class="img" style="background-image: url('/src/assets/604f2bab0952610803240324-140-140.png'); background-size: 100%; "></div>
  76. <div class="details">
  77. <!--路径单独写-->
  78. <div class="title-box">
  79. <p class="title"> <span class="text">Vue.js 从入门到精通</span> <span class="tag lujing">路线</span> </p>
  80. </div>
  81. <div class="bottom">
  82. <span class="difficulty">4步骤</span> &middot;
  83. <span class="difficulty">4门课</span> &middot;
  84. <span class="num">19697人收藏</span>
  85. </div>
  86. </div> </a>
  87. </div>
  88. </div>
  89. <div class="submenu" v-if="state.current_menu==1">
  90. <div class="inner-box">
  91. <h2 class="type">后端开发</h2>
  92. <div class="tag clearfix">
  93. </div>
  94. <div class="lore">
  95. <span class="title">知识点:</span>
  96. <p class="lores clearfix">
  97. <a target="_blank" href="">Java</a>
  98. <a target="_blank" href="">SpringBoot</a>
  99. <a target="_blank" href="">Spring Cloud</a>
  100. <a target="_blank" href="">SSM</a>
  101. <a target="_blank" href="">PHP</a>
  102. <a target="_blank" href="">.net</a>
  103. <a target="_blank" href="">Python</a>
  104. <a target="_blank" href="">爬虫</a>
  105. <a target="_blank" href="">Django</a>
  106. <a target="_blank" href="">Flask</a>
  107. <a target="_blank" href="">Tornado</a>
  108. <a target="_blank" href="">Go</a>
  109. <a target="_blank" href="">C</a>
  110. <a target="_blank" href="">C++</a>
  111. <a target="_blank" href="">C#</a>
  112. <a target="_blank" href="">Ruby</a></p>
  113. </div>
  114. </div>
  115. <div class="recomment clearfix">
  116. <a href="" target="_blank" title="Java工程师2021" class="recomment-item">
  117. <div class="img" style="background-image: url('/src/assets/60a777ef0942d7bf06960344.png'); background-size: 100%; "></div>
  118. <div class="details">
  119. <div class="title-box">
  120. <p class="title"> <span class="text">Java工程师2021</span> <span class="tag tixi">体系</span> </p>
  121. </div>
  122. <div class="bottom">
  123. <span class="discount-name">优惠价</span>
  124. <span class="price">¥4399.00</span> &middot;
  125. <span class="difficulty"> 零基础 </span> &middot;
  126. <span class="num"><i class="imv2-set-sns"></i> 15052</span>
  127. </div>
  128. </div> </a>
  129. <a href="" target="_blank" title="Python工程师(全能型)" class="recomment-item">
  130. <div class="img" style="background-image: url('/src/assets/60a77721093df37606960344.png'); background-size: 100%; "></div>
  131. <div class="details">
  132. <!--路径单独写-->
  133. <div class="title-box">
  134. <p class="title"> <span class="text">Python工程师(全能型)</span> <span class="tag tixi">体系</span> </p>
  135. </div>
  136. <div class="bottom">
  137. <span class="discount-name">优惠价</span>
  138. <span class="price">¥4366.00</span> &middot;
  139. <span class="difficulty"> 零基础 </span> &middot;
  140. <span class="num"><i class="imv2-set-sns"></i> 10786</span>
  141. </div>
  142. </div> </a>
  143. <a href="" target="_blank" title="Java全栈工程师" class="recomment-item">
  144. <div class="img" style="background-image: url('/src/assets/5dd6567b09d9d01c06000338.png'); background-size: 100%; "></div>
  145. <div class="details">
  146. <!--路径单独写-->
  147. <div class="title-box">
  148. <p class="title"> <span class="text">Java全栈工程师</span> <span class="tag tixi">体系</span> </p>
  149. </div>
  150. <div class="bottom">
  151. <span class="discount-name">优惠价</span>
  152. <span class="price">¥3380.00</span> &middot;
  153. <span class="difficulty"> 进阶 </span> &middot;
  154. <span class="num"><i class="imv2-set-sns"></i> 1853</span>
  155. </div>
  156. </div> </a>
  157. <a href="" target="_blank" title="" class="recomment-item">
  158. <div class="img" style="background-image: url('/src/assets/604f2bb6099d6a8803240324-140-140.png'); background-size: 100%; "></div>
  159. <div class="details">
  160. <!--路径单独写-->
  161. <div class="title-box">
  162. <p class="title"> <span class="text">SpringBoot从入门到精通</span> <span class="tag lujing">路线</span> </p>
  163. </div>
  164. <div class="bottom">
  165. <span class="difficulty">3步骤</span> &middot;
  166. <span class="difficulty">5门课</span> &middot;
  167. <span class="num">11092人收藏</span>
  168. </div>
  169. </div> </a>
  170. </div>
  171. </div>
  172. <div class="submenu" v-if="state.current_menu==2">
  173. <div class="inner-box">
  174. <h2 class="type">移动开发</h2>
  175. <div class="tag clearfix">
  176. </div>
  177. <div class="lore">
  178. <span class="title">知识点:</span>
  179. <p class="lores clearfix"></p>
  180. </div>
  181. </div>
  182. <div class="recomment clearfix">
  183. <a href="" target="_blank" title="移动端架构师成长体系课" class="recomment-item">
  184. <div class="img" style="background-image: url('/src/assets/5ec5ddf209cd2c8606000338.png'); background-size: 100%; "></div>
  185. <div class="details">
  186. <!--路径单独写-->
  187. <div class="title-box">
  188. <p class="title"> <span class="text">移动端架构师成长体系课</span> <span class="tag tixi">体系</span> </p>
  189. </div>
  190. <div class="bottom">
  191. <span class="discount-name">优惠价</span>
  192. <span class="price">¥4888.00</span> &middot;
  193. <span class="difficulty"> 进阶 </span> &middot;
  194. <span class="num"><i class="imv2-set-sns"></i> 402</span>
  195. </div>
  196. </div> </a>
  197. <a href="" target="_blank" title="Flutter高级进阶实战 仿哔哩哔哩APP 一次性深度掌握Flutter高阶技能" class="recomment-item">
  198. <div class="img" style="background-image: url('/src/assets/60497caf0971842912000676-360-202.png'); background-size: 100%; "></div>
  199. <div class="details">
  200. <!--路径单独写-->
  201. <div class="title-box">
  202. <p class="title"> <span class="text">Flutter高级进阶实战 仿哔哩哔哩APP 一次性深度掌握Flutter高阶技能</span> <span class="tag shizhan">实战</span> </p>
  203. </div>
  204. <div class="bottom">
  205. <span class="price">368.00</span> &middot;
  206. <span class="difficulty"> 高级 </span> &middot;
  207. <span class="num"><i class="imv2-set-sns"></i> 646</span>
  208. </div>
  209. </div> </a>
  210. <a href="" target="_blank" title="音视频基础+ffmpeg原理+项目实战 一课完成音视频技术开发入门" class="recomment-item">
  211. <div class="img" style="background-image: url('/src/assets/5e5621d0092c054612000676-360-202.png'); background-size: 100%; "></div>
  212. <div class="details">
  213. <!--路径单独写-->
  214. <div class="title-box">
  215. <p class="title"> <span class="text">音视频基础+ffmpeg原理+项目实战 一课完成音视频技术开发入门</span> <span class="tag shizhan">实战</span> </p>
  216. </div>
  217. <div class="bottom">
  218. <span class="price">288.00</span> &middot;
  219. <span class="difficulty"> 入门 </span> &middot;
  220. <span class="num"><i class="imv2-set-sns"></i> 1303</span>
  221. </div>
  222. </div> </a>
  223. <a href="" target="_blank" title="" class="recomment-item">
  224. <div class="img" style="background-image: url('/src/assets/604f2b52090de67603240324-140-140.png'); background-size: 100%; "></div>
  225. <div class="details">
  226. <!--路径单独写-->
  227. <div class="title-box">
  228. <p class="title"> <span class="text">Android工程师高薪面试突破路线</span> <span class="tag lujing">路线</span> </p>
  229. </div>
  230. <div class="bottom">
  231. <span class="difficulty">3步骤</span> &middot;
  232. <span class="difficulty">3门课</span> &middot;
  233. <span class="num">1471人收藏</span>
  234. </div>
  235. </div> </a>
  236. </div>
  237. </div>
  238. <div class="menuContent">
  239. <div class="item" :class="{'js-menu-item-on': state.current_menu==0}" @mouseover="state.current_menu=0">
  240. <span class="title">前端开发:</span>
  241. <span class="sub-title">HTML5 / Vue.js / Node.js</span>
  242. <i class="imv2-arrow1_r"></i>
  243. </div>
  244. <div class="item" :class="{'js-menu-item-on': state.current_menu==1}" @mouseover="state.current_menu=1">
  245. <span class="title">后端开发:</span>
  246. <span class="sub-title">Java / Python / Go</span>
  247. <i class="imv2-arrow1_r"></i>
  248. </div>
  249. <div class="item" :class="{'js-menu-item-on': state.current_menu==2}" @mouseover="state.current_menu=2">
  250. <span class="title">移动开发:</span>
  251. <span class="sub-title">Flutter / Android / iOS </span>
  252. <i class="imv2-arrow1_r"></i>
  253. </div>
  254. </div>
  255. <!-- 轮播图-->
  256. <div class="g-banner-content" @mouseover="state.current_menu=-1">
  257. <el-carousel :interval="5000" arrow="always" height="482px">
  258. <el-carousel-item>
  259. <img src="http://fuguangapi.oss-cn-beijing.aliyuncs.com/1.jpg" alt="" style="width: 100%;height: 100%;">
  260. </el-carousel-item>
  261. <el-carousel-item>
  262. <img src="http://fuguangapi.oss-cn-beijing.aliyuncs.com/2.jpg" alt="" style="width: 100%;height: 100%;">
  263. </el-carousel-item>
  264. <el-carousel-item>
  265. <img src="http://fuguangapi.oss-cn-beijing.aliyuncs.com/3.jpg" alt="" style="width: 100%;height: 100%;">
  266. </el-carousel-item>
  267. <el-carousel-item>
  268. <img src="http://fuguangapi.oss-cn-beijing.aliyuncs.com/4.jpg" alt="" style="width: 100%;height: 100%;">
  269. </el-carousel-item>
  270. <el-carousel-item>
  271. <img src="http://fuguangapi.oss-cn-beijing.aliyuncs.com/5.jpg" alt="" style="width: 100%;height: 100%;">
  272. </el-carousel-item>
  273. </el-carousel>
  274. </div>
  275. </div>
  276. </div>
  277. </template>
  278. <script setup>
  279. import {reactive} from "vue"
  280. const state = reactive({
  281. current_menu: -1,
  282. })
  283. </script>
  284. <style scoped>
  285. .banner-box {
  286. padding: 32px 0;
  287. }
  288. .system-class-show {
  289. width: 1152px;
  290. height: 100px;
  291. margin: 0 auto;
  292. background: #FFFFFF;
  293. box-shadow: 0 5px 20px 0 rgba(0, 0, 0, 0.3);
  294. border-radius: 0 0 8px 8px;
  295. }
  296. .system-class-show .show-box {
  297. display: block;
  298. width: 192px;
  299. height: 45px;
  300. float: left;
  301. margin: 28px 0 0 16px;
  302. cursor: pointer;
  303. }
  304. .system-class-show .show-box .system-class-icon {
  305. float: left;
  306. width: 45px;
  307. height: 45px;
  308. border-radius: 50%;
  309. background-size: cover;
  310. margin-right: 8px;
  311. transition: all .2s;
  312. }
  313. .system-class-show .show-box .describe {
  314. float: left;
  315. }
  316. .system-class-show .show-box .describe h4 {
  317. width: 139px;
  318. font-family: PingFangSC-Medium;
  319. font-size: 16px;
  320. color: #1C1F21;
  321. letter-spacing: 0.76px;
  322. line-height: 22px;
  323. margin-bottom: 4px;
  324. white-space: nowrap;
  325. overflow: hidden;
  326. }
  327. .system-class-show .show-box .describe p {
  328. width: 139px;
  329. font-family: PingFangSC-Regular;
  330. font-size: 12px;
  331. color: #545C63;
  332. line-height: 18px;
  333. white-space: nowrap;
  334. overflow: hidden;
  335. }
  336. .system-class-show .show-box:hover .system-class-icon {
  337. transform: translateY(-2px);
  338. }
  339. .system-class-show .show-box:hover .describe h4 {
  340. color: #F01414;
  341. }
  342. .system-class-show .line {
  343. float: left;
  344. height: 36px;
  345. border: 1px solid #E8E8E8;
  346. margin-left: 16px;
  347. margin-top: 33px;
  348. }
  349. .system-class-show .all-btn {
  350. position: relative;
  351. display: block;
  352. height: 100%;
  353. cursor: pointer;
  354. overflow: hidden;
  355. }
  356. .system-class-show .all-btn .mini-title {
  357. font-family: PingFangSC-Medium;
  358. font-size: 12px;
  359. color: #1C1F21;
  360. text-align: center;
  361. line-height: 14px;
  362. margin-top: 40px;
  363. }
  364. .system-class-show .all-btn .more-btn {
  365. font-family: PingFangSC-Regular;
  366. font-size: 12px;
  367. color: #545C63;
  368. line-height: 12px;
  369. margin-left: 30px;
  370. position: relative;
  371. }
  372. .system-class-show .all-btn .more-btn .icon-right2 {
  373. position: absolute;
  374. top: 1px;
  375. left: 28px;
  376. transition: all .2s;
  377. }
  378. .system-class-show .all-btn:hover .more-btn {
  379. color: #1C1F21;
  380. }
  381. .system-class-show .all-btn:hover .more-btn .icon-right2 {
  382. transform: translateX(3px);
  383. }
  384. .g-banner {
  385. position: relative;
  386. overflow: hidden;
  387. width: 1400px;
  388. margin: auto;
  389. border-radius: 8px 8px 0 0;
  390. }
  391. .g-banner .g-banner-content {
  392. position: relative;
  393. float: left;
  394. width: 1142px;
  395. }
  396. .g-banner .g-banner-content .g-banner-box {
  397. position: relative;
  398. height: 316px;
  399. }
  400. .g-banner .g-banner-content .notice {
  401. position: absolute;
  402. top: 8px;
  403. left: 0;
  404. background: #FF9900;
  405. box-shadow: 0 2px 4px 0 rgba(7, 17, 27, 0.2);
  406. padding: 6px 12px 6px 8px;
  407. z-index: 1;
  408. border-top-right-radius: 20px;
  409. border-bottom-right-radius: 20px;
  410. }
  411. .g-banner .g-banner-content .notice .imv2-vol_up {
  412. font-size: 16px;
  413. color: #FFFFFF;
  414. display: inline-block;
  415. line-height: 20px;
  416. margin-top: 1px;
  417. margin-right: 4px;
  418. vertical-align: sub;
  419. }
  420. .g-banner .g-banner-content .notice .notice-txt {
  421. display: inline-block;
  422. width: auto;
  423. font-size: 12px;
  424. color: #FFFFFF;
  425. line-height: 20px;
  426. z-index: 1;
  427. white-space: nowrap;
  428. }
  429. .g-banner .g-banner-content .notice .notice-close {
  430. font-size: 16px;
  431. margin: 6px 0 6px 12px;
  432. color: rgba(255, 255, 255, 0.6);
  433. line-height: 20px;
  434. }
  435. .g-banner .g-banner-content .notice .notice-close:hover {
  436. color: #fff;
  437. }
  438. .g-banner .g-banner-content .notice.closed {
  439. transition: all .3s;
  440. background: rgba(255, 153, 0, 0.6);
  441. box-shadow: 0 2px 4px 0 rgba(7, 17, 27, 0.2);
  442. }
  443. .g-banner .g-banner-content .notice.closed .notice-txt {
  444. overflow: hidden;
  445. }
  446. .g-banner .g-banner-content .notice.closed .notice-close {
  447. display: none;
  448. }
  449. .g-banner .banner-anchor {
  450. position: absolute;
  451. top: 50%;
  452. margin-top: -24px;
  453. width: 48px;
  454. height: 48px;
  455. background: rgba(28, 31, 33, 0.1) url(/src/assets/icon-left-small.png) no-repeat center / 16px auto;
  456. border-radius: 50%;
  457. color: #FFFFFF;
  458. transition: all .2s;
  459. }
  460. .g-banner .banner-anchor:hover {
  461. background-color: rgba(28, 31, 33, 0.5);
  462. }
  463. .g-banner .next {
  464. right: 16px;
  465. transform: rotate(180deg);
  466. }
  467. .g-banner .prev {
  468. left: 16px;
  469. }
  470. .g-banner .g-banner-box > a:first-child .banner-slide {
  471. display: block;
  472. }
  473. .g-banner .banner-slide {
  474. position: absolute;
  475. display: none;
  476. width: 896px;
  477. height: 316px;
  478. /*margin: auto;*/
  479. left: 0;
  480. right: 0;
  481. top: 0;
  482. bottom: 0;
  483. background-repeat: no-repeat;
  484. background-position: center 0;
  485. }
  486. .g-banner .banner-slide .festival {
  487. position: absolute;
  488. top: 450px;
  489. right: 75px;
  490. }
  491. .g-banner .banner-slide .festival a {
  492. display: block;
  493. width: 190px;
  494. height: 120px;
  495. }
  496. .g-banner .banner-slide .festival a:hover {
  497. background-position: 0 0;
  498. }
  499. .g-banner .banner-slide img {
  500. width: 100%;
  501. height: 100%;
  502. }
  503. .g-banner .inner {
  504. position: relative;
  505. width: 1200px;
  506. margin: 0 auto;
  507. }
  508. .g-banner .banner-dots {
  509. position: absolute;
  510. bottom: 20px;
  511. left: 0;
  512. right: 0;
  513. text-align: right;
  514. padding-right: 24px;
  515. line-height: 12px;
  516. }
  517. .g-banner .banner-dots span {
  518. display: inline-block;
  519. *display: inline;
  520. *zoom: 1;
  521. width: 8px;
  522. height: 8px;
  523. border-radius: 4px;
  524. margin-left: 8px;
  525. background: rgba(255, 255, 255, 0.75);
  526. transition: all .2s;
  527. cursor: pointer;
  528. }
  529. .g-banner .banner-dots span.active {
  530. width: 20px;
  531. }
  532. .submenu {
  533. position: absolute;
  534. left: 256px;
  535. width: 776px;
  536. height: 482px;
  537. background: #FFFFFF;
  538. box-shadow: 0 4px 8px 0 rgba(7, 17, 27, 0.1);
  539. border-radius: 0 12px 12px 0;
  540. z-index: 33;
  541. box-sizing: border-box;
  542. }
  543. .submenu .inner-box {
  544. height: 188px;
  545. padding: 28px 36px 0;
  546. box-sizing: border-box;
  547. }
  548. .submenu .inner-box .type {
  549. margin-bottom: 10px;
  550. font-size: 16px;
  551. color: #1C1F21;
  552. line-height: 22px;
  553. font-weight: bold;
  554. }
  555. .submenu .inner-box .tag {
  556. margin-bottom: 12px;
  557. }
  558. .submenu .inner-box .tag a {
  559. float: left;
  560. font-size: 12px;
  561. line-height: 1;
  562. color: #E02020;
  563. border-radius: 100px;
  564. border: 1px solid #E02020;
  565. padding: 5px 10px;
  566. margin-right: 10px;
  567. }
  568. .submenu .inner-box .tag a:last-child {
  569. margin-right: 0;
  570. }
  571. .submenu .inner-box .lore {
  572. font-size: 12px;
  573. line-height: 24px;
  574. color: #6D7278;
  575. margin-bottom: 8px;
  576. display: -webkit-box;
  577. display: -ms-flexbox;
  578. display: -webkit-flex;
  579. display: flex;
  580. }
  581. .submenu .inner-box .lore .title {
  582. color: #1C1F21;
  583. font-weight: bold;
  584. }
  585. .submenu .inner-box .lore .lores {
  586. width: 0;
  587. -webkit-box-flex: 1;
  588. -ms-flex: 1;
  589. -webkit-flex: 1;
  590. flex: 1;
  591. }
  592. .submenu .inner-box .lore .lores a {
  593. float: left;
  594. color: #6D7278;
  595. margin-right: 24px;
  596. }
  597. .submenu .inner-box .lore .lores a:last-child {
  598. margin-right: 0;
  599. }
  600. .submenu .recomment {
  601. padding: 35px 36px;
  602. height: 204px;
  603. background-color: #F3F5F6;
  604. box-sizing: border-box;
  605. }
  606. .submenu .recomment .recomment-item {
  607. width: 329px;
  608. float: left;
  609. display: -webkit-box;
  610. display: -ms-flexbox;
  611. display: -webkit-flex;
  612. display: flex;
  613. }
  614. .submenu .recomment .recomment-item:nth-child(2n) {
  615. margin-left: 30px;
  616. }
  617. .submenu .recomment .recomment-item:nth-child(-n+2) {
  618. margin-bottom: 30px;
  619. }
  620. .submenu .recomment .recomment-item .img {
  621. width: 90px;
  622. height: 50px;
  623. margin-right: 11px;
  624. border-radius: 4px;
  625. background-position: center;
  626. image-rendering: -moz-crisp-edges;
  627. /* Firefox */
  628. image-rendering: -o-crisp-edges;
  629. /* Opera */
  630. image-rendering: -webkit-optimize-contrast;
  631. /*Webkit (non-standard naming) */
  632. image-rendering: crisp-edges;
  633. -ms-interpolation-mode: nearest-neighbor;
  634. /* IE (non-standard property) */
  635. box-shadow: 0 6px 10px 0 rgba(95, 101, 105, 0.15);
  636. }
  637. .submenu .recomment .recomment-item .details {
  638. height: 50px;
  639. font-size: 12px;
  640. width: 0;
  641. -webkit-box-flex: 1;
  642. -ms-flex: 1;
  643. -webkit-flex: 1;
  644. flex: 1;
  645. }
  646. .submenu .recomment .recomment-item .details .title-box {
  647. margin-bottom: 10px;
  648. display: -webkit-box;
  649. display: -ms-flexbox;
  650. display: -webkit-flex;
  651. display: flex;
  652. -webkit-box-align: center;
  653. -ms-flex-align: center;
  654. -webkit-align-items: center;
  655. align-items: center;
  656. }
  657. .submenu .recomment .recomment-item .details .title-box .title {
  658. display: flex;
  659. align-items: center;
  660. color: #1C1F21;
  661. width: 228px;
  662. }
  663. .submenu .recomment .recomment-item .details .title-box .title .text {
  664. display: inline-block;
  665. overflow: hidden;
  666. text-overflow: ellipsis;
  667. white-space: nowrap;
  668. max-width: calc(100% - 4em);
  669. }
  670. .submenu .recomment .recomment-item .details .title-box .title .tag {
  671. display: inline-block;
  672. width: 2em;
  673. color: #fff;
  674. opacity: .6;
  675. border-radius: 2px;
  676. line-height: 1;
  677. padding: 2px 4px;
  678. margin-left: 5px;
  679. }
  680. .submenu .recomment .recomment-item .details .title-box .title .tag.shizhan {
  681. background-color: #FA6400;
  682. }
  683. .submenu .recomment .recomment-item .details .title-box .title .tag.tixi {
  684. background-color: #E02020;
  685. }
  686. .submenu .recomment .recomment-item .details .title-box .title .tag.lujing {
  687. background-color: #0091FF;
  688. }
  689. .submenu .recomment .recomment-item .details .bottom {
  690. color: #9199A1;
  691. line-height: 18px;
  692. }
  693. .submenu .recomment .recomment-item .details .bottom .discount-name,
  694. .submenu .recomment .recomment-item .details .bottom .tag {
  695. display: inline-block;
  696. color: #fff;
  697. background-color: rgba(242, 13, 13, 0.6);
  698. border-radius: 2px;
  699. padding: 2px 4px;
  700. line-height: 1;
  701. }
  702. .submenu .recomment .recomment-item .details .bottom .discount-name {
  703. background: rgba(242, 13, 13, 0.6);
  704. }
  705. .submenu .recomment .recomment-item .details .bottom .price:not(.free) {
  706. font-weight: bold;
  707. color: #F01414;
  708. }
  709. .menuContent {
  710. position: relative;
  711. float: left;
  712. width: 256px;
  713. height: 482px;
  714. z-index: 2;
  715. padding-top: 17px;
  716. box-sizing: border-box;
  717. background: #39364d;
  718. border-bottom-left-radius: 4px;
  719. font-weight: 400;
  720. }
  721. .menuContent .item {
  722. line-height: 50px;
  723. cursor: pointer;
  724. position: relative;
  725. color: #fff;
  726. padding: 0 14px;
  727. border-top-left-radius: 4px;
  728. border-bottom-left-radius: 4px;
  729. height: 50px;
  730. transition: all .1s;
  731. font-size: 14px;
  732. }
  733. .menuContent .item .sub-title {
  734. font-size: 12px;
  735. }
  736. .menuContent .item i {
  737. position: absolute;
  738. right: 4px;
  739. top: 16px;
  740. color: rgba(255, 255, 255, 0.5);
  741. font-size: 16px;
  742. }
  743. .menuContent .item.js-menu-item-on {
  744. color: #fff;
  745. background-color: rgba(255, 255, 255, 0.1);
  746. }
  747. </style>

Home.vue,代码:

<template>
<div class="home">
    <Header></Header>
    <div id="main">
      <Banner></Banner>
    </div>
    <Footer></Footer>
</div>
</template>

<script setup>
import Header from "../components/Header.vue"
import Footer from "../components/Footer.vue"
import Banner from "../components/Banner.vue"

</script>

<style scoped>

</style>

提交版本

cd ~/Desktop/luffycity
git add .
git commit -m "feature:客户端展示轮播图界面效果"
git push origin develop

2.1 安装依赖模块和配置

安装图片处理模块,前面已经安装了,如果没有安装则需要安装

pip install pillow

填写上传文件的相关配置,settings/dev.py

# 访问静态文件的url地址前缀
STATIC_URL = '/static/'
# 设置django的静态文件目录[手动创建]
STATICFILES_DIRS = [
    BASE_DIR / "static",
]

# 项目中存储上传文件的根目录[手动创建],注意,uploads目录需要手动创建否则上传文件时报错
MEDIA_ROOT = BASE_DIR / "uploads"
# 访问上传文件的url地址前缀
MEDIA_URL = "/uploads/"

总路由luffycityapi.urls.py新增代码:

from django.contrib import admin
from django.urls import path,re_path,include

from django.conf import settings
from django.views.static import serve # 静态文件代理访问模块

urlpatterns = [
    path('admin/', admin.site.urls),
    re_path(r'uploads/(?P<path>.*)', serve, {"document_root": settings.MEDIA_ROOT}),
    path("", include("home.urls")),
]

提交版本

cd ~/Desktop/luffycity
git add .
git commit -m "feature:服务端提供访问静态文件(static和uploads)的url配置"
git push origin develop

2.2 创建轮播图的模型

home/models.py,代码:

from models import BaseModel,models
# Create your models here.

class Banner(BaseModel):
    image = models.ImageField(upload_to="banner/%Y/", verbose_name="图片地址")
    link = models.CharField(max_length=500, verbose_name="链接地址")
    note = models.CharField(max_length=150, verbose_name='备注信息')
    is_http = models.BooleanField(default=False, verbose_name="是否外链地址", help_text="站点链接地址:http://www.baidu.com/book<br>站点链接地址:/book/")

    class Meta:
        db_table = "fg_banner"
        verbose_name = "轮播广告"
        verbose_name_plural = verbose_name

数据迁移

cd luffycityapi
python manage.py makemigrations
python manage.py migrate

把课件中素材目录下的图片保存到项目上传文件存储目录下luffycityapi/uploads/banner/2021/,并添加测试数据到MySQL。

INSERT INTO luffycity.lf_banner (id, name, orders, is_show, is_deleted, created_time, updated_time, image, note, link, is_http) VALUES (1, '1', 1, 1, 0, '2021-07-15 03:39:49.859000', '2021-07-15 03:39:51.437000', 'banner/2022/1.jpg', '暂无', '/project', 0);
INSERT INTO luffycity.lf_banner (id, name, orders, is_show, is_deleted, created_time, updated_time, image, note, link, is_http) VALUES (2, '2', 1, 1, 0, '2021-07-15 03:39:49.859000', '2021-07-15 03:39:51.437000', 'banner/2022/2.jpg', '暂无', '/project', 0);
INSERT INTO luffycity.lf_banner (id, name, orders, is_show, is_deleted, created_time, updated_time, image, note, link, is_http) VALUES (3, '3', 1, 1, 0, '2021-07-15 03:39:49.859000', '2021-07-15 03:39:51.437000', 'banner/2022/3.jpg', '暂无', '/project', 0);
INSERT INTO luffycity.lf_banner (id, name, orders, is_show, is_deleted, created_time, updated_time, image, note, link, is_http) VALUES (4, '4', 1, 1, 0, '2021-07-15 03:39:49.859000', '2021-07-15 03:39:51.437000', 'banner/2022/4.jpg', '暂无', '/project', 0);
INSERT INTO luffycity.lf_banner (id, name, orders, is_show, is_deleted, created_time, updated_time, image, note, link, is_http) VALUES (5, '5', 1, 1, 0, '2021-07-15 03:39:49.859000', '2021-07-15 03:39:51.437000', 'banner/2022/5.jpg', '暂无', '/project', 0);

2.3 序列化器

home/serializers.py

class BannerModelSerializer(serializers.ModelSerializer):
    """
    轮播广告的序列化器
    """
    class Meta:
        model = Banner
        fields = ["image", "name", "link", "is_http"]

2.4 视图代码

home/views.py

import constants
from rest_framework.generics import ListAPIView
from .models import Nav, Banner
from .serializers import NavModelSerializer, BannerModelSerializer

# 中间代码省略

class BannerListAPIView(ListAPIView):
    """轮播广告视图"""
    queryset = Banner.objects.filter(is_show=True, is_deleted=False).order_by("orders", "-id")[:constants.BANNER_SIZE]
    serializer_class = BannerModelSerializer

2.5 路由代码

home/urls.py,代码:

from django.urls import path
from . import views
urlpatterns = [
    path("nav/header/", views.NavHeaderListAPIView.as_view()),
    path("nav/footer/", views.NavFooterListAPIView.as_view()),
    path("banner/", views.BannerListAPIView.as_view()),
]

utils/constants.py,常量文件:

# 轮播广告显示的最大数量
BANNER_SIZE = 10

提交git版本

cd ~/Desktop/luffycity
git add .
git commit -m "feature:api服务端实现轮播广告接口"
git push origin develop

2.6 客户端获取轮播广告的数据

src/api/banner.js,代码:

import http from "../utils/http"
import {reactive, ref} from "vue"

const banner = reactive({
    banner_list: [], // 轮播广告列表
    get_banner_list(){
        // 获取轮播广告
        return http.get("/home/banner/")
    },

})

export default banner;

src/components/Banner.vue,代码:

<template>
   <div class="bk"></div>
   <div class="bgfff banner-box">
    <div class="g-banner pr" @mouseleave="state.current_menu=-1">
     <!-- 商品课程分类信息 -->
     <div class="submenu" v-if="state.current_menu==0">
      <div class="inner-box">
       <h2 class="type">前端开发</h2>
       <div class="tag clearfix">
       </div>
       <div class="lore">
        <span class="title">知识点:</span>
        <p class="lores clearfix"><a target="_blank" href="">Vue.js</a>
          <a target="_blank" href="">Typescript</a>
          <a target="_blank" href="">React.JS</a>
          <a target="_blank" href="">HTML/CSS</a>
          <a target="_blank" href="">JavaScript</a>
          <a target="_blank" href="">Angular</a>
          <a target="_blank" href="">Node.js</a>
          <a target="_blank" href="">jQuery</a>
          <a target="_blank" href="">Bootstrap</a>
          <a target="_blank" href="">Sass/Less</a>
          <a target="_blank" href="">WebApp</a>
          <a target="_blank" href="">小程序</a>
          <a target="_blank" href="">前端工具</a>
          <a target="_blank" href="">CSS</a>
          <a target="_blank" href="">Html5</a>
          <a target="_blank" href="">CSS3</a>
        </p>
       </div>
      </div>
      <div class="recomment clearfix">
        <a href="" target="_blank" title="" class="recomment-item">
        <div class="img" style="background-image: url('/src/assets/60a7779909e3fc1206960344.png'); background-size: 100%; "></div>
        <div class="details">
         <!--路径单独写-->
         <div class="title-box">
          <p class="title"> <span class="text">前端工程师2021</span> <span class="tag tixi">体系</span> </p>
         </div>
         <div class="bottom">
          <span class="discount-name">优惠价</span>
          <span class="price">¥4599.00</span> &middot;
          <span class="difficulty"> 零基础 </span> &middot;
          <span class="num"><i class="imv2-set-sns"></i> 19322</span>
         </div>
        </div> </a>
       <a href="" target="_blank" title="前端框架及项目面试 聚焦Vue3/React/Webpack" class="recomment-item">
        <div class="img" style="background-image: url('/src/assets/5e3cfea008e9a61b06000338-360-202.jpg')"></div>
        <div class="details">
         <!--路径单独写-->
         <div class="title-box">
          <p class="title"> <span class="text">前端框架及项目面试 聚焦Vue3/React/Webpack</span> <span class="tag shizhan">实战</span> </p>
         </div>
         <div class="bottom">
          <span class="price">399.00</span> &middot;
          <span class="difficulty"> 中级 </span> &middot;
          <span class="num"><i class="imv2-set-sns"></i> 2946</span>
         </div>
        </div> </a>
       <a href="" target="_blank" title="从0打造微前端框架,实战汽车资讯平台,系统掌握微前端架构设计与落地能力" class="recomment-item">
        <div class="img" style="background-image: url('/src/assets/60d44ec8084b799712000676-360-202.jpg')"></div>
        <div class="details">
         <!--路径单独写-->
         <div class="title-box">
          <p class="title"><span class="text">从0打造微前端框架,实战汽车资讯平台,系统掌握微前端架构设计与落地能力</span><span class="tag shizhan">实战</span> </p>
         </div>
         <div class="bottom">
          <span class="discount-name">限时优惠</span>
          <span class="price">¥328.00</span> &middot;
          <span class="difficulty"> 高级 </span> &middot;
          <span class="num"><i class="imv2-set-sns"></i> 109</span>
         </div>
        </div> </a>
       <a href="" target="_blank" title="" class="recomment-item">
        <div class="img" style="background-image: url('/src/assets/604f2bab0952610803240324-140-140.png'); background-size: 100%; "></div>
        <div class="details">
         <!--路径单独写-->
         <div class="title-box">
          <p class="title"> <span class="text">Vue.js 从入门到精通</span> <span class="tag lujing">路线</span> </p>
         </div>
         <div class="bottom">
          <span class="difficulty">4步骤</span> &middot;
          <span class="difficulty">4门课</span> &middot;
          <span class="num">19697人收藏</span>
         </div>
        </div> </a>
      </div>
     </div>
     <div class="submenu" v-if="state.current_menu==1">
      <div class="inner-box">
       <h2 class="type">后端开发</h2>
       <div class="tag clearfix">
       </div>
       <div class="lore">
        <span class="title">知识点:</span>
        <p class="lores clearfix">
          <a target="_blank" href="">Java</a>
          <a target="_blank" href="">SpringBoot</a>
          <a target="_blank" href="">Spring Cloud</a>
          <a target="_blank" href="">SSM</a>
          <a target="_blank" href="">PHP</a>
          <a target="_blank" href="">.net</a>
          <a target="_blank" href="">Python</a>
          <a target="_blank" href="">爬虫</a>
          <a target="_blank" href="">Django</a>
          <a target="_blank" href="">Flask</a>
          <a target="_blank" href="">Tornado</a>
          <a target="_blank" href="">Go</a>
          <a target="_blank" href="">C</a>
          <a target="_blank" href="">C++</a>
          <a target="_blank" href="">C#</a>
          <a target="_blank" href="">Ruby</a></p>
       </div>
      </div>
      <div class="recomment clearfix">
        <a href="" target="_blank" title="Java工程师2021" class="recomment-item">
        <div class="img" style="background-image: url('/src/assets/60a777ef0942d7bf06960344.png'); background-size: 100%; "></div>
        <div class="details">
         <div class="title-box">
          <p class="title"> <span class="text">Java工程师2021</span> <span class="tag tixi">体系</span> </p>
         </div>
         <div class="bottom">
          <span class="discount-name">优惠价</span>
          <span class="price">¥4399.00</span> &middot;
          <span class="difficulty"> 零基础 </span> &middot;
          <span class="num"><i class="imv2-set-sns"></i> 15052</span>
         </div>
        </div> </a>
       <a href="" target="_blank" title="Python工程师(全能型)" class="recomment-item">
        <div class="img" style="background-image: url('/src/assets/60a77721093df37606960344.png'); background-size: 100%; "></div>
        <div class="details">
         <!--路径单独写-->
         <div class="title-box">
          <p class="title"> <span class="text">Python工程师(全能型)</span> <span class="tag tixi">体系</span> </p>
         </div>
         <div class="bottom">
          <span class="discount-name">优惠价</span>
          <span class="price">¥4366.00</span> &middot;
          <span class="difficulty"> 零基础 </span> &middot;
          <span class="num"><i class="imv2-set-sns"></i> 10786</span>
         </div>
        </div> </a>
       <a href="" target="_blank" title="Java全栈工程师" class="recomment-item">
        <div class="img" style="background-image: url('/src/assets/5dd6567b09d9d01c06000338.png'); background-size: 100%; "></div>
        <div class="details">
         <!--路径单独写-->
         <div class="title-box">
          <p class="title"> <span class="text">Java全栈工程师</span> <span class="tag tixi">体系</span> </p>
         </div>
         <div class="bottom">
          <span class="discount-name">优惠价</span>
          <span class="price">¥3380.00</span> &middot;
          <span class="difficulty"> 进阶 </span> &middot;
          <span class="num"><i class="imv2-set-sns"></i> 1853</span>
         </div>
        </div> </a>
       <a href="" target="_blank" title="" class="recomment-item">
        <div class="img" style="background-image: url('/src/assets/604f2bb6099d6a8803240324-140-140.png'); background-size: 100%; "></div>
        <div class="details">
         <!--路径单独写-->
         <div class="title-box">
          <p class="title"> <span class="text">SpringBoot从入门到精通</span> <span class="tag lujing">路线</span> </p>
         </div>
         <div class="bottom">
          <span class="difficulty">3步骤</span> &middot;
          <span class="difficulty">5门课</span> &middot;
          <span class="num">11092人收藏</span>
         </div>
        </div> </a>
      </div>
     </div>
     <div class="submenu" v-if="state.current_menu==2">
      <div class="inner-box">
       <h2 class="type">移动开发</h2>
       <div class="tag clearfix">
       </div>
       <div class="lore">
        <span class="title">知识点:</span>
        <p class="lores clearfix"></p>
       </div>
      </div>
      <div class="recomment clearfix">
       <a href="" target="_blank" title="移动端架构师成长体系课" class="recomment-item">
        <div class="img" style="background-image: url('/src/assets/5ec5ddf209cd2c8606000338.png'); background-size: 100%; "></div>
        <div class="details">
         <!--路径单独写-->
         <div class="title-box">
          <p class="title"> <span class="text">移动端架构师成长体系课</span> <span class="tag tixi">体系</span> </p>
         </div>
         <div class="bottom">
          <span class="discount-name">优惠价</span>
          <span class="price">¥4888.00</span> &middot;
          <span class="difficulty"> 进阶 </span> &middot;
          <span class="num"><i class="imv2-set-sns"></i> 402</span>
         </div>
        </div> </a>
       <a href="" target="_blank" title="Flutter高级进阶实战  仿哔哩哔哩APP 一次性深度掌握Flutter高阶技能" class="recomment-item">
        <div class="img" style="background-image: url('/src/assets/60497caf0971842912000676-360-202.png'); background-size: 100%; "></div>
        <div class="details">
         <!--路径单独写-->
         <div class="title-box">
          <p class="title"> <span class="text">Flutter高级进阶实战 仿哔哩哔哩APP 一次性深度掌握Flutter高阶技能</span> <span class="tag shizhan">实战</span> </p>
         </div>
         <div class="bottom">
          <span class="price">368.00</span> &middot;
          <span class="difficulty"> 高级 </span> &middot;
          <span class="num"><i class="imv2-set-sns"></i> 646</span>
         </div>
        </div> </a>
       <a href="" target="_blank" title="音视频基础+ffmpeg原理+项目实战 一课完成音视频技术开发入门" class="recomment-item">
        <div class="img" style="background-image: url('/src/assets/5e5621d0092c054612000676-360-202.png'); background-size: 100%; "></div>
        <div class="details">
         <!--路径单独写-->
         <div class="title-box">
          <p class="title"> <span class="text">音视频基础+ffmpeg原理+项目实战 一课完成音视频技术开发入门</span> <span class="tag shizhan">实战</span> </p>
         </div>
         <div class="bottom">
          <span class="price">288.00</span> &middot;
          <span class="difficulty"> 入门 </span> &middot;
          <span class="num"><i class="imv2-set-sns"></i> 1303</span>
         </div>
        </div> </a>
       <a href="" target="_blank" title="" class="recomment-item">
        <div class="img" style="background-image: url('/src/assets/604f2b52090de67603240324-140-140.png'); background-size: 100%; "></div>
        <div class="details">
         <!--路径单独写-->
         <div class="title-box">
          <p class="title"> <span class="text">Android工程师高薪面试突破路线</span> <span class="tag lujing">路线</span> </p>
         </div>
         <div class="bottom">
          <span class="difficulty">3步骤</span> &middot;
          <span class="difficulty">3门课</span> &middot;
          <span class="num">1471人收藏</span>
         </div>
        </div> </a>
      </div>
     </div>
     <div class="menuContent">
      <div class="item" :class="{'js-menu-item-on': state.current_menu==0}" @mouseover="state.current_menu=0">
       <span class="title">前端开发:</span>
       <span class="sub-title">HTML5 / Vue.js / Node.js</span>
       <i class="imv2-arrow1_r"></i>
      </div>
      <div class="item" :class="{'js-menu-item-on': state.current_menu==1}" @mouseover="state.current_menu=1">
       <span class="title">后端开发:</span>
       <span class="sub-title">Java / Python / Go</span>
       <i class="imv2-arrow1_r"></i>
      </div>
      <div class="item" :class="{'js-menu-item-on': state.current_menu==2}" @mouseover="state.current_menu=2">
       <span class="title">移动开发:</span>
       <span class="sub-title">Flutter / Android / iOS </span>
       <i class="imv2-arrow1_r"></i>
      </div>
     </div>
      <!-- 轮播图-->
      <div class="g-banner-content"  @mouseover="state.current_menu=-1">
        <el-carousel :interval="5000" arrow="always" height="482px" v-if="banner.banner_list[0]">
          <el-carousel-item v-for="item,key in banner.banner_list" :key="key">
            <a :href="item.link" v-if="item.is_http"><img :src="item.image" alt="" style="width: 100%;height: 100%;"></a>
            <router-link :to="item.link" v-else><img :src="item.image" alt="" style="width: 100%;height: 100%;"></router-link>
          </el-carousel-item>
        </el-carousel>
     </div>
    </div>
   </div>
</template>

<script setup>
import {reactive} from "vue"
import banner from "../api/banner";

// 获取轮播广告列表
banner.get_banner_list().then(response=>{
  banner.banner_list = response.data
})

const state = reactive({
  current_menu: -1,
})
</script>

提交git版本

cd ~/Desktop/luffycity
git add .
git commit -m "feature:客户端展示轮播广告数据"
git push origin develop

3. 缓存导航与轮播图数据

因为导航菜单或轮播广告在项目中每一个页面都会被用户访问到,所以我们可以实现缓存,减少MySQL数据库的查询压力,使用内存缓存可以加快数据查询速度。

视图缓存:https://docs.djangoproject.com/zh-hans/3.2/topics/cache/#the-per-view-cache

装饰类视图:https://docs.djangoproject.com/zh-hans/3.2/topics/class-based-views/intro/#decorating-the-class

utils/views.py,代码:

import constants
from rest_framework.generics import ListAPIView
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page


class CacheListAPIView(ListAPIView):
    """列表缓存视图"""
    @method_decorator(cache_page(constants.LIST_PAGE_CACHE_TIME))
    def get(self,request, *args, **kwargs):
        # 重写ListAPIView的get方法,但是不改动源代码。仅仅装饰而已
        return super().get(request, *args, **kwargs)

utils/constants.py,代码:

# 列表页数据的缓存周期,单位:秒
LIST_PAGE_CACHE_TIME = 24 * 60 * 60

home/views.py,代码:

import constants
from views import CacheListAPIView
from .models import Nav, Banner
from .serializers import NavModelSerializer, BannerModelSerializer



class NavHeaderListAPIView(CacheListAPIView):
    """顶部导航视图"""
    queryset = Nav.objects.filter(position=constants.NAV_HEADER_POSITION, is_show=True, is_deleted=False).order_by("orders", "-id")[:constants.NAV_HEADER_SIZE]
    serializer_class = NavModelSerializer


class NavFooterListAPIView(CacheListAPIView):
    """脚部导航视图"""
    queryset = Nav.objects.filter(position=constants.NAV_FOOTER_POSITION, is_show=True, is_deleted=False).order_by("orders", "-id")[:constants.NAV_FOOTER_SIZE]
    serializer_class = NavModelSerializer


class BannerListAPIView(CacheListAPIView):
    """轮播广告视图"""
    queryset = Banner.objects.filter(is_show=True, is_deleted=False).order_by("orders", "-id")[:constants.BANNER_SIZE]
    serializer_class = BannerModelSerializer

注意:此处数据使用了缓存,那么将来admin站点在修改此处相关的数据库的数据时,admin站点中我们就需要在更新数据时对缓存进行删除,这块业务逻辑等我们后面登陆注册功能以后搭建admin后面时会带着小伙伴们完成。

提交git版本

cd ~/Desktop/luffycity
git add .
git commit -m "perf:服务端实现导航和轮播的数据缓存"
git push origin develop