一、轮播图功能实现
1.安装依赖模块和配置
图片处理模块
pip install pillow
上传文件相关配置
settings.py,由于我们需要在后台上传我们的轮播图图片,所以我们需要在django中配置一下上传文件的相关配置,有了它之后,就不需要我们自己写上传文件,保存文件的操作了,看配置:
# 访问静态文件的url地址前缀
STATIC_URL = '/static/'
# 设置django的静态文件目录
# STATICFILES_DIRS = [
# os.path.join(BASE_DIR,"static")
# ]
# 项目中存储上传文件的根目录[暂时配置],注意,uploads目录需要手动创建否则上传文件时报错
MEDIA_ROOT=os.path.join(BASE_DIR,"uploads")
# 访问上传文件的url地址前缀
MEDIA_URL ="/media/"
在django项目中转换上传文件的url地址,总路由urls.py新增代码:
from django.urls import re_path
from django.conf import settings
from django.views.static import serve
urlpatterns = [
...
re_path(r'media/(?P<path>.*)', serve, {"document_root": settings.MEDIA_ROOT}),
]
2.注册home子应用
因为当前功能是drf的第一个功能,所以我们先创建一个子应用home,创建在lufapi/apps目录下:E:\axiangmu\luffcc\lufapi\lufapi\apps>python …/…/manage.py startapp home
注册home子应用,因为子应用的位置发生了改变,所以为了原来子应用的注册写法,所以新增一个导包路径:
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
# BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 新增一个系统导包路径
import sys
#sys.path使我们可以直接import导入时使用到的路径,所以我们直接将我们的apps路径加到默认搜索路径里面去,那么django就能直接找到apps下面的应用了
sys.path.insert(0,os.path.join(BASE_DIR,"apps"))
INSTALLED_APPS = [
# 注意,加上drf框架的注册
'rest_framework',
# 子应用
'home',
]
新建开发分支进行独立开发
# 新建一个分支
git branch 分支名称
# 查看所有分支
git branch
# 切换分支[-b表示新建分支的同时并切换到新分支]
git checkout -b 分支名称
# 删除分支
git branch -d 分支名称
接下来,我们可以创建一个dev开发分支并在开发分支下:
git branch dev
git checkout dev
3.创建轮播图的模型
home/models.py
from django.db import models
# Create your models here.
class Banner(models.Model):
"""轮播广告图模型"""
# 模型字段
title = models.CharField(max_length=500, verbose_name="广告标题")
link = models.CharField(max_length=500, verbose_name="广告链接")
# upload_to 设置上传文件的保存子目录,将来上传来的文件会存到我们的media下面的banner文件夹下,这里存的是图片地址。
image_url = models.ImageField(upload_to="banner", null=True, blank=True, max_length=255, verbose_name="广告图片")
remark = models.TextField(verbose_name="备注信息")
is_show = models.BooleanField(default=False, verbose_name="是否显示") #将来轮播图肯定会更新,到底显示哪些
orders = models.IntegerField(default=1, verbose_name="排序")
is_deleted = models.BooleanField(default=False, verbose_name="是否删除")
# 表信息声明
class Meta:
db_table = "ly_banner"
verbose_name = "轮播广告"
verbose_name_plural = verbose_name
# 自定义方法[自定义字段或者自定义工具方法]
def __str__(self):
return self.title
数据迁移
python manage.py makemigrations
python manage.py migrate
4.序列化器
home/serializers.py
from rest_framework import serializers
from . import models
class BannerModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Banner
fields = ['id', 'image_url', 'link']
5.视图代码
views.py
from django.shortcuts import render
# Create your views here.
from rest_framework.generics import ListAPIView
from lufapi.settings import contains
from . import models
from .serializers import BannerModelSerializer
class BannerView(ListAPIView):
queryset = models.Banner.objects.filter(is_show=True, is_deleted=False)[0:contains.BANNER_LENGTH]
serializer_class = BannerModelSerializer
在settings配置文件夹中创建一个contains.py配置文件,将来里面存放我们所有的一些常量信息配置,比如上面的轮播图数据切片长度
BANNER_LENGTH = 3
6.路由代码
home/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('banner/', views.BannerView.as_view()),
]
把home的路由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'media/(?P<path>.*)', serve, {"document_root": settings.MEDIA_ROOT}),
path('home/', include("home.urls") ),
]
二、xadmin配置
1.基本配置
需要有一个后台提供数据.安装xadmin
pip install https://codeload.github.com/sshwsfc/xadmin/zip/django2 -i https://pypi.douban.com/simple/
在配置文件中注册如下应用
INSTALLED_APPS = [
...
'xadmin',
'crispy_forms',
'reversion',
...
]
# 修改使用中文界面
LANGUAGE_CODE = 'zh-Hans'
# 修改时区
TIME_ZONE = 'Asia/Shanghai'
xadmin有建立自己的数据库模型类,需要进行数据库迁移
python manage.py makemigrations
python manage.py migrate
在总路由中添加xadmin的路由信息
import xadmin
xadmin.autodiscover()
# version模块自动注册需要版本控制的 Model
from xadmin.plugins import xversion
xversion.register_models()
urlpatterns = [
path(r'xadmin/', xadmin.site.urls),
]
如果之前没有创建超级用户,需要创建,如果有了,则可以直接使用之前的。
python manage.py createsuperuser
2.给xadmin设置基本站点配置信息
在当前子应用中创建adminx.py:
import xadmin
from xadmin import views
class BaseSetting(object):
"""xadmin的基本配置"""
enable_themes = True # 开启主题切换功能
use_bootswatch = True
xadmin.site.register(views.BaseAdminView, BaseSetting)
class GlobalSettings(object):
"""xadmin的全局配置"""
site_title = "31期学城" # 设置站点标题
site_footer = "31期学城有限公司" # 设置站点的页脚
menu_style = "accordion" # 设置菜单折叠
xadmin.site.register(views.CommAdminView, GlobalSettings)
3.注册轮播图模型到xadmin中
在当前子应用中创建adminx.py,添加如下代码
# 轮播图
from . import models
class BannerXAdmin(object):
list_display = ['id', 'title', 'link','image_url']
search_fields = ['id', 'title']
xadmin.site.register(models.Banner, BannerXAdmin)
4.修改后端xadmin中子应用名称
子应用/apps.py
class HomeConfig(AppConfig):
name = 'home'
verbose_name = '我的首页'
在home这个app下面的__init__.py
中设置下面的内容
default_app_config = "home.apps.HomeConfig"
下面给轮播图添加测试数据,如图:
postman测试图:
5.客户端代码获取数据
在前端lufcity的Banner.vue中的代码:
<template>
<el-carousel indicator-position="outside" height="400px">
<el-carousel-item v-for="(value,index) in banner_list" :key="value.id">
<a :href="value.link">
<!-- router-link 站内跳转 -->
<!-- a标签 站外跳转 -->
<img :src="value.imag_url" alt="" style="width: 100%;height: 400px;">
<!-- <img src="@/assets/banner1.png" alt="">-->
</a>
</el-carousel-item>
</el-carousel>
</template>
<script>
export default {
name: "Banner",
data(){
return {
banner_list:[
]
}
},
methods:{
get_banner_data(){
this.$axios.get(`${this.$settings.Host}/home/banner`)
.then((res)=>{
console.log(res);
this.banner_list = res.data;
}
)
.catch((error)=>{
}
)
}
},
created(){
this.get_banner_data();
},
}
</script>
<style scoped>
</style>
在开发之前应该将分支切换至dev,现在进行提交代码
git add .
git commit -m 'v1轮播图提交'
git checkout master
git merge dev
git add .
git commit -m 'v2master轮播图提交'
git push origin master
git remote add origin https://gitee.com/cui_wenjun/study.git
git push -u origin master
三、导航功能实现
1.创建模型
引入一个公共模型【抽象模型,不会在数据迁移的时候为它创建表】lufapi的apps/home/modes.py
from django.db import models
from lufapi.utils.models import BaseModel
# Create your models here.
class Banner(models.Model):
"""轮播广告图模型"""
# 模型字段
title = models.CharField(max_length=500, verbose_name="广告标题")
link = models.CharField(max_length=500, verbose_name="广告链接")
# upload_to 设置上传文件的保存子目录,将来上传来的文件会存到我们的media下面的banner文件夹下,这里存的是图片地址。
image_url = models.ImageField(upload_to="banner", null=True, blank=True, max_length=255, verbose_name="广告图片")
remark = models.TextField(verbose_name="备注信息")
is_show = models.BooleanField(default=False, verbose_name="是否显示") #将来轮播图肯定会更新,到底显示哪些
orders = models.IntegerField(default=1, verbose_name="排序")
is_deleted = models.BooleanField(default=False, verbose_name="是否删除")
# 表信息声明
class Meta:
db_table = "ly_banner"
verbose_name = "轮播广告"
verbose_name_plural = verbose_name
# 自定义方法[自定义字段或者自定义工具方法]
def __str__(self):
return self.title
class Nav(BaseModel):
"""导航菜单模型"""
POSITION_OPTION = (
(1, "顶部导航"),
(2, "脚部导航"),
)
title = models.CharField(max_length=500, verbose_name="导航标题")
link = models.CharField(max_length=500, verbose_name="导航链接")
position = models.IntegerField(choices=POSITION_OPTION, default=1, verbose_name="导航位置")
is_site = models.BooleanField(default=False, verbose_name="是否是站外地址")
class Meta:
db_table = 'ly_nav'
verbose_name = '导航菜单'
verbose_name_plural = verbose_name
# 自定义方法[自定义字段或者自定义工具方法]
def __str__(self):
return self.title
公共模型,保存项目的公共代码库目录下lufapi/utils/models.py文件中。
from django.db import models
class BaseModel(models.Model):
"""公共模型"""
is_show = models.BooleanField(default=False, verbose_name="是否显示")
orders = models.IntegerField(default=1, verbose_name="排序")
is_deleted = models.BooleanField(default=False, verbose_name="是否删除")
created_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
updated_time = models.DateTimeField(auto_now=True, verbose_name="修改时间")
class Meta:
abstract = True #执行数据库同步指令是,不会生成表
数据迁移
python manage.py makemigrations
python manage.py migrate
2.序列化器代码
在上面的序列化器中:
class NavModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Nav
fields = ['id', 'title', 'link', 'is_site']
3.视图代码
view.py
from .serializers import BannerModelSerializer,NavModelSerializer
class NavView(ListAPIView):
queryset = models.Nav.objects.filter(is_deleted=False, is_show=True, position=1)[0:contains.NAV_TOP_LENGH]
serializer_class = NavModelSerializer
4.常量配置
settings/contains.py
BANNER_LENGTH = 3
NAV_TOP_LENGH = 5
NAV_BOTTOM_LENGH = 5
5.路由代码
urls.py
urlpatterns = [
path('banner/', views.BannerView.as_view()),
path('nav/top/', views.NavView.as_view()),
# path(r'nav/bottom/', views.BottomView.as_view(),),
]
6.注册导航模型到xadmin中
在当前子应用adminx.py,添加如下代码:
class NavXAdmin(object):
list_display = ['id', 'title', 'link']
search_fields = ['id', 'title']
xadmin.site.register(models.Nav, NavXAdmin)
添加测试数据
7.客户端代码获取数据
VHeader.vue代码:
<template>
<div class="total-header">
<div class="header">
<el-container>
<el-header height="80px" class="header-cont">
<el-row>
<el-col class="logo" :span="3">
<a href="/">
<img src="@/assets/header-logo.svg" alt="">
</a>
</el-col>
<el-col class="nav" :span="10">
<el-row>
<el-col :span="3" v-for="(value,index) in nav_data_list" :key="index">
<a :href="value.link" class="active" v-if="value.is_site">{{value.title}}</a>
<router-link :to="value.link" v-else>{{value.title}}</router-link>
</el-col>
</el-row>
</el-col>
<el-col :span="11" class="header-right-box">
<div class="search">
<input type="text" id="Input" placeholder="请输入想搜索的课程" style="" @blur="inputShowHandler" ref="Input"
v-show="!s_status">
<ul @click="ulShowHandler" v-show="s_status" class="search-ul" ref="xx">
<span>Python</span>
<span>Linux</span>
</ul>
<p>
<img class="icon" src="@/assets/sousuo1.png" alt="" v-show="s_status">
<img class="icon" src="@/assets/sousuo2.png" alt="" v-show="!s_status">
<img class="new" src="@/assets/new.png" alt="">
</p>
</div>
<div class="register" v-show="!token">
<router-link to="/user/login">
<button class="signin">登录</button>
</router-link>
|
<a target="_blank" href="https://www.luffycity.com/signup">
<router-link to="/register">
<button class="signup">注册</button>
</router-link>
</a>
</div>
<div class="shop-car" v-show="token">
<router-link to="/cart">
<b>6</b>
<img src="@/assets/shopcart.png" alt="">
<span>购物车 </span>
</router-link>
</div>
<div class="nav-right-box" v-show="token">
<div class="nav-right">
<router-link to="/myclass">
<div class="nav-study">我的教室</div>
</router-link>
<div class="nav-img" @mouseover="personInfoList" @mouseout="personInfoOut">
<img src="@/assets/touxiang.png" alt="" style="border: 1px solid rgb(243, 243, 243);">
<ul class="home-my-account" v-show="list_status" @mouseover="personInfoList">
<li>
我的账户
<img src="https://hcdn2.luffycity.com/media/frontend/activity/back_1568185800.821227.svg"
alt="">
</li>
<li>
我的订单
<img src="https://hcdn2.luffycity.com/media/frontend/activity/back_1568185800.821227.svg"
alt="">
</li>
<li>
贝里小卖铺
<img src="https://hcdn2.luffycity.com/media/frontend/activity/back_1568185800.821227.svg"
alt="">
</li>
<li>
我的优惠券
<img src="https://hcdn2.luffycity.com/media/frontend/activity/back_1568185800.821227.svg"
alt="">
</li>
<li>
<span>
我的消息
<b>(26)</b>
</span>
<img src="https://hcdn2.luffycity.com/media/frontend/activity/back_1568185800.821227.svg"
alt="">
</li>
<li @click="logout">
退出
<img src="https://hcdn2.luffycity.com/media/frontend/activity/back_1568185800.821227.svg"
alt="">
</li>
</ul>
</div>
</div>
</div>
</el-col>
</el-row>
</el-header>
</el-container>
</div>
</div>
</template>
<script>
export default {
name: "Vheader",
data() {
return {
token: false, // 登录成功与否的标记
s_status: true, // 放大镜效果切换控制,默认input标签不显示
list_status: false, // 个人中心下拉菜单是否显示
nav_data_list:[],
}
},
created(){
this.get_nav_data();
this.check_login();
},
methods: {
ulShowHandler() {
// console.log(this);
this.s_status = false;
console.log(this.$refs);
this.$nextTick(function () {
console.log(this);
this.$refs.Input.focus();
});
},
inputShowHandler() {
console.log('xxxxx')
this.s_status = true;
},
personInfoList() {
this.list_status = true;
},
personInfoOut() {
this.list_status = false;
},
get_nav_data(){
this.$axios.get(`${this.$settings.Host}/home/nav/top/`)
.then((res)=>{
this.nav_data_list = res.data;
})
},
check_login(){
this.token = localStorage.token || sessionStorage.token;
//console.log(this.token);
},
// 退出登录
logout(){
sessionStorage.removeItem('token');
sessionStorage.removeItem('username');
sessionStorage.removeItem('id');
localStorage.removeItem('token');
localStorage.removeItem('username');
localStorage.removeItem('id');
console.log('xxxxxx')
this.check_login();
// this.token = false;
}
}
}
</script>
<style scoped>
.header-cont .nav .active {
color: #f5a623;
font-weight: 500;
border-bottom: 2px solid #f5a623;
}
.total-header {
min-width: 1200px;
z-index: 100;
box-shadow: 0 4px 8px 0 hsla(0, 0%, 59%, .1);
}
.header {
width: 1200px;
margin: 0 auto;
}
.header .el-header {
padding: 0;
}
.logo {
height: 80px;
/*line-height: 80px;*/
/*text-align: center;*/
display: flex; /* css3里面的弹性布局,高度设定好之后,设置这个属性就能让里面的内容居中 */
align-items: center;
}
.nav .el-row .el-col {
height: 80px;
line-height: 80px;
text-align: center;
}
.nav a {
font-size: 15px;
font-weight: 400;
cursor: pointer;
color: #4a4a4a;
text-decoration: none;
}
.nav .el-row .el-col a:hover {
border-bottom: 2px solid #f5a623
}
.header-cont {
position: relative;
}
.search input {
width: 185px;
height: 26px;
font-size: 14px;
color: #4a4a4a;
border: none;
border-bottom: 1px solid #ffc210;
outline: none;
}
.search ul {
width: 185px;
height: 26px;
display: flex;
align-items: center;
padding: 0;
padding-bottom: 3px;
border-bottom: 1px solid hsla(0, 0%, 59%, .25);
cursor: text;
margin: 0;
font-family: Helvetica Neue, Helvetica, Microsoft YaHei, Arial, sans-serif;
}
.search .search-ul, .search #Input {
padding-top: 10px;
}
.search ul span {
color: #545c63;
font-size: 12px;
padding: 3px 12px;
background: #eeeeef;
cursor: pointer;
margin-right: 3px;
border-radius: 11px;
}
.hide {
display: none;
}
.search {
height: auto;
display: flex;
}
.search p {
position: relative;
margin-right: 20px;
margin-left: 4px;
}
.search p .icon {
width: 16px;
height: 16px;
cursor: pointer;
}
.search p .new {
width: 18px;
height: 10px;
position: absolute;
left: 15px;
top: 0;
}
.register {
height: 36px;
display: flex;
align-items: center;
line-height: 36px;
}
.register .signin, .register .signup {
font-size: 14px;
color: #5e5e5e;
white-space: nowrap;
}
.register button {
outline: none;
cursor: pointer;
border: none;
background: transparent;
}
.register a {
color: #000;
outline: none;
}
.header-right-box {
height: 100%;
display: flex;
align-items: center;
font-size: 15px;
color: #4a4a4a;
position: absolute;
right: 0;
top: 0;
}
.shop-car {
width: 99px;
height: 28px;
border-radius: 15px;
margin-right: 20px;
background: #f7f7f7;
display: flex;
align-items: center;
justify-content: center;
position: relative;
cursor: pointer;
}
.shop-car b {
position: absolute;
left: 28px;
top: -1px;
width: 18px;
height: 16px;
color: #fff;
font-size: 12px;
font-weight: 350;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
background: #ff0826;
overflow: hidden;
transform: scale(.8);
}
.shop-car img {
width: 20px;
height: 20px;
margin-right: 7px;
}
.nav-right-box {
position: relative;
}
.nav-right-box .nav-right {
float: right;
display: flex;
height: 100%;
line-height: 60px;
position: relative;
}
.nav-right .nav-study {
font-size: 15px;
font-weight: 300;
color: #5e5e5e;
margin-right: 20px;
cursor: pointer;
}
.nav-right .nav-study:hover {
color: #000;
}
.nav-img img {
width: 26px;
height: 26px;
border-radius: 50%;
display: inline-block;
cursor: pointer;
}
.home-my-account {
position: absolute;
right: 0;
top: 60px;
z-index: 101;
width: 190px;
height: auto;
background: #fff;
border-radius: 4px;
box-shadow: 0 4px 8px 0 #d0d0d0;
}
li {
list-style: none;
}
.home-my-account li {
height: 40px;
font-size: 14px;
font-weight: 300;
color: #5e5e5e;
padding-left: 20px;
padding-right: 20px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
}
.home-my-account li img {
cursor: pointer;
width: 5px;
height: 10px;
}
.home-my-account li span {
height: 40px;
display: flex;
align-items: center;
}
.home-my-account li span b {
font-weight: 300;
margin-top: -2px;
}
</style>
底部导航待补充