总页面
二次封装Response模块
# drf提供的Response,前端想接收到的格式 {code:xx,msg:xx}
后端返回,前端收到:APIResponse(tokne='asdfa.asdfas.asdf')---->{code:100,msg:成功,token:asdfa.asdfas.asdf} APIResponse(code=101,msg='用户不存在') ---->{code:101,msg:用户不存在} APIResponse(results=[{},{}])---->{code:100,msg:成功,results:[{},{}]} APIResponse(msg='创建成功')---->{code:100,msg:创建成功} APIResponse(token=sd.11.22,icon='用户头像')---->{code:100,msg:创建成功,token:sd.11.22,icon:'用户头像'} APIResponse(msg='创建成功',headers={'xx':xxx})---->{code:100,msg:创建成功}
# 开始封装:
# utills/common_response.py from rest_framework.response import Response class APIResponse(Response):def __init__(self, code=100, msg='成功', status=None, headers=None, **kwargs):data = {'code': code, 'msg': msg}if kwargs: # kwargs={token:xx,icon:zz}data.update(kwargs)# super().__init__()---等同于 -->Response(data=data)super().__init__(data=data, headers=headers, status=status)
# views.py from utils.common_response import APIResponse class TestResponseView(APIView):def get(self, request):# retur n APIResponse(token='ss.ee.ss',icon='/media/icon/default.png')# return APIResponse(msg='创建成功')# return APIResponse(msg='用户不存在',code=101)# return APIResponse(results=[{},{}])return APIResponse(headers={'xx': 'yy'})# raise APIException(detail='用户名或密码错误') # {code:999,msg:用户名或密码错误}
admin和国际化问题
# admin 注释掉,重新启动
* url中注册app
* 重新迁移
# 国际化LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False
5个视图扩展类封装
# utills/mixins.py from rest_framework.mixins import ListModelMixin, CreateModelMixin, DestroyModelMixin, UpdateModelMixin, \RetrieveModelMixinfrom .common_response import APIResponseclass CommonListModelMixin(ListModelMixin):def list(self, request, *args, **kwargs):# Response 的对象---》res.datares = super().list(request, *args, **kwargs)return APIResponse(results=res.data) # {code:100,msg:成功,results:[{},{},{}]}class CommonCreateModelMixin(CreateModelMixin):def create(self, request, *args, **kwargs):res = super().create(request, *args, **kwargs)return APIResponse(msg='新增成功', result=res.data) # {code:100,msg:新增成功,result:{}}class CommonDestroyModelMixin(DestroyModelMixin):def destroy(self, request, *args, **kwargs):super().destroy(request, *args, **kwargs)return APIResponse(msg='删除成功') # {code:100,msg:删除成功}class CommonUpdateModelMixin(UpdateModelMixin):def update(self, request, *args, **kwargs):super().update(request, *args, **kwargs)return APIResponse(msg='修改成功') # {code:100,msg:修改成功}class CommonRetrieveModelMixin(RetrieveModelMixin):def retrieve(self, request, *args, **kwargs):res = super().retrieve(request, *args, **kwargs)return APIResponse(result=res.data) # {code:100,msg:成功,result:{}}
开启media访问
# 上传文件( ImageField,FileField ),会自动传到 media文件夹下的 to 的路径
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = 'media/'
# 在总路由中配置:
from django.conf import settings from django.views.static import serve path('media/<path:path>', serve, kwargs={'document_root': settings.MEDIA_ROOT}), # http://127.0.0.1:8000/media/icon/2.png
前端创建--vue2
前端使用vue2搭建,使用pycharm打开运行,删除不需要的样式和vue文件
vue create luffy_city
前端配置
1、全局样式:
# 在main.js里引入:
//在这里导入即可--全局样式生效 import '@/assets/css/global.css'
/* assets/css/global.css */ /* 声明全局样式和项目的初始化样式 */ body, h1, h2, h3, h4, h5, h6, p, table, tr, td, ul, li, a, form, input, select, option, textarea {margin: 0;padding: 0;font-size: 15px; }a {text-decoration: none;color: #333; }ul {list-style: none; }table {border-collapse: collapse; /* 合并边框 */ }
2、配置文件(路由):
# 在main.js中注册
以后再任意组件中,this.$settings.BASE_URL # 拿到基地址
// main.js import settings from "@/assets/js/settings"; Vue.prototype.$settings = settings/* asesst/ js/ settings.jss */ export default {BASE_URL: 'http://127.0.0.1:8000/api/v1/' }
3、axios:
# 安装:cnpm install -S axios
// main.js import axios from "axios"; Vue.prototype.$axios = axios// 以后再任意组件中直接使用 this.$axios.get(this.$settings.BASE_URL+'user/user/loign/')
4、使用elementui:
# 安装: cnpm install element-ui -S
# 网址:组件 | Element
// main.js import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI);
5、操作cookie:
# 安装:cnpm install vue-cookies
# 以后任意组件直接使用:this.$cookies.set / this.$cookies.get
// main.js import cookies from 'vue-cookies' Vue.prototype.$cookies = cookies;
6、使用bootstrap:
# 安装:cnpm install bootstrap@5卸载:cnpm remove bootstrap@4
# 在组件中使用: <button class="btn btn-danger">点我看美女</button>
// main.js import 'bootstrap/dist/css/bootstrap.min.css'
后端之轮播图
首页home---轮播图接口
轮播图表:#utils / common_model.py from django.db import modelsclass 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/models 继承common_models.py from utills.common_model import BaseModel# 通过写一个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='详情')
轮播图接口:这里运用了自定义配置common_settings,参考下列知识
# home/serializers.py from rest_framework import serializers from .models import Bannerclass BannerSerializer(serializers.ModelSerializer):class Meta:model = Bannerfields = ['id', 'title', 'image', 'link']
# home/views.py from .models import Banner from rest_framework.viewsets import GenericViewSet from utills.mixins import CommonListModelMixin from .serializers import BannerSerializer from django.conf import settings# 查询所有轮播图 class BannerView(GenericViewSet, CommonListModelMixin):# qs对象可以切片----》 limit 2queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[:settings.BANNER_COUNT]serializer_class = BannerSerializer
# urls.py from django.urls import path,include urlpatterns = [path('api/v1/home/', include('home.urls')), ]# home/urls.py from rest_framework.routers import SimpleRouter from .views import BannerViewrouter=SimpleRouter() router.register('banner',BannerView,'banner')urlpatterns = [ ]urlpatterns += router.urls
自定义配置
1、以后咱们会有自定义的配置 common_settings.py
# settings/common_settings # 自定义配置 BANNER_COUNT=3# 还有自己其他的配置,都写在这里
2、将自定义配置引入总 settings/dev.py 中
# 导入common_settings.py from .common_settings import *
3、只需要在配置文件中导入:from .common_settings import *
前端页面
vue里面都是首页+组件
唯一首页:
//luffy_city //views/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" class="course_detail"><el-card :body-style="{ padding: '0px' }"><img src="http://photo.liuqingzheng.top/2023%2002%2022%2021%2057%2011%20/image-20230222215707795.png"class="image"><div style="padding: 14px;"><span>推荐课程</span><div class="bottom clearfix"><time class="time">价格:999</time><el-button type="text" class="button">查看详情</el-button></div></div></el-card></el-col></el-row></div><img src="http://photo.liuqingzheng.top/2023%2003%2001%2016%2010%2034%20/1.png" alt="" width="100%" height="500px"><Footer></Footer></div> </template><script> import Footer from '@/components/Footer' import Header from '@/components/Header' import Banner from "@/components/Banner";export default {name: 'HomeView',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_detail {padding: 50px; } </style>
三个组件:
// components/Banner.vue <template><div class="banner"><el-carousel height="400px"><el-carousel-item v-for="banner in bannerList" :key="banner.id"><div v-if="banner.link.startsWith('http:')"><a :href="banner.link"><img :src="banner.image" alt=""></a></div><div v-else><router-link :to="banner.link"><img :src="banner.image" alt=""></router-link></div></el-carousel-item></el-carousel></div> </template><script> export default {name: "Banner",data() {return {bannerList: []}},created() {this.$axios.get(this.$settings.BASE_URL + 'home/banner/').then(res => {if (res.data.code == 100) {this.bannerList = res.data.results} else {this.$message.error(res.data.msg);}}).catch(res => {this.$message.error('系统异常,请稍后再试');})} } </script><style scoped> .el-carousel__item {height: 400px;min-width: 1200px; }.el-carousel__item img {height: 400px;margin-left: calc(50% - 1920px / 2); } </style>
// 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>
// 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"><span>登录</span><span class="line">|</span><span>注册</span></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; }.search {float: right;position: relative;margin-top: 22px;margin-right: 10px; }.search input, .search button {border: none;outline: none;background-color: white; }.search input {border-bottom: 1px solid #eeeeee; }.search input:focus {border-bottom-color: orange; }.search input:focus + button {color: orange; }.search .tips {position: absolute;bottom: 3px;left: 0; }.search .tips span {border-radius: 11px;background-color: #eee;line-height: 22px;display: inline-block;padding: 0 7px;margin-right: 3px;cursor: pointer;color: #aaa;font-size: 14px;}.search .tips span:hover {color: orange; } </style>
前后端打通
前后端打通:轮播图需在后端数据库中取
后端:表模型、接口、路由
前端:在banner组件中creat()中发送axios在响应路由地方拿到轮播图数据
再展示在轮播图里
created() {this.$axios.get(this.$settings.BASE_URL + 'home/banner/').then(res => {if (res.data.code == 100) {this.bannerList = res.data.results} else {this.$message.error(res.data.msg);}}).catch(res => {this.$message.error('系统异常,请稍后再试');})}
跨域问题详解
# 同源策略(Same origin policy)是一种约定,它规定了 请求的url地址,必须与浏览器上的url地址处于同域上,也就是域名,端口,协议相同,如果不一致,请求会发送成功,后端会正常响应,但是浏览器对非同源请求返回的结果做了拦截
只要做前后端分离,就会出跨域
# 解决跨域问题:
CORS :跨域资源共享, 向响应头中加数据,允许跨域
后端代码处理
nginx代理
JSONP :利用有的标签没有跨域问题 script img
websocket:长链接,不存在跨域
前端代理:开发阶段用,上线不用
# CORS请求分成两类:
简单请求---只发送一次:
非简单请求---发送两次,第一次是OPTIONS预检请求,第二次是真正的请求
#请求方法是以下三种方法之一: HEAD、GET、 POST
# HTTP的头信息不超出以下几种字段:Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
# 解决跨域: 统一写个中间件,处理所有跨域
# utills/common_cors.py from django.utils.deprecation import MiddlewareMixin class CorsMiddleWare(MiddlewareMixin):def process_response(self,request,response):if request.method=="OPTIONS":#可以加*response["Access-Control-Allow-Headers"]="*"res['Access-Control-Allow-Methods'] = '*'response["Access-Control-Allow-Origin"] = "*"return response
# 第三方解决方案:
使用pip安装:pip install django-cors-headers
配置文件settings/dev.py# setting的app中 INSTALLED_APPS = [...'corsheaders',... ] # 添加中间件 MIDDLEWARE = [ ...'corsheaders.middleware.CorsMiddleware',... ]# 底部添加 CORS_ORIGIN_ALLOW_ALL = True 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','token' )