接下来实现项目的导航功能,包括头部导航和脚部导航。
E-R图,如下:
01. 创建模型
导航数据的存储,必须要找出导航数据的结构,
也就是有哪些属性?
- 导航位置(头部或脚部)
- 导航名称
- 导航链接
- 导航序号(用于排序)
- 是否显示
- 是否外链
- 添加时间
- 更新时间
- 是否删除(逻辑删除)
home/models.py,代码:
from fuguang_api.utils.models import models, BaseModel
# Create your models here.
class Nav(BaseModel):
"""导航菜单"""
# 字段选项
# 模型对象.<字段名> ---> 实际数据
# 模型对象.get_<字段名>_display() --> 文本提示
POSITION_OPTION = (
# (实际数据, "文本提示"),
(0, "顶部导航"),
(1, "脚部导航"),
)
link = models.CharField(max_length=255, verbose_name="导航连接")
is_http = models.BooleanField(default=False, verbose_name="是否是外部链接")
position = models.IntegerField(choices=POSITION_OPTION, default=0)
class Meta:
db_table = "fg_nav"
verbose_name = "导航菜单"
verbose_name_plural = verbose_name
实际开发过程中,不同的模型类往往有很多相同的字段,此时可以使用抽象模型。
例如,上面的Nav类没有继承models.Model,而是继承BaseModel;
BaseModel是一个公共模型;【抽象模型,不会在数据迁移的时候为它创建表】,
保存在项目的公共代码库目录下,luffycityapi/utils.py文件中。
luffycityapi/utils/models.py
,代码:
from django.db import models
class BaseModel(models.Model):
"""
公共模型
保存项目中的所有模型的公共属性和公共方法的声明
"""
title = models.CharField(max_length=255, default="", verbose_name="名称/标题")
is_deleted = models.BooleanField(default=False, verbose_name="是否删除")
orders = models.IntegerField(default=0, verbose_name="序号")
is_show = models.BooleanField(default=True, verbose_name="是否显示")
# auto_now_add=True 当数据被创建时,以当前时间作为默认值写入当前字段
created_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
# auto_now=True 当数据被更新时,以当前时间作为值写入当前字段
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
# 设置当前模型类并非真正的模型,而是一种保存公共代码的抽象模型类
# 这种模型在数据迁移中不会被当做数据模型来创建数据表
abstract = True
在开发中,对于删除数据,我们往往使用逻辑删除,而不是真正从磁盘上物理删除数据。
我们可以把utils设置为系统导包路径,方便我们后面开发——导包时可以简写路径。
在settings/dev.py中配置导包路径,代码:
import sys
sys.path.insert(0, str( BASE_DIR / "apps") ) # 将apps设置为导包路径
sys.path.insert(0, str( BASE_DIR / "utils") ) # 将utils设置为导包路径
home/models.py中,导包路径便可简写:
# 原来的
from fuguang_api.utils.models import models, BaseModel
# 修改为
from models import BaseModel, models
创建好了模型类,接下来进行数据库迁移。
实际工作中,有些大厂企业不需要我们使用django的数据迁移操作,
如果公司有DBA的话,往往DBA已经提前设计好数据表结构了,
我们只需要根据表结构声明模型代码,保证模型代码的表名、字段与数据库中的表结构对应上,就可以在django中直接调用ORM模型对象操作数据库中的数据了。
cd ~/Desktop/fuguang/fuguang_api
python manage.py makemigrations
python manage.py migrate
上面仅仅创建的是数据表结构而已,接下来我们如果要实现客户端展示导航功能,
还需要在admin后台手动添加测试数据,或者在MySQL交互终端下添加测试数据。
测试数据如下:
INSERT INTO fuguang.fg_nav (id, title, orders, is_show, is_deleted, created_time, updated_time, link, is_http, position) VALUES (1, '免费课', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 0);
INSERT INTO fuguang.fg_nav (id, title, orders, is_show, is_deleted, created_time, updated_time, link, is_http, position) VALUES (2, '进阶课', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/project', 0, 0);
INSERT INTO fuguang.fg_nav (id, title, orders, is_show, is_deleted, created_time, updated_time, link, is_http, position) VALUES (3, '大师课', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/position', 0, 0);
INSERT INTO fuguang.fg_nav (id, title, orders, is_show, is_deleted, created_time, updated_time, link, is_http, position) VALUES (4, '习题库', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/exam', 0, 0);
INSERT INTO fuguang.fg_nav (id, title, orders, is_show, is_deleted, created_time, updated_time, link, is_http, position) VALUES (5, '浮光学院', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', 'https://www.fuguang.com', 1, 0);
INSERT INTO fuguang.fg_nav (id, title, orders, is_show, is_deleted, created_time, updated_time, link, is_http, position) VALUES (6, '企业服务', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 1);
INSERT INTO fuguang.fg_nav (id, title, orders, is_show, is_deleted, created_time, updated_time, link, is_http, position) VALUES (7, '关于我们', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 1);
INSERT INTO fuguang.fg_nav (id, title, orders, is_show, is_deleted, created_time, updated_time, link, is_http, position) VALUES (8, '联系我们', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 1);
INSERT INTO fuguang.fg_nav (id, title, orders, is_show, is_deleted, created_time, updated_time, link, is_http, position) VALUES (9, '商务合作', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 1);
INSERT INTO fuguang.fg_nav (id, title, orders, is_show, is_deleted, created_time, updated_time, link, is_http, position) VALUES (10, '帮助中心', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 1);
INSERT INTO fuguang.fg_nav (id, title, orders, is_show, is_deleted, created_time, updated_time, link, is_http, position) VALUES (11, '意见反馈', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 1);
INSERT INTO fuguang.fg_nav (id, title, orders, is_show, is_deleted, created_time, updated_time, link, is_http, position) VALUES (12, '新手指南', 1, 1, 0, '2021-07-15 01:27:27.350000', '2021-07-15 01:27:28.690000', '/free', 0, 1);
02. 序列化器
home.serializers,代码:
from rest_framework import serializers
from .models import Nav
class NavModelSerializer(serializers.ModelSerializer):
"""
导航菜单的序列化器
"""
class Meta:
model = Nav
fields = ["title", "link", "is_http"]
03. 视图代码
home/views.py
import constants
from rest_framework.generics import ListAPIView
from .models import Nav
from .serializers import NavModelSerializer
class NavHeaderListAPIView(ListAPIView):
"""顶部导航视图"""
queryset = Nav.objects.filter(position=constants.NAV_HEADER_POSITION, is_show=True, is_deleted=False).order_by("orders", "-id")[:constants.NAV_HEADER_SIZE]
serializer_class = NavModelSerializer
class NavFooterListAPIView(ListAPIView):
"""脚部导航视图"""
queryset = Nav.objects.filter(position=constants.NAV_FOOTER_POSITION, is_show=True, is_deleted=False).order_by("orders", "-id")[:constants.NAV_FOOTER_SIZE]
serializer_class = NavModelSerializer
3.1 常量配置
开发过程中,使用到的一些产量,往往是统一存放于一个文件中,以导包的形式调用,以方便修改。
utils/constants.py,代码:
"""常量配置文件"""
# 导航的位置 --> 顶部
NAV_HEADER_POSITION = 0
# 导航的位置 --> 脚部
NAV_FOOTER_POSITION = 1
# 顶部导航显示的最大数量
NAV_HEADER_SIZE = 5
# 脚部导航显示的最大数量
NAV_FOOTER_SIZE = 10
04. 路由代码
home/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("nav/header/", views.NavHeaderListAPIView.as_view()),
path("nav/footer/", views.NavFooterListAPIView.as_view()),
]
完成了上面操作以后,可以直接访问url地址或者通过postman来请求测试接口数据是否正确。
05. 客户端获取导航数据
所有与api服务端进行交互的操作代码,可以单独保存api目录下,根据数据表的不同单独创建js文件,方便将来代码复用。
src/api/nav.js,代码:
import http from "../utils/http"
import {reactive, ref} from "vue"
const nav = reactive({
header_nav_list: [], // 头部导航列表
footer_nav_list: [], // 脚部导航列表
get_header_nav(){
// 获取头部导航
return http.get("/home/nav/header/")
},
get_footer_nav(){
// 获取脚部导航
return http.get("/home/nav/footer/")
},
})
export default nav;
components/Header.vue代码:
<template>
<div class="header-box">
<div class="header">
<div class="content">
<div class="logo">
<router-link to="/"><img src="../assets/logo.svg" alt=""></router-link>
</div>
<ul class="nav">
<li v-for="nav in nav.header_nav_list">
<a :href="nav.link" v-if="nav.is_http">{{nav.title}}</a>
<router-link :to="nav.link" v-else>{{nav.title}}</router-link>
</li>
</ul>
<div class="search-warp">
<div class="search-area">
<input class="search-input" placeholder="请输入关键字..." type="text" autocomplete="off">
<div class="hotTags">
<router-link to="/search/?words=Vue" target="_blank" class="">Vue</router-link>
<router-link to="/search/?words=Python" target="_blank" class="last">Python</router-link>
</div>
</div>
<div class="showhide-search" data-show="no"><img class="imv2-search2" src="../assets/search.svg" /></div>
</div>
<div class="login-bar">
<div class="shop-cart full-left">
<img src="../assets/cart.svg" alt="" />
<span><router-link to="/cart">购物车</router-link></span>
</div>
<div class="login-box full-left">
<span>登录</span>
/
<span>注册</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import nav from "../api/nav";
// 请求头部导航列表
nav.get_header_nav().then(response=>{
nav.header_nav_list = response.data
})
</script>
components/Footer.vue代码:
<template>
<div class="footer">
<ul>
<li v-for="nav in nav.footer_nav_list">
<a :href="nav.link" v-if="nav.is_http">{{nav.title}}</a>
<router-link :to="nav.link" v-else>{{nav.title}}</router-link>
</li>
</ul>
<p>Copyright © luffycity.com版权所有 | 京ICP备17072161号-1</p>
</div>
</template>
<script setup>
import nav from "../api/nav";
// 获取脚部导航列表
nav.get_footer_nav().then(response=>{
nav.footer_nav_list = response.data
})
</script>