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属性,

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

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

友情链接更多精彩内容