09、 项目主页、轮播图、跨域问题
1、主页-前台搭建
我们利用vue初步搭建一个我们项目的框架,配置基本的样式、结构等
但是目前该项目前台只是一个静态页面,需要配合后端传输数据

<template> <div class="home"> <Header></Header> <Banner></Banner> <!-- 推荐课程--> <div class="course"> <el-row> <el-col :span="6" v-for="(o, index) in 8" :key="o"> <el-card :body-style="{ padding: '0px' }" class="course_card"> <img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h1g0zd133mj20l20a875i.jpg" class="image"> <div style="padding: 14px;"> <span>推荐的课程</span> <div class="bottom clearfix"> <time class="time">价格:100元</time> <el-button type="text" class="button">查看详情</el-button> </div> </div> </el-card> </el-col> </el-row> </div> <img src="https://tva1.sinaimg.cn/large/e6c9d24egy1h1g112oiclj224l0u0jxl.jpg" alt="" height="500px" width="100%"> <Footer></Footer> </div> </template> <script> import Footer from "@/components/Footer"; import Header from "@/components/Header"; import Banner from "@/components/Banner"; export default { name: 'HomeView', data() { return {} }, components: { Footer, Header, Banner } } </script> <style scoped> .time { font-size: 13px; color: #999; } .bottom { margin-top: 13px; line-height: 12px; } .button { padding: 0; float: right; } .image { width: 100%; display: block; } .clearfix:before, .clearfix:after { display: table; content: ""; } .clearfix:after { clear: both } .course { margin-left: 20px; margin-right: 20px; } .course_card { margin: 50px; } </style>

<template> <div class="banner"> <el-carousel :interval="5000" arrow="always" height="400px"> <el-carousel-item v-for="item in 4" :key="item"> <img src="../assets/img/banner1.png" alt=""> </el-carousel-item> </el-carousel> </div> </template> <script> export default { name: "Banner" } </script> <style scoped> el-carousel-item { height: 400px; min-width: 1200px; } .el-carousel__item img { height: 400px; margin-left: calc(50% - 1920px / 2); } </style>

<template> <div class="header"> <div class="slogan"> <p>老男孩IT教育 | 帮助有志向的年轻人通过努力学习获得体面的工作和生活</p> </div> <div class="nav"> <ul class="left-part"> <li class="logo"> <router-link to="/"> <img src="../assets/img/head-logo.svg" alt=""> </router-link> </li> <li class="ele"> <span @click="goPage('/free-course')" :class="{active: url_path === '/free-course'}">免费课</span> </li> <li class="ele"> <span @click="goPage('/actual-course')" :class="{active: url_path === '/actual-course'}">实战课</span> </li> <li class="ele"> <span @click="goPage('/light-course')" :class="{active: url_path === '/light-course'}">轻课</span> </li> </ul> <div class="right-part"> <div> <span>登录</span> <span class="line">|</span> <span>注册</span> </div> </div> </div> </div> </template> <script> export default { name: "Header", data() { return { url_path: sessionStorage.url_path || '/', } }, methods: { goPage(url_path) { // 已经是当前路由就没有必要重新跳转 if (this.url_path !== url_path) { this.$router.push(url_path); } sessionStorage.url_path = url_path; }, }, created() { sessionStorage.url_path = this.$route.path; this.url_path = this.$route.path; } } </script> <style scoped> .header { background-color: white; box-shadow: 0 0 5px 0 #aaa; } .header:after { content: ""; display: block; clear: both; } .slogan { background-color: #eee; height: 40px; } .slogan p { width: 1200px; margin: 0 auto; color: #aaa; font-size: 13px; line-height: 40px; } .nav { background-color: white; user-select: none; width: 1200px; margin: 0 auto; } .nav ul { padding: 15px 0; float: left; } .nav ul:after { clear: both; content: ''; display: block; } .nav ul li { float: left; } .logo { margin-right: 20px; } .ele { margin: 0 20px; } .ele span { display: block; font: 15px/36px '微软雅黑'; border-bottom: 2px solid transparent; cursor: pointer; } .ele span:hover { border-bottom-color: orange; } .ele span.active { color: orange; border-bottom-color: orange; } .right-part { float: right; } .right-part .line { margin: 0 10px; } .right-part span { line-height: 68px; cursor: pointer; } </style>

<template> <div class="footer"> <ul> <li>关于我们</li> <li>联系我们</li> <li>商务合作</li> <li>帮助中心</li> <li>意见反馈</li> <li>新手指南</li> </ul> <p>Copyright © luffycity.com版权所有 | 京ICP备17072161号-1</p> </div> </template> <script> export default { name: "Footer" } </script> <style scoped> .footer { width: 100%; height: 128px; background: #25292e; color: #fff; } .footer ul { margin: 0 auto 16px; padding-top: 38px; width: 810px; } .footer ul li { float: left; width: 112px; margin: 0 10px; text-align: center; font-size: 14px; } .footer ul::after { content: ""; display: block; clear: both; } .footer p { text-align: center; font-size: 12px; } </style>
2、主页-后台轮播图接口
创建home应用,注册
创建
# 进入apps目录 cd luffy_api/apps # 创建home应用 python ../../manage.py startapp home
注册
# setting/dev.py中注册home INSTALLED_APPS = [ #... 'home', #... ]
虚拟表设计
utils/model.py/BaseModel(公共表,放在utils中)这是一张虚拟表,不在数据库中生成,他只是用来作继承
因为这张表的字段,在接下来的很多表中都会用到,为了方便,现在统一创建在这张虚拟表里
以后想使用,直接继承这张表,在这张虚拟表的基础上扩写自己的字段
from django.db import models class BaseModel(models.Model): created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_time = models.DateTimeField(auto_now=True, verbose_name='最后更新时间') is_delete = models.BooleanField(default=False, verbose_name='是否删除') is_show = models.BooleanField(default=True, verbose_name='是否上架') orders = models.IntegerField(verbose_name='优先级') class Meta: abstract = True # 虚拟表,可被其他表继承,(仿的AbstractUser)from django.contrib.auth.models import AbstractUser
轮播图表
继承BaseModel表,可以用到创建时间、最后更新时间、是否上架、是否删除、优先级字段
我们只需要自己的字段
from django.db import models from utils.model import BaseModel class Banner(BaseModel): # 图片名称、图片地址、跳转连接、图片介绍 title = models.CharField(max_length=16, unique=True, verbose_name='名称') image = models.ImageField(upload_to='banner', verbose_name='图片') link = models.CharField(max_length=64, verbose_name='跳转链接') info = models.TextField(verbose_name='详情') # 也可以用详情表,宽高出处 class Meta: db_table = 'luffy_banner' # 数据库中表名 verbose_name_plural = '轮播图表' # 后台管理显示中文 def __str__(self): return self.title
# 根路径下迁移数据 python manage.py makemigrations #如果没有变化,是app没注册 python manage.py migrate # 创建超级用户 python manage.py createsuperuser
django的后台管理过于单调,我们使用simpleui丰富我们的后台管理
# 下载 pip install django-simpleui # 注册app(注意放在第一位) INSTALLED_APPS = [ 'simpleui', ... ] # 在home/admin中写 from django.contrib import admin from .models import Banner @admin.register(Banner) class BannerAdmin(admin.ModelAdmin):
# 后台管理显示的字段 list_display = ('id', 'title', 'link','is_show', 'is_delete') # 增加一个自定义按钮 actions = ['make_copy']
# 通过选中该按钮,触发函数执行,如下: def make_copy(self, request, queryset): # 选中一些数据,点击 【自定义按钮】 触发方法执行,传入你选中 queryset # 保存,删除 print(queryset) make_copy.short_description = '自定义按钮' # 给按钮命名
轮播图接口
接口返回格式
# 返回数据格式 {code:100,msg:成功,result:[{img:地址,link:跳转地址,orders:顺序,title:名字},{img:地址,link:跳转地址,orders:顺序,title:名字}]}
配置路由
总路由 luffy_api/urls.py
from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/v1/home/', include('home.urls')), # http://127.0.0.1:8000/api/v1/home/banner/ ]
home路由 luffy_api/apps/home/urls.py
from django.urls import path, include from rest_framework.routers import SimpleRouter from .views import BannerView router = SimpleRouter() router.register('banner', BannerView, 'banner') urlpatterns = [ path('', include(router.urls)), ]
视图类
from .models import Banner from .serializer import BannerSerializer from utils.response import APIResponse from rest_framework.viewsets import GenericViewSet from rest_framework.mixins import ListModelMixin class BannerView(GenericViewSet,ListModelMixin): # 获取所有接口ListModelMixin,自动生成路由GenericViewSet queryset = Banner.objects.filter(is_delete=False,is_show=True).order_by('orders') # 查询所有对象集 serializer_class =BannerSerializer # 绑定序列化类 # 重写list,返回符合我们需求的格式 def list(self, request, *args, **kwargs): res=super().list(request, *args, **kwargs) return APIResponse(result=res.data)
序列化类
from rest_framework import serializers from .models import Banner class BannerSerializer(serializers.ModelSerializer): class Meta: model = Banner fields = ['title', 'image', 'link', 'orders']
3、跨域问题
1 同源策略
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现
请求的url地址,必须与浏览器上的url地址处于同域上,也就是域名,端口,协议相同.
比如:
我的项目前端:http://127.0.0.1:8080
我的项目后端:http://127.0.0.1:8000
这两个是协议、地址相同,但是端口不同,这就是不同源。
我们的前后端混合项目是同源所以不会出现跨域问题。
但是前后端分离项目中,当我的前端向后端发送请求,如果不解决跨域问题,后端返回给前端的数据会被浏览器拦截,浏览器就会报错。
这就是同源策略的保护,如果浏览器对javascript没有同源策略的保护,那么一些重要的机密网站将会很危险。
# 注意: 浏览器是对非同源请求返回的结果做了拦截
2 CORS(跨域资源共享)简介
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
# 注意:jsonp是利用标签等特殊符号不会被浏览器拦截的bug,把信息放在标签内,实现跨域资源共享的,这项技术已经不合时宜了
3 CORS基本流程
浏览器将CORS请求分成两类
简单请求
浏览器发出CORS简单请求,只需要在头信息之中增加一个Origin字段
非简单请求
浏览器发出CORS非简单请求,**会在正式通信之前,增加一次HTTP查询请求,OPTIONS预检请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest真实请求,否则就报错。
4 CORS两种请求详解
只要同时满足以下两大条件,就属于简单请求,否则是非简单请求:
''' (1) 请求方法是以下三种方法之一:
HEAD GET POST (2)HTTP的头信息不超出以下几种字段:
Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain '''
浏览器对这两种请求的处理,是不一样的
* 简单请求和非简单请求的区别?
# 简单请求:一次请求
# 非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。
* 关于“预检”
# 请求方式:OPTIONS
# “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
# 如何“预检”
# => 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过
# Access-Control-Request-Method
# => 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过
# Access-Control-Request-Headers
支持跨域,简单请求
服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'
支持跨域,复杂请求
由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。
“预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
“预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers
5 解决方法
方式一:自己写一个中间件处理跨域
# 自行解决跨域:django中写一个中间件cors.py文件,处理跨域 from django.utils.deprecation import MiddlewareMixin class CorsMiddleWare(MiddlewareMixin): def process_response(self,request,response): # 处理非简单请求走if,处理简单请求不走if if request.method=="OPTIONS": response["Access-Control-Allow-Headers"]="Content-Type" response["Access-Control-Allow-Origin"] = "*" return response # 配置到配置文件中,在setting的中间件MIDDLEWARE中,添加 'app01.cors.MyMiddleware'
方式二:第三方模块处理跨域
# 第一步:下载 pip install django-cors-headers # 第二步:app中注册 INSTALLED_APPS = ( ... 'corsheaders', ... ) # 第三步:中间件注册 MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10 ... 'corsheaders.middleware.CorsMiddleware', ... ] # 第四步:配置文件配置 CORS_ALLOW_CREDENTIALS = True CORS_ORIGIN_ALLOW_ALL = True # CORS_ORIGIN_WHITELIST = ( # '*' # ) CORS_ALLOW_METHODS = ( 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'VIEW', ) CORS_ALLOW_HEADERS = ( 'XMLHttpRequest', 'X_FILENAME', 'accept-encoding', 'authorization', 'content-type', 'dnt', 'origin', 'user-agent', 'x-csrftoken', 'x-requested-with', 'Pragma', )
4、前后端打通
这是个轮播图Banner组件(src/compenents/Banner.vue)

<template> <div class="banner"> <el-carousel :interval="5000" arrow="always" height="400px"> <el-carousel-item v-for="item in banner_list"> <img :src="item.image" alt=""> </el-carousel-item> </el-carousel> </div> </template> <script> export default { name: "Banner", data() { return { banner_list: [] } }, created() { this.$axios.get(this.$settings.base_url + 'home/banner/').then(res => { if (res.data.status == 100) { this.banner_list = res.data.result console.log(this.banner_list) } }) } } </script> <style scoped> el-carousel-item { height: 400px; min-width: 1200px; } .el-carousel__item img { height: 400px; margin-left: calc(50% - 1920px / 2); } </style>
到这里主页的轮播基本就实现了
5、轮播图后端自定义设置
我们可以在后端自定义前台轮播图显示几张图片
# 在setting文件夹下新建 user_settings.py # 用户自己的配置,单独放到另一个py文件中 # 轮播图个数 BANNER_COUNT=3 # 在dev.py中导入 # 导入用户自定义的轮播图个数配置 from .user_settings import *
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)