文章目录
前言
在当今数字化教育浪潮中,构建一个高效且用户友好的线上教育平台至关重要。本博客将指导您使用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"
> {{ s.name }} </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> ·
<span class="difficulty"> {{c.describe}} </span> ·
<span class="difficulty" v-if="c.level==1"> 0基础 </span> ·
<span class="difficulty" v-if="c.level==2"> 中级 </span> ·
<span class="difficulty" v-if="c.level==3"> 高级 </span> ·
<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;