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

前台主页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// 需要在 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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<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>

公共基表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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 # 表示它是虚拟的,不在数据库中生成表,它只用来做继承

轮播图接口

1
2
3
4
5
6
7
8
9
# 创建 home app
cd luffy_api/apps
python ../../manage.py startapp home

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

表设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 轮播图表设计  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

迁移数据

1
2
3
python manage.py makemigrations  # 如果没有变化,是app没注册
python manage.py migrate
python manage.py createsuperuser # 创建个用户

后台引入simpleui

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 下载
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

接口代码

接口格式
1
2
3
4
5
6
7
8
9
10
11
12
{
code:100,
msg:成功,
result:[
{
img:地址,
link:跳转地址,
orders:顺序,
title:名字
}
]
}
总路由
1
2
3
4
5
6
7
8
9
10
11
12
# 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路由
1
2
3
4
5
6
7
8
9
10
11
# 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))
]
视图类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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)
序列化类
1
2
3
4
5
6
7
8
# 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']

跨域问题

跨域错误和同源策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 现在写好了后端接口,前端加载数据时会报如下错误
'''
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
2
3
4
5
6
7
1. jsonp 通过 img,script,link 这些标签回调(已经不常用了)
https://www.zhihu.com/question/19966531

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

CORS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 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-urlencoded、multipart/form-data、text/plain

问:post,josn格式是什么请求? 非简单

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

解决跨域问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
简单请求,在响应头中加入 "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

前后端打通

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<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>

后端自定义配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# 创建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] # 自定义轮播图片数量
...