5.首页-轮播图-导航栏菜单
目录
首页
1.对于首页要展示的数据和功能,我们先创建一个单独的字应用来完成。
cd renranapi/apps/
python ../../manage.py startapp home
2.总路由urls.py配置home路径
from django.urls import path,include
urlpatterns = [
path(r'xadmin/', xadmin.site.urls),
path('users/', include('users.urls')),
path('home/', include('home.urls')),
]
3.在apps/home/下创建子路由文件urls.py
from django.urls import path
from . import views
urlpatterns = [
# path('login/', obtain_jwt_token),
]
4.注册子应用,settings/dev.py,代码:
INSTALLED_APPS = [
'home',
]
1.轮播图功能实现
安装依赖模块和配置
图片处理模块
前面已经安装了,如果没有安装则需要安装
pip install pillow
上传文件相关配置
settings/dev.py
# 项目中存储上传文件的根目录[暂时配置],注意,media目录需要手动创建否则上传文件时报错
MEDIA_ROOT=os.path.join(BASE_DIR,'media')
# 访问上传文件的url地址前缀(MEDIA_ROOT路径别名)
MEDIA_URL ="/media/"
在xadmin中输出上传文件的Url地址
总路由urls.py新增代码:
from django.urls import re_path
from django.conf import settings
from django.views.static import serve
urlpatterns = [
# 前端发来的路径经过serve响应,到后台静态文件找到路径图片,再经过serve响应到前端显示
re_path(r'media/(?P<path>.*)', serve, {"document_root": settings.MEDIA_ROOT}),
]
创建轮播图的模型
home/models.py
from django.db import models
# 论波图模型类
class Carousel(models.Model):
"""轮播图"""
# upload_to 存储子目录,真实存放地址会使用配置中的MADIE_ROOT+upload_to
image = models.ImageField(upload_to='carousel', verbose_name='轮播图', null=True,blank=True)
name = models.CharField(max_length=150, verbose_name='轮播图名称')
note = models.CharField(max_length=150, verbose_name='备注信息')
link = models.CharField(max_length=150, verbose_name='轮播图广告地址')
orders = models.IntegerField(verbose_name='显示顺序')
is_show=models.BooleanField(verbose_name="是否上架",default=False)
is_delete=models.BooleanField(verbose_name="逻辑删除",default=False) # 假删除
class Meta:
db_table = 'rr_carousel'
verbose_name = '轮播图'
verbose_name_plural = verbose_name # 改掉名字的复数形式
def __str__(self):
return self.name
数据迁移
python manage.py makemigrations
python manage.py migrate
注册轮播图模型到xadmin中
import xadmin
from xadmin import views
from .models import Carousel
class CarouselModelAdmin(object):
list_display = ["id","name","link","is_show",] # 后台显示字段
list_editable = ["is_show",] # 可编辑列表
# 注册Carousel到xadmin中
xadmin.site.register(Carousel,CarouselModelAdmin)
修改后端xadmin中子应用名称
home/apps.py
class HomeConfig(AppConfig):
name = 'home'
verbose_name = '我的首页'
home/__init__.py
default_app_config = "home.apps.HomeConfig"
后台接口
1.路由home/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('carousel/list/', views.CarouselView.as_view()),
]
2.视图代码
settings/contains.py加入轮播图常量
# 轮波图数量
CAROUSEL_COUNTS = 3
视图 home/views.py
from django.shortcuts import render
from rest_framework.generics import ListAPIView
from .models import Carousel # 引入模型类
from renranapi.settings import contains # 引入常量
from .serializers import CarouselModelSerializer # 引入序列化器类
class CarouselView(ListAPIView):
# 就筛选3张图片轮播,放在序列化器中进行加工
queryset = Carousel.objects.filter(is_delete=False,is_show=True).order_by('orders','id')[0:contains.CAROUSEL_COUNTS]
serializer_class = CarouselModelSerializer
3.序列化器
home/serializers.py
from rest_framework import serializers
from .models import Carousel
class CarouselModelSerializer(serializers.ModelSerializer):
class Meta:
model = Carousel
fields = ['id','image','name','link']
客户端代码获取数据
Home.vue代码:
<template>
...
<div class="banner">
<el-carousel height="272px" indicator-position="none" :interval="interval">
<el-carousel-item v-for="(carousel_values,carousel_index) in carousel_list" :key="carousel_index">
<a :href="carousel_values.link">
<img :src="carousel_values.image" alt="">
</a>
</el-carousel-item>
</el-carousel>
</div>
...
</template>
<script>
import Header from "./common/Header";
import Footer from "./common/Footer";
export default {
name:"Home",
data(){
return {
carousel_list:[], //轮波图列表
interval:3000, //轮波间隔时间
}
},
// 页面加载,自动加载数据(轮波图)
created() {
this.$axios.get(`${this.$settings.host}/home/carousel/list/`)
.then((res)=>{//成功
this.carousel_list = res.data //响应回来的数据
}).catch((error)=>{//失败
})
},
components:{
Header,
Footer,
}
}
</script>
<style>
.banner img{
max-height: 100%;
max-width: 100%;
}
</style>
2.导航功能实现
调整首页头部子组件的页面,Header.vue,效果:
<template>
<div class="header">
<nav class="navbar">
<div class="width-limit">
<!-- 左上方 Logo -->
<a class="logo" href="/"><img src="/static/image/nav-logo.png" /></a>
<!-- 右上角 -->
<!-- 未登录显示登录/注册/写文章 -->
<a class="btn write-btn" target="_blank" href="/writer"><img class="icon-write" src="/static/image/write.svg">写文章</a>
<router-link class="btn sign-up" id="sign_up" to="/user/register">注册</router-link>
<router-link class="btn log-in" id="sign_in" to="/user/login">登录</router-link>
<div class="container">
<div class="collapse navbar-collapse" id="menu">
<ul class="nav navbar-nav">
<li class="tab active">
<a href="/">
<i class="iconfont ic-navigation-discover menu-icon"></i>
<span class="menu-text">首页</span>
</a>
</li>
<li class="tab">
<a href="/">
<i class="iconfont ic-navigation-follow menu-icon"></i>
<span class="menu-text">关注</span>
</a>
<ul class="dropdown-menu">
<li><a href=""><i class="iconfont ic-comments"></i> <span>评论</span></a></li>
<li><a href=""><i class="iconfont ic-chats"></i> <span>简信</span></a></li>
<li><a href=""><i class="iconfont ic-requests"></i> <span>投稿请求</span></a></li>
<li><a href=""><i class="iconfont ic-likes"></i> <span>喜欢和赞</span></a></li>
<li><a href=""><i class="iconfont ic-follows"></i> <span>关注</span></a></li>
<li><a href=""><i class="iconfont ic-money"></i> <span>赞赏和付费</span></a></li>
<li><a href=""><i class="iconfont ic-others"></i> <span>其它提醒</span></a></li>
</ul>
</li>
<li class="tab">
<a href="/">
<i class="iconfont ic-navigation-notification menu-icon"></i>
<span class="menu-text">消息</span>
</a>
</li>
<li class="search">
<form target="_blank" action="/search" accept-charset="UTF-8" method="get">
<input type="text" name="q" id="q" value="" autocomplete="off" placeholder="搜索" class="search-input">
<a class="search-btn" href="javascript:void(0)"></a>
</form>
</li>
</ul>
</div>
</div>
<!-- 如果用户登录,显示下拉菜单 -->
</div>
</nav>
</div>
</template>
<script>
export default {
name: "Header"
}
</script>
<style scoped>
.header{
height: 56px;
}
.container {
width: 960px;
margin-right: auto;
margin-left: auto;
padding-left: 15px;
padding-right: 15px;
}
.container:after, .container:before {
content: " ";
display: table;
}
.container:after {
clear: both;
}
.navbar {
background-color: #fff;
border-color: #f0f0f0;
top: 0;
border-width: 0 0 1px;
border-radius: 0;
}
.navbar-nav {
float: left;
margin: 0;
}
.navbar:after, .navbar:before {
content: " ";
display: table;
box-sizing: border-box;
}
.nav:after, .nav:before {
content: " ";
display: table;
}
nav .width-limit {
min-width: 768px;
max-width: 1440px;
margin: 0 auto;
}
nav .logo {
float: left;
height: 56px;
padding: 0;
}
nav .logo img {
height: 100%;
vertical-align: middle;
border: 0;
}
.btn {
display: inline-block;
margin-bottom: 0;
font-weight: 400;
text-align: center;
vertical-align: middle;
touch-action: manipulation;
cursor: pointer;
background-image: none;
border: 1px solid transparent;
white-space: nowrap;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857;
border-radius: 4px;
}
nav .write-btn {
float: right;
width: 100px;
height: 24px;
line-height: 24px;
margin: 8px 12px 0;
border-radius: 20px;
font-size: 15px;
color: #fff;
background-color: #ea6f5a;
text-decoration: none;
}
nav .log-in, nav .log-in:hover {
color: #969696;
}
nav .log-in {
float: right;
margin: 11px 6px 0 10px;
font-size: 15px;
}
nav .sign-up {
float: right;
width: 80px;
height: 24px;
line-height: 24px;
margin: 9px 5px 0 15px;
border: 1px solid rgba(236,97,73,.7);
border-radius: 20px;
font-size: 15px;
color: #ea6f5a;
background-color: transparent;
}
nav .icon-write {
margin-right: 3px;
width: 19px;
height: 19px;
vertical-align: middle;
}
nav .menu-text{
font-size: 17px;
}
nav .active a{
color: #ea6f5a;
}
nav .menu-icon {
width: 20px;
height: 20px;
vertical-align: baseline;
margin-right: 3px;
}
.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;
float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;
font-size:14px;text-align:left;background-color:#fff;border:1px solid #ccc;
border:1px solid rgba(0,0,0,.15);border-radius:4px;
box-shadow:0 6px 12px rgba(0,0,0,.175);
background-clip:padding-box}
.dropdown-menu.pull-right{right:0;left:auto}
.dropdown-menu .divider{
height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}
.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;
line-height:1.42857;color:#333;white-space:nowrap}
.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{
text-decoration:none;color:#262626;background-color:#f5f5f5}
.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{
color:#fff;text-decoration:none;outline:0;background-color:#337ab7}
.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{
color:#777
}
.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{
text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:not-allowed}
.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}
@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}
.navbar-right .dropdown-menu-left{left:0;right:auto}}
.dropdown-menu{
width:200px;margin-top:-1px;border-radius:0 0 4px 4px
}
.dropdown-menu li{margin:0}
.dropdown-menu a{height:auto;padding:10px 20px;line-height:30px}
.dropdown-menu a:hover{background-color:#f5f5f5}
.dropdown-menu i{margin-right:15px;font-size:22px;color:#ea6f5a;
vertical-align:middle
}
.dropdown-menu span{vertical-align:middle}
.dropdown-menu .badge{position:absolute;right:15px;margin-top:7px}
nav .nav .tab a {
height: 56px;
line-height: 26px;
padding: 15px;
background: none;
}
.nav .tab:hover .dropdown-menu{
display: block;
}
nav .navbar-nav li {
margin-right: 10px;
float: left;
position: relative;
display: block;
box-sizing: border-box;
height: 56px;
line-height: 56px;
}
.navbar-nav {
float: left;
margin: 0;
}
nav form {
position: relative;
top: 9px;
margin: 0 0 20px;
box-sizing: border-box;
line-height: 20px;
}
nav form .search-input {
padding: 0 40px 0 20px;
height: 38px;
font-size: 14px;
border: 1px solid #eee;
border-radius: 40px;
background: #eee;
transition: width .5s;
width: 240px;
outline: none;
}
nav form .search-input:focus {
width: 320px;
outline: none;
}
.navbar-default .navbar-collapse, .navbar-default .navbar-form {
border-color: #e7e7e7;
padding-left: 0;
padding-right: 0;
box-sizing: border-box;
width: auto;
border-top: 0;
box-shadow: none;
}
.navbar {
background-color: #fff;
top: 0;
border-radius: 0;
position: fixed;
right: 0;
left: 0;
z-index: 1030;
min-height: 50px;
margin-bottom: 20px;
border-bottom: 1px solid #f0f0f0;
}
nav {
height: 56px;
}
.navbar:after, .navbar:before {
content: " ";
display: table;
}
nav form .search-btn {
position: absolute;
display: block;
top: 0;
right: 10px;
width: 30px;
height: 30px;
padding: 0;
margin: 5px -1px 0 0;
background: transparent url("../../../static/image/search-focus.svg") no-repeat 6px 6px;
background-size: 20px;
}
nav form .search-input:focus~a{
border-radius: 50%;
background-color: #696969;
background-image: url("../../../static/image/search-blur.svg");
}
nav .sign-up:hover {
color: #ec6149;
border-color: #ec6149;
background-color: rgba(236,97,73,.05);
}
nav .write-btn:focus, nav .write-btn:hover {
color: #fff;
background-color: #ec6149;
}
</style>
创建模型
id | name | pid |
---|---|---|
1 | 消息 | 0 |
2 | 广场 | 0 |
3 | 评论 | 1 |
4 | 留言 | 1 |
1.引入一个公共模型【抽象模型,不会在数据迁移的时候为它创建表】
公共模型,保存项目的公共代码库目录下renranapi/utils/model.py文件中。
from django.db import models
# 公共模型类
class BaseModel(models.Model):
name = models.CharField(max_length=150, verbose_name='标题',null=True, blank=True)
orders = models.IntegerField(verbose_name='显示顺序',null=True, blank=True)
is_show = models.BooleanField(verbose_name="是否上架", default=True)
is_delete = models.BooleanField(verbose_name="逻辑删除", default=False)
created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间",null=True, blank=True)
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间",null=True, blank=True)
class Meta:
# 设置当前模型在数据迁移的时候不要为它创建表
abstract = True
def __str__(self):
return self.name
2.home/models.py创建导航栏模型类
from django.db import models
from renranapi.utils.models import BaseModel
# 导航栏菜单模型类,继承公共模型类
class Nav(BaseModel):
"""导航菜单"""
POSITION = (
(1,"头部导航"),
(2,"脚部导航"),
)
name = models.CharField(max_length=64, null=True, blank=True, verbose_name='导航名称')
# 站内rourer-link 站外a标签
is_http= models.BooleanField(default=True, verbose_name="是否站内的链接", help_text="如果是站内地址,则默认勾选")
link = models.CharField(max_length=500, verbose_name='导航地址', help_text="如果是站外链接,必须加上协议, 格式如: http://www.renran.cn")
# 自关联 两种写法 related_name 在定义主表的外键的时候,给这个外键定义好一个名称
pid = models.ForeignKey("Nav", related_name="son", null=True, blank=True, on_delete=models.DO_NOTHING, verbose_name="父亲导航",)
# pid = models.ForeignKey("self", related_name="son", null=True, blank=True, on_delete=models.DO_NOTHING, verbose_name="父亲导航",)
option = models.SmallIntegerField(choices=POSITION, default=1, verbose_name="导航位置")
# element-ui 组件中 icon图名称可以写到这里
icon = models.CharField(max_length=64,null=True, blank=True,verbose_name='导航图标')
class Meta:
db_table = 'rr_nav'
verbose_name = '导航菜单'
verbose_name_plural = verbose_name
@property # 把方法变成属性
def son_list(self):
"""子导航列表"""
# 自关联反向查询
result = self.son.filter(is_show=True,is_delete=False).order_by("orders")
data = []
for nav in result:
data.append({ # 二级菜单想要返回的数据
"name": nav.name,
"link": nav.link,
"is_http": nav.is_http,
"icon": nav.icon,
})
return data
数据迁移
python manage.py makemigrations
python manage.py migrate
注册导航模型到xadmin中
在home子应用adminx.py,添加如下代码
from .models import Nav
# 注册导航栏菜单
class NavModelAdmin(object):
list_display=["name","link","is_http"] # 后台显示字段
xadmin.site.register(Nav,NavModelAdmin)
后台接口
1.路由代码home/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('carousel/list/', views.CarouselView.as_view()),
path('nav/top/', views.NavTopView.as_view()),
path('nav/bottom/', views.NavBottomView.as_view()),
]
2.视图代码home/views.py
from rest_framework.generics import ListAPIView
from .models import Carousel,Nav # 引入模型类
from renranapi.settings import contains # 引入常量
from .serializers import CarouselModelSerializer,NavTopModelSerializer,NavBottomModelSerializer # 引入序列化器类
# 顶部导航栏菜单
class NavTopView(ListAPIView):
# 筛选1级菜单进行序列化
queryset = Nav.objects.filter(is_delete=False,is_show=True,option=1,pid=None).order_by('orders')
serializer_class = NavTopModelSerializer
# 底部导航栏菜单
class NavBottomView(ListAPIView):
# 筛选1级菜单进行序列化
queryset = Nav.objects.filter(is_delete=False,is_show=True,option=2,pid=None).order_by('orders')
serializer_class = NavBottomModelSerializer
3.序列化器home/serializers.py,代码:
from rest_framework import serializers
from .models import Nav
# 头部导航栏序列化器类
class NavTopModelSerializer(serializers.ModelSerializer):
class Meta:
model = Nav
# 客户端需要展示的数据
fields = ['id','son_list','name','is_http','link','icon']
# 尾部导航栏序列化器类
class NavBottomModelSerializer(serializers.ModelSerializer):
class Meta:
model = Nav
# 客户端需要展示的数据
fields = ['id','son_list','name','is_http','link','icon']
客户端获取导航数据
头部导航:Header.vue代码:
<template>
<div class="header">
<nav class="navbar">
<div class="width-limit">
<!-- 左上方 Logo -->
<a class="logo" href="/"><img src="/static/image/nav-logo.png" /></a>
<!-- 右上角 -->
<!-- 未登录显示登录/注册/写文章 -->
<a class="btn write-btn" target="_blank" href="/writer"><img class="icon-write" src="/static/image/write.svg">写文章</a>
<router-link class="btn sign-up" id="sign_up" to="/user/register">注册</router-link>
<router-link class="btn log-in" id="sign_in" to="/user/login">登录</router-link>
<div class="container">
<div class="collapse navbar-collapse" id="menu">
<ul class="nav navbar-nav">
<li class="tab active">
<a href="/">
<i class="iconfont ic-navigation-discover menu-icon"></i>
<span class="menu-text">首页</span>
</a>
</li>
<li class="tab" v-for="(nav_top_value,nav_top_index) in nav_top_list" :key="nav_top_index">
<router-link :to="nav_top_value.link" v-if="nav_top_value.is_http">
<i class="menu-icon" :class="nav_top_value.icon"></i> <!--图标-->
<span class="menu-text">{{ nav_top_value.name }}</span> <!--一级菜单名称-->
</router-link>
<a :href="nav_top_value.link" v-else>
<i class="menu-icon" :class="nav_top_value.icon"></i> <!--图标-->
<span class="menu-text">{{ nav_top_value.name }}</span> <!--一级菜单名称-->
</a>
<ul class="dropdown-menu" v-if="nav_top_value.son_list.length>0">
<li v-for="(nav_son_value,nav_son_index) in nav_top_value.son_list" :key="nav_son_index">
<router-link :to="nav_son_value.link" v-if="nav_son_value.http">
<i :class="nav_son_value.icon"></i>
<span>{{nav_son_value.name}}</span>
</router-link>
<a :href="nav_son_value.link" v-else>
<i :class="nav_son_value.icon"></i> <!--图标-->
<span>{{nav_son_value.name}}</span> <!--二级菜单名称-->
</a>
</li>
</ul>
</li>
<li class="search">
<form target="_blank" action="/search" accept-charset="UTF-8" method="get">
<input type="text" name="q" id="q" value="" autocomplete="off" placeholder="搜索" class="search-input">
<a class="search-btn" href="javascript:void(0)"></a>
</form>
</li>
</ul>
</div>
</div>
<!-- 如果用户登录,显示下拉菜单 -->
</div>
</nav>
</div>
</template>
<script>
export default {
name: "Header",
data(){
return{
nav_top_list:[],
}
},
methods:{
get_navtop_list() {
this.$axios.get(`${this.$settings.host}/home/nav/top/`)
.then((res) => {
this.nav_top_list = res.data //响应回来的数据
}).catch((error) => {
this.$message.error("无法获取头部导航信息");
})
},
},
// 页面加载,自动加载数据
created() {
this.get_navtop_list();
},
}
</script>
尾部导航: Footer.vue
<template>
<footer class="container">
<div class="row">
<div class="main">
<span v-for="(nav_bottom_value,nav_bottom_index) in nav_bottom_list" :key="nav_bottom_index" >
<router-link target="_blank" :to="nav_bottom_value.link" v-if="nav_bottom_value.is_http">{{nav_bottom_value.name}}</router-link>
<a target="_blank" :href="nav_bottom_value.link" v-else>{{nav_bottom_value.name}}</a>
<em> · </em>
</span>
<div class="icp">©2016-2019 广州荏苒信息科技有限公司 / 荏苒 / 粤ICP备16018329号-5 /</div>
</div>
</div>
</footer>
</template>
<script>
export default {
name: "Footer",
data(){
return{
nav_bottom_list:[],
}
},
methods:{
get_navbottom_list() {
this.$axios.get(`${this.$settings.host}/home/nav/bottom/`)
.then((res) => {
this.nav_bottom_list = res.data //响应回来的数据
}).catch((error) => {
this.$message.error("无法获取尾部导航信息");
})
},
},
created() {
this.get_navbottom_list();
},
}
</script>