Vue实战项目开发 您所在的位置:网站首页 gofun城市列表 Vue实战项目开发

Vue实战项目开发

2023-09-18 01:26| 来源: 网络整理| 查看: 265

城市选择页面路由配置 创建一个city-router的分支,用于城市列表的功能实现路由配置,修改router/index.js的内容 import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Home', component: () => import( '@/pages/home/Home') }, { path: '/city', name: 'City', component: () => import( '@/pages/city/City') } ] })

然后在pages文件夹下新建city文件夹,并创建City.vue,最后在city文件夹下新建components文件夹,然后创建City.vue的顶部Header.vue             

City.vue: import CityHeader from './components/Header' export default { name:'City', components: { CityHeader }, data () { return { } } }

Header.vue:

城市选择  export default { name:'CityHeader', data () { return { } } } @import '~styles/varibles.styl' .header position relative height $headerHeight line-height $headerHeight text-align center color #fff background $bgColor font-size .32rem .header-back position absolute top 0 left 0 width .64rem text-align center font-size .4rem color #ffffff

 

实现点击首页的header部分的成都进入城市列表和点击城市列表的返回按钮返回首页,这里使用router-link实现的,router-link类似a标签 {{this.city}}  

 

 

代码优化:这里把顶部的height在varibles.styl文件中定义为一个常量,方便后期修改 $headerHeight = .86rem

使用

@import '~styles/varibles.styl' (这里需要先引入才可以使用) height $headerHeight line-height $headerHeight

最后,把代码提交到GitHub上

搜索框布局 创建city-search分支,然后把本地的项目改在新建的这个分支上进行开发在components中新建Search.vue文件 export default { name:'CitySearch', data () { return { } } } @import '~styles/varibles.styl' .search height .72rem padding 0 .1rem background $bgColor .search-input box-sizing border-box width 100% height .62rem padding 0 .1rem line-height .62rem text-align center border-radius .06rem color #666666

然后在City.vue中引入并使用

import CityHeader from './components/Header' import CitySearch from './components/Search' export default { name:'City', components: { CityHeader, CitySearch }, data () { return { } } }

然后搜索框布局就完成了,最后把代码提交到GitHub上

列表布局 创建city-list分支,然后把本地的项目改在新建的这个分支上进行开发在components中新建List.vue文件 当前城市 成都 热门城市 成都 成都 成都 成都 A 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 B 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 C 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 D 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 export default { name:'CityList', } @import '~styles/varibles.styl' .border-topbottom &:before border-color #ccc &:after border-color #ccc .border-bottom &:before border-color #ccc .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 text-align center border .02rem solid #ccc border-radius .06rem .item-list .item line-height .76rem padding-left .2rem

然后在City.vue中引入并使用

import CityHeader from './components/Header' import CitySearch from './components/Search' import CityList from './components/List' export default { name:'City', components: { CityHeader, CitySearch, CityList } }

然后列表布局就完成了,最后把代码提交到GitHub上

Better-scroll的使用及字母表布局 安装better-scroll插件 npm install better-scroll --save

在文档可以看到better-scroll的使用better-scroll的GitHub地址

 修改List.vue的代码 (使用ref来获取当前元素)

引入并使用

import BScroll from 'better-scroll' export default { name:'CityList', mounted () { this.scroll = new BScroll(this.$refs.wrapper) } }

完整代码

当前城市 成都 热门城市 成都 成都 成都 成都 A 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 B 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 C 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 D 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 阿拉尔 import BScroll from 'better-scroll' export default { name:'CityList', mounted () { this.scroll = new BScroll(this.$refs.wrapper) } } @import '~styles/varibles.styl' .border-topbottom &:before border-color #ccc &:after border-color #ccc .border-bottom &:before border-color #ccc .list overflow hidden position absolute top 1.58rem left 0 right 0 bottom 0 .title line-height .54rem 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 text-align center border .02rem solid #ccc border-radius .06rem .item-list .item line-height .76rem padding-left .2rem

better-scroll就引入并使用完成了

字母表布局:在componens文件夹下新建Alphabet.vue A A A A A export default { name:'CityAlphabet', } @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

然后在City.vue中引入:

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 { } } }

字母表布局就完成了,最后把代码提交到线上

页面的动态数据渲染 创建city-Ajax分支,然后把本地的项目改在新建的这个分支上进行开发使用Ajax模拟网络请求,在static/mock下新建一个city.json(有需要的可以在我的GitHub上拉取),在使用axios来模拟请求数据City.vue的js部分: 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) }, handleGetCityInfoSucc (res) { console.log(res) res = res.data if(res.ret && res.data){ const data = res.data this.cities = data.cities this.hotCities = data.hotCities } } }, mounted () { this.getCityInfo() } }

 City.vue:

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) }, handleGetCityInfoSucc (res) { console.log(res) res = res.data if(res.ret && res.data){ const data = res.data this.cities = data.cities this.hotCities = data.hotCities } } }, mounted () { this.getCityInfo() } }

List.vue:

当前城市 成都 热门城市 {{item.name}} {{key}} {{innerItem.name}} import BScroll from 'better-scroll' export default { name:'CityList', props: { hot: Array, cities: Object }, mounted () { this.scroll = new BScroll(this.$refs.wrapper) } } @import '~styles/varibles.styl' .border-topbottom &:before border-color #ccc &:after border-color #ccc .border-bottom &:before border-color #ccc .list overflow hidden position absolute top 1.58rem left 0 right 0 bottom 0 .title line-height .54rem 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 text-align center border .02rem solid #ccc border-radius .06rem .item-list .item line-height .76rem padding-left .2rem

Alphabet.vue:

{{key}} export default { name:'CityAlphabet', props: { cities: Object } } @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

 

最后看一下最后的效果,记住把代码提交到GitHub上

           

兄弟组建间联动 实现点击右侧字母,左边的内容跟着字母变化

             

         就是点击H字母,城市列表就跳到H首字母的城市列表

         这里是通过在Alphabet.vue监听点击字母的点击事件,然后把点击对应的元素的值传给父组件,父组件再将这个值传给List.vue子组件的

        Alphabet.vue(增加了handleLetterClick,通过emit把值传给父组件的):

{{key}} export default { name:'CityAlphabet', props: { cities: Object }, methods: { handleLetterClick (e) { this.$emit('change',e.target.innerText) } } } @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

   City.vue(通过@change="handleLetterChange"来接受Alphabet.vue子组件传过来的值,并在data中定义letter值,在前面的方法把从子组件得到的值赋给data中定义好的letter,然后把这个值通过属性传给List.vue子组件):

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: [], letter: '' } }, methods: { getCityInfo () { axios.get('/api/city.json') .then(this.handleGetCityInfoSucc) }, handleGetCityInfoSucc (res) { res = res.data if(res.ret && res.data){ const data = res.data this.cities = data.cities this.hotCities = data.hotCities } }, handleLetterChange (letter) { this.letter = letter; } }, mounted () { this.getCityInfo() } }

List.vue(在这里使用props接受父组件传来的letter值,并使用watch来监听letter值的变化,然后让内容跟随右侧字母的变化):

当前城市 成都 热门城市 {{item.name}} {{key}} {{innerItem.name}} import BScroll from 'better-scroll' export default { name:'CityList', props: { hot: Array, cities: Object, letter: String }, mounted () { this.scroll = new BScroll(this.$refs.wrapper) }, watch: { letter () { if (this.letter) { const element = this.$refs[this.letter][0] this.scroll.scrollToElement(element) } } } } @import '~styles/varibles.styl' .border-topbottom &:before border-color #ccc &:after border-color #ccc .border-bottom &:before border-color #ccc .list overflow hidden position absolute top 1.58rem left 0 right 0 bottom 0 .title line-height .54rem 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 text-align center border .02rem solid #ccc border-radius .06rem .item-list .item line-height .76rem padding-left .2rem  在左侧字母表中做上下拖拽的时候,城市列表内容也跟着变化

          主要是监听拖拽时间,在li增加了三个事件,分别是touchstart、touchmove、touchend来监听拖拽事件,同时在li上使用ref,在data定义了一个touchStatus来保存是否在拖拽的状态,并使用计算属性把cities中的字母取出来放在数组letters中,最后计算拖拽到那个字母:

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 }

    Alphabet.vue:

{{item}} export default { name:'CityAlphabet', props: { cities: Object }, data () { return { touchStatus: false } }, computed: { letters () { const letters = [] for( let i in this.cities) { letters.push(i) } return letters } }, methods: { handleLetterClick (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 } } } @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 列表切换性能优化 把A字母离顶部的高度使用updated钩子函数来计算,不用每次执行handleTouchMove函数的时候都计算

           

函数节流(定义了一个timer,如果正在执行拖拽操作,让它延迟16毫秒来执行,如果在16毫秒之间又执行了手指滑动操作,它就会把上次要做的操作清楚掉,重新执行这次要做的事情,从而减少handleTouchMove函数的执行频率)

         

Alphabet.vue:

{{item}} export default { name:'CityAlphabet', props: { cities: Object }, data () { return { touchStatus: false, startY: 0, timer: '' } }, computed: { letters () { const letters = [] for( let i in this.cities) { letters.push(i) } return letters } }, updated () { this.startY = this.$refs['A'][0].offsetTop }, methods: { handleLetterClick (e) { this.$emit('change',e.target.innerText) }, 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) { this.$emit('change',this.letters[index]) } }, 16) } }, handleTouchEnd () { this.touchStatus = false } } } @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

最后git提交代码到GitHub上

搜索功能实现 City.vue传值到Search.vue子组件,通过watch监听用户输入,从而修改输入框下面显示的数组使用v-show判断用户是否输入了数据,从而实现查找到的数组内容区域的显示隐藏 使用v-show判断用户输入的值是否有对应的值,来实现查找到的数组数据的显示和没有查到内容时的提示还使用了函数节流,让用户在输入后100毫秒再执行,如果用户在100毫秒之间再输入就会把上次的操作清楚掉,增强性能

       

      Search.vue:

{{item.name}} 没有找到匹配数据 import Bscroll from 'better-scroll' export default { name:'CitySearch', props: { cities: Object }, data () { return { keyword: '', list: [], timer: null } }, computed: { hasNoData () { return !this.list.length } }, 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) } } @import '~styles/varibles.styl' .search height .72rem padding 0 .1rem background $bgColor .search-input box-sizing border-box width 100% height .62rem padding 0 .1rem line-height .62rem text-align center border-radius .06rem color #666666 .search-content z-index 1 position absolute overflow hidden top 1.58rem left 0 right 0 bottom 0 background #eee .search-item line-height .62rem padding-left .2rem background #ffffff color #666 使用Vuex实现数据共享 首先创建一个city-vuex的分支,这里需要实现的功能是点击城市列表页面中城市,首页的城市会发生改变vuex数据框架是vue大型项目的数据传输使用的框架

       重要: vuex,我们可以想象成一个仓库,其中有State、Actions、Mutations;State是所有的公用数据都存在State中,组建需要使用公用的数据,直接去调用State就可以了;Actions是我们可以把需要实现异步操作或者是一些比较复杂的同步操作(批量的同步操作)存在Actions中;Mutations是对State同步的修改操作;组件中怎么使用?(1.组件先去调用Actions,紧接着Actions去调用Mutations,然后Motations去修改State中的数据;2.组件直接调用Motations去修改State中的数据)

         

在项目中使用,先安装这个vuex npm install vuex --save

然后在项目中创建这个"仓库区域",我们把它叫做store,并创建index.js文件,最后在main.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 } } })

在组件中的使用(在List.vue和Search.vue中使用到了,实现效果就是在点击城市列表页的时候,通过调用mutations中的方法来改变State中的数据),对对应的地方绑定handleCityClick()方法并且在methods中定义这个方法

{{item.name}} {{innerItem.name}} handleCityClick (city) { this.$store.commit('changeCity', city) this.$router.push('/') }

这个调用mutations中的方法使用的是commit,可以从上面的图看到;这样最简单的使用vuex就完成了

Vuex的高级使用及localStorage 上面实现的功能会有问题,就是每次重启应用的时候,选择的城市会重置为State中预设的城市名,为了解决这个问题,在这里就引入了h5中的localStorage本地缓存来实现 import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { city: localStorage.city || '成都' }, mutations: { changeCity (state, city) { state.city = city localStorage.city = city } } })

改进版(防止用户没有开启本地缓存功能或者是隐身模式):

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 { localStorage.city = city } catch (e) {} } } })

最终版(拆分成了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.use(Vuex) export default new Vuex.Store({ state, mutations })

 

还有个样式问题,就是选择四个字的城市时,顶部会被撑开,直接修改home/components下的Header.vue中的.header-right样式 .header-right min-width 1.04rem //修改的 padding 0 .1rem //新增的 float right text-align center color #ffffff

 

优化代码:使用vuex提供的mapState来简化代码 // home下的Header.vue的js部分 import { mapState } from 'vuex' //新增 export default { name: 'HomeHeader', computed: { //计算属性新增的 ...mapState(['city']) } }

使用:

{{this.city}} //从this.$store.state.city精简成的this.city 

 city/components中List.vue:

当前城市 {{this.currentCity}} 热门城市 {{item.name}} {{key}} {{innerItem.name}} import BScroll from 'better-scroll' import { mapState, mapMutations } from 'vuex' export default { name:'CityList', computed: { ...mapState({ currentCity: 'city' //这里使用的对象,就是把state中的city映射到这个组件的计算属性中这个值 }) }, props: { hot: Array, cities: Object, letter: String }, methods: { handleCityClick (city) { // this.$store.commit('changeCity', city) 使用mutations的方法之前的调用 this.changeCity(city) this.$router.push('/') }, ...mapMutations(['changeCity']) //使用mapMutations来简化mutations中的方法调用 }, mounted () { this.scroll = new BScroll(this.$refs.wrapper) }, watch: { letter () { if (this.letter) { const element = this.$refs[this.letter][0] this.scroll.scrollToElement(element) } } } } @import '~styles/varibles.styl' .border-topbottom &:before border-color #ccc &:after border-color #ccc .border-bottom &:before border-color #ccc .list overflow hidden position absolute top 1.58rem left 0 right 0 bottom 0 .title line-height .54rem 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 text-align center border .02rem solid #ccc border-radius .06rem .item-list .item line-height .76rem padding-left .2rem

其他的组件一样使用这种方法来精简代码(这里我把之前创建的没有用的文件夹List和组件也删除了)

另外讲一下vuex中的getter和module,这 getter就是store 的计算属性,通过把State中的数据计算后,组件就可以之间调用getter使用;module是当由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

        

使用keep-alive优化页面性能 首先创建在GitHub一个city-keepalive的分支在App.vue中使用标签 export default { name: 'App' }

作用是路由的内容加载过一次之后,就把路由中的内容放到内存之中,下次再重新进这个路由时,不需要重新渲染组件

还有逻辑问题,就是在重新选择城市后在Home.vue中需要重新进行Ajax请求,在Home.vue中引入vuex import { mapState } from 'vuex'

 修改getHomeinfo方法,在请求的时候带上state中的city 

getHomeinfo () { axios.get('/api/index.json?city=' + this.city) .then(this.getHomeinfoSucc) },

这样修改后,重新选择城市也不会重新进行Ajax请求,这里就需要用到activated(使用keep-alive才有的钩子函数)使用mounted和activated这两个钩子函数来实现

mounted () { this.lastCity = this.city this.getHomeinfo() }, activated () { if (this.lastCity !== this.city) { this.lastCity = this.city this.getHomeinfo() } }

最后把代表提交到GitHub上



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有