vue+django实战开发h5旅游网(复习)

需求描述

需要开发一个在线旅游网站,主要在移动端使用,界面美观,体验要好。可以查看景点并在线预订门票信息,游客可以查看景点和门票信息,如果要预订门票需要注册成为会员。为了方便联系,必须收集用户的真实手机号码,景点数据较多,首页数据要快。另外,可以在线维护景点及门票信息,且有统计功能。开发周期有限,希望快速看到效果

需求分析

功能性需求非功能性需求
登录、注册首页速度要快
景点、门票展示界面美观,体验要好
在线预订门票移动端项目
后台管理时间有限
统计报表便于维护,按模块开发

技术栈选择

Django+vue+mysql+redis

移动端前后端完成分离开发

版本迭代计划

v1.0: 首页基本功能

v1.1:景点详情、评论、搜索

v1.2:用户的注册和登录功能

v1.3 : 提交订单、订单管理

v 1.4 :后台管理、报表统计

一、首页拆解

 拆分为

  • 标题
  • 轮播图
  • 热门推荐
  • 精选景点
  • 底部导航(固定在底部)

可以使用VantUI开发

Vant 2 - Mobile UI Components built on Vue

1.1 轮播图开发

开发步骤:

第一步,查找Vant中可以使用的组件

第二步,实现组件模板部分

第三步,模型准备数据

第四步,模拟数据,实现轮播效果

1.2 热门推荐组件开发

第一步,查找Vant中可以使用的组件

第二步,完成布局,实现组件模板部分

拆分为 热门推荐标题,图片,景点名称,价格,是可以左右滑动,所以要加滚动条 overflow-x: scroll;和采用flex布局,flex-direction:column

1.3 精选景点开发

第一步:查找Vant中可以使用的组件  

        精选标题,更多 使用 vant 中cell单元格,评分使用 Rate评分

第二步,完成布局,实现组件模板部分

第三步,模型准备数据

第四步,模拟数据,实现效果

景点列表组件开发

因为精选景点点击右边菜单还有个列表,对应一个新的页面,但这个页面和当前这个精选页面有重复的代码,所以单独抽取出来

开发步骤:

第一步:分析组件的复用情况

第二步:新建组件文件,实现组件内容

第三步:设置props从父组件传递数据

1.4 页面底部组件开发

第一步:查找Vant中可以使用的组件

        使用Tabbar标签栏

第二步,完成布局,实现组件模板部分

第三步,模型准备数据

第四步,模拟数据,实现效果

底部导航有多个页面用到所以应抽出为公共部分,

二、ORM模型设计

开发步骤:

第一步:分析并设计数据库模型

第二步:完成ORM模型编码

第三步:监测ORM模型

第四步:模型同步

2.1 分析并设计数据库模型

系统模块:轮播图,用户反馈

景点模块:景点、景点详情,景点评论

用户模块:用户,用户详细信息,登录历史

订单模块:订单,订单明细,支付相关

轮播图(system_slider)字段

名称(name),描述(desc),展现位置(types),图片地址(img),排序字段(reorder),生效开始时间(start_time),生效结束时间(end_time),跳转地址(targer_url),是否删除(is_valid),创建时间(created_at),修改时间(update_at)

class Slider(models.Model):
    """轮播图"""
    name = models.CharField('名称',max_length=32)
    desc = models.CharField('描述', max_length=100,null=True,blank=True)
    types = models.SmallIntegerField('展现的位置', default=10)
    img = models.ImageField('图片地址', max_length=255,upload_to='%Y%m/slider')
    reorder = models.SmallIntegerField('排序字段', default=0,help_text='数字越大越靠前')
    start_time = models.DateTimeField('生效开始时间', null=True,blank=True)
    end_time = models.DateTimeField('生效结束时间', null=True,blank=True)
    target_url = models.CharField('跳转的地址', max_length=255,null=True,blank=True)
    is_valid = models.BooleanField('是否有效', default=True)
    created_at = models.DateTimeField('创建时间',auto_now_add=True)
    updated_at = models.DateTimeField('修改时间',auto_now=True)

    class Meta:
        db_table = "system_slider"
        ordering = ['-reorder']

2.2 设计轮播图接口

开发步骤:

  1. 设计接口返回标准 (定义接口返回结构,接口错误信息约定)
  2. 编写接口代码
  3. 模拟HTTP请求,测试验证接口

接口返回结构

data = {
        "mata":{},
        "objects":[]
    }

def slider_list(request):
    """轮播图接口"""

    data = {
        "mata":{},
        "objects":[]
    }

    queryset = Slider.objects.filter(is_valid=True)
    for item in queryset:
        data['objects'].append({
            'id':item.id,
            'img':item.img.url,
            'target_url':item.target_url,
            'name':item.name
        })
    return http.JsonResponse(data)

2.3 景点ORM模型设计

景点字段名:

class Sight(models.Model):
    """景点基础信息"""

    name = models.CharField('名称',max_length=64)
    desc = models.CharField('描述',max_length=128)
    main_img = models.ImageField('主图',upload_to='%Y%m/sight',max_length=256)
    banner_img = models.ImageField('详细主图',upload_to='%Y%m/sight',max_length=256)
    content = models.TextField('详细')
    score = models.FloatField('评分',default=5)
    min_price = models.FloatField('最低价格',default=0)
    province = models.CharField('省份',max_length=32)
    city = models.CharField('市区',max_length=32)
    area = models.CharField('区/县',max_length=32,null=True)
    town = models.CharField('乡镇',max_length=32,null=True)

    is_top = models.BooleanField('是否为精选景点',default=False)
    is_hot = models.BooleanField('是否为热门景点',default=False)

    is_valid = models.BooleanField('是否有效',default=True)
    created_at = models.DateTimeField('创建时间',auto_now_add=True)
    updated_at = models.DateTimeField('修改时间',auto_now=True)

    class Meta:
        db_table = 'sight'

 2.4 景点列表API接口

开发步骤

  1. 设计接口返回内容及字段
  2. 编写接口代码(查数据,分页)
  3. 模拟HTTP请求,测试验证接口

接口结构

 data = {
            'meta':{

            },
            'objects':[
                    ]
        }

可能景点有很多,需要用到分页,这里使用Django中的ListView,使用面向对象的方式写视图函数

class SightListView(ListView):
    """景点列表"""
    # 设置每页的数目
    paginate_by = 5

    def get_queryset(self):
        """重写查询方法"""
        query = Q(is_valid=True)
        # 1 热门景点
        is_hot = self.request.GET.get('is_hot', None)
        if is_hot:
            query = query & Q(is_hot=True)

        # 2 精选景点
        is_top = self.request.GET.get('is_top', None)
        if is_top:
            query = query & Q(is_top=True)
        # 3 按景点名称搜索
        queryset = Sight.objects.filter(query)
        return queryset

    def render_to_response(self, context, **response_kwargs):
        page_obj = context['page_obj']
        data = {
            'meta': {
                # 总共多少条记录
                'total_count': page_obj.paginator.count,
                # 总共有多少页
                'page_count': page_obj.paginator.num_pages,
                # 当前是多少页
                'current_page': page_obj.number,
            },
            'objects': [

            ]
        }

        for item in page_obj.object_list:
            data['objects'].append({
                'id': item.id,
                'name': item.name,
                'main_img': item.main_img.url,
                'score': item.score,
                'province': item.province,
                'city': item.city,
                'comment_count': 0

            })
        return http.JsonResponse(data)

2.5 轮播图接口的数据获取(接口联调)

实现步骤

  1. 阅读接口文档
  2. 配置接口地址
  3. 使用axios获取数据
  4. 将数据设置到模型层

建立utils 文件夹,下面新建apis.js,用来存放接口

//接口地址

const apiHost = 'http://localhost:8080/api'


/**
 * 系统模块的接口
 */
const SystemApis = {
    // 轮播图列表
    sliderListUrl: apiHost + '/system/slider/list/'
}

export{
    
    SystemApis,
}

utils下新建ajax.js文件,

import axios from 'axios'

export const ajax = axios.create({
    headers: {
        source: 'h5',
        icode: 'acbd',
        'Content-Type': 'application/x-www-form-urlencoded'
    },
    withCredentials: true
})
ajax.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    console.log('请求拦截到了')
    // window.app.$toast.loading({
    //     message: '加载中...',
    //     forbidClick: true,
    //     loadingType: 'spinner'
    // })
    return config
}, function (error) {
    // 对请求错误做些什么
    // window.app.$toast.clear()
    return Promise.reject(error)
})

ajax.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    console.log('响应拦截到了')
    // window.app.$toast.clear()
    return response
}, function (error) {
    // 对响应错误做点什么
    if (error.response) {
        if (error.response.status === 401) {
            window.alert('未登录,即将跳转到登录页面')
        } else if (error.response.status === 500) {
            window.app.$notify({
                message: '服务器正忙,请稍后重试',
                type: 'danger'
            })
            // window.alert('服务器正忙,请稍后重试')
        }
    }
    // window.app.$toast.clear()
    return Promise.reject(error)
})

根目录新建vue.config.js,在里面解决跨域问题

配置中添加

devServer:{
    proxy: {
      '/api': {
        target: 'http://127.0.0.1:8000/', //跨域请求的公共地址
        ws: false, //也可以忽略不写,不写不会影响跨域
        changeOrigin: true, //是否开启跨域,值为 true 就是开启, false 不开启
        pathRewrite: {
          '^/api': '' //注册全局路径, 但是在你请求的时候前面需要加上 /api  
        }
      }
    },

  }

在轮播图模块,获取轮播图数据,并按照接口返回的字段在模板中进行绑定

<script>
import {ajax} from '@/utils/ajax'
import { SystemApis } from "@/utils/apis";

export default{
    data(){
        return{
            bannerList:[]
        }
    },
    methods:{
        getDataList(){
            //获取轮播图数据
            ajax.get(SystemApis.sliderListUrl).then(res => {
                console.log('res',res.data.objects)
                this.bannerList = res.data.objects
            })
        }
    },
    created(){
        this.getDataList()
        
    }
}
</script>

2.6 景点列表接口数据获取

                 

实现步骤

  1. 阅读接口文档
  2. 配置接口地址
  3. 使用axios获取数据
  4. 将数据设置到模型层

三 、景点搜索页面

实现步骤

  1. 查找Vant中可以使用的组件
  2. 实现组件模板部分
  3. 模型层数据准备

可以将页面拆分为标题,搜索框,景点列表,分页,和底部导航

标题:可以使用Vant中的 navbar导航栏

搜索框:使用Vant中的Search搜索

景点列表:已抽出为公共部分可以导入使用

分页:可以使用Vant中的 pagination分页

底部导航 :已拆分为公共部分,可直接导入使用

新建Search.vue完成上面所拆分的页面

使用VueRouter添加Search搜索页面,在底部导航中将使用:to进行name绑定

在router下面的index.js中添加

import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
import Search from "../views/Search";

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: HomeView
  },
//搜索页面
  {
    path: '/search',
    name: 'Search',
    component: Search
  },
]

const router = new VueRouter({
  routes
})

export default router

底部导航路由绑定

<template>
    <!--底部导航(公共组件)-->
    <div>
        <van-tabbar v-model="active" route>
            <van-tabbar-item name="home" icon="home-o" :to="{'name':'Home'}">首页</van-tabbar-item>
            <van-tabbar-item name="search" icon="search" :to="{'name':'Search'}">搜索</van-tabbar-item>
            <van-tabbar-item name="mine" icon="friends-o">我的</van-tabbar-item>
            
        </van-tabbar>
    </div>
</template>

 四、搭建前端详情页

实现步骤:

  1. 设计URL路由规则
  2. 新建详情页页面
  3. 修改景点列表组件,支持路由跳转
  4. 拆分详情页组件 

详情页可拆分为,图片页,评论页,景点介绍页,门票页(后面做),新建相关页面使用<router-link>绑定路由链接,

分析首页精选列表和热门榜单,都可以点击更多和单个点击,使用<router-link>为其绑定相关路由链接

4.1 详情页相关组件

详情页可拆分为 页面头部(固定),大图,评分和景点介绍,地址,门票列表,评论列表

实现步骤:

第一步:查找Vant中可以使用的组件

第二步,完成布局,实现组件模板部分

第三步,模型准备数据

第四步,模拟数据,实现效果

页面头部:NavBar 导航栏

大图:Vant中Image图片

评分和景点介绍:可以从Vant中寻找icon图标

地址:使用Vant中的单元格

门票列表:可以用Vant中的Cell单元格(标题),Tag标记,Button按钮

热门评论:很多地方用到,应单独抽取出来,可以重复用

五、ORM模型设计

开发步骤:

  1. 分析并设计模型
  2. 完成ORM模型编码
  3. 检测ORM模型
  4. 模型同步

景点详细信息、景点评论、景点门票、景点图片、用户账户

景点详细信息

class Info(models.Model):
    """ 景点详情 """
    sight = models.OneToOneField(Sight, on_delete=models.CASCADE)
    entry_explain = models.CharField('入园参考', max_length=1024, null=True, blank=True)
    play_way = models.TextField('特色玩法',null=True, blank=True)
    tips = models.TextField('温馨提示', null=True, blank=True)
    traffic = models.TextField('交通到达', null=True, blank=True)

    class Meta:
        db_table = 'sight_info'

 景点评论

class Comment(CommonModel):
    """ 评论及回复 """
    user = models.ForeignKey(User, verbose_name='评论人',
                             related_name='comments',
                             on_delete=models.CASCADE)
    sight = models.ForeignKey(Sight, verbose_name='景点',
                              related_name='comments',
                              on_delete=models.CASCADE)
    content = models.TextField('评论内容', blank=True, null=True)
    is_top = models.BooleanField('是否置顶', default=False)
    love_count = models.IntegerField('点赞次数', default=0)
    score = models.FloatField('评分', default=5)

    ip_address = models.CharField('IP地址', blank=True, null=True, max_length=64)
    is_public = models.SmallIntegerField('是否公开', default=1)
    reply = models.ForeignKey(
        'self', blank=True, null=True,
        related_name='reply_comment',
        verbose_name='回复',
        on_delete=models.CASCADE)

    images = GenericRelation(ImageRelated,
                             verbose_name='关联的图片',
                             related_query_name="rel_comment_images")

    class Meta:
        db_table = 'sight_comment'
        ordering = ['-love_count', '-created_at']

 景点门票

class Ticket(CommonModel):
    """ 门票 """
    sight = models.ForeignKey(Sight, related_name='tickets', verbose_name='景点门票',
                              on_delete=models.PROTECT)
    name = models.CharField('名称', max_length=128)
    desc = models.CharField('描述', max_length=64, null=True, blank=True)
    types = models.SmallIntegerField('类型',
                                     choices=TicketTypes.choices,
                                     default=TicketTypes.ADULT,
                                     help_text='默认为成人票')
    price = models.FloatField('价格(原价)')
    discount = models.FloatField('折扣', default=10)
    total_stock = models.PositiveIntegerField('总库存', default=0)
    remain_stock = models.PositiveIntegerField('剩余库存', default=0)
    expire_date = models.IntegerField('有效期', default=1)
    return_policy = models.CharField('退改政策', max_length=64, default='条件退')
    has_invoice = models.BooleanField('是否提供发票', default=True)
    entry_way = models.SmallIntegerField('入园方式',
                                         choices=EntryWay.choices,
                                         default=EntryWay.BY_TICKET)
    tips = models.TextField('预定须知',null=True, blank=True)
    remark = models.TextField('其他说明', null=True, blank=True)
    status = models.SmallIntegerField('状态',
                                      choices=TicketStatus.choices,
                                      default=TicketStatus.OPEN)
    
    class Meta:
        db_table = 'sight_ticket'

景点图片

class ImageRelated(models.Model):
    """图片关联"""
    img = models.ImageField('图片',upload_to='%Y%m/file/',max_length=155)
    summary = models.CharField('图片说明',max_length=32,null=True,blank=True)
    user = models.ForeignKey(User,related_name='upload_images',verbose_name='上传的用户',on_delete=models.SET(None))
    content_type = models.ForeignKey(ContentType,on_delete=models.CASCADE)
    object_id = models.IntegerField('关联的模型')
    content_object = GenericForeignKey('content_type','object_id')

    class Meta:
        db_table = 'system_image_related'

 用户账户

class User(CommonModel):
    """用户模型"""
    username = models.CharField('用户名',max_length=32,unique=True)
    password = models.CharField('密码',max_length=256)
    nickname = models.CharField('昵称',max_length=32,unique=True)
    avatar = models.ImageField('用户头像',upload_to='avatar/%Y%m',null=True,blank=True)




    class Meta:
        db_table = 'account_user'

5.1 重构响应对象

开发步骤:

  1. 设计响应单个对象的基类
  2. 设计响应列表的基类
  3. 设计错误基础类

接口处 有很多重复的代码,接口结构都是{meta:{},objects:[]},重构后便于维护和修改

首先在utils下新建serializers.py

class BaseSerializer(object):

    def __init__(self, obj):
        self.obj = obj

    def to_dict(self):
        return {}

class MetaSerializer(object):
    """分页元数据"""

    def __init__(self, page, page_count, total_count):
        """

        :param page: 当前页
        :param page_count:总页数
        :param total_count: 总记录数
        """
        self.page = page
        self.page_count = page_count
        self.total_count = total_count

    def to_dict(self):
        return {
            'total_count': self.total_count,
            'page_count': self.page_count,
            'current_page': self.page
        }


class BaseListPageSerializer(object):
    """分页类的封装"""

    def __init__(self, page_obj, paginator=[], object_list=[]):
        """

        :param page_obj: 当前页的对象
        :param paginator: 分页器的对象
        :param object_list: 当前页的数据列表
        """
        self.page_obj = page_obj
        self.paginator = paginator if paginator else page_obj.paginator
        self.object_list = object_list if object_list else page_obj.object_list

    def get_object(self, obj):
        """对象内容,子类进行重写"""
        return {}

    def to_dict(self):
        page = self.page_obj.number
        page_count = self.paginator.num_pages
        total_count = self.paginator.count
        meta = MetaSerializer(page=page, page_count=page_count, total_count=total_count).to_dict()
        objects = []

        for obj in self.object_list:
            objects.append(self.get_object(obj))

        return {
            'meta': meta,
            'objects': objects
        }

sight下新建serializers.py文件类继承着BaseListPageSerializer,对get_object方法进行重写

from utils.serializers import BaseListPageSerializer

class SightListSerializer(BaseListPageSerializer):
    """景点列表"""

    def get_object(self, obj):
        return {
            'id': obj.id,
            'name': obj.name,
            'main_img': obj.main_img.url,
            'score': obj.score,
            'province': obj.province,
            'city': obj.city,
            'price': obj.min_price,
            'comment_count': 0
        }

设计错误类,比如400错误响应字段

from django.http import JsonResponse


class NotFoundJsonResponse(JsonResponse):

    """400对应JSON响应"""
    def __init__(self,*args,**kwargs):

        data = {
            'error_code':'404000',
            'error_msg':'您访问的内容不存在或已被删除'
        }
        super().__init__(data,*args,**kwargs)

5.2 景点详情接口

开发步骤:

  1. 设计接口返回内容及字段
  2. 编写接口代码
  3. 模拟HTTP请求,测试验证接口

视图继承DetailView,get_query(写出从哪个模型获取数据),重写render_to_response(),返回对象

sight/serializers继承BaseSerializer重写to_dict函数,写出接口返回对象

class SightDetailSerializer(BaseSerializer):

    def to_dict(self):
        obj = self.obj
        return {
            'id' : obj.id,
            'name':obj.name,
            'img':obj.banner_img.url,
            'content':obj.content,
            'score':obj.score,
            'province':obj.province,
            'min_price':obj.min_price,
            'area':obj.area,
            'city':obj.city,
            'town':obj.town,
            'comment_count':0

        }
class SightDetailView(DetailView):

    """景点详细接口"""
    def get_queryset(self):
        return Sight.objects.all()

    def render_to_response(self, context, **response_kwargs):
        page_obj = context['object']
        if page_obj:
            if page_obj.is_valid == True:
                data = serializers.SightDetailSerializer(page_obj).to_dict()
                return http.JsonResponse(data)
        return NotFoundJsonResponse()

5.3 景点评论列表开发

开发步骤:

  • 设计接口及返回字段
  • 编写接口代码
  • 模拟HTTP请求,测试验证接口

视图中使用继承ListView的方法来写接口,注意设置paginate_by大小

class SightCommentView(ListView):
    """景点评论接口"""
    paginate_by = 5
    def get_queryset(self):
        sight_id = self.kwargs.get('pk',None)
        sight = Sight.objects.filter(id=sight_id,is_valid=True).first()

        if sight:
            return Comment.objects.filter(sight=sight,is_valid=True)
        return Comment.objects.none()

    def render_to_response(self, context, **response_kwargs):
        page_obj = context['page_obj']
        print(page_obj)
        if page_obj:

            data = serializers.CommentListSerializer(page_obj).to_dict()
            return http.JsonResponse(data)
        return NotFoundJsonResponse()
class CommentListSerializer(BaseListPageSerializer):
    """评论列表"""

    def get_object(self, obj):
        user = obj.user
        images = []
        for image in obj.images.filter(is_valid=True):
            images.append({
                'img': image.img.url,
                'summary': image.summary,
            })

        return {
            'user': {
                'pk': user.pk,
                'nickname': user.nickname},
            'pk': obj.pk,
            'content': obj.content,
            'is_top': obj.is_top,
            'love_count': obj.love_count,
            'score': obj.score,
            'images': images,
            //时间转换
            'create_at': obj.created_at.strftime('%Y-%m-%d')
        }

5.4 门票列表接口开发

开发步骤:

  1. 设计接口返回内容及字段
  2. 编写接口代码
  3. 模拟HTTP请求,测试验证接口

视图使用面向对象的方法继承ListView,

class SightTicketView(ListView):
    """景点下的接口列表"""

    paginate_by = 10

    def get_queryset(self):
        sight_id = self.kwargs.get('pk', None)
        return Ticket.objects.filter(is_valid=True, sight_id=sight_id)

    def render_to_response(self, context, **response_kwargs):
        page_obj = context['page_obj']
        print(page_obj)
        if page_obj:
            data = serializers.TicketListSerializer(page_obj).to_dict()
            return http.JsonResponse(data)
        return NotFoundJsonResponse()
class TicketListSerializer(BaseListPageSerializer):
    """门票列表"""

    def get_object(self, obj):
        return {
            'pk': obj.pk,
            'name': obj.name,
            'desc':obj.desc,
            'types': obj.types,
            'price': obj.price,
            'discount': obj.discount,
            'total_stock': obj.total_stock,
            'remain_stock': obj.remain_stock
        }

5.5 景点详细信息接口

开发步骤:

  1. 设计接口返回内容及字段
  2. 编写接口代码
  3. 模拟HTTP请求,测试验证接口

视图使用面向对象的方法,继承DetailView,

class SightInfoDetailView(DetailView):
    """景点介绍"""
    slug_field = 'sight_pk'

    def get_queryset(self):
        return Info.objects.all()

    def render_to_response(self, context, **response_kwargs):
        page_obj = context['object']
        if page_obj:
            data = serializers.SightInfoSerializer(page_obj).to_dict()
            return http.JsonResponse(data)
        return NotFoundJsonResponse()
class SightInfoSerializer(BaseSerializer):
    """景点介绍"""

    def to_dict(self):
        obj = self.obj
        return {
            'pk': obj.sight.pk,
            'entry_explain': obj.entry_explain,
            'play_way': obj.play_way,
            'tips': obj.tips,
            'traffic': obj.traffic
        }

六、景点搜索接口联调

实现步骤:

  1. 修改景点列表接口,实现搜索
  2. 配置接口地址
  3. 使用axios获取数据
  4. 将数据设置到模型层

由前端指定每页数据大小

Django重写get_paginate_by方法

request.GET.get()从前端传过来的数据,返回

实现从首页热门推荐和精选景点点击全部跳转到搜索页,将热门推荐榜单和精选景点榜单全部展示出来

七、景点详情接口联调 

实现步骤:

  1. 阅读接口文档
  2. 配置接口地址
  3. 使用axios获取数据
  4. 将数据设置到模型层

景点详情url是动态的id,用特殊字符替换id,页面传递时再用实际的id替换回来

SightDatailUrl: apiHost + '/sight/sight/detail/#{id}/',
 getDetailList() {

            const url = SightApis.SightDatailUrl.replace('#{id}', this.id);

            ajax.get(url).then(({ data }) => {
                this.detailList = data
            })

        },

7.1 景点门票接口联调

实现步骤:

  1. 阅读接口文档
  2. 配置接口地址
  3. 使用axios获取数据
  4. 将数据设置到模型层
 getTicketList() {
            const url = SightApis.SightTicketUrl.replace('#{id}', this.id)
            console.log(url)
            ajax.get(url).then(({ data: { objects } }) => {

                this.ticketList = objects
            })
        },

7.2 景点评论接口联调

实现步骤:

  1. 阅读接口文档
  2. 配置接口地址
  3. 使用axios获取数据
  4. 将数据设置到模型层

7.3 前端代码优化

添加loading动画(使用Vant中的Toast 轻提示)

添加请求拦截 

添加滚动加载和下拉刷新 (使用vant中的List 列表)

<div class="page-sight-comment">
        <van-nav-bar left-text="返回" left-arrow @click-left="goBack" fixed />
        <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
            <van-list class="sight-comment" v-model="loading" :finished="finished" finished-text="没有更多了" :error.sync="error"
                 error-text="请求失败,点击重新加载" @load="getCommentList">
                <!-- <van-cell v-for="item in list" :key="item" :title="item" /> -->
        
                <CommentItem v-for="item in commentList" :key="item.pk" :item="item" />
            </van-list>
        </van-pull-refresh>
    </div>
import { ajax } from "@/utils/ajax";
import { SightApis } from "@/utils/apis";
import CommentItem from '@/components/sight/CommentItem'
export default {
    
    data() {
        return {
            // 评论列表
            commentList: [],
            // 当前的页码
            currentPage: 1,
            // 正在加载中
            loading: false,
            // 所有的内容加载完
            finished: false,
            // 请求失败
            error: false,
            // 是否正在下拉刷新中
            refreshing: false
        }
    },
    components: {
        CommentItem
    },
    methods: {
        goBack() {
            this.$router.go(-1)
        },

        
        onRefresh() {
            // 清空数据
            this.commentList = []
            this.currentPage = 1

            this.finished = false
            this.error = false

            // 重新加载数据
            this.getCommentList()
        },
       
        getCommentList() {
            const url = SightApis.SightCommenttUrl.replace('#{id}', this.id)
            ajax.get(url, {
                params: {
                    page: this.currentPage
                }
            }).then(({ data: { meta, objects } }) => {
                this.commentList = this.commentList.concat(objects)
                // 加载状态结束
                this.loading = false
                // 设置下一页的页码
                this.currentPage = meta.current_page + 1
                // 数据全部加载完成: 当前页面 == 总页数
                if (meta.current_page === meta.page_count) {
                    this.finished = true
                }
                this.refreshing = false
            }).catch(() => {
                this.loading = false
                this.error = true
                this.refreshing = false
            })
        }

    },
    mounted() {
        this.id = this.$route.params.id



    }

}
</script>

八、用户登录页面开发

登录页面拆分

顶部导航条 (Vant NavBar 导航栏)

表单 (Vant Form表单)

登录按钮 (Vant Form表单)

登录注意信息

8.1 用户注册页面的开发

 实现步骤:

  1. 查找Vant中可以使用的组件
  2. 实现组件模板部分
  3. 实现验证码组件(需要用到window中的setInterval和clearInterval)

 验证码点击后不可再点击可以使用 disabled参数

vue中ref相当于给Dom起了个名字,

<template>
    <van-button size="small" type="primary" @click.prevent="sendSmsCode" :disabled="isSmsSend">{{sendBtnText}}
    </van-button>
</template>

<script>
export default {
    props: ['phoneNum'],

    data() {
        return {
            isSmsSend: false,
            sendBtnText: '发送验证码',
            counter: 60,
            timer: null
        }
    },
    methods: {
        
        //重置验证码
        reset() {
            this.isSmsSend = false,
            this.sendBtnText = '发送验证码'
            if (this.timer) {
                clearInterval(this.timer)
                this.counter = 60
                this.timer = null
            }
        },
        //倒计时
        countDown() {
            this.timer = setInterval(() => {
                this.sendBtnText = `${this.counter}秒后重新发送`
                this.counter--
                if (this.counter < 0) {

                    this.reset()
                }
            }, 1000)

        },

        sendSmsCode() {
            //判断手机号是否输入
            if (!this.phoneNum) {
                this.$notify('请输入手机号')
                return false
            }

            //调用接口
            //开启倒计时,60秒后才可点击
            this.isSmsSend = true
            this.countDown()

        }




    }
}

</script>

8.2 个人中心页面开发

拆分:

顶部标题(vant NavBar 导航栏)

头像 

欢迎你 

订单 (vant 上查找图标)

底部菜单  (引用之前写的组件)

九、用户登录,退出接口开发

开发步骤

  1. 设计接口返回内容及字段
  2. 编写接口代码
  3. 模拟HTTP请求,测试验证接口(使用postman)

登录为post请求

9.1 用户详情接口开发

开发步骤

  1. 设计接口返回内容及字段
  2. 编写接口代码
  3. 模拟HTTP请求,测试验证接口(使用postman)

用户登录了才能查询信息(可以使用is_authenticated判断是否登录)

9.2 短信验证码接口

开发步骤

  1. 设计接口返回内容及字段
  2. 编写接口代码
  3. 模拟HTTP请求,测试验证接口(使用postman)

验证码使用redis缓存,需要在根settings中配置

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/2",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

在系统模块中新建表单完成验证码功能

import random
import re

from django import forms
from django.core.cache import cache


class SendSmsCodeForm(forms.Form):
    """ 发送验证码的表单 """
    phone_num = forms.CharField(label='手机号码', required=True, error_messages={
        'required': '请输入手机号码'
    })

    def clean_phone_num(self):
        """ 验证是否为手机号 """
        phone_num = self.cleaned_data['phone_num']
        pattern = r'^1[0-9]{10}$'
        if not re.search(pattern, phone_num):
            raise forms.ValidationError('手机号%s输入不正确',
                                        code='invalid_phone',
                                        params=(phone_num, ))
        return phone_num

    def send_sms_code(self):
        """ 生成验证码并发送短信 """
        sms_code = random.randint(100000, 999999)
        phone_num = self.cleaned_data.get('phone_num', None)
        try:
            # TODO 调用发送验证码的短信接口
            # redis中的key
            key = 'sms_code_{}'.format(phone_num)
            # 将验证码存入redis
            timeout = 5 * 60
            cache.set(key, sms_code, timeout=timeout)
            return {
                'phone_num': phone_num,
                'sms_code': sms_code,
                'timeout': timeout
            }
        except Exception as e:
            print(e)
            return None

视图中

class SmsCodeView(FormView):
    form_class = SendSmsCodeForm

    # 1. 拿到手机号,判断是否为真实的手机号码

    # 2. 生成验证码,并存储
    # TODO 3. 调用短信的发送接口
    # 4. 告诉用户验证码发送是否成功(会把验证码直接告诉用户)

    def form_valid(self, form):
        """ 表单已经通过验证 """
        data = form.send_sms_code()
        if data is not None:
            return http.JsonResponse(data, status=201)
        return ServerErrorJsonResponse()

    def form_invalid(self, form):
        """ 表单没有通过验证 """
        err_list = json.loads(form.errors.as_json())
        return BadrequestJsonResponse(err_list)

9.3 用户注册接口

开发步骤

  1. 设计接口返回内容及字段
  2. 编写接口代码
  3. 模拟HTTP请求,测试验证接口(使用postman)

1.表单验证用户输入信息

2.创建用户基础信息表,用户详细信息表

3.执行登录

4.保存登录日志

注册的表单

class RegisterForm(forms.Form):
    username = forms.CharField(label='手机号码', max_length=16, required=True, error_messages={
        'required': '请输入手机号码'
    })

    password = forms.CharField(label='密码', max_length=266, required=True, error_messages={
        'required': '请输入密码'
    })

    nickname = forms.CharField(label='验证码', max_length=16, required=True, error_messages={
        'required': '请输入昵称'
    })

    sms_code = forms.CharField(label='验证码', max_length=6, required=True, error_messages={
        'required': '请输入验证码'
    })

    def clean_username(self):
        """验证用户名的钩子函数"""
        username = self.cleaned_data['username']
        pattern = r'^1[0-9]{10}$'
        if not re.search(pattern, username):
            raise forms.ValidationError('手机号%输入不正确', params=(username,), code='invalid_phone')
        if User.objects.filter(username=username).exists():
            raise forms.ValidationError('用户名已被使用')
        return username

    def clean_nickname(self):
        """昵称验证"""
        nickname = self.cleaned_data['nickname']
        if User.objects.filter(nickname=nickname).exists():
            raise forms.ValidationError('昵称已被使用')
        return nickname

    def clean(self):
        data = super().clean()
        if self.errors:
            return
            # 获取手机号
        phone_num = self.cleaned_data.get('username', None)
        # 获取验证码
        sms_code = self.cleaned_data.get('sms_code', None)

        key = '{}{}'.format(REGISTER_MSM_CODE_KEY, phone_num)
        code = cache.get(key)
        if code is None:
            raise forms.ValidationError('验证码已经失效')
        if str(code) != sms_code:
            raise forms.ValidationError('验证码输入错误')
        return data

    @transaction.atomic
    def do_register(self, request):
        """执行注册"""
        # 创建基础信息表
        try:
            data = self.cleaned_data
            version = request.headers.get('version', ''),
            source = request.headers.get('source', '')
            user = User.objects.create_user(
                username=data.get('username', None),
                password=data.get('password', None),
                nickname=data.get('nickname', None)
            )
            # 创建详细信息表
            profile = Profile.objects.create(
                user=user,
                username=user.username,
                version=version,
                source=source

            )
            # 执行登录
            login(request, user)
            # 记录登录日志
            user.last_login = now()
            user.save()
            ip = request.META.get('REMOTE_ADDR', '')
            user.add_login_record(username=user, ip=ip, source=source, version=version)
            return user, profile
        except Exception as e:
            print(e)
            return None

注册的视图

class UserRegisterView(FormView):
    form_class = RegisterForm
    http_method_names = ['post']

    def form_valid(self, form):
        result = form.do_register(request=self.request)
        if result is not None:
            user, profile = result
            data = {
                'user': serializers.UserSerializer(user).to_dict(),
                'profile': serializers.UserProfileSerializer(profile).to_dict()
            }
            return http.JsonResponse(data, status=201)
        return ServerErrorJsonResponse()

    def form_invalid(self, form):
        """ 表单没有通过验证 """
        err_list = json.loads(form.errors.as_json())
        return BadrequestJsonResponse(err_list)

9.4 用户登录接口联调

实现步骤

第一步:在登录组件中调用登录接口

第二步:设置用户信息到Vuex

第三步:在个人中心显示用户信息

第四步:刷新时调用个人信息接口

9.5 验证码获取接口联调

实现步骤

第一步:阅读接口文档

第二步:配置接口地址

第三步:使用axios获取数据

第四步:将数据设置到模型层

9.6 用户注册接口联调

实现步骤

第一步:阅读接口文档

第二步:配置接口地址

第三步:使用axios获取数据

第四步:将数据设置到模型层

 十、订单页面开发

页面拆分

页面标题

景点门票标题(预定须知)

出行日期(Vant中日历组件)

购买数量(Vant Stepper 步进器)

收件人,手机号码

订单合计  (Vant SubmitBar 提交订单栏)

10.1 提交订单页 

实现步骤:

  1. 配置路由规则,新建组件
  2. 查找Vant中可以使用的组件
  3. 实现组件模板部分
  4. 模拟数据,实现效果

10.2 订单支付页面

 拆分 

顶部导航 (Vant navbar)

订单号 (Vant中cell表格)

门票 (使用flex布局)

确认按钮(Vant SubmitBar 提交订单栏)

弹出的确认栏 (Vant中的Dialog 弹出框)

实现步骤:

  1. 配置路由规则,新建组件
  2. 查找Vant中可以使用的组件
  3. 实现组件模板部分
  4. 模拟数据,实现效果

10.3 我的订单列表页面开发 

拆分:

顶部导航

订单状态tab切换(Vant中可找到tab)

十一、订单模块ORM模型设计

订单表

 

class Order(CommonModel):
    """ 订单 """
    sn = models.CharField('订单编号', max_length=32)
    user = models.ForeignKey(User, related_name='orders', on_delete=models.PROTECT)
    buy_count = models.IntegerField('购买数量', default=1)
    buy_amount = models.FloatField('总价')

    to_user = models.CharField('收货人', max_length=32)
    to_area = models.CharField('省市区', max_length=32, default='')
    to_address = models.CharField('详细地址', max_length=256, default='')
    to_phone = models.CharField('手机号码', max_length=32)

    remark = models.CharField('备注', max_length=255, null=True, blank=True)

    # 快递信息
    express_type = models.CharField('快递', max_length=32, null=True, blank=True)
    express_no = models.CharField('单号', max_length=32, null=True, blank=True)

    status = models.SmallIntegerField('订单状态',
                                      choices=OrderStatus.choices,
                                      default=OrderStatus.SUBMIT)
    types = models.SmallIntegerField('订单类型',
                                     choices=OrderTypes.choices,
                                     default=OrderTypes.SIGHT_TICKET)

    class Meta:
        db_table = 'order'

 订单明细表

class OrderItem(CommonModel):
    """ 订单明细 """
    user = models.ForeignKey(User, related_name='order_items',
                             on_delete=models.CASCADE)
    order = models.ForeignKey(Order, verbose_name='订单',
                              related_name='order_items',
                              null=True,
                              on_delete=models.CASCADE)
    # 商品快照
    flash_name = models.CharField('商品名称', max_length=128)
    flash_img = models.ImageField('商品的主图')
    flash_price = models.FloatField('兑换价格')
    flash_origin_price = models.FloatField('原价')
    flash_discount = models.FloatField('折扣')

    count = models.PositiveIntegerField('购买数量')
    amount = models.FloatField('总额')

    status = models.SmallIntegerField('状态',
                                      choices=OrderStatus.choices,
                                      default=OrderStatus.SUBMIT)
    remark = models.CharField('备注', max_length=255, null=True, blank=True)

    # 复合关联
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    class Meta:
        db_table = 'order_item'

 支付凭证

class Payment(CommonModel):
    """ 支付凭证 """

    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='payments')
    order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='payments')
    amount = models.FloatField('金额', help_text='支付实际的金额')
    sn = models.CharField('流水号', max_length=32)
    third_sn = models.CharField('第三方订单号', max_length=128, null=True, blank=True)

    status = models.SmallIntegerField('支付状态', default=1)

    meta = models.CharField('其他数据', max_length=128, null=True, blank=True)
    remark = models.CharField('备注信息', max_length=128, null=True, blank=True)

    class Meta:
        db_table = 'order_payment'

 11.1 门票下单接口开发

开发步骤:

  1. 设计接口返回内容及字段
  2. 编写接口代码
  3. 模拟HTTP请求,测试验证接口

1.判断用户是否已经登录

2.获取post数据 

3.数据的验证(手机号,门票ID,库存)

4.关联用户,生成订单号,计算购买总价,生成订单

5.返回内容:订单ID

from django import forms
from django.contrib.contenttypes.models import ContentType
from django.db import transaction
from django.db.models import F

from order.models import Order, OrderItem
from sight.models import Ticket
from utils import tools


class SubmitTicketForm(forms.ModelForm):
    """门票订单提交表单"""

    ticket_id = forms.IntegerField(label='门票ID', required=True)
    play_date = forms.DateField(label='出行的时间', required=True)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.ticket = None

    class Meta:
        model = Order
        fields = ('to_user', 'to_phone', 'buy_count',)

    def clean_ticket_id(self):
        ticket_id = self.cleaned_data['ticket_id']
        ticket = Ticket.objects.filter(is_valid=True, pk=ticket_id).first()



        if ticket is None:
            raise forms.ValidationError('门票信息不存在')

        else:
            if ticket.remain_stock <= 0:
                raise forms.ValidationError('当日门票已售完')
        self.ticket = ticket
        return ticket_id

    @transaction.atomic
    def save(self, user, commit=False):
        obj = super().save(commit=commit)
        obj.user = user
        obj.sn = tools.gen_trans_id()

        # 计算价格
        buy_count = self.cleaned_data['buy_count']
        buy_amount = self.ticket.sell_price * buy_count
        obj.buy_amount = buy_amount
        obj.save()

        # 扣减库存
        self.ticket.remain_stock = F('remain_stock') - buy_count
        self.ticket.save()

        # 关联订单明细,保存快照
        ctype = ContentType.objects.get_for_model(Ticket)

        OrderItem.objects.create(
            user=user,
            order=obj,
            flash_name=self.ticket.name,
            flash_img=self.ticket.sight.main_img,
            flash_price=self.ticket.sell_price,
            flash_origin_price=self.ticket.price,
            flash_discount=self.ticket.discount,
            count=buy_count,
            amount=buy_amount,
            content_type=ctype,
            object_id=self.ticket.id,
            remark='出行时间:{}'.format(self.cleaned_data['play_date'])
        )
        return obj
method_decorator(login_required,name='dispatch')
class TicketOrderSubmitView(FormView):
    """门票订单提交接口"""
    form_class = SubmitTicketForm

    def form_invalid(self, form):
        err = json.loads(form.errors.as_json())
        return BadrequestJsonResponse(err)

    def form_valid(self, form):
        obj = form.save(user=self.request.user)
        return http.JsonResponse({
            'sn':obj.sn
        },status=201)

11.2 订单详情接口开发

开发步骤:

设计接口返回内容及字段 

编写接口代码

模拟HTTP请求,测试验证接口

使用BaseDetailView来写视图函数,使用sn号来查询订单的详情

class OrderDetail(BaseDetailView):
    slug_field = 'sn'
    slug_url_kwarg = 'sn'

    def get_queryset(self):
        user = self.request.user
        obj = Order.objects.filter(user=user,is_valid=True)
        return obj

    def get(self,request,*args,**kwargs):
        order_obj = self.get_object()
        data = serializers.OrderDetailSerializer(order_obj).to_dict()
        return http.JsonResponse(data)
class OrderItemSerializer(BaseSerializer):
    """订单明细"""
    def to_dict(self):
        obj = self.obj
        return {
            'pk':obj.pk,
            'flash_img':obj.flash_name,
            'flash_name':obj.flash_img.url,
            'flash_price':obj.flash_price,
            'flash_origin_price':obj.flash_origin_price,
            'flash_discount':obj.flash_discount,
            'count':obj.count,
            'amount':obj.amount,
            'object_id':obj.object_id,
            'app_label':obj.content_type.app_label,
            'model':obj.content_type.model
        }

class OrderDetailSerializer(BaseSerializer):

    def to_dict(self):
        obj = self.obj
        items=[]
        for item in obj.order_items.all():
            items.append(OrderItemSerializer(item).to_dict())
        return {
            'sn':obj.sn,
            'buy_count':obj.buy_count,
            'buy_amount':obj.buy_count,
            'types':obj.types,
            'status':obj.status,
            'created_at':obj.created_at,
            'remark':obj.remark,
            'to_user':obj.to_user,
            'to_area':obj.to_area,
            'to_address':obj.to_address,
            'to_phone':obj.to_phone,
            'express_type':obj.express_type,
            'express_no':obj.express_no,
            'items':items
        }

11.3 门票支付接口开发

开发步骤:

设计接口返回内容及字段 

编写接口代码

模拟HTTP请求,测试验证接口

使用面向对象方式,dispatch进行分发,根据不同的请求方式,执行不同的逻辑

@method_decorator(login_required, name='dispatch')
class OrderDetail(BaseDetailView):
    slug_field = 'sn'
    slug_url_kwarg = 'sn'

    def get_queryset(self):
        user = self.request.user
        obj = Order.objects.filter(user=user, is_valid=True)
        return obj

    def get(self, request, *args, **kwargs):
        order_obj = self.get_object()
        data = serializers.OrderDetailSerializer(order_obj).to_dict()
        return http.JsonResponse(data)

    @transaction.atomic
    def post(self, request, *args, **kwargs):
        """订单支付"""
        # 选择支付方式
        # 数据验证
        # 完成支付,改变订单状态
        order_obj = self.get_object()
        if order_obj.status == choices.OrderStatus.SUBMIT:
            order_obj.status = choices.OrderStatus.PAID
            order_obj.save()
            order_obj.order_items.update(status=choices.OrderStatus.PAID)
            return http.JsonResponse('', status=201, safe=False)
        return http.JsonResponse('', status=200, safe=False)

11.4 取消订单接口开发

开发步骤:

设计接口返回内容及字段 

编写接口代码

模拟HTTP请求,测试验证接口

@transaction.atomic
    def put(self, request, *args, **kwargs):
        """取消订单"""
        # 获取订单对象
        # 数据验证

        order_obj = self.get_object()
        if order_obj.status == choices.OrderStatus.SUBMIT:
            # 改变状态
            order_obj.status = choices.OrderStatus.CANCELED
            order_obj.save()
            # 改变状态
            items = order_obj.order_items.filter(status=choices.OrderStatus.SUBMIT)

            # 加回已经扣减的库存
            for item in items:
                obj = item.content_object
                obj.remain_stock = F('remain_stock') + item.count
                obj.save()
            items.update(status=choices.OrderStatus.CANCELED)
            return http.JsonResponse('', status=201, safe=False)
        return http.JsonResponse('', 200, safe=False)

11.5 删除订单接口开发

开发步骤:

设计接口返回内容及字段 

编写接口代码

模拟HTTP请求,测试验证接口

    def delete(self,request,*args,**kwargs):
        """删除订单"""
        # 获取订单对象
        # 验证数据(已支付,已取消)
        # 是否已经删除过了

        order_obj = self.get_object()
        if order_obj.status == choices.OrderStatus.PAID or order_obj.status == choices.OrderStatus.CANCELED:
            if order_obj.is_valid:
                order_obj.is_valid = False
                order_obj.save()
                return http.JsonResponse('',status=201,safe=False)
        return http.JsonResponse('',status=200,safe=False)

11.6 我的订单列表接口开发

开发步骤:

设计接口返回内容及字段 

编写接口代码

模拟HTTP请求,测试验证接口

视图中继承ListView,

1.获取当前用户

2.查询当前用户,is_valid=True和订单状态(前台穿过来)查询

class OrderListView(ListView):

    paginate_by = 5

    def get_queryset(self):
        # 获取用户
        user = self.request.user
        # 获取前台传过来的status
        status = self.request.GET.get('status',None)
        query = Q(user=user,is_valid=True)
        if status and status !='0':
            query = query & Q(status=status)
        # 在Order中查询
        return Order.objects.filter(query)
    def render_to_response(self, context, **response_kwargs):
        page_obj = context['page_obj']
        if page_obj:
            data = serializers.OrderListSerializer(page_obj).to_dict()
            return http.JsonResponse(data)
        return NotFoundJsonResponse()

    def get_paginate_by(self, queryset):
        """根据接口参数limit来控制分页大小"""
        page_size = self.request.GET.get('limit',None)
        return page_size or self.paginate_by

11.7 门票详细信息接口开发

开发步骤:

设计接口返回内容及字段 

编写接口代码

模拟HTTP请求,测试验证接口

class TicketDetailView(DetailView):
    """门票列表"""
    def get_queryset(self):
        return Ticket.objects.filter(is_valid=True)

    def render_to_response(self, context, **response_kwargs):
        page_obj = context['object']
        if page_obj:
            data = serializers.TicketDetailSerializer(page_obj).to_dict()
            return http.JsonResponse(data)
        return NotFoundJsonResponse()

十二、门票接口联调

12.1 门票下单接口联调

实现步骤

  1. 完成页面间的跳转
  2. 配置接口地址及请求参数
  3. 处理数据返回

12.2 门票详细信息接口联调

实现步骤:

  1. 阅读接口文档
  2. 配置接口地址
  3. 使用axios获取数据
  4. 将数据设置到模型层

12.3 门票支付接口联调

实现步骤:

  1. 阅读接口文档
  2. 配置接口地址
  3. 使用axios获取数据
  4. 将数据设置到模型层

用到两个接口,订单支付,订单信息接口

12.4 我的订单列表接口联调

  1. 完成页面间的跳转
  2. 配置接口地址及请求参数
  3. 处理数据返回
  4. 路由切换后的数据处理

12.4 取消订单接口联调

  1. 完成页面间的跳转
  2. 配置接口地址及请求参数
  3. 处理数据返回

12.5 删除订单接口联调

  1. 阅读接口文档
  2. 配置接口地址
  3. 使用axios获取数据
  4. 将数据设置到模型层

删除后的订单要在页面消失,在删除的ajax中将item.sn设为控制,再使用v-show将item.sn绑定,以为删除的该条目的sn为空值,所以为false,会在页面中隐藏

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Python+Vue+Django前后端分离项目实战,涉及到使用Python语言编写后端服务,使用Vue框架构建前端界面,并通过Django框架作为API接口实现前后端数据交互的方式。在实战项目中也会涉及到数据库设计和调用、权限控制、文件上传等问题的解决。 ### 回答2: Python、VueDjango是现在非常流行的前后端开发技术。如果你想学习这些技术,那么前后端分离是一个很好的选择。在这个项目中使用Python作为后端编程语言Django作为Web框架,Vue作为前端框架。我们可以在这个项目中学习到很多关于前后端分离的经验。 首先,前后端分离可以让我们更好地组织我们的代码。在一个分离的项目中,我们可以通过明确的接口来分离前端和后端。这样一来,我们可以专注于开发代码,而不必担心前后端之间的交互问题。 其次,前后端分离可以使我们更好地实现可重用性。在这个项目中,我们可以编写可重用的Vue组件,并在不同的前端页面中使用它们。同样地,我们也可以编写可重用的Django应用程序,并在不同的后端端点中使用它们。这会使我们的代码更加清晰简洁,也可以提高我们的开发效率。 最后,前后端分离可以让我们更好地实现可维护性。在一个分离的项目中,我们可以更轻松地进行单元测试和端到端测试。这可以帮助我们保持代码的质量,并尽早发现和解决潜在的问题。 总之,Python、VueDjango前后端分离项目是一个非常好的学习项目。通过这个项目,我们可以从实践中学习如何使用这些技术,并在以后的项目中更好地应用它们。 ### 回答3: Python、VueDjango是目前非常流行的技术栈之一,其中Python和Django主要用于后端开发Vue则是一款流行的前端框架。Python和Django的优点在于它们易于学习、可扩展性强、社区活跃,并且可以用于构建各种类型的Web应用程序。而Vue的优点在于它简洁、高效、组件化,并且可以很好地配合其他框架和库使用。 前后端分离是一种可以将前端和后端分别开发的方法,这种方法具有很多优点。首先,可以使开发人员更加专注于各自的领域,减少互相干扰和影响。其次,这种方法可以提高应用程序的可维护性和可扩展性。最后,这种方法还可以提高开发效率,使开发人员能够更加高效地开发应用程序。 在前后端分离项目实战中,需要注意以下几点。首先,需要确定应用程序的需求,确定前端和后端的接口规范。其次,需要使用合适的工具和框架来完成前后端的开发任务。对于后端开发,可以使用Django的REST框架,这个框架可以很快地构建RESTful API,并且配合数据库使用。而对于前端开发,则可以使用Vue框架来开发Vue可以很好地处理数据和UI逻辑。最后,需要着重测试和调试应用程序,确保应用程序的正确性和可用性。 总的来说,Python、VueDjango是一组非常流行的技术栈,非常适合用于前后端分离项目的开发。在开发前需要确定好需求,选择好工具和框架,开发期间需要注重测试和调试,这样才能开发出高质量的应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值