Vue实战开发移动端旅游网站

Vue实战开发旅游网站

  • 项目搭建
    • 搭建步骤
      • 项目结构
      • 首页拆解
        • 首页组件拆分
        • 步骤
  • 网络请求库axios
    • axios与ajax
      • Promise
    • 安装axios
    • 如何使用axios
    • 请求和响应的拦截
      • 使用场景:
      • 使用axios请求拦截的实现方法
  • 跨域问题
    • 什么是跨域
      • 如何认定其他网站:同源策略
    • 跨域的影响
    • 解决跨域问题
      • 在Vue.js中解决跨域问题
      • 在Django中解决跨域问题
  • 前端组件开发
    • VantUI初探
    • 轮播图组件开发
    • 热门推荐组件开发
    • 精选景点组件开发
    • 页面底部组件开发
  • 后台接口开发
    • ORM模型设计
    • 轮播图ORM模型
    • 轮播图接口实现
    • 景点列表ORM模型
    • 景点列表接口实现
  • 接口联调
  • VueRouter实现多个页面
    • Vue中的路由管理
      • 路由的使用
    • 路由的参数传递
      • 动态路由匹配
        • 按照path跳转
        • 按照路由名称跳转
        • js实现页面跳转
        • 其他常见的跳转方式
  • 景点详情页面开发
    • 景点搜索页面

项目搭建

搭建步骤

Vue实战开发移动端旅游网站_第1张图片

项目结构

公共的样式:src/assets/style/common.less
公共的js(工具函数,接口地址,配置文件)

  1. src/utils/apis.js(接口地址配置)
  2. src/utils/constants.js(常量配置)
  3. src/utils/filters.js(工具函数)

创建好项目后,将准备好的静态文件夹(static)放入public/目录下。

在src/assets/下创建style文件夹,并在style文件夹下创建common.less文件。

在src/下创建utils文件夹,并在utils文件夹下分别创建apis.js,constants.js,filters.js文件。

注!需要在main.js中注册全局过滤器到vue实例上:
import * as filters from ‘./utils/filters’
Object.keys(filters).forEach(k => Vue.filter(k, filters[k]))

最终项目结构图:
Vue实战开发移动端旅游网站_第2张图片

首页拆解

Vue实战开发移动端旅游网站_第3张图片

首页组件拆分

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

步骤

1.分析首页结构
2.新建页面(views中)
3.新建对应组件(components)

注意思考:组件是否只用于当前页面(在src/components下新建文件夹用来存放对应页面所需的组件),或者组件用于多个页面(在src/components下新建文件夹用来存放公共的组件)

网络请求库axios

axios与ajax

ajax是通过浏览器后台与服务器通信的技术

axios是基于Promise的HTTP库(网络异步请求库),用来发送和处理http请求。

Promise

Promise是一种异步编程解决方案。

通过axios异步请求可以得到Promise对象,将它返回的结果进行处理。
Vue实战开发移动端旅游网站_第4张图片

Vue实战开发移动端旅游网站_第5张图片

安装axios

Vue实战开发移动端旅游网站_第6张图片
Vue实战开发移动端旅游网站_第7张图片

如何使用axios

Vue实战开发移动端旅游网站_第8张图片
1.GET请求
Vue实战开发移动端旅游网站_第9张图片
请求完毕后会返回一个响应,通过then函数获取响应(对结果进行处理)。如果返回异常则跳到catch函数。

2.POST请求
Vue实战开发移动端旅游网站_第10张图片
url后面的参数是一个js对象,是需要post过去的数据。区别get请求的参数params。

请求和响应的拦截

1.了解请求响应拦截的使用场景

2.掌握如何对请求响应做统一的处理

使用场景:

  • 设置自定义请求头
  • 默认携带上次的cookie(主要解决用户登录的问题)
  • 添加loading动画(请求发起前显示,完成后隐藏)
  • 统一的错误处理

使用axios请求拦截的实现方法

创建axios实例

const ajax = axios.create(options)

options:js对象,例如:

  • headers:设置请求头
  • timeout:超时的毫秒数
  • data:POST/PUT/PATCH请求的数据
  • params:url中的参数
  • responseType:默认响应json数据

在src/utils/下新建文件ajax.js用来配置请求响应的拦截
Vue实战开发移动端旅游网站_第11张图片
在请求发出之前进行拦截
Vue实战开发移动端旅游网站_第12张图片
Vue实战开发移动端旅游网站_第13张图片
Vue实战开发移动端旅游网站_第14张图片

跨域问题

什么是跨域

跨域就是浏览器出于安全的考虑,要求你的网站不能执行或者不能访问其他网站的资源。

如何认定其他网站:同源策略

http://www.baidu.com:80
http(s):协议
www.baidu.com:域名/主机
80:端口(默认)

跨域的影响

  • 浏览器本地存储无法使用(Cookie,LocalStorage,IndexDB)
  • DOM对象,JS对象无法获取
  • AJAX无法使用

解决跨域问题

  1. 利用HTML标签的特性(script,a,iframe,img等)获取其他域的资源。只能解决GET请求
  2. 在前端使用代理解决
  3. 在服务器端解决,CORS-跨域资源共享,通过添加响应头信息来解决

在Vue.js中解决跨域问题

1.添加配置文件vue.config.js(项目根路径下)
2.添加配置
Vue实战开发移动端旅游网站_第15张图片
target:目标地址
changeOrigin:变更请求头中设置的host(以xxx的名义进行访问)
pathRewrite:url的重写规则

例如要访问http://localhost:8080/api/test => 代理http://localhost:8000/test

在Django中解决跨域问题

步骤:

  1. 安装第三方扩展 pip install django-cors-headers。
  2. 配置 settings.py INSTALLED_APPS=[‘corsheaders’]。
  3. 配置中间件 ,放在SessionMiddleware和CommonMiddleware之间。
    Vue实战开发移动端旅游网站_第16张图片
  4. 添加白名单

Vue实战开发移动端旅游网站_第17张图片

  1. 可选配置
    (请求头是指自定义的请求头)
    Vue实战开发移动端旅游网站_第18张图片

前端组件开发

VantUI初探

VantUI:开源免费,轻量可靠的移动端Vue组件库。

安装:npm i vant -S

导入所有的组件:

import Vue from ‘vue’;

import Vant from ‘vant’;

import ‘vant/lib/index.css’;

Vue.use(Vant);

轮播图组件开发

Vue实战开发移动端旅游网站_第19张图片

热门推荐组件开发

Vue实战开发移动端旅游网站_第20张图片

精选景点组件开发

Vue实战开发移动端旅游网站_第21张图片

页面底部组件开发

Vue实战开发移动端旅游网站_第22张图片

后台接口开发

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

接口返回结构

{
   "meta": {},
   "objects": []
} 

接口的错误信息返回结构

{
    "error_code": '400000',
    "error_meg": "该字段不能为空。",
    "error_list": [
        "password": [
            "该字段不能为空。"
        ]
    ]
}

ORM模型设计

  • 系统模块:轮播图,用户反馈
  • 景点模块:景点,景点详情,景点评论
  • 用户模块:用户,用户详细信息,登录历史
  • 订单模块:订单,订单明细,支付相关

轮播图ORM模型

PDMAN建模
Vue实战开发移动端旅游网站_第23张图片
Vue实战开发移动端旅游网站_第24张图片

class Slider(models.Model):
    """ 轮播图 """
    name = models.CharField('名称', max_length=64)
    desc = models.CharField('描述信息', max_length=256, null=True, blank=True)
    types = models.SmallIntegerField('轮播图类型', default=10)
    img = models.ImageField('图片地址', max_length=256, upload_to='%Y%m/slider')
    start_time = models.DateTimeField('生效时间', null=True, blank=True)
    end_time = models.DateTimeField('失效时间', null=True, blank=True)
    reorder = models.SmallIntegerField('排序字段', default=0, help_text='数字越大靠前')
    target_url = models.CharField('跳转地址', max_length=256, 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'
    # 按reorder降序排列
    ordering = ['-reorder']

轮播图接口实现

def slider_list(request):
    """ 轮播图接口 """
    data = {
        "meta": {},
        "objects": []
    }
    query = Q(is_valid=True)
    queryset = Slider.objects.filter(query)
    for item in queryset:
        data['objects'].append({
            'id': item.id,
            # img字段是ImageField,它有一个属性url,可以访问图片存在磁盘上的路径
            'img_url': item.img.url,
            'target_url': item.target_url,
            'name': item.name,
            'types': item.types
        })
    return JsonResponse(data)

景点列表ORM模型

Vue实战开发移动端旅游网站_第25张图片

 class Sight(models.Model):
    """ 景点基础信息 """
    name = models.CharField('名称', max_length=64)
    desc = models.CharField('描述', max_length=64)
    main_img = models.ImageField('主图', upload_to='%Y%m/sight', max_length=512)
    banner_img = models.ImageField('详细主图', upload_to='%Y%m/sight', max_length=512)
    content = models.TextField('详细信息')
    score = models.FloatField('评分', default=5)
    province = models.CharField('省份', max_length=32)
    city = models.CharField('市区', max_length=32)
    area = models.CharField('区/县', max_length=32, null=True, blank=True)
    town = models.CharField('乡镇', max_length=32, null=True, blank=True)
    min_price = models.FloatField('最低价格', default=0)
    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'
        # 最新更新的排前面
        ordering = ['-updated_at']

景点列表接口实现

class SightListView(ListView):
    """ 景点列表接口 """
    # 分页,每页放5条数据
    paginate_by = 5

    def get_queryset(self):
        """ 重写查询方法  """
        query = Q(is_valid=True)
        # 查询热门景点
        is_hot = self.request.GET.get('is_hot', None)
        if is_hot:
            query = query & Q(is_hot=True)
        # 查询精选景点
        is_top = self.request.GET.get('is_top', None)
        if is_top:
            query = query & Q(is_top=True)
        # TODO:按照景点名称搜索
        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,
                'min_price': item.min_price,
                # TODO:评论数量暂时无法获取
                'comment_count': 0
            })
        return http.JsonResponse(data)

接口联调

步骤:

  1. 阅读文档
  2. 配置接口地址
  3. axios获取数据
  4. 将数据设置到模型层
const apiHost = "http://localhost:8080/api";

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

/**景点模块 */
const SightApis = {
  // 景点列表接口
  sightListUrl: apiHost + "/sight/sight/list/"
};

export { 
  SystemApis,
  SightApis
};

轮播图:
Vue实战开发移动端旅游网站_第26张图片
精选景点:
Vue实战开发移动端旅游网站_第27张图片
热门景点:
Vue实战开发移动端旅游网站_第28张图片

设置代理解决跨域问题
vue.config.js:

module.exports = {
  // 例如要访问http://localhost:8080/api/system/slider/list/ => 代理http://127.0.0.1:8000/system/slider/list/
  devServer: {
    proxy: {
      "/api": {
        target: "http://127.0.0.1:8000/",
        changeOrigin: true,
        pathRewrite: {
          "^/api": ""
        }
      }
    }
  }
};

VueRouter实现多个页面

Vue实战开发移动端旅游网站_第29张图片

Vue中的路由管理

安装

npm install vue-router -S

路由的使用

Vue实战开发移动端旅游网站_第30张图片
1.设置路由规则

const routes = [
{ path:'/home',component:Home },
{ paht:'/about',component:About }
]

2.配置路由规则

// 在项目根路径下新建router.js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.user(VueRouter)
const router = new VueRouter({
	routes //相当于routes:routes
})

// 在main.js中配置
new Vue({
	el:'#app'
	router
})

3.设置路由切换后页面的显示位置
在模板中指定路由切换后组件的渲染位置

<router-view></router-view>

4.在模板中实现路由跳转

<router-link to="/about"></router-link>

路由的参数传递

动态路由匹配

  1. 设置动态匹配规则
  2. 在组件中获取匹配参数和查询

设置动态匹配规则
动态路由参数 :name

const router = new VueRouter({
	routes: [
		{ path:'/detail/:id', component:Detail }
	]
})

在组件中获取匹配参数
在JS中获取参数

let id = this.$route.params.id

响应参数变化

watch: {
	$route(to, from){
	// 对路由变化做出响应
	}
}

在组件中获取查询参数
URL中的查询参数(/sight/list?/name=‘xxx’)
let name = this.$route.query.name

按照path跳转

Vue实战开发移动端旅游网站_第31张图片

按照路由名称跳转

Vue实战开发移动端旅游网站_第32张图片
Vue实战开发移动端旅游网站_第33张图片

js实现页面跳转

Vue实战开发移动端旅游网站_第34张图片
this:vue实例
$router:来自main.js中router
Vue实战开发移动端旅游网站_第35张图片
Vue实战开发移动端旅游网站_第36张图片

其他常见的跳转方式

页面的前进或后退

this.$router.go(n)

n:正数负数(整数)
-1:返回上次

替换浏览历史记录

this.$router.replace({name:'detail', parmas:{id:'123'}, query:{name:'xxx'}})

景点详情页面开发

景点搜索页面

你可能感兴趣的:(笔记)