【Django+Vue3 线上教育平台项目实战】构建高效线上教育平台之首页模块

avatar
作者
猴君
阅读量:0

在这里插入图片描述


文章目录


前言

   在当今数字化教育浪潮中,构建一个高效且用户友好的线上教育平台至关重要。本博客将指导您使用Django作为后端框架,结合Vue 3的强大前端能力,快速搭建平台首页的核心功能,包括导航栏、轮播图、侧边栏、标签栏及分类课程推荐。我们将探讨前后端数据交互、Vue组件化开发等关键技术,轻松构建出既美观又实用的线上教育平台。


  最终实现效果图如下:
在这里插入图片描述


一、导航功能实现

a.效果图:

在这里插入图片描述

b.后端代码

导航表模型类:

class NavigationModel(BaseModel):     name = models.CharField(max_length=100)     url = models.CharField(max_length=100)     is_url = models.BooleanField(default=False)     def __str__(self):         return self.name     class Meta:         verbose_name = '导航表'         verbose_name_plural = '导航表'         db_table = 'navigation' 

导航表序列化器:

class NavigationSerializer(serializers.ModelSerializer):     class Meta:         model = NavigationModel         fields = ('id','name','url','is_url')         # fields = '__all__' 

获取所有头部导航栏信息:

class NavigationView(APIView):     def get(self, request):         nav_list = NavigationModel.objects.all()         ser = NavigationSerializer(nav_list, many=True)         return Response({"code":"200", "data":ser.data}) 

配置url信息:

urlpatterns = [     path('nav/header/', NavigationView.as_view()),     ... ] 

c.前端代码

components/Header.vue:

<!-- 0.导航栏 --> <ul class="nav">    <li v-for="(item,index) in nav.header_nav_list " :key="index">      <a :href="item.url" v-if="item.is_url">{{item.name}}</a>      <router-link :to="item.url" v-else>{{item.name}}</router-link>    </li> </ul> <script setup> import nav from "../api/nav" 	// 获取顶部导航菜单 	nav.get_header_nav() </script> 

src/api/nav.js:

import http from "../http"; import {reactive} from "vue"; const nav = reactive({  header_nav_list: [], // 头部导航列表  get_header_nav(){    // 获取头部导航菜单    http.get("/home/nav/header/").then(response=>{      this.header_nav_list = response.data;    })  }, }) export default nav; 

二、轮播图功能实现

a.效果图

在这里插入图片描述

b.后端代码

轮播图模型类:

class BannerModel(BaseModel):     image = models.CharField(max_length=255)     link = models.CharField(max_length=255)     is_http = models.BooleanField(default=False)     def __str__(self):         return self.image     class Meta:         verbose_name = "轮播图表"         verbose_name_plural = "轮播图表"         db_table = 'banner' 

轮播图序列化器:

class BannerSerializer(serializers.ModelSerializer):     class Meta:         model = BannerModel         fields = '__all__' 

获取轮播图数据:

class BannerView(APIView):     def get(self, request):         banners = BannerModel.objects.all()         ser = BannerSerializer(banners, many=True)         return Response({"code":"200", "data":ser.data}) 

配置url信息:

    path('banner/', BannerView.as_view()), 

c.前端代码

src/components/Banner.vue:

<!-- 焦点图、轮播图--> <div class="g-banner-content" @mouseover="state.current_menu = -1">   <el-carousel height="382px" indicator-position="bottom" @change="handleCarouselChange">     <el-carousel-item v-for="(item, key) in banner.bannerImg" :key="key">       <img :src="item.image" alt="" style="width: 100%; height: 100%" />     </el-carousel-item>   </el-carousel> </div> 
<script setup> import banner from "../api/banner"; banner.get_banner_list();  // 轮播图列表  接口数据替换 // http://192.168.56.1:3000/src/assets/img/course1.jpg const bannerImg = reactive([{   image: '/src/assets/img/course1.jpg',   link: '',   is_http: false, }])  // 当前轮播 banner背景 const nowBannerImg = reactive({ src: bannerImg[0].image }); //轮播切换赋值 const handleCarouselChange = (index) => {   // 更新当前banner图片地址   nowBannerImg.src = banner.bannerImg[index].image; };  <script> 

src/api/banner.js:

import { reactive } from "vue"; import http from "../http"; const banner = reactive({   bannerImg: [],  // 轮播广告列表    get_banner_list() {     // 获取轮播广告列表     return http.get("/home/banner/").then(response => {       // console.log("Bannner---response.data");       // console.log(response.data);       this.bannerImg = response.data.data;       // console.log("bannerImg");       // console.log(response.data.data);     })   }, })  export default banner; 

三、标签栏功能实现

a.效果图

在这里插入图片描述

b.后端代码

标签表模型类:

class DirectionModel(BaseModel):     direction = models.CharField(max_length=255)     desc = models.CharField(max_length=255)     icon = models.CharField(max_length=255)     def __str__(self):         return self.direction     class Meta:         verbose_name = "方向表"         verbose_name_plural = "标签表"         db_table = 'direction' 

标签 / 方向表序列化器:

class DirectionSerializer(serializers.ModelSerializer):     class Meta:         model = DirectionModel         fields = ['id','direction','desc','icon']         # fields = '__all__' 

获取所有标签数据:

class DirectionView(APIView):     def get(self, request):         directions = DirectionModel.objects.all()         ser = DirectionSerializer(directions, many=True)         return Response({"code":"200", "data":ser.data}) 

配置urls:

    path('directions/', DirectionView.as_view()), 

c.前端代码

src/components/Banner.vue:

    <!-- 标签表(方向表) -->     <div class="system-class-show">       <a class="show-box" v-for="(item, index) in directions.directions_list" :key="index">         <div class="system-class-icon" :style="{ 'background-image': `url('${item.icon}')` }"></div>         <div class="describe">           <h4>{{ item.direction }}</h4>           <p>{{ item.desc }}</p>         </div>       </a>       <div class="line"></div>       <a class="all-btn">                  <div class="mini-title">体系课</div>         <div class="more-btn">more</div>       </a>     </div> 
import directions from '../api/directions';  directions.get_directions_list(); 

src/api/directions.js:

import { reactive } from "vue"; import http from "../http";  const directions = reactive({     directions_list: [],  // 标签列表     get_directions_list() {         // 获取标签列表         return http.get("/home/directions/").then(response => {             // console.log("11111111111111");             // console.log("directions_list---response.data");             // console.log(response.data);             this.directions_list = response.data.data;             // console.log(response.data.data);         })     },  })  export default directions; 

四、侧边栏功能实现

1.整体效果图

在这里插入图片描述

2.侧边栏功能实现

a.效果图

在这里插入图片描述

b.后端代码

分类表模型类:

class CategoryModel(BaseModel):     id = models.AutoField(primary_key=True)  # 通常Django会自动为主键添加AutoField,这里显式写出也可以     name = models.CharField(max_length=255, unique=True)  # 假设分类名最大长度为255个字符     parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='son')  # 自关联字段,表示父分类     recommend = models.BooleanField(default=False)     def __str__(self):         return self.name     class Meta:         verbose_name = '分类表'         verbose_name_plural = '分类表'         db_table = 'category' 

父级分类序列化器、子级分类序列化器:

# 子类序列化器---二级分类 class SonCategorySerializer(serializers.ModelSerializer):     class Meta:         model = CategoryModel         fields = ('id', 'name')         # fields = '__all__'  # 父类序列化器--一级分类 class CategorySerializer(serializers.ModelSerializer):     son = SonCategorySerializer(many=True, read_only=True)     class Meta:         model = CategoryModel         fields = '__all__'         # fields = ('id','name','son') 

获取父级与子级分类:

# 2.获取一、二级分类 class CategoryView(APIView):     def get(self, request):         # 查询所有一级分类:parent is null         # query_set         categories = CategoryModel.objects.filter(is_delete=0,parent__id__isnull=True)  #query_set          clist = [] #侧边栏 二级分类显示几个         for category in categories:             # 获取一级下面所有的二级分类,操作显示二级分类数据条数             sondata = category.son.all()[0:2] #query_set             # d对二级数据进行序列化操作             son = SonCategorySerializer(sondata, many=True)             clist.append({"id": category.id, "name": category.name, "son": son.data})          return Response({"code":"200", "data":clist}) 

配置url:

    path('nav/cates/', CategoryView.as_view()), #侧边栏-获取一二级分类  

c.前端代码

src/components/Banner.vue:

<!-- 左侧边栏Banner---二级分类 -->  <div class="menuContent">    <div      v-for="(item, index) in cates.cates_list"      :key="index"      class="item"      :class="{ 'js-menu-item-on': state.current_menu == 0 }"      @mouseover="fnMethod(item.id)"    ><!-- item.id 一级分类id -->      <span class="title">{{ item.name }}:</span>      <span class="sub-title" v-for="(s, index) in item.son" :key="index"        >&nbsp;&nbsp;{{ s.name }}&nbsp;&nbsp;</span      >      <i class="imv2-arrow1_r"></i>    </div>   </div> 
import cates from "../api/cates";  cates.get_cates_list(); // 定义方法-展示侧边栏所有二级分类以及所有分类下的课程 const fnMethod = (cateid) => {   state.current_menu = 0;   cates.get_coures_list(cateid); }; 

src/api/cates.js:

import http from "../http"; import { reactive } from "vue";  const cates = reactive({   cates_list: [], // Banner---两级分类列表      get_cates_list() {     // 获取两级分类     return http.get("/home/nav/cates/").then(response => {       // console.log("左侧边栏获取两级分类response.data:");       // console.log(response.data);       this.cates_list = response.data.data;     })   }, }) export default cates; 

3.侧边栏展示分类及课程信息功能实现

点击分类(侧边栏触发),获取此分类下所有的二级分类(@mouseover)以及此分类下推荐的课程

a.效果图

在这里插入图片描述

b.后端代码

+课程表模型类:

class CourseModel(BaseModel):     id = models.AutoField(primary_key=True)     name = models.CharField(max_length=255, unique=True)      # parent 指向 Category 分类id     parent = models.ForeignKey(CategoryModel, on_delete=models.CASCADE, related_name='course',verbose_name="parent-父级分类")     topid = models.IntegerField(verbose_name="topid-顶级分类")      recommend = models.BooleanField(default=False)     picurl = models.CharField(max_length=100)     price = models.FloatField()     level = models.IntegerField(verbose_name="1零基础 2中级 3高级")     sales = models.IntegerField(default=0,verbose_name="销量")     describe = models.TextField()          def __str__(self):         return self.name     class Meta:         verbose_name = '课程表'         verbose_name_plural = '课程表'         db_table = 'course' 

课程序列化器:

class CourseSerializer(serializers.ModelSerializer):     # teacher = TeachersSerializer(many=True, read_only=True)     # teacher = TeachersSerializer()     class Meta:         model = CourseModel         fields = '__all__' 

获取所有分类及其推荐课程:

class CategoryCourseView(APIView):     def get(self, request):         #获取一级分类id         cateid = request.GET.get("cateid")         #根据id查询分类:一级分类和二级分类         cate = CategoryModel.objects.filter(id=cateid).first()         #通过id查询推荐课程         ser = CategorySerializer(cate)         #返回结果         #print(cate.id)         courses = CourseModel.objects.filter(topid=cateid,recommend=True)         #print(courses)         cSer = CourseSerializer(courses, many=True)         #print(cSer.data)         return Response({"code":"200", "clist":ser.data,"courses":cSer.data}) 

配置urls:

	path('nav/catescourses/', CategoryCourseView.as_view()),#侧边栏-传一级分类id->展示子分类及其所有课程 

c.前端代码

src/components/Banner.vue:

<!-- 侧边栏触发显示:分类信息、课程信息 --> <div class="submenu" v-if="state.current_menu === 0">   <!-- 1.2.1侧边栏触发显示:商品课程二级分类信息  -->   <div class="inner-box">     <h2 class="type">{{ cates.cc_list.name }}</h2>     <div class="tag clearfix"></div>     <div class="lore">       <span class="title">知识点:</span>       <p class="lores clearfix">         <a target="_blank" v-for="(item, index) in cates.cc_list.son" :key="index" href="">{{ item.name }}</a>       </p>     </div>   </div>    <!-- 1.2.2侧边栏触发显示:分类下的课程信息--->   <div class="recomment clearfix">     <a href="" target="_blank" title="" class="recomment-item" v-for="(c,index) in cates.course_list" :key="index">       <div class="img" :style="{ 'background-image': `url('${c.picurl}')`, 'background-size': '100%' }"></div>       <div class="details">         <div class="title-box">           <p class="title">             <span class="text">{{c.name}}</span>             <span class="tag tixi">体系</span>           </p>         </div>         <div class="bottom">           <span class="discount-name">优惠价:</span>           <span class="price">¥{{c.price}}</span> &middot;           <span class="difficulty"> {{c.describe}} </span> &middot;           <span class="difficulty" v-if="c.level==1"> 0基础 </span> &middot;           <span class="difficulty" v-if="c.level==2"> 中级 </span> &middot;           <span class="difficulty" v-if="c.level==3"> 高级 </span> &middot;           <span class="num"><i class="imv2-set-sns"></i> {{c.sales}}人</span>         </div>       </div>     </a>   </div>  </div> 

src/api/cates.js:

import http from "../http"; import { reactive } from "vue";  const cates = reactive({   cates_list: [], // Banner---两级分类列表   cc_list: {}, //Banner---触发显示:显示所有二级分类   course_list: [], //Banner ---触发显示:显示分类下的课程    get_cates_list() {     // 获取两级分类     return http.get("/home/nav/cates/").then(response => {       this.cates_list = response.data.data;     })   },    get_coures_list(cateid) {     // 获取所有二级分类 及其 课程     return http.get("/home/nav/catescourses/?cateid=" + cateid).then(response => {       // console.log("左侧边栏获取课程分类及课程response.data:");       // console.log(response.data);       this.cc_list = response.data.clist;       this.course_list = response.data.courses;     })   },  })  export default cates; 

五、分类课程推荐(楼层设计)功能实现

首页分类课程推荐设计:
    显示推荐分类,获取不同楼层不同分类下的课程,点击不同分类时获取当前楼层分类下的推荐课程并显示

a.效果图

在这里插入图片描述

b.后端代码

为了便于理解,建立三张表:频道表、频道分类表、频道课程表,模型类如下:

# a.频道表 class ChannelModel(BaseModel):     name = models.CharField(max_length=255, unique=True)     picurl = models.CharField(max_length=100)     sort = models.IntegerField()     def __str__(self):         return self.name     class Meta:         verbose_name = '频道表'         verbose_name_plural = '频道表'         db_table = 'channel'  # b.频道分类表 id,name,显示顺序,频道id,类别(1-添加的 2-分类的),分类id class ChannelCategoryModel(BaseModel):     name = models.CharField(max_length=255)     sort = models.IntegerField()     channel = models.ForeignKey(ChannelModel, on_delete=models.CASCADE, related_name='cates')     type = models.IntegerField()     cateid = models.IntegerField()     def __str__(self):         return self.name      class Meta:         verbose_name = '频道分类表'         verbose_name_plural = '频道分类表'         db_table = 'channel_cates'  # c.频道分类课程表 id,name,图标,价格,难度,购买人数,频道分类id class ChannelCoursesModel(BaseModel):     name = models.CharField(max_length=255,unique=True)     picurl = models.CharField(max_length=100)     price = models.FloatField()     sales = models.IntegerField(default=0)     level = models.IntegerField()     ccates = models.ForeignKey(ChannelCategoryModel, on_delete=models.CASCADE, related_name='courses')     def __str__(self):         return self.name     class Meta:         verbose_name = '频道分类课程表'         verbose_name_plural = '频道分类课程表'         db_table = 'channel_courses' 

频道、频道分类、频道课程序列化器:

# -- # c.频道分类课程序列化器 class ChannelCourseSerializer(serializers.ModelSerializer):     class Meta:         model = ChannelCoursesModel         fields = '__all__'  # b.频道分类序列化器- class ChannelCategorySerializer(serializers.ModelSerializer):     courses = ChannelCourseSerializer(many=True, read_only=True)     class Meta:         model = ChannelCategoryModel         fields = '__all__'  # a.频道序列化器 class ChannelSerializer(serializers.ModelSerializer):     cates = ChannelCategorySerializer(many=True, read_only=True)     class Meta:         model = ChannelModel         fields = '__all__' 

获取首页推荐课程分类信息:

# 6.2 楼层-课程卡片--(无顺序版-->直接嵌套序列化器) class HomeCourseView(APIView):     def get(self, request):         channels = ChannelModel.objects.order_by('sort').all()         ser_channels = ChannelSerializer(channels, many=True)         return Response({"code":"200", "data":ser_channels.data}) 

配置urls:

    path('homecourse/', HomeCourseView.as_view()), #首页推荐分类课程 

c.前端代码

src/components/NewCourse.vue:

<template>   <div class="bg000">     <div class="container-types new-course" v-for="record,index in course.data" :key="index">       <!-- 第一级:pic -->       <h3           class="types-title justify-content_flex-start"           :style="{ 'background-image': `url('${record.picurl}')` }"       >{{index}}          <!-- 第二级 eg:推荐、前端课程 -->         <ul class="menu">           <li :class="{'curr': state.current_menu[index]==key}"               v-for="item,key in record.cates" :key="key"               @click="selectTab(item,key,index)"           >             <a>{{ item.name }}</a>           </li>         </ul>        </h3>              <!-- 对应分类下的课程信息 -->       <div class="list clearfix show" >         <a class="item" v-for="citem,cindex in record.cates[courseList.data[index]].courses" :key="cindex">           <div class="img"                :style="{ 'background-image': `url('${citem.picurl}')` }">           </div>           <div class="title ellipsis2">{{ citem.name }}</div>           <div class="difficulty">{{ citem.level }} · {{ citem.person }}人报名</div>           <div class="bottom clearfix">             <span class="price l red bold">¥{{ citem.price }}</span>           </div>         </a>       </div>       </div>     </div> </template> 
<script setup> import {reactive} from "vue" // 接口取回的数据 import course from "../api/home";  course.get_courses_list();  // 定义每个频道TAB的下标 let courseList = reactive({data: [0,0,0]})  // 点击事件item-cates,key-二级index,index-一级index const selectTab = (item,key,index) => {   courseList.data[index] = key   state.current_menu[index] = key } const state = reactive({   current_menu: [0,0,0], })  </script> 

src/api/home.js:

import { reactive } from "vue"; import http from "../http"; const course = reactive({   data: [],  // 分类下课程信息    get_courses_list() {     // 获取分类下课程信息     return http.get("/home/homecourse/").then(response => {       console.log("response.data.data");       console.log(response.data.data);       this.data = response.data.data;   },  })  export default course;  

在这里插入图片描述

    广告一刻

    为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!