导航

后台导航初模型:home/models.py
from django.db import models
class Banner(models.Model):
    """轮播图"""
    # upload_to 存储子目录,真实存放地址会使用配置中的MADIE_ROOT+upload_to
    image = models.ImageField(upload_to='banner', 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 = 'ly_banner'
        verbose_name = '轮播图'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

class Nav(models.Model):
    """导航"""
    NAV_POSITION = (
        (0, '顶部导航'),
        (1, '底部导航') 
    )
    name = models.CharField(max_length=50, verbose_name='导航名称')
    link = models.CharField(max_length=250, verbose_name='导航地址')
    opt = models.SmallIntegerField(choices=NAV_POSITION, default=0, 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 = 'luffy_nav'
        verbose_name = '导航'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name
改造model:home/models.py => luffyapi/utils/models.py
# home/models.py 
# 轮播图模型
from django.db import models
from luffyapi.utils.models import BaseModel
class Banner(models.Model):
    """轮播图"""
    # upload_to 存储子目录,真实存放地址会使用配置中的MADIE_ROOT+upload_to
    image = models.ImageField(upload_to='banner', 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 = 'ly_banner'
        verbose_name = '轮播图'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

class Nav(models.Model):
    """导航"""
    NAV_POSITION = (
        (0, '顶部导航'),
        (1, '底部导航') 
    )
    name = models.CharField(max_length=50, verbose_name='导航名称')
    link = models.CharField(max_length=250, verbose_name='导航地址')
    opt = models.SmallIntegerField(choices=NAV_POSITION, default=0, 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 = 'ly_nav'
        verbose_name = '导航'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name
# luffyapi/utils/models.py

from django.db import models

class BaseModel(models.Model):
    """公共模型"""
    orders = models.IntegerField(verbose_name='显示顺序')
    is_show = models.BooleanField(verbose_name="是否上架", default=False)
    is_delete = models.BooleanField(verbose_name="逻辑删除", default=False)
    created_time = models.DateTimeField(verbose_name="创建时间", auto_now_add=True, null=True, blank=True)
    updated_time = models.DateTimeField(verbose_name="更新时间", auto_now=True, null=True, blank=True)
    class Meta:
        # 抽象模型,一般用于设置公共模型字段的,一旦设置这个相关以后,那么dajngo在数据迁移的时候就不会为这个模型单独创建一个数据表了
        abstract=True
# 在项目根目录下的终端
python manage.py makemigrations
python manage.py migrate
序列化:home/serializers.py
from .models import Nav
class NavModelSerializer(serializers.ModelSerializer):
    """导航菜单序列化器"""
    class Meta:
        model = Nav
        fields = ["name", "link", "opt"]
视图:home/views.py
from .models import Nav
from .serializers import NavModelSerializer
class NavListAPIView(ListAPIView):
    queryset = Nav.objects.filter(is_show=True, is_delete=False).order_by("orders")[:constant.NAV_LENGTH]
    serializer_class = NavModelSerializer
配置常量:settings/constant.py
# 导航的显示数量
NAV_LENGTH = 7
子路由:home/nav
# home/nav
path("nav/",views.NavListAPIView.as_view()),
xadmin注册model
from .models import Nav
xadmin.site.register(Nav)

录入数据
"""
1 上架 免费课 /course 顶部导航
2 上架 轻客 /light-course 顶部导航
10 上架 关于我们 /about 底部导航
...
"""

前端组件改造
<template>
    <div class="header-box">
        <div class="header">
            <div class="content">
                <div class="logo full-left">
                    <router-link to="/"><img @click="jump('/')" src="@/assets/image/logo.svg" alt=""></router-link>
                </div>
                <ul class="nav full-left">
                    <li v-for="nav in nav_list"><span @click="jump(nav.link)" :class="this_nav==nav.link?'this':''">{{nav.name}}</span></li>
                </ul>
                <div class="login-bar full-right">
                    <div class="shop-cart full-left">
                        <img src="@/assets/image/cart.svg" alt="">
                        <span><router-link to="/cart">购物车</router-link></span>
                    </div>
                    <div class="login-box full-left">
                        <span>登录</span>
                        &nbsp;|&nbsp;
                        <span>注册</span>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "Header",
        data() {
            return {
                this_nav: "",
                nav_list: [],
            }
        },
        created() {
            this.this_nav = localStorage.this_nav;

            let _this = this
            // 获取服务端的导航信息
            this.$axios({
                url: this.$settings.Host + "/home/nav/"
            }).then(function (response) {
                _this.nav_list = response.data
            }).catch(function(error) {
                console.log(error)
            });
        },
        methods: {
            jump(location) {
                localStorage.this_nav = location;
                // vue-router除了提供router-link标签跳转页面以外,还提供了 js跳转的方式
                this.$router.push(location);
            }
        }
    }
</script>

<style scoped>
    .header-box {
        height: 80px;
    }

    .header {
        width: 100%;
        height: 80px;
        box-shadow: 0 0.5px 0.5px 0 #c9c9c9;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        margin: auto;
        z-index: 99;
        background: #fff;
    }

    .header .content {
        max-width: 1200px;
        width: 100%;
        margin: 0 auto;
    }

    .header .content .logo {
        height: 80px;
        line-height: 80px;
        margin-right: 50px;
        cursor: pointer; /* 设置光标的形状为爪子 */
    }

    .header .content .logo img {
        vertical-align: middle;
    }

    .header .nav li {
        float: left;
        height: 80px;
        line-height: 80px;
        margin-right: 30px;
        font-size: 16px;
        color: #4a4a4a;
        cursor: pointer;
    }

    .header .nav li span {
        padding-bottom: 16px;
        padding-left: 5px;
        padding-right: 5px;
    }

    .header .nav li span a {
        display: inline-block;
    }

    .header .nav li .this {
        color: #4a4a4a;
        border-bottom: 4px solid #ffc210;
    }

    .header .nav li:hover span {
        color: #000;
    }

    .header .login-bar {
        height: 80px;
    }

    .header .login-bar .shop-cart {
        margin-right: 20px;
        border-radius: 17px;
        background: #f7f7f7;
        cursor: pointer;
        font-size: 14px;
        height: 28px;
        width: 88px;
        margin-top: 30px;
        line-height: 32px;
        text-align: center;
    }

    .header .login-bar .shop-cart:hover {
        background: #f0f0f0;
    }

    .header .login-bar .shop-cart img {
        width: 15px;
        margin-right: 4px;
        margin-left: 6px;
    }

    .header .login-bar .shop-cart span {
        margin-right: 6px;
    }

    .header .login-bar .login-box {
        margin-top: 33px;
    }

    .header .login-bar .login-box span {
        color: #4a4a4a;
        cursor: pointer;
    }

    .header .login-bar .login-box span:hover {
        color: #000000;
    }
</style>


底部导航

视图改造
from .models import Nav
from .serializers import NavModelSerializer

class NavHeaderListAPIView(ListAPIView):
    queryset = Nav.objects.filter(opt=1,is_show=True, is_delete=False).order_by("orders")[:constant.NAV_LENGTH]
    serializer_class = NavModelSerializer

class NavFooterListAPIView(ListAPIView):
    queryset = Nav.objects.filter(opt=2,is_show=True, is_delete=False).order_by("orders")[:constant.NAV_LENGTH]
    serializer_class = NavModelSerializer

子路由改造
path("nav/header/",views.NavHeaderListAPIView.as_view()),
path("nav/footer/",views.NavFooterListAPIView.as_view()),

xadmin注册model改造
from .models import Nav
xadmin.site.register(Nav)

前端组件改造
""" Header.vue

<ul class="nav full-left">
	<li v-for="nav in nav_list">
		<span @click="jump(nav.link)" :class="this_nav==nav.link?'this':''">{{nav.name}}</span>
	</li>
</ul>


created() {
	this.this_nav = localStorage.this_nav;

    let _this = this
    // 获取服务端的导航信息
    this.$axios({
        url: this.$settings.Host + "/home/nav/header/"
    }).then(function (response) {
        _this.nav_list = response.data
    }).catch(function(error) {
        console.log(error)
    });
},

"""

""" Footer.vue

<ul>
	<li v-for="nav in nav_list">
		<a :href="nav.link">{{nav.name}}</a>
	</li>
</ul>


data: function () {
    return {
        nav_list:[]
    }
}

created: function ()  {
    let _this = this
    // 获取服务端的导航信息
    this.$axios({
        url: this.$settings.Host + "/home/nav/footer/"
    }).then(function (response) {
        _this.nav_list = response.data
    }).catch(function(error) {
        console.log(error)
    });
},

.footer a {
    color: white;
}
"""