首页主体、轮播功能和跨域问题

前台主页

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>

Header.vue

// 需要在 assets目录创建 img 目录放入相关图片 否则会报错
<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>

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>

Homeview.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>
.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>

公共基表

# utils/model.py
from django.db import models


# 5个公共字段
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  # 表示它是虚拟的,不在数据库中生成表,它只用来做继承

轮播图接口

# 创建 home app
cd luffy_api/apps
python ../../manage.py startapp home

# 配置文件中注册
INSTALLED_APPS = [
    # ...
    'home',
]

表设计

# 轮播图表设计  luffy_api/apps/home/models.py
from django.db import models
from utils.model import BaseModel

# 继承基表之后只需要写 title,image,info,link 字段
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

# 下载
pip install django-simpleui

# setting/dev.py 注册app
INSTALLED_APPS = [
      'simpleui',
      ...
  ]

# 在 apps/home/admin.py 中添加内容
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 = '自定义按钮'

# 访问后台录入数据

image-20220420193319631

image-20220420193332208

接口代码

接口格式
{
  code:100,
  msg:成功
  result:[
    {
      img:地址
      link:跳转地址
      orders:顺序
      title:名字
    }
  ]
}
总路由
# luffy_api/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.views.static import serve

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/home/', include('home.urls')),  # http://127.0.0.1:8000/api/v1/home/banner/
    path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT})
]
home 路由
# luffy_api/apps/home/urls.py
from django.contrib import admin
from django.urls import path, include
from .views import BannerView
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register('banner', BannerView, 'banner')
urlpatterns = [
    path('', include(router.urls))
]
视图类
# luffy_api/apps/home/views.py
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from utils.APIResponse import APIResponse
from .models import Banner
from .serializer import BannerSerializer

class BannerView(GenericViewSet, ListModelMixin):
    # 获取所有接口 list,自动生成路由
    queryset = Banner.objects.filter(is_delete=False, is_show=True).order_by('orders')
    serializer_class = BannerSerializer

    def list(self, request, *args, **kwargs)  # 重写list 可以将这段提取出来做公共方法
        res = super().list(request, *args, **kwargs)
        return APIResponse(data=res.data)
序列化类
# luffy_api/apps/home/serializer.py
from rest_framework import serializers
from .models import Banner

class BannerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Banner
        fields = ['title', 'image', 'link', 'orders']

跨域问题

跨域错误和同源策略

# 现在写好了后端接口,前端加载数据时会报如下错误
'''
localhost/:1 Access to XMLHttpRequest at
'http://127.0.0.1:8000/api/v1/home/banner/'
from origin 'http://localhost:8080'
has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header
is present on the requested resource.
'''

该错误是跨域的错误

# 浏览器的同源策略
  请求的url地址,必须与浏览器上的url地址处于同域上,也就是域名,端口,协议相同否则加载回来的数据就会禁止
  前端http://127.0.0.1:8080
  后端http://127.0.0.1:8000
  这俩属于不同源协议地址一样但是端口不一样所以请求成功但是到了浏览器被禁止掉了因为浏览器的同源策略
  前后端分离就会遇到这个问题解决这个问题

解决方式

1. jsonp 通过 imgscriptlink 这些标签回调(已经不常用了)
   https://www.zhihu.com/question/19966531

2. 通过CORS(跨域资源共享)
   CORS需要浏览器和服务器同时支持目前所有浏览器都支持该功能
   实现CORS通信的关键是服务器只要服务器实现了CORS接口就可以跨源通信
   只需要在响应头中指定允许跨域即可

CORS

# CORS请求种类
  浏览器将CORS请求分成两类
  简单请求simple request
  非简单请求not-so-simple request

# 种类的划分
只要同时满足以下两大条件就属于简单请求否则就是非简单请求
1.请求方法是以下三种方法之一
  HEAD
  GET
  POST
2.HTTP的头信息不超出以下几种字段
  Accept
  Accept-Language
  Content-Language
  Last-Event-ID
  Content-Type只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

postjosn格式是什么请求 非简单

# 简单请求和非简单请求的区别
简单请求一次请求直接发真正的请求如果允许数据拿回来如果不允许浏览器拦截
非简单请求两次请求在发送数据之前会先发一次请求用于做预检”,只有预检通过后才再发送一次请求用于数据传输非简单请求发两次第一次是OPTIONS请求如果允许跨域再发真正的请求

解决跨域问题

简单请求在响应头中加入 "Access-Control-Allow-Origin":"*"
  headers={"Access-Control-Allow-Origin": "*"}

非简单请求需要增加判断如果是OPTIONS请求在请求头中加入允许

# 放到中间件处理复杂和简单请求
from django.utils.deprecation import MiddlewareMixin
class CorsMiddleWare(MiddlewareMixin):
    def process_response(self,request,response):
        if request.method=="OPTIONS":
            #可以加*
            response["Access-Control-Allow-Headers"]="Content-Type"
        response["Access-Control-Allow-Origin"] = "*"
        return response

# 使用第三方模块
## 下载
pip install django-cors-headers

## 配置文件中注册app
INSTALLED_APPS = [
    ...
    'corsheaders',
    ...
]

## 中间件注册
MIDDLEWARE = [  # Or MIDDLEWARE_CLASSES on Django < 1.10
    ...
    'corsheaders.middleware.CorsMiddleware',
    ...
]

## 配置文件中配置如下参数
    # 跨域问题处理
    # 允许简单请求,所有地址 相当于CORS_ORIGIN_ALLOW_ALL="*"
    CORS_ALLOW_ALL_ORIGINS = True
    # 运行的请求
    CORS_ALLOW_METHODS = (
        'DELETE',
        'GET',
        'OPTIONS',
        'PATCH',
        'POST',
        'PUT',
        'VIEW',
    )

    # 允许的请求头
    CORS_ALLOW_HEADERS = (
      'XMLHttpRequest',
      'X_FILENAME',
      'accept-encoding',
      'authorization',  # jwt
      'content-type',  # json
      'dnt',
      'origin',
      'user-agent',
      'x-csrftoken',
      'x-requested-with',
      'Pragma',
    )

简单请求

image-20220420213246456

image-20220420213221975

非简单请求

image-20220420213100218

image-20220420213028715

前后端打通

Banner.vue

<template>
  <div class="banner">
    <el-carousel :interval="5000" arrow="always" height="400px">
      <el-carousel-item v-for="item in banner_list" :key="item.image">
        // 不能直接使用item对象
        <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.data;
      }
    });
  },
};
</script>

<style scoped>
el-carousel-item {
  height: 400px;
  min-width: 1200px;
}

.el-carousel__item img {
  height: 400px;
  margin-left: calc(50% - 1920px / 2);
}
</style>

后端自定义配置

# 创建luffy_api/setting/user_settings.py
	BANNER_COUNT=3

# 在 luffy_api/setting/dev.py 中导入
# 导入用户自定义的配置
from .user_settings import *

# 使用,例如在views.py中
from django.conf import settings
...
    queryset = Banner.objects.filter(is_delete=False, is_show=True).order_by('orders')[
               :settings.BANNER_COUNT]  # 自定义轮播图片数量
...