(1)在router路径下的index.js文件中,配置city页面的路由
import Vue from 'vue' import Router from 'vue-router' import Home from '@/pages/home/Home' import City from '@/pages/city/City' Vue.use(Router) export default new Router({ routes: [{ path: '/', name: 'Home', component: Home }, { path: '/city', name: 'City', component: City }], })(2)创建city组件 (3)在header组件中,点击city跳转到city页面
<!-- 路由跳转到city页面中 --> <router-link to='/city'> <div class="header-right"> <!-- this.city在vuex中,由mapState映射进该组件中 --> {{this.city}} <!-- {{$store.state.city}} --> <span class="iconfont arrow-icon"></span> </div> </router-link>在City的Search.vue文件中:
<template> <div> <div class="search"> <input class="search-input" type="text" placeholder="输入城市名或拼音" /> </div> </div> </template>布局如下:
<style lang="stylus" scoped> @import '~styles/varibles.styl' .search height: .72rem padding: 0 .1rem background: $bgColor .search-input // 设置box-sizing为border-box是让padding生效 // 告诉浏览器设置的border和padding的值是包含在width内的 box-sizing: border-box width: 100% height: .62rem padding: 0 .1rem line-height: .62rem text-align: center border-radius: .06rem color: #666 </style>box-sizing: border-box,告诉浏览器设置的border和padding的值是包含在width内的
(1)安装BetterScroll插件
npm install better-scroll --save(2)插件的使用方式,组件的dom结构必须符合规定要求
<div class="wrapper"> <ul class="content"> <li>...</li> <li>...</li> ... </ul> <!-- you can put some other DOMs here, it won't affect the scrolling --> </div>(3)使用插件
<script> import Bscroll from 'better-scroll' // 步骤一 export default{ name: 'CityList', mounted(){ this.scroll = new Bscroll(this.$refs.wrapper); // 步骤二 } } </script>字母表布局:
<style lang="stylus" scoped> @import '~styles/varibles.styl' .list // 设置竖直方向上的居中显示 display: flex flex-direction: column justify-content: center position: absolute top: 1.58rem right: 0 bottom: 0 width: .4rem .item line-height: .4rem text-align: center color: $bgColor </style>处理点击字母的事件 (1)在Alphabet组件中,点击字母,向city父组件触发change事件,并传递字母数据
handleLetterClick(e) { // 触发change事件,该事件在City.vue中被监听 this.$emit('change', e.target.innerText) }(2)通过city父组件将其传递给list组件
<template> <div> <city-header></city-header> <city-search :cities="cities"></city-search> <city-list :cities="cities" :hot="hotCities" :letter="letter" ></city-list> <city-alphabet :cities="cities" @change="handleLetterChange" ></city-alphabet> </div> </template> handleLetterChange(letter) { this.letter = letter }(3)在list组件中,监听letter,获取字母对应的区域的元素,通过better-scroll插件,滚动到对应区域。
<!-- 城市列表 --> <!-- 因为cities是对象,所以用key进行遍历而不是数组中的index --> <!-- :ref="key"中ref用来获取dom元素 --> <!-- 因为key是变量,所以绑定ref的时候,需要加上冒号 --> <div class="area" v-for="(item, key) of cities" :key="key" :ref="key" > <div class="title border-topbottom">{{key}}</div> <div class="item-list"> <!-- 遍历abc各项中的城市 --> <!-- click事件点击列表中的城市也能实现切换 --> <div class="item border-bottom" v-for="innerItem of item" :key="innerItem.id" @click="handleCityClick(innerItem.name)" > {{innerItem.name}} </div> </div> </div> // 监听letter的变化 watch: { letter() { if (this.letter) { // 获取字母对应的区域的元素 // this.$refs[this.letter]返回的是数据,此处选择数组的首项 const element = this.$refs[this.letter][0] // 滚动到对应的元素 this.scroll.scrollToElement(element) } } }, mounted() { // 创建better-scroll实例 this.scroll = new Bscroll(this.$refs.wrapper) }(4)上下滑动字母表组件,list组件也随之滚动,需绑定3个事件
<template> <ul class="list"> <!-- touchstart.prevent中的prevent修饰符可以阻止该事件的默认行为 --> <!-- :ref="item"是为了获取dom元素 --> <li class="item" v-for="item of letters" :key="item" :ref="item" @touchstart.prevent="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd" @click="handleLetterClick" > {{item}} </li> </ul> </template> handleTouchStart() { this.touchStatus = true }, handleTouchMove(e) { if (this.touchStatus) { if (this.timer) { clearTimeout(this.timer) } // 通过函数节流,减少过高频次的操作,提高性能 this.timer = setTimeout(() => { // 当前手指滑动的位置(离顶部的距离) const touchY = e.touches[0].clientY - 79 // 当前手指滑动位置所对应的字母下标 const index = Math.floor((touchY - this.startY) / 20) if (index >= 0 && index < this.letters.length) { // 触发change事件,该事件在City.vue中被监听 this.$emit('change', this.letters[index]) } }, 16) } }, handleTouchEnd() { this.touchStatus = false }(5)通过截留函数,管理滑动事件
在页面更新完之后,在计算距离
(1)v-model对输入的关键字进行双向绑定 (2)通过输入的关键字管理是否显示搜索结果
<template> <div> <div class="search"> <!-- v-model对关键字进行双向绑定 --> <input v-model="keyword" class="search-input" type="text" placeholder="输入城市名或拼音" /> </div> <div class="search-content" v-show="keyword" ref="search" > <!-- 将搜索后的结果渲染出来 --> <ul> <li class="search-item border-bottom" v-for="item of list" :key="item.id" @click="handleCityClick(item.name)" > {{item.name}} </li> <!-- 没有匹配数据的情况 --> <li class="search-item border-bottom" v-show="hasNoData"> 没有找到匹配数据 </li> </ul> </div> </div> </template>(3)监听关键字,运用截留函数,在city中查找数据,并将结果添加到list数组中。 (4)当list中无数据时,显示没有找到匹配数据
(1)安装vuex
npm install vuex –save(2)Vuex可以看做一个仓库store,有State,Mutations组成。 1)State存储所有的公用数据,组件使用数据时,调动State即可。 2)Mutations中放置的是一个个对State同步的修改方法。
(3)在src目录下,新建store文件夹,在store文件夹下新建index.js文件。
// store文件夹下index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); export default new Vuex.Store({ state: { city: '北京' }, mutations: { changeCity(state, city){ state.city = city; } } })(4)在main.js中引入store并使用。这样每个组件中都可以访问到store中管理的数据
import Vue from 'vue' import App from './App' import router from './router' // 作用是引入vuex,vuex在该文件夹下被引用 import store from './store' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, store, components: { App }, template: '<App/>' })(5)添加点击城市事件,通过路由的push方法跳转到首页
methods: { // 将city作为参数 handleCityClick (city) { // 直接通过commit调用mutations方法,而不使用actions方法 // this.$store.commit('changeCity', city) // 直接使用vuex中mutation暴露出来的changeCity方法 this.changeCity(city) // 改变城市后,跳转到首页 this.$router.push('/') }, ...mapMutations(['changeCity']) }(1)使用localStorage功能记录之前操作的内容
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); let defaultCity = '北京'; try { if (localStorage.city) { defaultCity = localStorage.city; } } catch (e) { } export default new Vuex.Store({ state: { city: defaultCity }, mutations: { changeCity(state, city){ state.city = city; try { if (localStorage.city) { defaultCity = localStorage.city; } } catch (e) { } } } })(2)store中的内容变多,有必要对其进行拆分 1)创建state.js文件
// 建议在使用localStorage的时候,就要使用try-catch let defaultCity = '深圳' try { if (localStorage.city) { defaultCity = localStorage.city } } catch (e) {} export default { city: defaultCity }2)创建mutations.js文件
export default { changeCity (state, city) { state.city = city // 使用localStorage记录数据 try { localStorage.city = city } catch (e) {} } }3)在index.js中引入state.js和mutations.js文件
import Vue from 'vue' import Vuex from 'vuex' import state from './state' import mutations from './mutations' Vue.use(Vuex) export default new Vuex.Store({ state, mutations })(3)mapState是vuex的高级用法,将vuex中的数据映射到city计算属性中 1)在home的header组件中,将vuex组件中的数据映射到该组件的计算属性中
<script> import { mapState } from 'vuex' export default { name: 'HomeHeader', // 将city放到vuex中,此处无需接收 // props: { // city: String // } // 将vuex组件中的数据映射到该组件的计算属性中 computed: { ...mapState(['city']) } } </script>2)其它相关组件同理也如此。
<script> import { mapState, mapMutations } from 'vuex' export default { name: 'CityList', props: { hot: Array, cities: Object, letter: String }, computed: { // mapState也可以放对象 ...mapState({ currentCity: 'city' }) } </script>(4)mapMutations是vuex的高级用法,将changeCity方法映射到各组件中 1)List组件和Search组件
<script> import { mapState, mapMutations } from 'vuex' export default { name: 'CityList', props: { hot: Array, cities: Object, letter: String }, methods: { // 将city作为参数 handleCityClick (city) { this.changeCity(city) this.$router.push('/') }, // mapMutations将changeCity方法映射到该组件中 ...mapMutations(['changeCity']) } } </script>(1)每次切换页面,ajax都会发送请求,可以使用keep-alive,路由中的内容被加载之后,就放到内存中,供下次使用
<template> <div id="app"> <!-- 使用keep-alive,路由中的内容被加载之后,就放到内存中,供下次使用 --> <keep-alive> <router-view/> </keep-alive> </div> </template>(2)但重新选择城市,发现因为keep-alive缓存的原因,并没有发起任何ajax请求。 解决方法: 1)使用keep-alive时,会多出个生命周期函数,当页面重新显示的时候,会执行activated函数 2)在该mounted生命周期函数内,记录当前城市
mounted () { // 记录当前的城市 this.lastCity = this.city // 发送ajax请求 this.getHomeInfo() }3)重新回到首页时,在activated生命周期函数中,判断当前城市与之前城市是否相同,如果不同,重新请求数据
activated () { // 当城市列表中选择的城市与之前的城市不一致时,重新请求ajax数据 if (this.lastCity !== this.city) { // 先记录城市 this.lastCity = this.city this.getHomeInfo() } }