欢迎来到十九分快乐的博客

生死看淡,不服就干。

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>
posted @ 2021-03-28 20:05  十九分快乐  阅读(439)  评论(0编辑  收藏  举报