(12)城市选择页开发——② “搜索框”、“列表”和“字母表”布局 | Vue.js 项目实战: 移动端“旅游网站”开发

339 阅读7分钟
转载请注明出处,未经同意,不可修改文章内容。

🔥🔥🔥“前端一万小时”两大明星专栏——“从零基础到轻松就业”、“前端面试刷题”,已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。

1 需求

本篇,我们要完成城市选择页剩余部分页面的布局,包括搜索输入框、城市选择(包含“当前城市”、“热门城市”和根据城市名称拼音首字母而进行排列的城市名)和右侧的字母表三部分:

travel_12-01.gif

2 搜索输入框布局

搜索输入框部分非常简单,只有一个带圆角的输入框,背景色与主题色相同:

1️⃣在 city 下的 components 中新建搜索输入框组件 Search.vue

<template>
  <div>this is Search.</div> <!-- 1️⃣-②:在模板中添加文字内容; -->
</template>

<script>
export default {
  name: 'CitySearch' /* 1️⃣-①:给 Search.vue 命名为 CitySearch; */
}
</script>

<style lang="stylus" scoped>
</style>

1️⃣-③:打开 city 下的 City.vue ,使用 Search.vue

<template>
  <div>
    <city-header></city-header>

    <city-search></city-search> <!-- 1️⃣-⑥:使用组件 Search.vue。 -->
  </div>
</template>

<script>
import CityHeader from './components/Header'
import CitySearch from './components/Search' /* 1️⃣-④:从当前目录下的 components 中
																						 引入组件 Search.vue; */

export default {
  name: 'City',
  components: {
    CityHeader,

    CitySearch /* 1️⃣-⑤:注册局部组件 Search.vue; */
  }
}
</script>

<style>
</style>

保存后,返回页面查看,内容正确显示,控制台无报错:

2️⃣打开 city 下 components 中的 Search.vue 编写结构与样式:

<template>
  <div class="search"> <!-- 2️⃣-①:给 div 添加一个类名 search,div 中有一个 input 标签; -->

    <!-- 2️⃣-②:给 input 标签添加类名 search-input;
    2️⃣-③:添加 placeholder 内容为“输入城市名或拼音”; -->
    <input class="search-input" type="text" placeholder="输入城市名或拼音">
  </div>
</template>

<script>
export default {
  name: 'CitySearch'
}
</script>

<style lang="stylus" scoped>
  
/* 2️⃣-④:从 styles 下引入 varibles.styl; */
@import '~styles/varibles.styl'

.search /*
  			2️⃣-⑤:设置 .search 高度为 0.72rem,左右 padding 为 0.1rem 增加输入框与两边的间距,
  			背景色为 $bgColor;
   			 */
  height: .72rem
  padding: 0 .1rem
  background: $bgColor

  .search-input /*
  							2️⃣-⑥:设置 .search-input 宽度为 100%;设置 box-sizing 为 border-box,
  							让宽度包含 padding;左右 padding 为 0.1rem 增加内容与两边的间距;设置高度
  							和行高为 0.62rem,字体颜色为 #666,文字居中显示,输入框有 0.06rem 的圆角。
  							 */
    box-sizing: border-box
    width: 100%
    padding: 0 .1rem
    height: .62rem
    line-height: .62rem
    color: #666
    text-align: center
    border-radius: .06rem
</style>

保存后,返回页面查看,输入框样式没有问题,当输入的内容特别多时,文字与输入框也保持了一定间距:

3 城市选择列表布局

3️⃣在 city 下的 components 中新建城市选择列表组件 List.vue

<template>
  <div>this is List.</div> <!-- 3️⃣-②:在模板中添加文字内容; -->
</template>

<script>
export default {
  name: 'CityList' /* 3️⃣-①:给 List.vue 命名为 CityList; */
}
</script>

<style lang="stylus" scoped>
</style>

3️⃣-③:打开 city 下的 City.vue ,使用 List.vue

<template>
  <div>
    <city-header></city-header>
    <city-search></city-search>

    <city-list></city-list> <!-- 3️⃣-⑥:使用组件 List.vue。 -->
  </div>
</template>

<script>
import CityHeader from './components/Header'
import CitySearch from './components/Search'
import CityList from './components/List' /*
																				 3️⃣-④:从当前目录下的 components 中
																				 引入组件 List.vue;
                                          */

export default {
  name: 'City',
  components: {
    CityHeader,
    CitySearch,
    CityList /* 3️⃣-⑤:注册局部组件 List.vue; */
  }
}
</script>

<style>
</style>

保存后,返回页面查看,内容正确显示,且控制台无报错:

仔细分析城市选择列表部分,大致有三个区域,每个区域有对应的标题和内容。第一个区域和第二个区域结构与样式相同,标题分别为“当前城市”和“热门城市”,内容展示标题所对应的“城市名按钮”。第三个区域标题为城市名拼音首字母,内容为以当前首字母开头的城市名称:

4️⃣打开 city 下 components 中的 List.vue 编写结构与样式:

<template>
  <div class="list"> <!-- 4️⃣-①:最外层 div 类名为 list,里面包裹三个类名为 area 的 div; -->

    <div class="area"> <!-- 4️⃣-②:第一个 .area 包含两个 div,分别为 标题 .title 和按钮列表
											 .button-list; -->
			
      <!-- 4️⃣-⑧:给标题添加 border-topbottom 类名,上、下各添加一像素边框; -->
      <div class="title border-topbottom">当前城市</div>

      <div class="button-list"> <!-- 4️⃣-③:.button-list 中,用类名为 button-wrapper 的
																div 包裹城市 .button; -->
        <div class="button-wrapper">
          <div class="button">北京</div>
        </div>
      </div>

    </div>

    <div class="area"> <!-- 4️⃣-④:第二个 .area 与第一个 .area 相同,标题内容更改为“热门城市”,
											 .button-list 中添加为 5 个城市; -->
      
      <!-- 4️⃣-⑨:给标题添加 border-topbottom 类名,上、下各添加一像素边框; -->
      <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>
    </div>

    <div class="area"> <!-- 4️⃣-⑤:第三个 .area 包含两个 div,一个为标题(即城市名拼音首字母),
											 一个为当前首字母的城市名列表; -->
      
      <div class="title border-topbottom">A</div> <!-- 4️⃣-⑩:给标题添加 border-topbottom
																									类名,上、下各添加一像素边框; -->

      <ul class="item-list"> <!-- 4️⃣-⑥:城市名列表类名为 item-list,列表里边的每一个类名为
                             .item 的 li 标签即以当前首字母对应的城市名; -->

        <li class="item border-bottom">阿拉尔</li> <!-- 4️⃣-⑯:添加类名 border-bottom,
                                                  给每一个 item 添加一像素边框; -->

        <li class="item border-bottom">阿拉尔</li>
        <li class="item border-bottom">阿拉尔</li>
        <li class="item border-bottom">阿拉尔</li>
        <li class="item border-bottom">阿拉尔</li>
        <li class="item border-bottom">阿拉尔</li>
        <li class="item border-bottom">阿拉尔</li>
        <li class="item border-bottom">阿拉尔</li>
      </ul>
    </div>
    
    <div class="area"> <!-- 4️⃣-⑱:增加“B”城市选择区域; -->
      <div class="title border-topbottom">B</div>
      <ul class="item-list">
        <li class="item border-bottom">北京</li>
        <li class="item border-bottom">北京</li>
        <li class="item border-bottom">北京</li>
        <li class="item border-bottom">北京</li>
        <li class="item border-bottom">北京</li>
        <li class="item border-bottom">北京</li>
        <li class="item border-bottom">北京</li>
        <li class="item border-bottom">北京</li>
      </ul>
    </div>

  </div>
</template>

<script>
export default {
  name: 'CityList'
}
</script>

<style lang="stylus" scoped>
/* 4️⃣-⑪:在 border-topbottom 的伪元素上设置颜色为 #ccc,让一像素边框颜色加深; */
.border-topbottom
  &:before
    border-color: #ccc
  &:after
    border-color: #ccc

.border-bottom /* 4️⃣-⑰:设置 border-bottom 一像素边框颜色为 #ccc; */
  &:before
    border-color: #ccc

.title /*
  		 4️⃣-⑦:标题增加 0.2rem 的 padding-left,高度为 0.54rem,背景色为 #eee,
  		 字体颜色为 #666,字体大小调整为 0.26rem;
   			*/
  padding-left: .2rem
  line-height: .54rem
  background: #eee
  color: #666
  font-size: .26rem

.button-list /*
  					 4️⃣-⑬:.button-list 添加 overflow: hidden 触发 BFC;设置上、下、左 padding
  					 为 0.1rem,右 padding 为 0.6rem,预留字母列表的空间;
  						*/
  overflow: hidden
  padding: .1rem .6rem .1rem .1rem

  .button-wrapper /* 4️⃣-⑫:.button-wrapper 左浮动,宽度为 33.33%; */
    float: left
    width: 33.33%

    .button /*
  					4️⃣-⑭:每个按钮的 margin 为 0.1rem;上、下 padding 为 0.1rem;
  				  给按钮添加 0.02rem 的实线浅灰边框,边框添加 0.06rem 的圆角;
  					 */
      margin: .1rem
      padding: .1rem 0
      text-align: center
      border: .02rem solid #ccc
      border-radius: .06rem

.item-list /*
  				 4️⃣-⑮:item-list 中的每一个 item 行高为 0.76rem;padding-left 为 0.2rem,
  				 与其他内容对齐;
  					*/
  .item
    line-height: .76rem
    padding-left: .2rem
</style>

❌保存后,返回页面查看效果,城市选择列表布局,基本完成。但,当页面上下滚动时,是整个页面内容一起滚动:

travel_12-02.gif

而我们要实现的效果,是头部和搜索输入框保持固定,仅城市选择列表内容滚动:

travel_12-03.gif

❓如何实现页面滚动时,头部和搜索输入框固定不动,仅城市选择列表内容滚动?

答:我们可以引入一个“BetterScroll”第三方插件。通过这个插件,可以让城市选择列表区域实现与原生 App 类似的拖拽效果。

5️⃣打开终端,在项目目录下运行命令 npm install better-scroll --save 安装 BetterScroll:

5️⃣-①:查看项目下方文档,文档内容指出 HTML 结构为由一层容器 wrapper 包裹内容 content ,内容区域可滚动。使用 BetterScroll 时,只需在项目中引入插件, new 一个 BScroll 实例并传入容器的 DOM 元素或 DOM 选择器;

5️⃣-②:打开 city 下 components 中的 List.vue

🔗前置知识:《深入理解 Vue 组件——① 使用组件的细节点》

<template>
  <div class="list" ref="wrapper"> <!-- 5️⃣-③:在最外层 div 上添加名为 wrapper 的 ref 
																	 属性(我们通过 ref 属性获取 DOM); -->

    <div> <!-- 5️⃣-④:使用一个 div 包裹列表区域的所有内容,让整个城市选择列表区域的 HTML 结构
					符合 BetterScroll 的要求; -->
      <div class="area">
        <div class="title border-topbottom">当前城市</div>
        <div class="button-list">
          <div class="button-wrapper">
            <div class="button">北京</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>
      </div>
      <div class="area">
        <div class="title border-topbottom">A</div>
        <ul class="item-list">
          <li class="item border-bottom">阿拉尔</li>
          <li class="item border-bottom">阿拉尔</li>
          <li class="item border-bottom">阿拉尔</li>
          <li class="item border-bottom">阿拉尔</li>
          <li class="item border-bottom">阿拉尔</li>
          <li class="item border-bottom">阿拉尔</li>
          <li class="item border-bottom">阿拉尔</li>
          <li class="item border-bottom">阿拉尔</li>
        </ul>
      </div>
      <div class="area">
        <div class="title border-topbottom">B</div>
        <ul class="item-list">
          <li class="item border-bottom">北京</li>
          <li class="item border-bottom">北京</li>
          <li class="item border-bottom">北京</li>
          <li class="item border-bottom">北京</li>
          <li class="item border-bottom">北京</li>
          <li class="item border-bottom">北京</li>
          <li class="item border-bottom">北京</li>
          <li class="item border-bottom">北京</li>
        </ul>
      </div>
    </div>
  </div>
</template>

<script>
/* 5️⃣-⑥:引入 better-scroll 插件; */
import BScroll from 'better-scroll'

export default {
  name: 'CityList',

  mounted () { /*
  						 5️⃣-⑦:在 mounted 中(即当页面挂载完毕后),创建一个 scroll 实例属性,它等于
  						 new 一个 BScroll,并传入名为 wrapper 的 DOM 节点。
                */
    this.scroll = new BScroll(this.$refs.wrapper)
  }
}
</script>

<style lang="stylus" scoped>
.border-topbottom
  &:before
    border-color: #ccc
  &:after
    border-color: #ccc
.border-bottom
  &:before
    border-color: #ccc

.list /*
  		5️⃣-⑤:给 .list 增加样式,使列表内容固定;添加 overflow: hidden,当内容溢出时隐藏;
  		让 .list 绝对定位,top 为 1.58rem,预留出头部和搜索输入框的高度,left、right、bottom 
  		都为 0;
  		 */
  overflow: hidden
  position: absolute
  top: 1.58rem
  left: 0
  right: 0
  bottom: 0

  /* ❗️原有的所有内容都在 .list 中,所以全部缩进一个单位。 */
  .title
    padding-left: .2rem
    line-height: .54rem
    background: #eee
    color: #666
    font-size: .26rem
  .button-list
    overflow: hidden
    padding: .1rem .6rem .1rem .1rem
    .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
</style>

保存后,返回页面查看,控制台无报错,头部和搜索框固定,城市选择列表区域可以滚动:

travel_12-04.gif

4 字母表布局

字母表的每个字母是从数据中动态获取的,它的布局相对简单:

6️⃣在 city 下的 components 中新建字母表组件 Alphabet.vue

<template>
  <ul class="list"> <!-- 6️⃣-②:template 中最外层 ul 类名为 list,里面包含的 li 标签类名为
										item,内容是字母(后面由 Ajax 获取到数据后进行循环); -->
    <li class="item">A</li>
    <li class="item">A</li>
    <li class="item">A</li>
    <li class="item">A</li>
    <li class="item">A</li>
    <li class="item">A</li>
    <li class="item">A</li>
    <li class="item">A</li>
  </ul>
</template>

<script>
export default {
  name: 'CityAlphabet' /* 6️⃣-①:给 Alphabet.vue 命名为 CityAlphabet; */
}
</script>

<style lang="stylus" scoped>
</style>

6️⃣-③:打开 city 下的 City.vue ,使用 Alphabet.vue

<template>
  <div>
    <city-header></city-header>
    <city-search></city-search>
    <city-list></city-list>
    <city-alphabet></city-alphabet> <!-- 6️⃣-⑥:使用组件 List.vue。 -->
  </div>
</template>

<script>
import CityHeader from './components/Header'
import CitySearch from './components/Search'
import CityList from './components/List'
import CityAlphabet from './components/Alphabet' /* 6️⃣-④:从当前目录下的 components 中
																								 引入组件 Alphabet.vue; */

export default {
  name: 'City',
  components: {
    CityHeader,
    CitySearch,
    CityList,
    CityAlphabet /* 6️⃣-⑤:注册局部组件 Alphabet.vue; */
  }
}
</script>

<style>
</style>

保存后,返回页面查看,控制台无报错,可以看到多个“A”:

7️⃣返回 city 下 components 中的 Alphabet.vue 编写样式:

<template>
  <ul class="list">
    <li class="item">A</li>
    <li class="item">A</li>
    <li class="item">A</li>
    <li class="item">A</li>
    <li class="item">A</li>
    <li class="item">A</li>
    <li class="item">A</li>
    <li class="item">A</li>
  </ul>
</template>

<script>
export default {
  name: 'CityAlphabet'
}
</script>

<style lang="stylus" scoped>
/* 7️⃣-③:从 styles 下引入 varibles.styl 文件; */
@import '~styles/varibles.styl'

.list
  position: absolute /*
  									 7️⃣-①:让 .list 绝对定位,top 为 1.58rem,与城市选择列表对齐;right、
  									 bottom 为 0,宽度为 0.4rem;
  									  */
  top: 1.58rem
  right: 0
  bottom: 0
  width: .4rem

  display: flex /* 7️⃣-②:使用 flex 布局,让字母纵向居中; */
  flex-direction: column
  justify-content: center

  .item /* 7️⃣-④:字母行高为 0.4rem,内容水平方向居中,颜色为 $bgColor。 */
    line-height: .4rem
    text-align: center
    color: $bgColor
</style>

保存后,返回页面查看:

travel_12-05.gif

以上,我们完成了城市选择页所有布局。

🏆本篇总结:

  • 在城市选择列表组件中,新引入了一个第三方插件“BetterScroll”进行布局;
  • 在使用插件时,通过特殊属性 ref 来获取 DOM。

下一篇中,我们将完成城市选择页的动态数据渲染。

祝好,qdywxs ♥ you!