09、 项目主页、轮播图、跨域问题

1、主页-前台搭建

我们利用vue初步搭建一个我们项目的框架,配置基本的样式、结构等

但是目前该项目前台只是一个静态页面,需要配合后端传输数据

主页面

这里是项目前台的主页面:views/Homeviwe.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>
View Code
复制代码

轮播图

这里是轮播图组件:src/components/Banner.vue

复制代码
<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>
View Code
复制代码

头部

这里是主页的头部组件:src/components/Header.vue

复制代码
<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>
View Code
复制代码

尾部

这里是主页的尾部组件:src/components/Footer.vue

复制代码
<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>
View Code
复制代码

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 

引入simpleui,录入数据

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>
View Code
复制代码

到这里主页的轮播基本就实现了

5、轮播图后端自定义设置

我们可以在后端自定义前台轮播图显示几张图片

复制代码
# 在setting文件夹下新建 user_settings.py
# 用户自己的配置,单独放到另一个py文件中
# 轮播图个数
BANNER_COUNT=3


# 在dev.py中导入
# 导入用户自定义的轮播图个数配置
from .user_settings import *
复制代码
posted @   三三得九86  阅读(69)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示