仿去哪儿网webapp(二)

1,热销组件开发

注,关于开启flex,flex子项开始省略号样式不生效情况,需要在flex:1的子项添加min-width:0,省略号即可生效

 

 原因

flex: 1的用处
在一个大的div中已知一个或多个内部div元素的宽度 为某个未知宽度的div元素设置flex:1 将沾满剩余空间

一般情况下,min-width的默认值是0,但是flexbox容器的flex项的min-width属性默认为auto 如图

 

 

   <div>
      <div class="title">热销推荐</div>
      <ul>
        <li class="item">
          <img src="http://img1.qunarzz.com/sight/p0/1511/d2/d2aec2dfc5aa771290.water.jpg_140x140_abb362a7.jpg"  class="item-img">
          <div class="item-info">
            <p class="item-title">大连圣亚海洋世界大连圣亚海洋世界大连圣亚海洋世界</p>
            <p class="item-desc">浪漫大连首站,浪漫的海洋主题乐园</p>
            <button class="item-buttton">查看看那详情</button>
          </div>
        </li>
      </ul>

    </div>

样式

  .item
    overflow :hidden    
    display :flex
    height :1.9rem
    .item-img
      flex: 0 0 1.7rem
      height :1.7rem
      padding :.1rem
    .item-info
      flex:1  
      padding :.1rem
      min-width: 0
      .item-title
        line-height :.54rem
        font-size:.32rem
        overflow: hidden
        white-space: nowrap
        text-overflow: ellipsis

 

2.关于父组件传数据,刷新页面,swiper轮播图最新展示的是最后一页图片,原因,刚开始刷新页面,数据异步请求,此时传递的是一个空数组, 需要在子组件中v-if判断数据是否请求

回来。

 

 

父组件发送给ajax请求,获取数据定义在父组件中

data() {
    return {
      swiperList: [],
 <home-swiper :list="swiperList"></home-swiper>

子组件接收数据

 <div class="wrapper" v-if="swiperList.length">
    <swiper ref="mySwiper" :options="swiperOptions" >
      <swiper-slide v-for="(item, index) in swiperList" :key="index">
        <img class="item" :src="item.imgUrl" alt="" />
      </swiper-slide>

      <div class="swiper-pagination" slot="pagination"></div>
    </swiper>
  </div>

 

3.移动端整个页面不想要浏览器系统滚动,可以给根标签添加如下样式,必须要采取定位,不加定位,没有效果

.list
    overflow: hidden
    position: absolute
    top: 0
left: 0 right: 0 bottom: 0

 

4.城市列表组件list, 城市序号组件alphapet

实现一个需求,当点击右边的字母时,右边的城市列表自动滚动到对应的字母城市

 

 

城市序号组件,注,cities是一个对象,vf循环,key是代表属性值(字母符号)

 

 

 

<template>
  <ul class="list">
    <li
      class="item"
      v-for="(item, key) in cities"
      :key="key"
      @click="handleLetterClick"
    >
      {{ key }}
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {};
  },

  methods: {
    // 点击字母排序列表
    handleLetterClick(e) {
      let letter = e.target.innerText;
      // 给list兄弟组件传递字母
      this.$bus.$emit("change", letter);
    }
  },

  props: ["cities"]
};
</script>

 

城市列表组件

注,1.传递过来的字母数据如何与每个城市列表对应呢,通过ref属性定位,better-scroll有个api, scrollToElement(), 可以滚动到对应元素

 

 2.this.$refs[letter]是一个数组,因为排序城市的节点是vf循环过来的

 

 

    <!-- 排序城市 -->
      <div class="area" v-for="(items, key) in cities" :key="key" :ref="key">
        <div class="title border-1px-bottom border-1px-top">{{ key }}</div>
        <div class="item-list " v-for="(item, index) in items" :key="item.id">
          <div class="item border-1px-bottom">
            {{ item.name }}
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import BSscroll from "better-scroll";
export default {
  data() {
    return {};
  },

  props: ["hotCities", "cities"],

  mounted() {
    this.__initScroll();
    this.$bus.$on("change", this.handleLetterChange);
  },
  methods: {
    __initScroll() {
      this.$nextTick(() => {
        if (!this.BSscroll) {
          this.BSscroll = new BSscroll(this.$refs.wrapper, {
            click: true,
            probeType: 1
          });
        } else {
          this.BSscroll.refresh();
        }
      });
    },

    // 获取兄弟组件的字母数据
    handleLetterChange(letter) {
      if (letter) {
        // 获取每个城市列表的节点,letter是变量
        // const cityNode = this.$refs[letter]
        const cityNode = this.$refs[letter][0];
        // console.log(cityNode)
        this.BSscroll.scrollToElement(cityNode);
      }
    }
  },

  watch: {
    // 监视数据,最后一次刷新
    hotCities(newVal) {
      this.__initScroll();
    },

    cities(newVal) {
      this.__initScroll();
    }
  }
};

 

5.实现一个需求,当滑动右侧的字母排序栏,对应的城市列表也会自动滚动到当前

逻辑分析,1.父组件传过来的cities数据是一个对象,将它计算成为一个数组letters,

2.添加触摸原生事件,获取A的位置startY(通过ref属性), 获取鼠标触摸到当前字母的位置endY, 每个字母的高度为20px, 获取当前字母在计算字母数组中的索引

3.用事件总线将字母传给兄弟组件list, 借用better-scroll的scrollToElement()让城市列表滚动到当前字母的

<template>
  <ul class="list" ref="listRef">
    <li
      class="item"
      v-for="(item, index) in letters"
      :key="index"
      :ref="item"
      @click="handleLetterClick"
      @touchstart="handleTouchStart"
      @touchmove="handleTouchMove"
      @touchend="handleTouchEnd"
    >
      {{ item }}
    </li>
  </ul>
</template>

<script>
// 每个字母高度20px
export default {
  data() {
    return {
      touchstatus: false
    };
  },

  mounted() {},
  computed: {
    letters() {
      // 将字母放入数组中
      const letters = [];
      for (let i in this.cities) {
        letters.push(i);
      }

      return letters;
    }
  },

  methods: {
    // 点击字母排序列表
    handleLetterClick(e) {
      // console.log('aaa')
      let letter = e.target.innerText;
      // 给list兄弟组件传递字母
      this.$bus.$emit("change", letter);
    },

    // 触摸刚开始
    handleTouchStart() {
      this.touchStatus = true;
    },

    // 在触摸中
    handleTouchMove(e) {
      // 获取A的位置,字母A到当前城市的距离,74
      const startY = this.$refs.A[0].offsetTop;
      // 获取鼠标滑动到当前字母到当前城市的距离, 79为header的高度
      const endY = e.touches[0].clientY - 79;
      // 计算字母在数组letters的索引,向下取整, 20为每个字母的高度
      let index = Math.floor((endY - startY) / 20);
      if (index >= 0 && index < this.letters.length) {
        this.$bus.$emit("change", this.letters[index]);
      }
      console.log(index);
    },

    // 触摸结束
    handleTouchEnd() {
      this.touchStatus = false;
    }
  },

  props: ["cities"]
};
</script>

 

在兄弟组件list监听change事件
  mounted() {
    this.__initScroll();
    this.$bus.$on("change", this.handleLetterChange);
  },


// 获取兄弟组件的字母数据
    handleLetterChange(letter) {
      if (letter) {
        // 获取每个城市列表的节点,letter是变量
        // const cityNode = this.$refs[letter]
        const cityNode = this.$refs[letter][0];
        // console.log(cityNode)
        this.BSscroll.scrollToElement(cityNode);
      }
    }

 

此时触摸事件的性能极差,我们需要优化代码,两方面优化

1.每次鼠标滑动,都要计算一次A的top值,浪费性能。刚开始cities是空数组传进来,之后异步请求数据后,cyties才有数据传过来,可以在update跟新钩子函数时,只计算一次A的

top值即可

2.可以采用节流,利用setTimeout()

在data中定义数据

  data() {
    return {
      touchstatus: false,
      startY: 0,
      timer: null
    };
  },

update钩子中

 updated() {
    // 获取A的位置,74
    this.startY = this.$refs.A[0].offsetTop;
  },
  // 在触摸中
    handleTouchMove(e) {
      // 每隔16毫秒,重新开启触摸事件,节流
      if (this.timer) {
        clearTimeout(this.timer);
        this.timer = null;
      }

      this.timer = setTimeout(() => {
        // 获取鼠标滑动到字母的位置
        const endY = e.touches[0].clientY - 79;
        // 计算字母在数组letters的索引
        let index = Math.floor((endY - this.startY) / 20);
        if (index >= 0 && index < this.letters.length) {
          this.$bus.$emit("change", this.letters[index]);
        }
      
      }, 16);
    },

此时,性能最大化了

 

6.实现一个需求,输入地名,底部会展示城市列表数据

逻辑分析,输入框输入关键字keyword

1.cities是一个对象,我们for循环对象,得到属性名,在forEach循环属性值(数组),判断keyword是不是在数组里头。定义空数组res,将刷选成功的城市对象push到res数组中,

然后赋值给list

2.监视list数组,创建scroll

3.用v-show判断是否显示搜索的城市,以及是否显示没有匹配到数据字段 

 

 

 

 

<template>
  <div>
    <div class="search">
      <input
        type="text"
        class="search-input"
        placeholder="输入城市名或拼音"
        v-model="keyword"
      />
    </div>
    <div class="search-content" ref="search" v-show="keyword">
      <ul>
        <li
          class="search-item border-1px-bottom"
          v-for="(item, index) in list"
          :key="item.id"
        >
          {{ item.name }}
        </li>
        <li class="search-item border-bottom" v-show="hasNoData">
          没有找到匹配数据
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
import BSscroll from "better-scroll";
export default {
  data() {
    return {
      keyword: "",
      list: [],
      timer: null
    };
  },
  props: ["cities"],

  computed: {
    hasNoData() {
      return !this.list.length;
    }
  },

  mounted() {
    this.__initscroll();
  },
  methods: {
    __initscroll() {
      this.$nextTick(() => {
        if (!this.SearchScroll) {
          this.SearchScroll = new BSscroll(this.$refs.search);
          console.log("bbb");
        } else {
          this.SearchScroll.refresh();
        }
      });
    }
  },

  watch: {
    // 监视keyword,节流搜索
    keyword(newVal) {
      const { keyword, cities } = this;
      if (this.timer) {
        clearTimeout(this.timer);
        this.timer = null;
      }
      if (!keyword) {
        this.list = [];
      }

      this.timer = setTimeout(() => {
        const res = [];
    //i是属性名,item为对象
for (let i in cities) { cities[i].forEach(item => { if ( item.spell.indexOf(keyword) !== -1 || item.name.indexOf(keyword) !== -1 ) { res.push(item); } }); } this.list = res; // 有list数据,就可以开启滚动 this.__initscroll(); }, 100); } } }; </script> <style scoped lang="stylus"> @import '~assets/style/mixins.styl' @import '~assets/style/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: #666 .search-content z-index: 1 overflow: hidden position: absolute top: 1.58rem left: 0 right: 0 bottom: 0 background: #eee .search-item line-height: .62rem padding-left: .2rem background: #fff color: #666 </style>

 

 7,实现一个需求,在热门城市或字母排序的城市,点击该城市,当前城市的名称改变,Home组件的城市名称也改变,用vuex实现数据管理

 

点击城市

<!-- 热门城市 -->
      <div class="area">
        <div class="title border-1px-bottom border-1px-top">热门城市</div>
        <div class="button-list ">
          <div
            class="button-wrapper"
            v-for="(hot, index) in hotCities"
            :key="hot.id"
            @click="handleCityClick(hot.name)"
          >
            <div class="button">{{ hot.name }}</div>
          </div>
        </div>
      </div>
      <!-- 排序城市 -->
      <div class="area" v-for="(items, key) in cities" :key="key" :ref="key">
        <div class="title border-1px-bottom border-1px-top">{{ key }}</div>
        <div
          class="item-list "
          v-for="(item, index) in items"
          :key="item.id"
          @click="handleCityClick(item.name)"
        >
          <div class="item border-1px-bottom">
            {{ item.name }}
          </div>

通过mutations关联vuex

  methods: {
    handleCityClick(city) {
      this.changeCity(city);

      this.$router.push("/");
    },

    ...mapMutations(["changeCity"]),

 

vuex中

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;
    }
  },
  actions: {},
  modules: {}
});

在home组件中

<router-link to="/city">
      <div class="header-right">
        {{city}}
        <span class="iconfont iconarrow-down-filling"></span>
      </div>
    </router-link>
  computed: {
    ...mapState(['city'])
  }

在list组件中

<!-- 当前城市 -->
      <div class="area">
        <div class="title border-1px-bottom border-1px-top">当前城市</div>
        <div class="button-list ">
          <div class="button-wrapper">
            <div class="button">{{ currentCity }}</div>
          </div>
        </div>
      </div>
  computed: {
    ...mapState({
      currentCity: "city"
    })
  },

 

此时在vuex中存储的city,不是永久的,刷新下网页,city还是默认的数据,我们借助localstorage实现永久存储,优化代码

某些浏览器用户关闭了存储功能,或者隐身模式,导致localstorage失效,需要try,catch下,(重要)

 

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) {}
    }
  },
  actions: {},
  modules: {}
});

 

 8, 当在home组件切换到city组件,需要发送请求,city组件返回到home组件也要发送请求,获取数据,此时浪费性能了

需要用到keep-alive组件保存组件,在app组件中设置

<template>
  <div>
    <!-- <Home></Home> -->
    <keep-alive>
      <router-view></router-view>
    </keep-alive>
    
  </div>
</template>

9. 此时在city组件中选中不同的城市,自动返回到home组件,因为keep-alive影响,不会发生请求了,但是我们需要在切换不同的城市时,需要发送不同城市的请求,

keep-alive
Props:

include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
max - 数字。最多可以缓存多少组件实例。
用法:

<keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 <transition> 相似,<keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。

当组件在 <keep-alive> 内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。2.2.0 及其更高版本中,activated 和 deactivated 将会在 <keep-alive> 树内的所有嵌套组件中触发。

主要用于保留组件状态或避免重新渲染。

 

此时需要用到activated钩子函数

逻辑分析,1.从vuex中获取切换到的city,  在mouted钩子函数保存第一次渲染的lastcity(keep-alive影响),请求ajax,加上个city的prama参数,

2.当切换的城市变更后(keep-alive影响),自动触发activated钩子,在次发送新的ajax请求

3.关键点activated钩子, lastcity和city判断

 data() {
    return {
      swiperList: [],
      iconList: [],
      recommendList: [],
      weekendList: [],
      // 保存切换不同的城市
      lastCity: ""
    };
  },

  mounted() {
  //第一次渲染,将第一个城市保存,应为keep-alive的影响,该钩子函数只执行一次
this.lastCity = this.city; this.reqdata(); }, computed: { ...mapState(["city"]) }, // 使用了keep-alive组件,切换后,自动触发activated钩子 activated() { // 判断第一次渲染的最后一个城市与当前切换的城市 if (this.lastCity !== this.city) { this.lastCity = this.city; this.reqdata(); } }, methods: { async reqdata() { // const { data: res } = await axios.get("/api/elm"); const { data: res } = await axios.get(`/api/elm?city=${this.city}`); // console.log(res) if (res.error === 0) { this.swiperList = res.data.swiperList; this.iconList = res.data.iconList; this.recommendList = res.data.recommendList; this.weekendList = res.data.weekendList; } } },

 

 

 

 
 

 

posted @ 2021-02-10 19:00  全情海洋  阅读(163)  评论(0编辑  收藏  举报