路飞项目--03

总页面

二次封装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, \
    RetrieveModelMixin

from .common_response import APIResponse


class CommonListModelMixin(ListModelMixin):
    def list(self, request, *args, **kwargs):
        # Response 的对象---》res.data
        res = 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 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  # 这样写了,这张表是个虚拟的,不会在数据库创建,只用来继承
# 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 Banner

class BannerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Banner
        fields = ['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 2
    queryset = 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 BannerView

router=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'
)

今日思维导图:

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值