先创建分支“city-router”,之后需要在pages
文件夹之下新建一个city/city.vue
。再切换到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
}]
})
对于City.vue
和router
配置好之后,我们需要实现的是点击首页的右上角的城市就可以进入到City.vue
的页面。
要实现该功能,需要使用
标签(它包裹的内容被点击时,就会跳转到指定的页面之上)
Header.vue:
<router-link to="/city">
<div class="header-right">{
{
this.city}}<span class="iconfont arrow-icon"></span></div>
</router-link>
再到city文件夹中新建components
文件夹,并且在components
新建Header.vue
组件。
Header.vue:
<template>
<div class="header">城市选择</div>
</template>
<script>
export default {
name: "CityHeader"
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.header
overflow:hidden
height:.86rem
line-height:.86rem
text-align:center
color:#fff
background:$bgColor
font-size:.4rem
</style>
完成以上步骤,切换到city.vue文件中。对header.vue进行引入
City.vue:
<template>
<city-header></city-header>
</template>
<script>
import CityHeader from './components/Header'
export default {
name: "City",
components:{
CityHeader
}
}
</script>
接下来,我们需要给当前城市页面添加一个返回主页的功能。这里我还是使用
来完成就可以了。
<template>
<div class="header">城市选择
<router-link to="/">
<div class="iconfont header-back"></div>
</router-link>
</div>
</template>
<style>
.header-back
position:absolute
top:0
left:0
width:.64rem
text-align:center
font-size:.4rem
color:#fff
</style>
当前这个分支内容就写完了,就可以提交到仓库了。
先新建一个分支“city-search”,在到city/components
下新建Search.vue
,代码如下:
<template>
<div class="search">
<input type="text" placeholder="输入城市名拼音" class="search-input" />
</div>
</template>
<script>
export default {
name: "CitySearch"
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.search
height: .72rem
background: $bgColor
padding: 0 .1rem
.search-input
box-sizing: border-box
height:.62rem
width:100%
text-align:center
border-radius:.06rem
color:#666
padding: 0 .2rem
</style>
最后带city.vue文件引入search.vue就可以了,写完别忘了提交到仓库
创建分支“city-list”,并新建List.vue
List.vue布局代码:
<template>
<div class="list">
<div class="area">
<div class="title border-topbottom">您的位置</div>
<div class="button-list">
<div class="button-wrapper">
<div class="button" style="border-color:#00bcd4">福州</div>
</div>
</div>
</div>
<div class="area">
<div class="title border-topbottom">热门城市</div>
<div class="button-list">
<div class="button-wrapper">
<div class="button">福州</div>
</div>
<div class="button-wrapper">
<div class="button">福州</div>
</div>
<div class="button-wrapper">
<div class="button">福州</div>
</div>
<div class="button-wrapper">
<div class="button">福州</div>
</div>
<div class="button-wrapper">
<div class="button">福州</div>
</div>
<div class="button-wrapper">
<div class="button">福州</div>
</div>
<div class="button-wrapper">
<div class="button">福州</div>
</div>
<div class="button-wrapper">
<div class="button">福州</div>
</div>
<div class="button-wrapper">
<div class="button">福州</div>
</div>
</div>
</div>
<div class="area">
<div class="title border-topbottom">A</div>
<div class="item-list">
<div class="item border-topbottom">阿拉尔</div>
<div class="item border-topbottom">阿拉尔</div>
<div class="item border-topbottom">阿拉尔</div>
<div class="item border-topbottom">阿拉尔</div>
</div>
</div>
<div class="area">
<div class="title border-topbottom">B</div>
<div class="item-list">
<div class="item border-topbottom">阿拉尔</div>
<div class="item border-topbottom">阿拉尔</div>
<div class="item border-topbottom">阿拉尔</div>
<div class="item border-topbottom">阿拉尔</div>
</div>
</div>
<div class="area">
<div class="title border-topbottom">C</div>
<div class="item-list">
<div class="item border-topbottom">阿拉尔</div>
<div class="item border-topbottom">阿拉尔</div>
<div class="item border-topbottom">阿拉尔</div>
<div class="item border-topbottom">阿拉尔</div>
</div>
</div>
<div class="area">
<div class="title border-topbottom">D</div>
<div class="item-list">
<div class="item border-topbottom">阿拉尔</div>
<div class="item border-topbottom">阿拉尔</div>
<div class="item border-topbottom">阿拉尔</div>
<div class="item border-topbottom">阿拉尔</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "CityList"
}
</script>
<style lang="stylus" scoped>
.border-topbottom
&:before
border-color: #ccc
&:after
border-color: #ccc
//加了list的样式 网页无法滑动 为什么这样写?是为了内容不被撑开
.list
overflow: hidden
position: absolute
top:1.58rem
left:0
right:0
bottom:0
.title
line-height: .44rem
background: #eee
padding-left: .2rem
color: #666
font-size: .26rem
.button-list
padding:.1rem .6rem .1rem .1rem
overflow:hidden
.button-wrapper
float:left
width:33.33%
.button
margin: .1rem
padding: .1rem 0
border-radius:.06rem
text-align:center
border: .02rem solid #CCC
.item-list
.item
line-height:.76rem
color:#666
padding-left:.2rem
</style>
在前面的一节,我们给页面的多余的代码给隐藏了,就导致了网页无法上下滑动。这节就需要用到第三发库Better-scroll
,它可以帮助我们实现同样的上下滑动的效果。
# 安装better-scroll
npm install better-scroll --save
安装好之后,要使用该插件,就需要按照一定布局格式来使用,我们需要把HTML的布局改一下:
<template>
//ref是用于接收DOM元素的
<div class="list" ref="wrapper">
<div> //这里额外的添加一个DIV标签
<div class="area">
<div class="title border-topbottom">您的位置</div>
<div class="button-list">
<div class="button-wrapper">
<div class="button" style="border-color:#00bcd4">福州</div>
</div>
</div>
</div>
//...
</div>
</div>
</template>
布局已经改好了,我们直接按照以下的方式就用用了。实现后,发现顶部的Header不会再随着下滑而消失了。
<script>
import Bscroll from 'better-scroll'
export default {
name: "CityList",
mounted (){
this.scroll = new Bscroll(this.$refs.wrapper)
}
}
</script>
接下来我们要实现的是,右侧的字母导航功能。实现这个功能,我们需要单独的创建一个组件“Alphabet.vue”。
右侧的字母导航布局的代码:
<template>
<ul class="list">
<li class="item">A</li>
<li class="item">B</li>
<li class="item">C</li>
<li class="item">D</li>
<li class="item">E</li>
<li class="item">F</li>
<li class="item">G</li>
</ul>
</template>
<script>
export default {
name: "CityAlphabet"
}
</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
text-align:center
line-height:.4rem
color:$bgColor
</style>
这里我们还是选择使用ajax来渲染。先创建分支“city-ajax”。json数据下载
有了城市json数据之后我们将它放到mock文件夹下。随后我们在city.vue
组件中引入axios
插件。
<template>
<div>
<city-header></city-header>
<city-search></city-search>
//给该组件传参
<city-list :cities="cities" :hot="hotCities"></city-list>
<city-alphabet></city-alphabet>
</div>
</template>
<script>
import axios from "axios"
import CityHeader from './components/Header'
import CitySearch from './components/Search'
import CityList from './components/List'
import CityAlphabet from './components/Alphabet'
export default {
name: "City",
components:{
CityHeader,
CitySearch,
CityList,
CityAlphabet
},
//定义返回给组件的数据
data(){
return {
cities:{
},
hotCities:[]
}
},
methods:{
getCityInfo(){
axios.get("/api/city.json").then(this.handleGetCityInfoSucc)
},
//给data方法中的变量进行赋值
handleGetCityInfoSucc(res){
res = res.data
if(res.ret && res.data){
const data = res.data
this.cities = data.cities
this.hotCities = data.hotCities
}
}
},
mounted(){
this.getCityInfo()
}
}
</script>
<style lang="stylus" scoped>
</style>
这时我们把目光切换到list.vue
,做出以下的修改:
<template>
<div class="list" ref="wrapper">
<div>
//省略前面部分代码
<div class="area">
<div class="title border-topbottom">热门城市</div>
<div class="button-list">
<div class="button-wrapper" v-for="item of hot" :key="item.id">
<div class="button">{
{
item.name}}</div>
</div>
</div>
</div>
<div class="area" v-for="item,key of cities" :key="key">
<div class="title border-topbottom">{
{
key}}</div>
<div class="item-list">
<div class="item border-topbottom" v-for="innerItem of item" :key="innerItem.id">{
{
innerItem.name}}</div>
</div>
</div>
</div>
</div>
</template>
<script>
//省略前面部分代码
props:{
hot:Array,
cities:Object
},
//省略后面部分代码
}
</script>
接下来我们继续渲染右侧字母导航:对Alphabet.vue
组件如下的修改:
<template>
<ul class="list">
<li class="item" v-for="item,key of cities" :key="key">{
{
key}}</li>
</ul>
</template>
<script>
export default {
name: "CityAlphabet",
props:{
cities:Object
}
}
</script>
最后别忘记把代码提交到线上仓库。
我们需要实现的就是点击字母导航就可以跳转到对于的城市区域。创建分支“city-components”。
兄弟组件间的传值,前面说到的是使用“总线”的方式来实现。所以这里我们实现的思路就是由Alphabet.vue
先给City.vue
传值,再有它分发给List.vue
组件。
Alphabet.vue:
<template>
<ul class="list">
//这里添加点击事件
<li class="item" v-for="item,key of cities" :key="key" @click="letterClick">{
{
key}}</li>
</ul>
</template>
<script>
methods:{
letterClick(e){
// 被点击的时候向外触发一个事件并携带被点击的数值
this.$emit('change',e.target.innerText)
}
}
</script>
City.vue:
<template>
<div>
//省略部分代码
<city-alphabet :cities="cities" @change="letterClick"></city-alphabet>
</div>
</template>
<script>
data(){
return {
cities:{
},
hotCities:[],
letter:""
}
},
methods:{
//省略部分代码....
//监听Alphabet.vue的change点击事件
letterClick(letter){
this.letter = letter
}
}
</script>
List.vue:
<template>
<div class="list" ref="wrapper">
<div>
//省略部分代码 添加了ref用户获取DOM元素
<div class="area" v-for="item,key of cities" :key="key" :ref="key">
//.....
</div>
</div>
</div>
</template>
<script>
import Bscroll from 'better-scroll'
export default {
//省略部分代码....
// 监听letter的变化
watch:{
letter(){
if (this.letter) {
// 由于获取到的是数组,所以需要加[0]
const element = this.$refs[this.letter][0]
// scrollToElement是由better scroll提供
this.scroll.scrollToElement(element)
}
}
}
}
</script>
完成以上步骤,就可以实现了点击字母就会跳转到对于的区域。现在需要给它做的是当滑动页面的时候,字母导航也会显示滑动的效果。要完成该功能只需对Alphabet.vue
做出修改就可以了。
完整代码:
<template>
<ul class="list">
<li
class="item"
v-for="item of letters"
:key="item"
:ref="item"
@click="letterClick"
@touchstart="handletouchStart"
@touchmove="handletouchMove"
@touchend="handletouchEnd"
>
{
{
item}}
</li>
</ul>
</template>
<script>
export default {
name: "CityAlphabet",
props:{
cities:Object
},
// 当触发touchstart之后才执行touchmove 所以定义该标识位
data (){
return {
touchStatus:false
}
},
// 计算出当前所在字母的位置
computed:{
letters (){
const letters = []
for (let i in this.cities) {
letters.push(i)
}
return letters
}
},
methods:{
letterClick(e){
// 被点击的时候向外触发一个事件并携带被点击的数值
this.$emit('change',e.target.innerText)
},
handletouchStart(){
this.touchStatus = true
},
// 获取手指移动到的地方并计算所接触的字母
handletouchMove(e){
if (this.touchStatus) {
const startY = this.$refs['A'][0].offsetTop
const touchY = e.touches[0].clientY - 79
const index = Math.floor((touchY - startY) / 20)
if(index >= 0 && index < this.letters.length){
this.$emit('change',this.letters[index])
}
}
},
handletouchEnd(){
this.touchStatus = false
}
}
}
</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
text-align:center
line-height:.4rem
color:$bgColor
</style>
去除重复性计算的代码:
<script>
data (){
return {
touchStatus:false,
// 添加了初始startY变量
startY:0
}
},
//....
// 当ajax获取到数据的时候开始计算
update(){
this.startY = this.$refs['A'][0].offsetTop
},
//...
handletouchMove(e){
if (this.touchStatus) {
//与前面比较已经把第一句删了
const touchY = e.touches[0].clientY - 79
//注意这里的startY是data中的。
const index = Math.floor((touchY - this.startY) / 20)
if(index >= 0 && index < this.letters.length){
this.$emit('change',this.letters[index])
}
}
}
</script>
给touchmove函数添加一个函数节流。由于前面的写法切换的台频繁:
<script>
data (){
return {
touchStatus:false,
startY:0,
timer:null
}
},
// 获取手指移动到的地方并计算所接触的字母
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){
this.$emit('change',this.letters[index])
}
},16)
}
}
</script>
完整代码:
<template>
<div>
<div class="search">
<input v-model="keyword" type="text" placeholder="输入城市名拼音" class="search-input" />
</div>
<div class="search-content" ref="search" v-show="keyword">
<ul>
<li class="search-item border-bottom" v-for="item of list" :key="item.id">{
{
item.name}}</li>
<li class="search-item border-bottom" v-show="hasData">未找到匹配数据</li>
</ul>
</div>
</div>
</template>
<script>
import Bscroll from "better-scroll"
export default {
name: "CitySearch",
props: {
cities: Object
},
data (){
return {
keyword: "",
list: [],
timer:null //用于节流声明
}
},
// 计算是否匹配到数据
computed:{
hasData (){
return !this.list.length
}
},
//监听搜索栏keyword的变化
watch:{
keyword (){
if (this.timer) {
clearTimeout(this.timer)
}
if (!this.keyword) {
this.list = []
return
}
this.timer = setTimeout(() => {
const result = []
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)
}
},
mounted () {
// 实现搜索结果页面的滑动
this.scroll = new Bscroll(this.$refs.search)
}
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.search
height: .72rem
background: $bgColor
padding: 0 .1rem
.search-input
box-sizing: border-box
height:.62rem
width:100%
text-align:center
border-radius:.06rem
color:#666
padding: 0 .2rem
.search-content
position:absolute
top:1.58rem
overflow:hidden
left:0
right:0
bottom:0
background:#eee
z-index:1
.search-item
line-height:.62rem
padding-left:.2rem
color:#666
background:#fff
</style>
这节课要实现的是首页与城市列表进行数据共享,意思就是点击城市,该城市传递给首页。首先先创建分支“city-vuex”。
再来介绍一个Vuex是由官网提供的大型数据框架,可以对数据进行共享、交互。
安装vuex:
npm install vuex --save
由于后期使用vuex开发数据还是很复杂的,所以便于后期维护,我们在src下新建一个文件夹store
,在该文件夹之下再创建一个index.js
。具体内容如下:
import Vue from 'vue'
import Vuex from 'vuex'
// 让vue使用vuex插件
Vue.use(Vuex)
// 导出的是vuex创建的一个仓库
export default new Vuex.Store({
// 公用的数据存储在state之中
state: {
city: "福州"
}
})
之后再将它引入到main.js
中使用。
import store from './store'
new Vue({
//....
store,
//...
})
通过前面的代码实现,我们已经完成了state的数据内容,根据这张图,接下来我们就应把数据共享到视图层(首页)。
因为前面开发的首页,右上角的城市是通过json直接传的,这样根本无法实现城市列表与首页进行数据共享,所以我们需要在home/Home.vue
组件中将相关的city
代码删除。在components/Header.vue
也用相关的代码,也要删除,对做出以下的修改:
<router-link to="/city">
<div class="header-right">{
{
this.$store.state.city}}<span class="iconfont arrow-icon"></span></div>
</router-link>
做到这里就已经成功的完成了首页的城市关联。接下来我们要对城市列表中的当前位置进行关联。代码如下:
<div class="area">
<div class="title border-topbottom">您的位置</div>
<div class="button-list">
<div class="button-wrapper">
<div class="button" style="border-color:#00bcd4">{
{
this.$store.state.city}}</div>
</div>
</div>
</div>
接下来,我们需要点击城市列表中的任意一个城市都能更改当前的城市。所以就需要为列表中的每个城市选项添加一个点击事件并且传入当前所点击的城市名,并且在逻辑区下事件的执行内容。代码如下:
<div class="item-list">
<div class="item border-topbottom" v-for="innerItem of item" :key="innerItem.id" @click="handleCityClick(innerItem.name)">{
{
innerItem.name}}</div>
</div>
methods: {
handleCityClick (city) {
// this.$store.dispatch('changeCity',city)
// 组件可以直接调用mutations
this.$store.commit('changeCity',city)
}
}
接下来我们回到state/index.js
添加以下代码:
import Vue from 'vue'
import Vuex from 'vuex'
// 让vue使用vuex插件
Vue.use(Vuex)
// 导出的是vuex创建的一个仓库
export default new Vuex.Store({
// 公用的数据存储在state之中
state: {
city: "厦门"
},
// 整个执行流程是:组件调用actions,actions调用mutations,mutations再改变state的数据
// actions: {
// // 接收页面传过来的参数并处理
// changeCity(ctx,city) {
// ctx.commit('changeCity',city)
// }
// },
mutations: {
changeCity (state,city) {
state.city = city
}
}
})
除了上面做出的修改,也要需要到search.vue
中做出同样的修改。
完成以上代码,最后我们需要点击某个城市之后,马上就跳转到首页,这里需要用到vue router
路由的知识点-编程式导航。具体实现代码如下:
methods: {
handleCityClick (city) {
// this.$store.dispatch('changeCity',city)
// 组件可以直接调用mutations
this.$store.commit('changeCity',city)
// 使用vue-router 跳转到首页
this.$router.push('/')
}
}
前面一节把store/index.js
中的city值给固定死了,所以当我们更改城市的时候,当刷新后,显示又是默认的城市。所以我们需要解决的是记住前面你所更改的城市。这里我们使用localStore来解决这个问题。
export default new Vuex.Store({
// 公用的数据存储在state之中
state: {
city: localStorage.city || "厦门"
},
mutations: {
changeCity (state,city) {
state.city = city
localStorage.city = city
}
}
})
这里建议使用localStorage的时候添加异常捕获,因为有些用的使用浏览器的时候,使用的是隐身模式,如果没有就报错。
import Vue from 'vue'
import Vuex from 'vuex'
// 让vue使用vuex插件
Vue.use(Vuex)
let defaultCity = "厦门"
try{
if (localStorage.city) {
defaultCity = localStorage.city
}
}catch (e) {
}
// 导出的是vuex创建的一个仓库
export default new Vuex.Store({
// 公用的数据存储在state之中
state: {
city: defaultCity
},
mutations: {
changeCity (state,city) {
state.city = city
try{
localStorage.city = city
}catch (e) {
}
}
}
})
我们会看到上面的代码,越来复杂,所以在实际的开发过程中呢,都会对代码进行拆分。我们分别在store文件夹下新建state.js
、mutations.js
文件。最后我们再到主文件中引入即可。
state.js代码如下:
let defaultCity = "厦门"
try{
if (localStorage.city) {
defaultCity = localStorage.city
}
}catch (e) {
}
export default {
city: defaultCity
}
mutations.js代码如下:
export default {
changeCity (state,city) {
state.city = city
try{
localStorage.city = city
}catch (e) {
}
}
}
index.js代码:
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
// 让vue使用vuex插件
Vue.use(Vuex)
// 导出的是vuex创建的一个仓库
export default new Vuex.Store({
// 公用的数据存储在state之中
state,
mutations
})
接下来,再介绍一个关于vuex的映射的知识点,具体使用看以下代码:
// 使用前需要先引入
import {
mapState,mapMutations } from 'vuex'
// 通过计算属性将state/index中的city内容映射到mapSate之中
computed: {
...mapState({
currentCity:'city'
})
},
methods: {
handleCityClick (city) {
//最后直接使用映射的函数
this.changeCity(city)
// 使用vue-router 跳转到首页
this.$router.push('/')
},
// 将state/index中的changeCity内容映射到mapMutations之中
...mapMutations (['changeCity'])
}
更多相关的内容可以去官方学习。
现在的问题是,当我们运行网页的时候,我们会发现每次在首页与城市列表两个页面来回切换的时候,每一次都会请求响应的json数据,这样就会造成过多的性能消耗的问题。
那么使用keep-alive就会有效的解决这个问题,keep-alive是vue官方提供的组件,它的意思是当我们切换路由时所加载的数据,会将所加载的数据文件存储到缓存中,这样就会避免多次加载的情况。
所以我们只需要在App.vue
文件中做出以下修改:
<template>
<div id="app">
<keep-alive>
<router-view/>
</keep-alive>
</div>
</template>
当我们切换城市的时候,希望在首页显示对应城市的数据。但是现在切换后首页的数据并没有重新获取,所以我们在Home.vue
组件中做出以下修改:
<script>
export default {
data (){
return {
lastCity: '',
//...
},
computed:{
...mapState(['city'])
},
methods:{
getHomeInfo (){
// 虽然这里我们写的是api的开头的地址,但是vue已经帮我们跳转到了我们本地的json文件
axios.get('/api/index.json?city=' + this.city)
.then(this.getHomeInfoSucc)
},
//...
},
mounted (){
this.lastCity = this.city
this.getHomeInfo()
},
// 当使用keep-alive的时,会多出该周期函数
activated () {
if (this.lastCity !== this.city) {
this.lastCity = this.city
this.getHomeInfo()
}
}
}
</script>