推荐博客

Vue 2.5 开发 去哪儿 旅游网站项目记录

项目源码 : https://gitee.com/doublesgzl/Travel

第六章 项目实战


6-1 环境配置 (建议看下视频)
6-2 项目代码介绍
6-3 单文件组件与Vue中的路由
6-4 单页应用VS多页应用 解析1、解析2、解析3 GOOD
6-5 项目代码初始化


第7章 旅游网站首页开发


1.首页header开发
2.stylus预处理器的使用
3.iconfont 的使用
4.首页轮播图
5.图标区域页面布局和逻辑实现 参考
6.热销推荐组件和周末游组件开发
7.ajax 请求获取首页数据


1.首页header开发

1-1 创建Home.vue的子组件

  • pages/home/components/Header.vue
  • pages/home/Home.vue组件中引入子组件Header.vue
<script>
import HomeHeader from './components/Header'
export default {name: 'Home',components: {HomeHeader}
}

2.stylus预处理器的使用

2-1 stylus :CSS预处理器,在css中使用变量等,便于快速编写css代码

  • 安装stylus相关的库
  • npm install stylus --save
  • npm install stylus-loader --save
  • 具体使用:<style lang="stylus" scoped>scoped 确保只对当前的组件有效
  • 使用css变量:在styles文件夹下创建iable.styl文件,写入$bgColor = #00bcd4,然后在Header.vue中的style内通过@import引入iable.styl文件,以下代码中列举了三种引入方式,最后一种是在build/webpack.base.conf.js 中的resolve下的alias中设置了styles别名,这样一来路径就可以简写,在main.js中也可以用别名进行路径的简写。
/*@import '../../../assets/styles/variable.styl'*/
/*@import '~@/assets/styles/variable.styl'*/
@import '~styles/variable.styl'.headerdisplay:flexheight:0.86remline-height:0.86rembackground-color:$bgColorcolor :#fff.header-leftwidth:0.64remfloat:lefttext-align :center.iconfontfont-size :0.4rem

3.iconfont 的使用

  • 在图标库->官方管理库->大麦官网图标库->选择需要的图标->加入购物车->到购物车中下载选中的图标
  • iconfont.css文件放入styles文件夹下,并创建一个iconfont文件夹,将下载的iconfont.eoticonfont.svgiconfont.ttficonfont.woff文件放入iconfont文件夹下
  • 打开iconfont.css,修改里面的路径
  • main.js中引入iconfont.css文件,import 'styles/iconfont.css'
  • Header.vue中相应的位置加入<span class="iconfont"></span>中间显示的是图标对应的代码,在图标网购物车中图标下面有显示,复制过来就行

4.首页轮播图

4-1 在git线上仓库中创建index-swiper分支,在终端Travel路径下依次执行

  • git pull
  • git checkout index-swiper
  • git status (查看现在的本地分支)

4-2 使用第三方轮播插件 vue-awesome-swiper快速构建轮播 (插件下载:github)

  • 插件安装 npm install vue-awesome-swiper@2.6.7 --save
  • 插件使用:在程序入口文件main.js中引入import VueAwesomeSwiper from 'vue-awesome-swiper'import 'swiper/dist/css/swiper.css',并使用轮播插件Vue.use(VueAwesomeSwiper);第二,在home/components底下再新建一个Swiper.vue组件,记得与Home.vue组件连接;第三,在Swiper.vue组件中的template中添加轮播的结构代码,如下所示,在exportreturn {swiperOption: {}},在<swiper-slide>中放置图片;
  • 注意事项:加载页面时,在轮播插件底下的文本内容会先显示,等轮播图片加载完成后,文本内容又被加载进来的图片撑到下面位置,这就导致页面的一个抖动现象。解决方法:在轮播组件外面放置一个<div class="wrapper">,设置该div的宽高比(代码在下面),保证轮播组件的位置,避免文本内容的抖动,写法比较特殊,要特别注意。
<template><swiper :options="swiperOption" ref="mySwiper" @someSwiperEvent="callback"><swiper-slide><img class="swipe-img" src="http://mp-piao-admincp.qunarzz.com/mp_piao_admin_mp_piao_admin/admin/20197/ae8987bff39ff10e82675a3643154e66.jpg_750x200_0f187b2e.jpg" alt="去哪儿门票" style="opacity: 1;"></swiper-slide><swiper-slide><img class="swipe-img" src="http://mp-piao-admincp.qunarzz.com/mp_piao_admin_mp_piao_admin/admin/20197/e43d3043ee612ad71dd6c68f29e3ed9a.jpg_750x200_b90f2963.jpg" alt="去哪儿门票" style="opacity: 1;"></swiper-slide><swiper-slide><img class="swipe-img" src="http://mp-piao-admincp.qunarzz.com/mp_piao_admin_mp_piao_admin/admin/20193/d7bbc21db442366a882e04ddc984669a.jpg_750x200_85e640d9.jpg" alt="去哪儿门票" style="opacity: 1;"></swiper-slide><!-- Optional controls --><div class="swiper-pagination"  slot="pagination"></div><div class="swiper-button-prev" slot="button-prev"></div><div class="swiper-button-next" slot="button-next"></div><div class="swiper-scrollbar"   slot="scrollbar"></div></swiper>
</template>
<script>
export default {name: 'HomeSwiper',data: function () {return {swiperOption: {}}}
}
</script>
<style lang="stylus" scoped>.swipe-imgwidth:100%
</style>
.wrapperoverflow :hiddenwidth: 100%height:0padding-bottom :31.25%

4-3 给轮播插件添加底部的轮播点
第一步,在data中返回pagination: '.swiper-pagination',发现底部出现蓝色的点;

 data: function () {return {swiperOption: {pagination: '.swiper-pagination'}}}

第二步,设置点的样式,不能写在.wrapper的下面,会失效!!!

// 样式穿透
.wrapper >>> .swiper-pagination-bullet-activebackground: #fff

4-4 swiper轮播图的图片循环

第一步,在swiper下面只保留一个swiper-slide标签,设置v-for=''item of SwiperList"

  <swiper-slide v-for="item of SwiperList" :key="item.id"><img class="swipe-img" :src="item.imgUrl" ></swiper-slide>

第二步,在data中加入轮播图片数据

data: function () {return {swiperOption: {pagination: '.swiper-pagination',loop: true  // 用于循环轮播,否则第一张图片前面就空了},SwiperList: [{id: '0001',imgUrl: 'http://mp-piao-admincp.qunarzz.com/mp_piao_admin_mp_piao_admin/admin/20197/ae8987bff39ff10e82675a3643154e66.jpg_750x200_0f187b2e.jpg'}, {id: '0002',imgUrl: 'http://mp-piao-admincp.qunarzz.com/mp_piao_admin_mp_piao_admin/admin/20197/e43d3043ee612ad71dd6c68f29e3ed9a.jpg_750x200_b90f2963.jpg'}, {id: '0003',imgUrl: 'http://mp-piao-admincp.qunarzz.com/mp_piao_admin_mp_piao_admin/admin/20193/d7bbc21db442366a882e04ddc984669a.jpg_750x200_85e640d9.jpg'}]}}

4-5 保存代码到本地仓库并提交到线上仓库

master分支放的是整个项目所有功能最新的代码,index-swiper分支上放的是具体功能开发完成的代码。一般开发时会自己开发一个新的分支,测试没问题后整合到master分支上。

  • git add .
  • git commit -m 'change'
  • git push (把本地index-swiper分支上的内容提交到线上index-swiper分支上)
  • git checkout master (合并,先切换到master分支上)
  • git merge origin/index-swiper (把线上index-swiper分支新增的内容合并到本地merge分支)
  • git push (把master分支的内容也提交到线上)

5.图标区域页面布局和逻辑实现

5-1 在线上git仓库上新建一个index-icons分支,然后在终端获取新创建的分支到本地

  • git pull
  • git checkout index-icons

5-2 实现图标区域布局,定义样式

5-3 图标区域逻辑实现,多页切换的轮播效果展示。
同Swiper组件在data中return一个iconsList数组,在template中使用轮播结构放入每个图标模块。 并在Chrome浏览器上安装Vue devtools插件(一种vue的调试工具),安装完毕后重启浏览器,在浏览器开发者工具的最上面一栏的最后就可以看见Vue一项了。

问题(1):当只有一条数据时,页面中仅可以操控图标模块范围进行滑动,在别的空白区域却不行,这里需要设置swiper的.swiper-container样式为icons的样式,从而解决此问题,.swiper-container类你可以在浏览器开发工具中选中区域进行查看。

<template>
<div class="icons"><swiper :options="swiperOption"><swiper-slide><div class="icon" v-for="item of iconsList" :key="item.id" ><div class="icon-img" ><img class="icon-img-content" :src="item.imgUrl" ></div><p class="icon-text">{{item.desc}}</p></div></swiper-slide></swiper>
</div>
</template>
.icons >>> .swiper-containerwidth :100%height :0padding-bottom :50%

问题(2):若共有10条数据,当前页面只能显示8条,另外两条被隐藏了,也无法滑动显示,此时,利用Vue中的计算属性来控制每个轮播页中数据的显示 ,更新swiper的数据绑定。

computed: {pages () {const pages = []this.iconsList.forEach((item, index) => {const page = Math.floor(index / 8)if (!pages[page]) {pages[page] = []}pages[page].push(item)})return pages}
}
   <swiper :options="swiperOption"><swiper-slide v-for="(page,index) of pages" :key="index"><div class="icon" v-for="item of page" :key="item.id" ><div class="icon-img" ><img class="icon-img-content" :src="item.imgUrl" ></div><p class="icon-text">{{item.desc}}</p></div></swiper-slide></swiper>

5-4 用省略符代替长串文字,通过CSS样式控制

.icon-text...overflow :hiddenwhite-space :nowraptext-overflow :ellipsis

5-5 将省略符的CSS代码封装
创建styles/mixins.styl文件,写入以下代码,在Icons.vue的style标签内导入@import '~styles/mixins.styl',然后用ellipse()一句话即可

ellipse()overflow :hiddenwhite-space :nowraptext-overflow :ellipsis
.icon-text...ellipse()

5-6 保存代码到本地仓库并提交到线上仓库

  • git add .
  • git commit -m 'change'
  • git push
  • git checkout master
  • git merge origin/index-swiper
  • git push

6.热销推荐组件和周末游组件开发

6-1 在git线上仓库中创建index-recommend分支,在终端Travel下依次执行

  • git pull
  • git checkout index-recommend

6-2 新建components/Recommend.vue组件,同Home.vue组件连接;

6-3 先搭建好页面结构和CSS样式,再循环显示数据,套路基本同上

<template><div class=""><div class="title">热销推荐</div><ul><!--border-bottom类:1像素边框,在项目最开始已经引入了border.css文件--><li class="item border-bottom" v-for="item of recommendList" :key="item.id"><img  class="item-img" :src="item.imgUrl" alt=""><div class="item-info"><p class="item-title">{{item.title}}</p><p class="item-desc">{{item.desc}}</p><button class="item-btn">查看详情</button></div></li></ul></div>
</template>

6-4 周末游组件开发 代码同6差不多 微调就行
6-5 保存代码到本地仓库并提交到线上仓库
- git branch (查看所有分支)

7.ajax 请求获取首页数据

7-1 线上git仓库创建index-ajax分支,下载到本地仓库

  • git pull
  • git checkout index-ajax

7-2 如何在没有后端支持的情况下实现数据的模拟??
创建static/mock/index.json文件,将swiperListiconsListrecommendListWeekendList等本地开发的模拟数据放置到index.json中。之所以放到static文件夹下,是因为static文件夹里的内容可直接被外界访问到,比如在浏览器中输入http://localhost:8080/static/mock/index.json,就可以显示出/index.json中的内容。

7-3 /index.json中存储的是一些本地开发的模拟数据,并不希望它同代码上传到本地/线上仓库,此时,在.gitignore文件中添加static/mock即可。

7-4 统一在Home.vue中发送一次Ajax请求,而不要试图在每个组件中都发送Ajax请求:
(1)首先在Home.vue中导入import axios from 'axios',记得提前安装axios库;
(2)让页面挂载好之后去执行getHomeInfo()方法;

mounted () {this.getHomeInfo()}

(3)在函数中定义getHomeInfo()方法,去获取Ajax数据,axios返回的结果是一个promise对象;数据获取成功后调用getHomeInfoSucc()输出结果。

methods: {getHomeInfo () {axios.get('/api/index.json').then(this.getHomeInfoSucc)},getHomeInfoSucc (res) {console.log(res)}}

7-5 在实际项目开发中,不建议随意改动axios.get(‘/api/index.json’)中的地址,那如何转换到本地模拟的接口地址(/static/mock/index.json)呢?
【解决】在vue中提供了proxy代理功能,以此实现接口地址的转换。在config/index.js文件中设置:

 proxyTable: {'/api':{target:'http://localhost:8080',pathRewrite:{'^/api':'/static/mock'}}}

这里理解成用"/api"代替target里面的地址,后面组件中调用接口时直接用api代替 。比如调用"http://localhost:8080/static/mock/index.json",直接写"/api/index.json"即可。注意,它不是vue提供的,而是webpack dev server 提供的。

7-6 首页父子组件的数据传递
在父组件中

<template><div><home-header :city="city"></home-header><home-swiper :list="SwiperList"></home-swiper></div>
</template>
export default {...data () {return {city: '',SwiperList: []}},mounted () {this.getHomeInfo()},methods: {getHomeInfo () {axios.get('/api/index.json').then(this.getHomeInfoSucc)},getHomeInfoSucc (res) {res = res.dataif (res.ret && res.data) {this.city = res.data.citythis.SwiperList = res.data.swiperList}}}}

在子组件中,props是子组件访问父组件数据的唯一接口。若子组件想引用父组件的数据,需要在props中声明一个变量city,变量city就可以引用父组件的数据(<home-header :city="theCity"></home-header>)。然后在子组件<template>模板里渲染变量city,此时渲染出来的就是父组件中的数据。

<template><div class="header">...<div class="header-right">{{this.theCity}}<span class="iconfont icon-right"></span></div></div>
</template>
<script>
export default {name: 'HomeHeader',props: {theCity: String}
}
</script>

7-7 在Swiper组件的数据传递中出现问题:默认显示的图片是第四张而不是第一章图片?这是因为Swiper最初是通过空数组创建的,然后Ajax获得数据后才变成真正的数据,为避免此问题,首先判断list数组是否为空,待获取到真正的数据才创建Swiper

<swiper :options="swiperOption" v-if="list.length">

进一步,在<template>模板中尽量避免出现一些逻辑性的代码,比如v-if="list.length",这里可以使用computed计算属性来实现数组长度的计算,然后在v-if中直接使用showSwiper"计算属性即可。

<swiper :options="swiperOption" v-if="showSwiper">
...
computed: {showSwiper () {return this.list.length}}

7-8 Icons.vueRecommend.vueWeekend.vue 数据绑定

7-9 Swiper.vue组件中的图片自动轮播

data: function () {return {swiperOption: {pagination: '.swiper-pagination', // 轮播点loop: true,autoplay: 3000 // autoplay: true虽然自动轮播了但是速度很快,直接在后面跟时间设置就好}}},

7-10 保存代码 合并到master分支

第8章 旅游网站"城市列表"页面开发


1 路由配置 + 搜索框布局
2 列表布局
3 BetterScroll 的使用和字母表布局
4 页面的Ajax动态数据渲染
5 兄弟组件数据传递 及 列表性能优化
6 搜索逻辑实现
7 Vuex实现数据共享
8 Vuex的高级使用及localStorage
9 使用keep-alive优化网页性能


1 路由配置

1-1 创建city-router分支。在router/index.js中配置City.vue的路由信息,导入import City from '@/pages/city/City'

 routes: [{path: '/',name: 'Home',component: Home},{path: '/city',name: 'City',component: City}]

1-2 【首页到城市列表页面的跳转】在Header.vue中,给“北京”的div外面包围<router-link to="/city"></router-link>,地址根据index.js的路由配置决定。

成功跳转后,“北京”字体颜色变成绿色,是因为添加<router-link to="/city"></router-link>过程中,给div包裹了一个a标签,默认显示绿色。这里给header-right设置字体颜色为白色即可。

<router-link to="/city"><div class="header-right">{{this.city}}<span class="iconfont icon-right"></span></div></router-link>

1-3 在“城市列表页面”左上角添加返回按钮,同样用router-link链接。
1-4【搜索框布局】创建Search.vue组件,添加input框设置样式即可。
1-5 将city-router分支合并到master分支上。

2 列表布局

2-1 新建city-list仓库。创建List.vue组件,添加3个list模块,设置样式。
2-2 给整个list设置position :absoluteoverflow :hidden,固定页面的高度,使屏幕不随内容的增加而变长。

.listoverflow :hiddenposition :absolutetop: 1.58remleft:0right :0bottom :0

3 BetterScroll 的使用和字母表布局

BetterScroll 插件(github)实现页面的滚动。

3-1 插件安装

  • npm install better-scroll --save
  • 以下是Better-scroll插件使用的结构要求,要按照这种结构编写项目代码
<div class="wrapper"><ul class="content"><li>...</li><li>...</li>...</ul>
</div>

3-2 插件使用

  • 导入import BScroll from 'better-scroll'
  • 设置<div class="list" ref="wrapper">ref 用于获取DOM元素
  • mounted中设置如下,然后页面可以上下拖动了
mounted () {// 获取DOM元素,相当于let list = document.querySelector('.list');let scroll = new BScroll(list)this.scroll = new BScroll(this.$refs.wrapper)}

3-3 字母表
新建Alphabet.vue组件,添加一组<div class="list"><ul><li class="item"></li>...</ul>来做字母表,设置绝对位置,样式如下。

.listdisplay :flex   // 弹性布局flex-direction :column  // 主轴方向为纵轴justify-content :center // 纵向居中position :absolute  // 绝对位置right:0   // 向右靠top:1.58rem // 设置顶部距离bottom :0  // 设置底部距离width:.4rem //设置字母表的宽度.itemtext-align :centerline-height :.4remcolor :$bgColor

3-4 将city-list分支合并到master分支上

4 页面的动态数据渲染(Ajax)

4-1 创建city-ajax分支。在City.vue中导入import axios from 'axios',通过ajaxstatic/mock/city.json中模拟数据的获取。

 data () {return {cities: {},hotCities: []}},mounted () {this.getCityInfo()},methods: {getCityInfo () {axios.get('/api/city.json').then(this.handleGetCityInfoSucc)},handleGetCityInfoSucc (res) {res = res.dataif (res.ret && res.data) {const data = res.datathis.cities = data.citiesthis.hotCities = data.hotCities}}}

4-2 在City.vue中的template中传递数据给子组件

<city-list :cities="cities" :hot ="hotCities"></city-list>

4-3 在List.vue子组件中,通过props获取父组件传递过来的数据,并使用v-for循环遍历输出。注意:v-for可以遍历数组对象,在遍历对象时,<div class="area" v-for="(items,key) of cities" :key="key">key表示对象中的属性,可以用来当做key,比如var list = {"A":[{"a"},{"b"}],"B":[{"c","d"}]};,其中,key代表的就是“A”,"B","C"

 props: {hot: Array,cities: Object}
<div class="area" v-for="(items,key) of cities"  :key="key"><div class="title border-topbottom">{{key}} </div><div class="item-list"><div class="item border-bottom"  v-for="innerItem of items" :key="innerItem.id">{{innerItem.name}}</div></div>
</div>

4-4 在Alphabet.vue组件中,用v-for循环遍历props从父组件获取的对象数据;

4-5 将city-ajax分支合并到master分支上;

5 兄弟组件数据传递

点击字母表,希望相应的内容能自动滚动到页面的显示区域中。

5-1 新建city-components分支;
5-2 在Alphabet.vue的循环项li中添加click事件,在methods中写入handleLetterClick()那么,如何在Alphabet.vueList.vue之间传值呢?

 <li class="item" v-for="(item,key) of cities" :key="key" @click="handleLetterClick">{{key}}</li>
methods: {handleLetterClick (e) {console.log(e.target.innerHTML)}}

5-3 (1)bus总线传值;(2)让Alphabet.vue把数据传给City.vue,再由City.vue把数据传递给List.vue

使用方式(2)进行传值。首先,由Alphabet.vue子组件在循环元素上添加点击事件handleLetterClick()中,通过this.$emit()件向外触发change事件,传递数据给City.vue父组件,this.$emit('change', e.target.innerHTML)其次,由City.vue父组件监听@change="handleLetterChange"事件,并在methods中定义handleLetterChange()来处理从Alphabet.vue接收到的数据。

<city-alphabet :cities="cities" @change="handleLetterChange"></city-alphabet>
handleLetterChange (letter) {console.log(letter)}

5-4 由City.vue父组件将接收到的数据通过属性的形式(:letter="letter")转发给List.vue子组件。

...
<city-list :cities="cities" :hot ="hotCities" :letter="letter"></city-list>
...data () {return {...letter: ''}},methods: {...handleLetterChange (letter) {this.letter = letter}}

5-5 最后,在List.vue子组件中通过props接收City.vue父组件传递过来的数据。

5-6 拿到点击的字母后(this.letter发生改变),如何使相应的列表内容滚动显示在页面中???

【解决】 List.vue子组件中用watch监听letter的变化。通过:refthis.$refs[this.letter]获取对应的DOM元素,然后通过this.scroll.scrollToElement(this.$refs[this.letter][0])`一步实现。

props: {hot: Array,cities: Object,letter: String}
...
<div class="area" v-for="(items,key) of cities"  :key="key" :ref="key">
...
import BScroll from 'better-scroll'
...
mounted () {// 获取DOM元素,相当于let list = document.querySelector('.list');let scroll = new BScroll(list)this.scroll = new BScroll(this.$refs.wrapper)},
watch: {// 在watch中监听letter的变化letter () {if (this.letter) {// 返回的ele是一个数组,但在this.scroll.scrollToElement()中参数必须是一个DOM元素,所以传入数据为ele[0]const ele = this.$refs[this.letter]this.scroll.scrollToElement(ele[0])}}}

5-7 滑动字母表,使左侧的城市列表也会随之变动。

Alphabet.vue中给循环项绑定三个事件:@touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd",在methods中定义这三个函数,为保证在touchStart 事件之后才可以触发touchMove事件,在data中定义touchStatus: false来控制事件顺序。

...
data () {return {touchStatus: false}
},
methods: {...handleTouchStart (e) {this.touchStatus = true},handleTouchMove () {},handleTouchEnd () {this.touchStatus = false}
}

接下来,需要知道当前滑动到的是字母表中的第几个字母,需要根据位置来进行判断。首先,通过计算属性 遍历cities获取存储字母表的数组letters

computed: {letters () {const letters = []for (let i in this.cities) {letters.push(i)}return letters}
}

获取到letters数组后,在template中用letters数组代替cities对象来遍历,

 <li class="item" v-for="item of letters" :key="item" @click="handleLetterClick"  @touchstart="handleTouchStart"         @touchmove="handleTouchMove" @touchend="handleTouchEnd">{{item}}
</li>

下一步,在handleTouchMove()事件中计算距离,来判断当前手指滑动所在位置的字母,并将该字母传递给City.vue父组件。

handleTouchMove (e) {if (this.touchStatus) {// startY是字母"A"至list顶部的距离const startY = this.$refs['A'][0].offsetTop// e.touches[0]表示一些手指的信息,clientY是手指到页面最顶部的距离// 当前手指到list顶部的距离 = 手指到页面最顶部的距离 - 页面顶部蓝色区域的高度const touchY = e.touches[0].clientY - 79// index从0计数: 当前手指所在的位置是第index个字母 = (当前手指到list顶部的距离 - 字母"A"至list顶部的距离)/ 每个字母的高度const index = Math.floor((touchY - startY) / 20)// 这里要判断index的值,不要让它超出范围,不然后面数据都不存在,会出错if (index >= 0 && index <= this.letters.length) {this.$emit('change', this.letters[index])}}}

5-8 列表性能优化
(1)在handleTouchMove()事件中,this.startY是固定的,不需要每次都计算一遍,因此把它提取出来,在updated()钩子中执行,在handleTouchMove()中只要调用this.startY即可,

 data () {return {touchStatus: false,startY: 0}},// 当页面的数据被更新的同时页面完成渲染后,updated钩子函数执行updated () {// startY是字母"A"至list顶部的距离this.startY = this.$refs['A'][0].offsetTop}

(2)函数节流 限制函数执行的频率

如果this.timer已经存在,把this.timer去除掉,否则创建一个新的this.timer
延迟16ms之后再执行,假设在这16ms之内又进行了手指的滚动,那么它会把上一次要做的动作清除掉并重新执行这一次要做的事情!!!

data () {return {touchStatus: false,startY: 0,timer: null}},handleTouchMove (e) {if (this.touchStatus) {if (this.timer) {clearTimeout(this.timer)}this.timer = setTimeout(() => {// e.touches[0]表示一些手指的信息,clientY是手指到页面最顶部的距离// 当前手指到list顶部的距离 = 手指到页面最顶部的距离 - 页面顶部蓝色区域的高度const touchY = e.touches[0].clientY - 79// index从0计数: 当前手指所在的位置是第index个字母 = (当前手指到list顶部的距离 - 字母"A"至list顶部的距离)/ 每个字母的高度const index = Math.floor((touchY - this.startY) / 20)// 这里要判断index的值,不要让它超出范围,不然后面数据都不存在,会出错if (index >= 0 && index <= this.letters.length) {this.$emit('change', this.letters[index])}}, 16)}}

6 搜索逻辑实现

功能 :当搜索城市名字或拼音时,能把搜索结果显示出来

6-1 新建city-search-logic分支。在data中存一个keyword数据,让input框的内容同keyword做一个双向绑定(v-model)。

<input type="text" class="searchInput" placeholder="输入城市名或拼音" v-model="keyword">
...
data () {return {keyword: ''}},

6-2 Search.vue子组件接受City.vue父组件传递的cities数据,用于搜索词的匹配。

<city-search :cities="cities"></city-search>
props: {cities: Object}

6-3 在data中定义list空数组,用watch监听keyword的变化,并增加一个节流函数,提高代码的执行效率

 <ul><li v-for="item of list" :key="item.id">{{item.name}}</li></ul>
...
data () {return {keyword: '',list: [],timer: null}},watch: {keyword () {if (this.timer) {clearTimeout(this.timer)}this.timer = setTimeout(() => {const result = []// 遍历cities对象中的内容for (let i in this.cities) {this.cities[i].forEach((value) => {if (value.spell.indexOf(this.keyword) > -1 ||value.name.indexOf(this.keyword) > -1) {result.push(value)}})}this.list = result}, 100)}

6-4 修改<li class="search-item">的样式

.search-content...background :#eee.search-itemline-height:.62rempadding-left :.2remcolor: #666background :#fff

6-5 若搜索结果很多,但是页面显示内容有限,如何实现页面搜索结果的滚动?
引入import BScroll from 'better-scroll',在mounted钩子函数中创建一个scroll,this.scroll = new BScroll(this.$refs.search),其中传入的是类名为search-contentdiv结构;

6-6 在搜索框中输入搜索内容,会显示搜索结果,但此时若是清空搜索框内容,页面中依然存在原来的搜索结果。
keyword监听代码中判断keyword是否为空,若为空,则设置list = [],返回即可。

if (!this.keyword) {this.list = []return}

6-7 在input中输入一长串内容,但无内容匹配。

<ul></ul>中再添加一个li标签,<li class="search-item border-bottom" v-show="list.length">没有找到匹配数据</li>,当list数组为空,则显示“没有找到匹配数据”。

前面说到,当尽量避免在template中使用逻辑运算,可用计算属性代替。

<li class="search-item border-bottom" v-show="hasNoData">没有找到匹配数据</li>
...
computed: {hasNoData () {return !this.list.length}}

6-8 实现页面搜索结果与城市列表的切换,即未搜索时显示城市列表,搜索时显示搜索结果。用v-show="keyword"来判断即可,keyword为空,则不显示。

6-9 保存代码,将city-search-logic分支合并到master分支

7 使用Vuex实现首页和城市列表页面的数据共享

功能:将城市页面中的数据传递给首页。

City.vue组件和Home.vue组件没有共有的父组件,无法实现数据的中转,而使用Bus总线又会很麻烦。在Vue中提供了一个vuex数据框架。

下图中虚线框部分称为store

7-1 vuex的初步使用

(0)新建city-vuex分支
(1)安装vuexnpm install vuex --save;
(2)创建src/store/main.js,写入

import Vue from 'vue'
import Vuex from 'vuex'// 使用Vuex插件
Vue.use(Vuex)// 导出Vuex创建的一个store仓库
export default new Vuex.Store({//  state存放的是全局公用的数据state: {city: '北京'}
})

(3)在src/main.js中 导入vuex

import store from './store/main.js'
...
new Vue({el: '#app',router,store,components: { App },template: '<App/>'
})

7-2 让Header.vue组件使用vuex的数据
以前,Header.vue中的city是由Home.vue父组件传递进来的,而且该数据是Home.vue父组件通过Ajax获取的。现在我们希望city是前端存储的,而不是后端返回给前端的,所以在Home.vue父组件中把city相关的数据都删除掉,把static/mock/city.json中的city数据也删除,此时页面就不会显示城市名。

如何将公用数据中的城市名称显示出来呢?

Header.vue中删除props数据,将{{this.city}}改成{{this.$store.state.city}},页面中就能正常显示城市名称了。

this.$store就是store/main.js中创建的Vuex.Store(),由于在src/main.js创建根实例时把store传递进去了,然后它会被派发到每一个子组件中,所以在每个子组件中都可以通过this.$store获取到公用数据。

7-3 在城市列表页面中,修改当前城市的显示。

<div class="button">{{this.$store.state.city}}</div>

7-4 当点击列表中的城市时,让公用数据中的this.$store.state.city发生变化,流程:子组件->Actions->Mutations->State

(1)给List.vue组件,热门城市中的每个循环项绑定一个@click=“handleCityClick(item.name)”事件,并传入相应的item.name

(2)在methods方法中,通过dispatch派发一个名字为changeCityaction,并传入参数city;另外,在store/main.js中定义action操作。

methods: {handleCityClick (city) {this.$store.dispatch('changeCity', city)}}
export default new Vuex.Store({//  state存放的是全局公用的数据state: {city: '北京'},actions: {changeCity (ctx, city) {console.log(city)}}
})

(3) 调用Mutations改变公用的数据state.city

 actions: {// ctx调用commit()方法changeCity (ctx, city) {ctx.commit('changeCityMutation', city)}},mutations: {// state参数指的是所有的公用数据changeCityMutation (state, city) {state.city = city}}

(4) 在上方操作中,并没有用到异步执行,可以直接跳过Actions步骤,让子组件直接使用Mutations变更状态。
List.vue中,将this.$store.dispatch('changeCity', city)替换为this.$store.commit('changeCityMutation', city) ,删除store/main.jsActions操作。

(5)实现:点击城市列表中的列表项,实现数据的动态更新。

<div class="item border-bottom"  v-for="innerItem of items" :key="innerItem.id" @click="handleCityClick(innerItem.name)">{{innerItem.name}}</div>

(6)实现:城市搜索时,点击下方搜索结果的城市,实现数据的动态更新。

(7)实现:在城市列表页面中选择某城市后,自动跳转到首页。
涉及到Vue Router中的编程式导航(I’m here)

methods: {handleCityClick (city) {this.$store.commit('changeCityMutation', city)// 跳转到首页,一步就好this.$router.push('/')}}

(8)页面跳转方式小结

网页页面跳转的两种方式 1)a标签的跳转 2)JS的window.location.href实现跳转
vue跳转的两种方式 1)router-link 标签的跳转 2)JS的编程式导航 router.push()实现页面跳转

7-5 合并city-vuex分支到master上。

后面的内容因为电脑故障 导致笔记丢失 可以点击前面章节中的链接分别查看相关内容。


第9章 旅游网站详情页面开发

渐隐渐显Header组件
公用组件的拆分
路由参数的获取与处理
递归组件的使用
通用动画效果的代码封装

9-1 动态路由和banner布局
9-2 公用图片画廊组件拆分
9-3 实现Header渐隐渐显效果
9-4 对全局事件的解绑
9-5 使用递归组件实现详情页列表
9-6 动态获取详情页面数据
9-7 在项目中加入基础动画

第10章 项目的联调,测试与发布上线

项目的联调,测试,及发布的详细流程;
过程中可能遇到的问题及修复方案;
异步组件,提高大型项目的首屏速度;

10-1 项目前后端联调
10-2 真机测试
10-3 打包上线
10-4 异步组件实现按需加载
10-5 课程总结与后续学习指南

Vue.js 入门 :去哪儿网APP案例 学习记录相关推荐

  1. Vue 实例实战之 Vue webpack 仿去哪儿网App页面开发(应用中的几个页面简单实现)

    Vue 实例实战之 Vue webpack 仿去哪儿网App页面开发(应用中的几个页面简单实现) 目录 Vue 实例实战之 Vue webpack 仿去哪儿网App页面开发(应用中的几个页面简单实现) ...

  2. Vue2.5开发去哪网App项目实战记录(根据慕课网课程编写)

    慕课网课程链接 目的:只是为了记录自己学习过程中遇到的问题以及整个项目的收获,方便回头复习,以及今后编写项目的一个教程规范,文中基础知识并没有详细介绍,但是附上了相关的学习链接 首先先说下这个项目涉及 ...

  3. 六十四、Vue项目去哪儿网App开发准备

    2020/10/20 . 周二.今天又是奋斗的一天. @Author:Runsen @Date:2020/10/20 写在前面:我是「Runsen」,热爱技术.热爱开源.热爱编程.技术是开源的.知识是 ...

  4. 六十五、完成Vue项目去哪儿网App首页开发(来源:慕课网)

    2020/10/21. 周三.今天又是奋斗的一天. @Author:Runsen @Date:2020/10/21 写在前面:我是「Runsen」,热爱技术.热爱开源.热爱编程.技术是开源的.知识是共 ...

  5. 【vue】二、vue2仿去哪儿网app——首页开发

    文章目录 二.vue2仿去哪儿网app--首页开发 Ⅰ 页面结构 Ⅱ 开发笔记及注意点 1.公共样式抽取 2.路径 --> 绝对路径 3.用padding-bottom实现固定宽高比 4.保证内 ...

  6. Vue.js入门教程(适合初学者)

    Vue.js入门教程 Vue官网网址:Vue.js 中文网 Vue.js Vue.js是渐进式JavaScript 框架,是一套构建用户界面的渐进式框架.也可以说Vue.js 是一个用来构建网页界面的 ...

  7. js模板字符串自定义类名_【Vue.js 入门到实战教程】07Vue 组件注册 | 基本使用和组件嵌套...

    来源 | https://xueyuanjun.com/post/21929除了前面介绍的基本语法之外,Vue.js 还支持通过组件构建复杂的功能模块,组件可以称得上是 Vue.js 的灵魂,是 Vu ...

  8. vue设置cookie的domain无效_【Vue.js入门到实战教程】16Tailwind 与 Bootstrap 的区别和使用入门...

    来源 | https://xueyuanjun.com/post/22065我们知道,从 Laravel 8 开始,自带前端脚手架代码默认兼容 Tailwind CSS 框架,取代了之前的 Boots ...

  9. Vue.js入门教程-组件注册

    一.组件创建 1.1 创建步骤 创建Vue的组件都有三个基本步骤是 [①创建组件构造器.②注册组件和③使用组件]. 1.2 基本示例 比如,我们创建一个Button组件. // 1. 创建一个组件构造 ...

最新文章

  1. tensorflow在训练和验证时监视不同的summary的操作
  2. ASP .NET Core Web Razor Pages系列教程三:自动生成Razor Pages (CRUD)
  3. LeetCode-笔记-112、路径总和
  4. 【Python之旅】第五篇(一):Python Socket通信原理
  5. QT的QSplitterHandle类的使用
  6. 洛谷 P1967货车运输 并查集+贪心 不需要用LCA!
  7. 性能测试概念点分析与过程讲解(一)
  8. 城镇化把握质量 数据分析让管理更创新
  9. sql server 2005安装需求
  10. 每个叶子节点(nil)是黑色。_129. 求根到叶子节点数字之和
  11. 2020软考论文想要拿高分,要避开这些坑!
  12. kindle 耗电飞速,电池坏了?你可能给kindle“吃了有毒的食物”
  13. python ttk.notebook_Ttk Notebook and PNotebook
  14. 机器学习之中文处理:文言文还是白话文
  15. 春秋杯CTF2022 WP
  16. 微信中H5+java+vue获取微信定位等JS-SDK
  17. WKWebView白屏问题
  18. QTableView中添加icon
  19. OpenWrt下使用docker安装icloudpd实现iPhone照片备份私有云盘nas
  20. linux 读取 SOC寄存器(物理内存)的机制方法

热门文章

  1. 通过网页直接打开微信关注页面方法
  2. 微信公众号关注页、微信关注链接 微信7.0 IOS 安卓
  3. 计算机16进制A3 B9,ASCII码16进制对照表
  4. 广西首届网络安全选拔赛 MISC Wirteup
  5. 电平触发,边沿触发,脉冲触发
  6. Dialogs 对话框
  7. java scanner 读取文件_Java读取文本文件
  8. t480s控制面板打开触摸板_ThinkPad T480s 用户指南V4.0--更换键盘部分
  9. 央视家庭厨房节目 <天天饮食> 43道家常菜
  10. hashtab:为文件属性添加一个数字指纹