Vue实战项目开发--城市列表页面

本是古典 何须时尚 2022-05-15 16:18 453阅读 0赞

城市选择页面路由配置

  • 创建一个city-router的分支,用于城市列表的功能实现
  • 路由配置,修改router/index.js的内容

    1. import Vue from 'vue'
    2. import Router from 'vue-router'
    3. Vue.use(Router)
    4. export default new Router({
    5. routes: [
    6. {
    7. path: '/',
    8. name: 'Home',
    9. component: () => import( '@/pages/home/Home')
    10. },
    11. {
    12. path: '/city',
    13. name: 'City',
    14. component: () => import( '@/pages/city/City')
    15. }
    16. ]
    17. })

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

  • City.vue:

    1. <template>
    2. <div>
    3. <city-header></city-header>
    4. </div>
    5. </template>
    6. <script>
    7. import CityHeader from './components/Header'
    8. export default {
    9. name:'City',
    10. components: {
    11. CityHeader
    12. },
    13. data () {
    14. return {
    15. }
    16. }
    17. }
    18. </script>
    19. <style lang="stylus" scoped>
    20. </style>

    Header.vue:

    1. <template>
    2. <div class="header">
    3. 城市选择
    4. <router-link to='/'>
    5. <div class="iconfont header-back"></div>
    6. </router-link>
    7. </div>
    8. </template>
    9. <script>
    10. export default {
    11. name:'CityHeader',
    12. data () {
    13. return {
    14. }
    15. }
    16. }
    17. </script>
    18. <style lang="stylus" scoped>
    19. @import '~styles/varibles.styl'
    20. .header
    21. position relative
    22. height $headerHeight
    23. line-height $headerHeight
    24. text-align center
    25. color #fff
    26. background $bgColor
    27. font-size .32rem
    28. .header-back
    29. position absolute
    30. top 0
    31. left 0
    32. width .64rem
    33. text-align center
    34. font-size .4rem
    35. color #ffffff
    36. </style>
  • 实现点击首页的header部分的成都进入城市列表和点击城市列表的返回按钮返回首页,这里使用router-link实现的,router-link类似a标签

    1. <router-link to='/city'>
    2. <div class="header-right">
    3. {
    4. {this.city}}
    5. <i class="iconfont arrow-icon"></i>
    6. </div>
    7. </router-link>
    8. <router-link to='/'>
    9. <div class="iconfont header-back"></div>
    10. </router-link>
  • 代码优化:这里把顶部的height在varibles.styl文件中定义为一个常量,方便后期修改

    1. $headerHeight = .86rem

    使用

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

    最后,把代码提交到GitHub上

搜索框布局

  • 创建city-search分支,然后把本地的项目改在新建的这个分支上进行开发
  • 在components中新建Search.vue文件

    1. <template>
    2. <div class="search">
    3. <input class="search-input" type="text" placeholder="输入城市名或拼音">
    4. </div>
    5. </template>
    6. <script>
    7. export default {
    8. name:'CitySearch',
    9. data () {
    10. return {
    11. }
    12. }
    13. }
    14. </script>
    15. <style lang="stylus" scoped>
    16. @import '~styles/varibles.styl'
    17. .search
    18. height .72rem
    19. padding 0 .1rem
    20. background $bgColor
    21. .search-input
    22. box-sizing border-box
    23. width 100%
    24. height .62rem
    25. padding 0 .1rem
    26. line-height .62rem
    27. text-align center
    28. border-radius .06rem
    29. color #666666
    30. </style>

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

    1. <template>
    2. <div>
    3. <city-header></city-header>
    4. <city-search></city-search>
    5. </div>
    6. </template>
    7. <script>
    8. import CityHeader from './components/Header'
    9. import CitySearch from './components/Search'
    10. export default {
    11. name:'City',
    12. components: {
    13. CityHeader,
    14. CitySearch
    15. },
    16. data () {
    17. return {
    18. }
    19. }
    20. }
    21. </script>
    22. <style lang="stylus" scoped>
    23. </style>

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

列表布局

  • 创建city-list分支,然后把本地的项目改在新建的这个分支上进行开发
  • 在components中新建List.vue文件

    1. <template>
    2. <div class="list">
    3. <div class="area">
    4. <div class="title border-topbottom">当前城市</div>
    5. <div class="button-list">
    6. <div class="button-wrapper">
    7. <div class="button">成都</div>
    8. </div>
    9. </div>
    10. </div>
    11. <div class="area">
    12. <div class="title border-topbottom">热门城市</div>
    13. <div class="button-list">
    14. <div class="button-wrapper">
    15. <div class="button">成都</div>
    16. </div>
    17. <div class="button-wrapper">
    18. <div class="button">成都</div>
    19. </div>
    20. <div class="button-wrapper">
    21. <div class="button">成都</div>
    22. </div>
    23. <div class="button-wrapper">
    24. <div class="button">成都</div>
    25. </div>
    26. </div>
    27. </div>
    28. <div class="area">
    29. <div class="title border-topbottom">A</div>
    30. <div class="item-list">
    31. <div class="item border-bottom">阿拉尔</div>
    32. <div class="item border-bottom">阿拉尔</div>
    33. <div class="item border-bottom">阿拉尔</div>
    34. <div class="item border-bottom">阿拉尔</div>
    35. <div class="item border-bottom">阿拉尔</div>
    36. <div class="item border-bottom">阿拉尔</div>
    37. </div>
    38. </div>
    39. <div class="area">
    40. <div class="title border-topbottom">B</div>
    41. <div class="item-list">
    42. <div class="item border-bottom">阿拉尔</div>
    43. <div class="item border-bottom">阿拉尔</div>
    44. <div class="item border-bottom">阿拉尔</div>
    45. <div class="item border-bottom">阿拉尔</div>
    46. <div class="item border-bottom">阿拉尔</div>
    47. <div class="item border-bottom">阿拉尔</div>
    48. </div>
    49. </div>
    50. <div class="area">
    51. <div class="title border-topbottom">C</div>
    52. <div class="item-list">
    53. <div class="item border-bottom">阿拉尔</div>
    54. <div class="item border-bottom">阿拉尔</div>
    55. <div class="item border-bottom">阿拉尔</div>
    56. <div class="item border-bottom">阿拉尔</div>
    57. <div class="item border-bottom">阿拉尔</div>
    58. <div class="item border-bottom">阿拉尔</div>
    59. </div>
    60. </div>
    61. <div class="area">
    62. <div class="title border-topbottom">D</div>
    63. <div class="item-list">
    64. <div class="item border-bottom">阿拉尔</div>
    65. <div class="item border-bottom">阿拉尔</div>
    66. <div class="item border-bottom">阿拉尔</div>
    67. <div class="item border-bottom">阿拉尔</div>
    68. <div class="item border-bottom">阿拉尔</div>
    69. <div class="item border-bottom">阿拉尔</div>
    70. </div>
    71. </div>
    72. </div>
    73. </template>
    74. <script>
    75. export default {
    76. name:'CityList',
    77. }
    78. </script>
    79. <style lang="stylus" scoped>
    80. @import '~styles/varibles.styl'
    81. .border-topbottom
    82. &:before
    83. border-color #ccc
    84. &:after
    85. border-color #ccc
    86. .border-bottom
    87. &:before
    88. border-color #ccc
    89. .list
    90. overflow hidden
    91. position absolute
    92. top 1.58rem
    93. left 0
    94. right 0
    95. bottom 0
    96. .title
    97. line-height .44rem
    98. background #eee
    99. padding-left .2rem
    100. color #666
    101. font-size .26rem
    102. .button-list
    103. padding .1rem .6rem .1rem .1rem
    104. overflow hidden
    105. .button-wrapper
    106. float left
    107. width 33.33%
    108. .button
    109. margin .1rem
    110. padding .1rem 0
    111. text-align center
    112. border .02rem solid #ccc
    113. border-radius .06rem
    114. .item-list
    115. .item
    116. line-height .76rem
    117. padding-left .2rem
    118. </style>

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

    1. <template>
    2. <div>
    3. <city-header></city-header>
    4. <city-search></city-search>
    5. <city-list></city-list>
    6. </div>
    7. </template>
    8. <script>
    9. import CityHeader from './components/Header'
    10. import CitySearch from './components/Search'
    11. import CityList from './components/List'
    12. export default {
    13. name:'City',
    14. components: {
    15. CityHeader,
    16. CitySearch,
    17. CityList
    18. }
    19. }
    20. </script>
    21. <style lang="stylus" scoped>
    22. </style>

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

Better-scroll的使用及字母表布局

  • 安装better-scroll插件

    1. npm install better-scroll --save

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

  • 修改List.vue的代码

    (使用ref来获取当前元素)

    <- 这里放之前的内容 ->

    引入并使用

    import BScroll from ‘better-scroll’
    export default {

    1. name:'CityList',
    2. mounted () {
    3. this.scroll = new BScroll(this.$refs.wrapper)
    4. }

    }

    完整代码

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

  • 字母表布局:在componens文件夹下新建Alphabet.vue

    1. <template>
    2. <ul class="list">
    3. <li class="item">A</li>
    4. <li class="item">A</li>
    5. <li class="item">A</li>
    6. <li class="item">A</li>
    7. <li class="item">A</li>
    8. </ul>
    9. </template>
    10. <script>
    11. export default {
    12. name:'CityAlphabet',
    13. }
    14. </script>
    15. <style lang="stylus" scoped>
    16. @import '~styles/varibles.styl'
    17. .list
    18. display flex
    19. flex-direction column
    20. justify-content center
    21. position absolute
    22. top 1.58rem
    23. right 0
    24. bottom 0
    25. width .4rem
    26. .item
    27. line-height .4rem
    28. text-align center
    29. color $bgColor
    30. </style>

    然后在City.vue中引入:

    1. <template>
    2. <div>
    3. <city-header></city-header>
    4. <city-search></city-search>
    5. <city-list></city-list>
    6. <city-alphabet></city-alphabet>
    7. </div>
    8. </template>
    9. <script>
    10. import CityHeader from './components/Header'
    11. import CitySearch from './components/Search'
    12. import CityList from './components/List'
    13. import CityAlphabet from './components/Alphabet'
    14. export default {
    15. name:'City',
    16. components: {
    17. CityHeader,
    18. CitySearch,
    19. CityList,
    20. CityAlphabet
    21. },
    22. data () {
    23. return {
    24. }
    25. }
    26. }
    27. </script>
    28. <style lang="stylus" scoped>
    29. </style>

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

页面的动态数据渲染

  • 创建city-Ajax分支,然后把本地的项目改在新建的这个分支上进行开发
  • 使用Ajax模拟网络请求,在static/mock下新建一个city.json(有需要的可以在我的GitHub上拉取),在使用axios来模拟请求数据
  • City.vue的js部分:

    1. <script>
    2. import axios from 'axios'
    3. import CityHeader from './components/Header'
    4. import CitySearch from './components/Search'
    5. import CityList from './components/List'
    6. import CityAlphabet from './components/Alphabet'
    7. export default {
    8. name:'City',
    9. components: {
    10. CityHeader,
    11. CitySearch,
    12. CityList,
    13. CityAlphabet
    14. },
    15. data () {
    16. return {
    17. cities: {},
    18. hotCities: []
    19. }
    20. },
    21. methods: {
    22. getCityInfo () {
    23. axios.get('/api/city.json')
    24. .then(this.handleGetCityInfoSucc)
    25. },
    26. handleGetCityInfoSucc (res) {
    27. console.log(res)
    28. res = res.data
    29. if(res.ret && res.data){
    30. const data = res.data
    31. this.cities = data.cities
    32. this.hotCities = data.hotCities
    33. }
    34. }
    35. },
    36. mounted () {
    37. this.getCityInfo()
    38. }
    39. }
    40. </script>

    City.vue:

    1. <template>
    2. <div>
    3. <city-header></city-header>
    4. <city-search></city-search>
    5. <city-list :cities="cities" :hot="hotCities"></city-list>
    6. <city-alphabet :cities="cities"></city-alphabet>
    7. </div>
    8. </template>
    9. <script>
    10. import axios from 'axios'
    11. import CityHeader from './components/Header'
    12. import CitySearch from './components/Search'
    13. import CityList from './components/List'
    14. import CityAlphabet from './components/Alphabet'
    15. export default {
    16. name:'City',
    17. components: {
    18. CityHeader,
    19. CitySearch,
    20. CityList,
    21. CityAlphabet
    22. },
    23. data () {
    24. return {
    25. cities: {},
    26. hotCities: []
    27. }
    28. },
    29. methods: {
    30. getCityInfo () {
    31. axios.get('/api/city.json')
    32. .then(this.handleGetCityInfoSucc)
    33. },
    34. handleGetCityInfoSucc (res) {
    35. console.log(res)
    36. res = res.data
    37. if(res.ret && res.data){
    38. const data = res.data
    39. this.cities = data.cities
    40. this.hotCities = data.hotCities
    41. }
    42. }
    43. },
    44. mounted () {
    45. this.getCityInfo()
    46. }
    47. }
    48. </script>
    49. <style lang="stylus" scoped>
    50. </style>

    List.vue:

    1. <template>
    2. <div class="list" ref='wrapper'>
    3. <div>
    4. <div class="area">
    5. <div class="title border-topbottom">当前城市</div>
    6. <div class="button-list">
    7. <div class="button-wrapper">
    8. <div class="button">成都</div>
    9. </div>
    10. </div>
    11. </div>
    12. <div class="area">
    13. <div class="title border-topbottom">热门城市</div>
    14. <div class="button-list">
    15. <div class="button-wrapper" v-for="item of hot" :key="item.id">
    16. <div class="button">{
    17. {item.name}}</div>
    18. </div>
    19. </div>
    20. </div>
    21. <div class="area" v-for="(item,key) of cities" :key="key">
    22. <div class="title border-topbottom">{
    23. {key}}</div>
    24. <div class="item-list">
    25. <div class="item border-bottom" v-for="innerItem of item" :key="innerItem.id">{
    26. {innerItem.name}}</div>
    27. </div>
    28. </div>
    29. </div>
    30. </div>
    31. </template>
    32. <script>
    33. import BScroll from 'better-scroll'
    34. export default {
    35. name:'CityList',
    36. props: {
    37. hot: Array,
    38. cities: Object
    39. },
    40. mounted () {
    41. this.scroll = new BScroll(this.$refs.wrapper)
    42. }
    43. }
    44. </script>
    45. <style lang="stylus" scoped>
    46. @import '~styles/varibles.styl'
    47. .border-topbottom
    48. &:before
    49. border-color #ccc
    50. &:after
    51. border-color #ccc
    52. .border-bottom
    53. &:before
    54. border-color #ccc
    55. .list
    56. overflow hidden
    57. position absolute
    58. top 1.58rem
    59. left 0
    60. right 0
    61. bottom 0
    62. .title
    63. line-height .54rem
    64. background #eee
    65. padding-left .2rem
    66. color #666
    67. font-size .26rem
    68. .button-list
    69. padding .1rem .6rem .1rem .1rem
    70. overflow hidden
    71. .button-wrapper
    72. float left
    73. width 33.33%
    74. .button
    75. margin .1rem
    76. padding .1rem 0
    77. text-align center
    78. border .02rem solid #ccc
    79. border-radius .06rem
    80. .item-list
    81. .item
    82. line-height .76rem
    83. padding-left .2rem
    84. </style>

    Alphabet.vue:

    1. <template>
    2. <ul class="list">
    3. <li class="item" v-for="(item,key) of cities" :key="key">{
    4. {key}}</li>
    5. </ul>
    6. </template>
    7. <script>
    8. export default {
    9. name:'CityAlphabet',
    10. props: {
    11. cities: Object
    12. }
    13. }
    14. </script>
    15. <style lang="stylus" scoped>
    16. @import '~styles/varibles.styl'
    17. .list
    18. display flex
    19. flex-direction column
    20. justify-content center
    21. position absolute
    22. top 1.58rem
    23. right 0
    24. bottom 0
    25. width .4rem
    26. .item
    27. line-height .4rem
    28. text-align center
    29. color $bgColor
    30. </style>
  • 最后看一下最后的效果,记住把代码提交到GitHub上

    1. ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dvXzkyMTExMA_size_16_color_FFFFFF_t_70][]

兄弟组建间联动

  • 实现点击右侧字母,左边的内容跟着字母变化

    1. ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dvXzkyMTExMA_size_16_color_FFFFFF_t_70 1][]
    2. 就是点击H字母,城市列表就跳到H首字母的城市列表
    3. 这里是通过在Alphabet.vue监听点击字母的点击事件,然后把点击对应的元素的值传给父组件,父组件再将这个值传给List.vue子组件的
    4. Alphabet.vue(增加了handleLetterClick,通过emit把值传给父组件的):


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


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

  1. <template>
  2. <div class="list" ref='wrapper'>
  3. <div>
  4. <div class="area">
  5. <div class="title border-topbottom">当前城市</div>
  6. <div class="button-list">
  7. <div class="button-wrapper">
  8. <div class="button">成都</div>
  9. </div>
  10. </div>
  11. </div>
  12. <div class="area">
  13. <div class="title border-topbottom">热门城市</div>
  14. <div class="button-list">
  15. <div class="button-wrapper" v-for="item of hot" :key="item.id">
  16. <div class="button">{
  17. {item.name}}</div>
  18. </div>
  19. </div>
  20. </div>
  21. <div
  22. class="area"
  23. v-for="(item,key) of cities"
  24. :key="key"
  25. :ref="key"
  26. >
  27. <div class="title border-topbottom">{
  28. {key}}</div>
  29. <div class="item-list">
  30. <div class="item border-bottom" v-for="innerItem of item" :key="innerItem.id">{
  31. {innerItem.name}}</div>
  32. </div>
  33. </div>
  34. </div>
  35. </div>
  36. </template>
  37. <script>
  38. import BScroll from 'better-scroll'
  39. export default {
  40. name:'CityList',
  41. props: {
  42. hot: Array,
  43. cities: Object,
  44. letter: String
  45. },
  46. mounted () {
  47. this.scroll = new BScroll(this.$refs.wrapper)
  48. },
  49. watch: {
  50. letter () {
  51. if (this.letter) {
  52. const element = this.$refs[this.letter][0]
  53. this.scroll.scrollToElement(element)
  54. }
  55. }
  56. }
  57. }
  58. </script>
  59. <style lang="stylus" scoped>
  60. @import '~styles/varibles.styl'
  61. .border-topbottom
  62. &:before
  63. border-color #ccc
  64. &:after
  65. border-color #ccc
  66. .border-bottom
  67. &:before
  68. border-color #ccc
  69. .list
  70. overflow hidden
  71. position absolute
  72. top 1.58rem
  73. left 0
  74. right 0
  75. bottom 0
  76. .title
  77. line-height .54rem
  78. background #eee
  79. padding-left .2rem
  80. color #666
  81. font-size .26rem
  82. .button-list
  83. padding .1rem .6rem .1rem .1rem
  84. overflow hidden
  85. .button-wrapper
  86. float left
  87. width 33.33%
  88. .button
  89. margin .1rem
  90. padding .1rem 0
  91. text-align center
  92. border .02rem solid #ccc
  93. border-radius .06rem
  94. .item-list
  95. .item
  96. line-height .76rem
  97. padding-left .2rem
  98. </style>
  • 在左侧字母表中做上下拖拽的时候,城市列表内容也跟着变化

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

    handleTouchStart () {

    1. this.touchStatus = true

    },
    handleTouchMove (e) {

    1. if(this.touchStatus){
    2. const startY = this.$refs['A'][0].offsetTop
    3. const touchY = e.touches[0].clientY -79
    4. const index = Math.floor((touchY-startY) / 20)
    5. if(index >= 0 && index < this.letters.length) {
    6. this.$emit('change',this.letters[index])
    7. }
    8. }

    },
    handleTouchEnd () {

    1. this.touchStatus = false

    }

    Alphabet.vue:


列表切换性能优化

  • 把A字母离顶部的高度使用updated钩子函数来计算,不用每次执行handleTouchMove函数的时候都计算

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

    1. ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dvXzkyMTExMA_size_16_color_FFFFFF_t_70 3][]

Alphabet.vue:

  1. <template>
  2. <ul class="list">
  3. <li
  4. class="item"
  5. v-for="item of letters"
  6. :key="item"
  7. :ref="item"
  8. @touchstart="handleTouchStart"
  9. @touchmove="handleTouchMove"
  10. @touchend="handleTouchEnd"
  11. @click="handleLetterClick"
  12. >
  13. {
  14. {item}}
  15. </li>
  16. </ul>
  17. </template>
  18. <script>
  19. export default {
  20. name:'CityAlphabet',
  21. props: {
  22. cities: Object
  23. },
  24. data () {
  25. return {
  26. touchStatus: false,
  27. startY: 0,
  28. timer: ''
  29. }
  30. },
  31. computed: {
  32. letters () {
  33. const letters = []
  34. for( let i in this.cities) {
  35. letters.push(i)
  36. }
  37. return letters
  38. }
  39. },
  40. updated () {
  41. this.startY = this.$refs['A'][0].offsetTop
  42. },
  43. methods: {
  44. handleLetterClick (e) {
  45. this.$emit('change',e.target.innerText)
  46. },
  47. handleTouchStart () {
  48. this.touchStatus = true
  49. },
  50. handleTouchMove (e) {
  51. if(this.touchStatus){
  52. if(this.timer) {
  53. clearTimeout(this.timer)
  54. }
  55. this.timer = setTimeout(()=> {
  56. const touchY = e.touches[0].clientY -79
  57. const index = Math.floor((touchY-this.startY) / 20)
  58. if(index >= 0 && index < this.letters.length) {
  59. this.$emit('change',this.letters[index])
  60. }
  61. }, 16)
  62. }
  63. },
  64. handleTouchEnd () {
  65. this.touchStatus = false
  66. }
  67. }
  68. }
  69. </script>
  70. <style lang="stylus" scoped>
  71. @import '~styles/varibles.styl'
  72. .list
  73. display flex
  74. flex-direction column
  75. justify-content center
  76. position absolute
  77. top 1.58rem
  78. right 0
  79. bottom 0
  80. width .4rem
  81. .item
  82. line-height .4rem
  83. text-align center
  84. color $bgColor
  85. </style>

最后git提交代码到GitHub上

搜索功能实现

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

    watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dvXzkyMTExMA_size_16_color_FFFFFF_t_70 4

    Search.vue:


使用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中的数据)

    1. ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dvXzkyMTExMA_size_16_color_FFFFFF_t_70 5][]
  • 在项目中使用,先安装这个vuex

    1. npm install vuex --save

    然后在项目中创建这个”仓库区域”,我们把它叫做store,并创建index.js文件,最后在main.js中引入这个文件

    1. import Vue from 'vue'
    2. import Vuex from 'vuex'
    3. Vue.use(Vuex)
    4. export default new Vuex.Store({
    5. state: {
    6. city: '成都'
    7. },
    8. mutations: {
    9. changeCity (state, city) {
    10. state.city = city
    11. }
    12. }
    13. })

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

    1. <div
    2. class="button-wrapper"
    3. v-for="item of hot"
    4. :key="item.id"
    5. @click="handleCityClick(item.name)"
    6. >
    7. <div class="button">{
    8. {item.name}}</div>
    9. </div>
    10. <div
    11. class="item border-bottom"
    12. v-for="innerItem of item"
    13. :key="innerItem.id"
    14. @click="handleCityClick(innerItem.name)"
    15. >
    16. {
    17. {innerItem.name}}
    18. </div>
    19. handleCityClick (city) {
    20. this.$store.commit('changeCity', city)
    21. this.$router.push('/')
    22. }

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

Vuex的高级使用及localStorage

  • 上面实现的功能会有问题,就是每次重启应用的时候,选择的城市会重置为State中预设的城市名,为了解决这个问题,在这里就引入了h5中的localStorage本地缓存来实现

    1. import Vue from 'vue'
    2. import Vuex from 'vuex'
    3. Vue.use(Vuex)
    4. export default new Vuex.Store({
    5. state: {
    6. city: localStorage.city || '成都'
    7. },
    8. mutations: {
    9. changeCity (state, city) {
    10. state.city = city
    11. localStorage.city = city
    12. }
    13. }
    14. })

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

    1. import Vue from 'vue'
    2. import Vuex from 'vuex'
    3. Vue.use(Vuex)
    4. let defaultCity = '上海'
    5. try {
    6. if (localStorage.city) {
    7. defaultCity = localStorage.city
    8. }
    9. } catch (e) {}
    10. export default new Vuex.Store({
    11. state: {
    12. city: defaultCity
    13. },
    14. mutations: {
    15. changeCity (state, city) {
    16. state.city = city
    17. try {
    18. localStorage.city = city
    19. } catch (e) {}
    20. }
    21. }
    22. })

    最终版(拆分成了state.js和mutations.js):

    1. // state.js
    2. let defaultCity = '上海'
    3. try {
    4. if (localStorage.city) {
    5. defaultCity = localStorage.city
    6. }
    7. } catch (e) {}
    8. export default {
    9. city: defaultCity
    10. }
    11. // mutations.js
    12. export default{
    13. changeCity (state, city) {
    14. state.city = city
    15. try {
    16. localStorage.city = city
    17. } catch (e) {}
    18. }
    19. }
    20. // index.js
    21. import Vue from 'vue'
    22. import Vuex from 'vuex'
    23. import state from './state'
    24. import mutations from './mutations'
    25. Vue.use(Vuex)
    26. export default new Vuex.Store({
    27. state,
    28. mutations
    29. })
  • 还有个样式问题,就是选择四个字的城市时,顶部会被撑开,直接修改home/components下的Header.vue中的.header-right样式

    1. .header-right
    2. min-width 1.04rem //修改的
    3. padding 0 .1rem //新增的
    4. float right
    5. text-align center
    6. color #ffffff
  • 优化代码:使用vuex提供的mapState来简化代码

    1. // home下的Header.vue的js部分
    2. <script>
    3. import { mapState } from 'vuex' //新增
    4. export default {
    5. name: 'HomeHeader',
    6. computed: { //计算属性新增的
    7. ...mapState(['city'])
    8. }
    9. }
    10. </script>

    使用:

    1. <router-link to='/city'>
    2. <div class="header-right">
    3. {
    4. {this.city}} //从this.$store.state.city精简成的this.city
    5. <i class="iconfont arrow-icon"></i>
    6. </div>
    7. </router-link>

    city/components中List.vue:

    1. <template>
    2. <div class="list" ref='wrapper'>
    3. <div>
    4. <div class="area">
    5. <div class="title border-topbottom">当前城市</div>
    6. <div class="button-list">
    7. <div class="button-wrapper">
    8. <div class="button">{
    9. {this.currentCity}}</div>
    10. </div>
    11. </div>
    12. </div>
    13. <div class="area">
    14. <div class="title border-topbottom">热门城市</div>
    15. <div class="button-list">
    16. <div
    17. class="button-wrapper"
    18. v-for="item of hot"
    19. :key="item.id"
    20. @click="handleCityClick(item.name)"
    21. >
    22. <div class="button">{
    23. {item.name}}</div>
    24. </div>
    25. </div>
    26. </div>
    27. <div
    28. class="area"
    29. v-for="(item,key) of cities"
    30. :key="key"
    31. :ref="key"
    32. >
    33. <div class="title border-topbottom">{
    34. {key}}</div>
    35. <div class="item-list">
    36. <div
    37. class="item border-bottom"
    38. v-for="innerItem of item"
    39. :key="innerItem.id"
    40. @click="handleCityClick(innerItem.name)"
    41. >
    42. {
    43. {innerItem.name}}
    44. </div>
    45. </div>
    46. </div>
    47. </div>
    48. </div>
    49. </template>
    50. <script>
    51. import BScroll from 'better-scroll'
    52. import { mapState, mapMutations } from 'vuex'
    53. export default {
    54. name:'CityList',
    55. computed: {
    56. ...mapState({
    57. currentCity: 'city' //这里使用的对象,就是把state中的city映射到这个组件的计算属性中这个值
    58. })
    59. },
    60. props: {
    61. hot: Array,
    62. cities: Object,
    63. letter: String
    64. },
    65. methods: {
    66. handleCityClick (city) {
    67. // this.$store.commit('changeCity', city) 使用mutations的方法之前的调用
    68. this.changeCity(city)
    69. this.$router.push('/')
    70. },
    71. ...mapMutations(['changeCity']) //使用mapMutations来简化mutations中的方法调用
    72. },
    73. mounted () {
    74. this.scroll = new BScroll(this.$refs.wrapper)
    75. },
    76. watch: {
    77. letter () {
    78. if (this.letter) {
    79. const element = this.$refs[this.letter][0]
    80. this.scroll.scrollToElement(element)
    81. }
    82. }
    83. }
    84. }
    85. </script>
    86. <style lang="stylus" scoped>
    87. @import '~styles/varibles.styl'
    88. .border-topbottom
    89. &:before
    90. border-color #ccc
    91. &:after
    92. border-color #ccc
    93. .border-bottom
    94. &:before
    95. border-color #ccc
    96. .list
    97. overflow hidden
    98. position absolute
    99. top 1.58rem
    100. left 0
    101. right 0
    102. bottom 0
    103. .title
    104. line-height .54rem
    105. background #eee
    106. padding-left .2rem
    107. color #666
    108. font-size .26rem
    109. .button-list
    110. padding .1rem .6rem .1rem .1rem
    111. overflow hidden
    112. .button-wrapper
    113. float left
    114. width 33.33%
    115. .button
    116. margin .1rem
    117. padding .1rem 0
    118. text-align center
    119. border .02rem solid #ccc
    120. border-radius .06rem
    121. .item-list
    122. .item
    123. line-height .76rem
    124. padding-left .2rem
    125. </style>

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

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

    1. ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dvXzkyMTExMA_size_16_color_FFFFFF_t_70 6][]

使用keep-alive优化页面性能

  • 首先创建在GitHub一个city-keepalive的分支
  • 在App.vue中使用标签

    1. <template>
    2. <div id="app">
    3. <keep-alive>
    4. <router-view/>
    5. <!-- 显示的是当前路由地址所对应的内容 -->
    6. </keep-alive>
    7. </div>
    8. </template>
    9. <script>
    10. export default {
    11. name: 'App'
    12. }
    13. </script>
    14. <style></style>

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

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

    1. import { mapState } from 'vuex'

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

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

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

    1. mounted () {
    2. this.lastCity = this.city
    3. this.getHomeinfo()
    4. },
    5. activated () {
    6. if (this.lastCity !== this.city) {
    7. this.lastCity = this.city
    8. this.getHomeinfo()
    9. }
    10. }

    最后把代表提交到GitHub上

发表评论

表情:
评论列表 (有 0 条评论,453人围观)

还没有评论,来说两句吧...

相关阅读