Vue项目总结

项目简介

去哪儿的webapp版,实现其中的home界面,城市选择页面,详情页部分逻辑和界面

代码结构

image.png
  • build与config
    使用vue-cli脚手架工具以及webpack搭建好的开发环境,build,config等目录中包含相关配置文件,具体待研究,但修改其中的代码后,本地服务器都要重新启动部分配置才能生route效。
  • node_modules
    用npm工具导入的部分插件和工具
  • src
    1.assets,存放项目的静态资源,如图标,图标css,以及常用的常量。
    2.common,放的是公共的组件,意为可能被各个页面引用的组件,包括两个组件,fade组件(封装着transition动画的样式),gallay(公共画廊组件:是一个轮播画廊)
    3.pages,最核心的代码放置处(再后面会详细介绍,分别对应三个页面的)


    image.png

    4.router,放有路由配置文件。
    5.store,放有vuex相关配置和代码

  • static
    此文件夹放有整个项目的json数据文件,因为没有后端服务器,所以请求的是本地的数据。(此处为了请求的路径定位,还修改了config->indexks文件的内容)

核心代码实现

  • router:
    router的作用就是路由,让url对应的path和应该调用的组件对应起来。其中详情页的页面比较特殊的,因为对于每个ID,有自己的详情页。
export default new Router({
  routes: [{
    path: '/',
    name: 'Home',
    component: Home
  }, {
    path: '/city',
    name: 'City',
    component: City
  }, {
    path: '/detail/:id',
    name: 'Detail',
    component: Detail
  }],
  scrollBehavior (to, from, savedPosition) {
    return { x: 0, y: 0 }
  }
})

  • main
    main是根组件所在地,我们看下它的结构,它引入了路由,fastClick是针对部分手机兼容的,几个CSS是一些基础样式,边框样式,图标样式,还引入了我们的awesomeSwiper,store。
    在根组件传入,路由,和store
import Vue from 'vue'
import App from './App'
import router from './router'
import fastClick from 'fastclick'
import 'styles/reset.css'
import 'styles/border.css'
import 'styles/iconfont.css'
import VueAwesomeSwiper from 'vue-awesome-swiper'
import store from './store'
import 'swiper/dist/css/swiper.css'

Vue.config.productionTip = false
fastClick.attach(document.body)
Vue.use(VueAwesomeSwiper)
/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})
  • Home
  1. Home.vue
    主页有个城市显示在右上位置。这个东西我们使用了vuex技术存储在了store里。
    通过这句可以取到store里的city值。
    这里首先使用了mapstate这个辅助函数,他可以把多个state状态转化成该子组件的计算属性。使用...对象展开运算符还可以把这个对象拆分成一个个属性。
...mapState(['city'])

这个页面主要使用ajax发送了一些请求,
其中在mounted也就是第一次挂载的时候,请求一次,且更换城市。
用一个lastCity保存一下上一次的城市,再一次切换到此城市的时候,有可能vuex里面的city已经改了,比如在城市选择页修改了,那么我们就要重新发ajax请求,请求这个city特应的主页。

  1. header
    这里也会取到vuex的city值,并放在header显示。
    这里有个小细节是把代码重复用到的变量放进styl这个常量表里。
  @import '~styles/varibles.styl'
  .header
    display: flex
    line-height: $headerHeight
  1. swiper
    这里用到了vue-asome-swiper这个插件。
    最外层是模板
    然后是wrapper包裹块
    然后是swiper这个标签,这是插件实现好的被你引入的。
    slide意为可滑动的区域。
    下面还有个swiper-pagenation表示显示几个点的选项卡。
<template>
  <div class="wrapper">
    <swiper :options="swiperOption" v-if="list.length">
      <!-- slides -->
      <swiper-slide v-for="item of list" :key="item.id">
        <img class="swiper-img" :src="item.imgUrl" />
      </swiper-slide>
      <div class="swiper-pagination"  slot="pagination"></div>
    </swiper>
  </div>

</template>

他们需要如此配置:(里面是各个参数的作用放在一起了)

      swiperOption: {
        pagination: '.swiper-pagination',
        loop: true
        //代表不自动滑动
        autoplay: false
      }
      //fraction代表,用1/2的方式,默认是...的方式。
        paginationType: 'fraction',
      //这两个的作用是:如果不加这两个参数,重新进入swiper的时候,宽度计算会出现问题。
        observeParents: true,
        observer: true
  1. icons
    这里主要实现细节:
    计算属性里把ajax得到的数据进行一番计算,生成pages数组,每个pages数组里放的又是一个个图标,这样可以达到分页的效果了。
  computed: {
    pages () {
      const pages = []
      this.iconsList.forEach((item, index) => {
        const page = Math.floor(index / 8)
        if (!pages[page]) {
          pages[page] = []
        }
        pages[page].push(item)
      })
      return pages
    }
  }

看swipter的实现细节,我们先在外层循环一下pages里面的页,再对每一页的数组进行一次维护。

    <swiper :options="swiperOption">
      <swiper-slide v-for="(page , index) of pages" :key = "index">
        <div class="icon" v-for="item of page" :key="item.id">
          <div class="icon-img">
            <img class="icon-img-content" :src='item.imgUrl'>
          </div>
          <p class="icon-desc">{{item.desc}}</p>
        </div>
      </swiper-slide>
    </swiper>

说一下用到的CSS穿透:swiper自带的一些class里面的属性我们怎么更改?


image.png

image.png
  1. Recommand
    这里要提到的是:
    ellipsis() 这个函数是为了让一行不用显示满,多余的不换行而是变成...
    把这个函数直接封装在常量表,他长这个样子
ellipsis()
  overflow: hidden
  white-space: nowrap
  text-overflow: ellipsis

还有,我们可以用一个router-link来进行跳转操作,目的地由:to属性指定,然后还可以指定这个标签的类型,用tag就好

      <router-link
        :to="'/detail/'+item.id"
        tag="li"
        class="item border-bottom"
        v-for="item of recommandList"
        :key="item.id"
      >

结构: ul>router-link>img+(div>title+desc+button)

  • city
  1. City.vue
    同样要ajax请求,同时这个页面有letter这个属性。这个字符串会跟其子组件的某些letter进行双向绑定。
    接收到ajax返回的数据后,会传给需要的子组件。
  2. header
    header没什么特别的,就布局一下。但是也有一个router-link标签to向返回主页。充当返回键。
  3. List
    这个组件的html结构是
    而且城市列表是一个嵌套循环结构。
    list>(当前城市+热门城市+城市列表)
    其中当前城市从mapstate中取得,并作为一个计算属性。
    整个页面是只有屏幕框大小的,overflow-hidden
    而list的滑动效果怎么实现呢?
 position: absolute
    overflow: hidden
    top: 1.58rem
    left: 0
    right: 0
    bottom: 0

Bscroll传一个元素进去,就可以在这个元素实现滑动效果,于是我们得以滑动list上的城市。

具体操作:在html标签上指定ref="wrapper",然后通过this.$refs.wrapper可以得到元素的引用。

  mounted () {
    this.scroll = new Bscroll(this.$refs.wrapper)
  }

同时,点击事件会更改当前城市,在热门城市和普通城市的div都绑定了handle事件,在其中触发changecity事件修改vuex中的事件

    handleCityClick (city) {
      // this.$store.commit('changeCity', city)
      this.changeCity(city)
      this.$router.push('/')
    },
    //这里用展开运算符和map辅助函数完成将mutations里面changeCity和this.changeCity绑定起来。
    ...mapMutations(['changeCity'])

还对letter这个变量进行了侦听器,当letter变化的时候。
通过refs取到各个list的子项元素,其是一个数组,且只有一项,然后让当前的scroll滚动到对应位置。

      if (this.letter) {
        const element = this.$refs[this.letter][0]
        this.scroll.scrollToElement(element)
      }
  1. alphabet
    字母表的标签指令如下:
        <li
          class="item"
          v-for="item of letters"
          :key="item"
          @click="handleClick"
          @touchstart.prevent="handleTouchStart"
          @touchmove="handleTouchMove"
          @touchend="handleTouchEnd"
          :ref = "item"
        >

class,外层循环,key,点击事件,拖动事件,ref属性方便取到组件。

      touchStatus: false,
      startY: 0,
      timer: null

updated在页面的data改变且页面要重新渲染的时候就会变化。

下面这个函数会在点击字母的时候向外相应一个change函数且被其父组件city组件捕捉到,而city组件根据传出来的参数改变其 this的letter,且这个letter又与list这个子组件的letter双向绑定,达到两个子组件通信的效果。

    handleClick (e) {
      this.$emit('change', e.target.innerText)
    },

下面看触摸事件的处理:

    handleTouchStart () {
      this.touchStatus = true
    },
  handleTouchEnd () {
      this.touchStatus = false
    }

这两个不必说,看最主要的函数:

 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)
      }
    },

这个函数首先使用了事件防抖,16ms内只触发一次函数,然后计算一下每次触摸大的位置,与字母表与顶端相减,然后算出你点了哪个元素,把这个字母通过index发送出去。

  1. search
    有一个li标签在最底下,它有v-show属性,根据有没有搜索到一些值来显示。
    用V-model对输入框的数据进行了双向绑定。然后watch了他的变化。也使用了事件防抖。遍历整个cities数组,用indexof查找spell和name有没有共同,有则加入结果,然后将this.list重新赋值为这个res。
    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)
  • Detail
  1. Detail.vue
    这里面同样发送了ajax请求。注意对get参数的传递方式。可以这样。其中route.params.id是从url取参数。
      axios.get('/api/detail.json?', {
        params: {
          id: this.$route.params.id
        }
      })
        .then(this.getHomeInfoSucc)
  1. banner
    banner中就调用了画廊组件,也即是公共组件里的gallary
    <fade-animation>
      <common-gallary
        :imgs="bannerImgs"
        v-show="showGallary"
        @close="handleGallaryClose"
      ></common-gallary>
    </fade-animation>

这个组件有个show属性,

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,494评论 18 139
  • 之前一段时间都在使用 vue 开发后台管理系统,在摸索的过程中对 vue 本身和模块化、规范化开发有了更深的认知,...
    威少_吴阅读 758评论 0 2
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,050评论 25 707
  • 上一篇文章http://www.jianshu.com/p/674e75b41642介绍了项目里文件夹的作用分类和...
    威少_吴阅读 1,228评论 0 4
  • 如果是大学的我 一定会说:很遗憾 有过矛盾的好朋友确实是会越走越远的。 就拿我和B来说。我们高中认识 是当时最好最...
    Milk牛奶阅读 623评论 0 2