1. 创建新的分支city-ajax:
下载city.json文件,然后保存到mock下。
同理,Ajax请求我们一般会放在最外层的City组件上来做:
- 首先引入
axios; - 利用
mounted函数,执行一个getCityInfo方法: - 在
methods里定义这个方法
发起ajax请求,然后触发一个handleGetCityInfoSucc方法,接收到的是模拟数据的结果res: - 接下来就可以做动态数据的渲染了:
- 当
ajax获取到数据后,我们这样写: - 然后把根组件中的数据传递给子组件
List: - 在
List组件中进行接收:
对热门城市进行处理
效果:
城市首字母处理:
将多余的代码删掉,只剩下一个area区域,循环area的时候,循环的数据ct是一个对象,所以与item对应的是key,内部再嵌套一个小循环:
就可以实现滑动显示所有城市了。 - 修改右侧城市首字母,在根组件
City中传allcities给子组件Alphabet:
在子组件中接收,类型是一个Object:
做循环,循环的是一个li标签:
最终的效果:
2. 提交到线上
3. 兄弟组件联动
我们要实现点击右侧的字母,会自动的滚动到对应城市区域,即按首字母索引。
- 创建项目分支
city-components: - 在alphabet组件中编辑:
给每一个循环绑定一个事件handleLetterClick:
在methods中定义事件触发该事件时,会接收到一个e,打印e.target.innerText。js中事件是会冒泡的,所以this是可以变化的,但event.target不会变化,它永远是直接接受事件的目标DOM元素:
可见打印出来的是点击的key值。 - 希望将这个值传递给兄弟组件
List组件,转到合适的区域。我们不采用总线机制,将这个值首先传递给父组件City组件,然后再由父组件传递给List组件:
当点击字母时,向外发布一个change事件(自己起的名),携带的内容为e.target.innerText:
父组件City监听这个change事件:
在methods中定义handleLetterChange,它用letter接收,其实就是e.target.innerText,打印出letter:
接下来要将数据传递给子组件List,在City中定义一个数据sigletter默认值为空字符串:
当City组件接收到外部传来的letter值后,我们令this.sigletter=letter:
最后只需要传递给子组件List就可以了:
4. List组件逻辑编写:
打开List组件,接收父组件传递过来的let,其类型为string:
接下来要做的事就是当子组件List发现let有改变的时候,就让自己的显示区域跳转到和let对应的首字母开头的城市区域显示出来,借助侦听器watch,在watch中侦听let的变化,点击的时候打印出let:
当我们侦听到let的变化时,且let不为空的时候,让better-scroll这个滚动区自动的滚动到某个区域,根据外部传入的let决定。给每一个area加一个ref,也等于key,这个key值就代表ABCD等字母:
接下来我们就可以由this.refs[this.let]获取到class=“area”的这样一个div区域:
这样写会报错,因为我们获取的ref是通过循环输出的ref,这样获得的element是一个数组如下:
但是better-scroll里面我们要求必须是个dom元素,所以这样写:
至此,就可以实现根据首字母来转换对应的城市区域了。
5. 我们要是实现在右侧字母表上进行上下拖拽时,也会引起左侧城市区域变动:
- 要做一个右侧字母的滚动监听,打开组件
Alphabet,让该组件绑定三个touch事件,并在methods中定义各个事件,我们希望在touchstart之后才触发touchmove,进而触发touchend,所以在data里定义一个标识位,默认false:
我们首先获得“A” 这个字母距离顶部的一个高度l1,然后滑动的时候获得手指距离顶部的一个高度l2,用l2减去l1就获得了手指滑动的位置距离字母“A”顶部的距离,在除以每个字母的高度,就知道当前的位置是第几个字母了,然后去取对应的字母,触发一个change事件给外部。想要根据下标找到对应的字母的话,需要一个数组来存储这个字母的列表,定义一个数组类型的数据:
我们就构建出一个letters的计算属性,返回的结果大概是[“A”, “B”, “C”]这样的数组。这一步其实就是将ct里的数据转化成了数组类型。接下来就要修改一下循环了,将循环对象的形式改为循环letters数组的形式: - 完成拖动
给每一个li标签也加一个ref:
计算出“A”这个字母距离蓝色区域底部的距离,offsetTop检测距离父盒子有定位的上面的距离,可见在右侧滚动的时候,距离一直为74:
执行handleTouchMove的时候,我们会接收到一个参数e,事件对象中会有一个touches的数组,第0项表示我们手指的一些信息,可以获取到手指的clientY的值,即手指距离浏览器可视区域最顶部的一个高度:
蓝色区域的的高度大约为79像素,所以要计算手指离蓝色区域底部的高度,这样写:
接下来定义一个值作为手指所在当前字母的下标,算出差值再除以字母的高度,在向下取整,然后设置条件,向外触发一个change事件,父组件监听,并跳转到相应的区域:
6. 城市列表性能优化
- 我们定义个一个
handleTouchMove方法,当我们的手指在字母表上滑动时,这个方法就会执行,但是这样写性能是比较低的,首先,字母“A”的offsetTop是一个固定的值,在我们每次触动这个方法的时候都会去运算一次,这样使得性能比较低。我们可以这样修改:在data中再定义一个变量,startY:0:
然后利用updated生命周期钩子,当数据进行更新且页面完成了渲染的时候,updated就会执行:
接下来就可以修改handleTouchMove中的内容,把对应的startY都换成this.startY: - 函数节流,当我们划动字母表的时候,频率是非常高的,通过节流限制一下函数执行的频率,函数节流的基本思想是设置一个定时器,在指定时间间隔内运行代码时清除上一次的定时器,并设置另一个定时器,直到函数请求停止并超过时间间隔才会执行。在数据中定义一个
timer,默认值为null:
如果this.timer不为零,清除定时器:
否则,创建一个timer,等于一个定时器,给一个16ms的时间间隔:
把相应的代码放入到定时器当中。
7. 搜索功能实现
- 创建
git分支city-search-logic,然后拉到线下,重启服务器。
我们希望达到的效果是当在搜索框里搜索城市时,会将我们搜索的城市显示出来,打开Search组件,修改模板,在最外层包裹一层div: - 下面对
search-content做布局:z-index属性指定了元素及其子元素的【z顺序】,而【z顺序】可以决定当元素发生覆盖的时候,哪个元素在上面。通常一个较大的z-index值的元素会覆盖较低的那一个。
我们希望搜索的内容展示在这个绿色的区块里,要将input搜索框里的搜索词和我们的数据做一个绑定,逻辑里定义一个data,data中存一个数据keyword,默认值为空,现在要和input框做一个双向绑定:Search这个组件还要接收到City组件传递过来的一个数据ct,Search组件进行接收,类型为Object:
然后在data中定义一个空数组list,创建一个侦听器,监听keyword的改变,在这里同样做一个节流函数,定义一个timer,keyword发生改变的时候,延时100毫秒后我们的箭头函数会执行,将输入的字母或者是关键词匹配到相应的城市名,然后push到list中:this.list就存储了包含我们关键词keyword的所有城市名称,接下来就可以在列表中做循环了:
接下来对样式进行一个修饰:
当我们输入a时,出现的城市太多,没办法滚动,这时可以还是用better-scroll,先引入better-scroll,借助生命周期函数mounted:
然后传入一个dom元素,必须是外层包裹你要滚动区域的元素,利用ref:
这样当城市显示过多的时候就可以正常滚动了。
8. 搜索优化
- 有这样一种情况当我们把搜索框中的“
a”,会发删除,发现下面的列表依然还在:
在对keyword进行监听的逻辑代码中我们加一条判断语句,一定要记得return:
就可以修复这种bug了。 - 当我们在搜索框中输入一长串
keyword时,并没有匹配上的时候,list当中会什么也不显示:
在li标签下增加一条li标签,样式一样,输出信息“没有找到匹配数据!”,但是出现的bug是,当我们输入的关键词很少的时候,也会显示出这条警示:
我们用v-show来限制这条警示语句的显示,即当list的长度为零的时候显示这条警示: - 解决
search-content一直显示的问题,我们希望当我们点击搜索框,即输入keyword的时候这个页面才出现,用v-show来限制它的显示,这样在一开始就又恢复到最初的界面了: - 我们可以发现在显示警示语句的时候,我们在
v-show当中加了逻辑符号“!”,在模板中写逻辑是不提倡的:
我们可以定义一个计算属性:
这样我们就把一些运算放在逻辑区域执行,而在模板中只需要接收数据就好:
最后给li标签里面的循环加上key值。
9. 最后将代码提交到线上。