zoukankan      html  css  js  c++  java
  • 仿去哪儿网webapp(二)

    1,热销组件开发

    注,关于开启flex,flex子项开始省略号样式不生效情况,需要在flex:1的子项添加min-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- 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 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; } } },
     
     
     
  • 相关阅读:
    shell得到两个文件的差集
    shell 单引号&双引号的使用
    kubernetes session and 容器root权限
    docker 使用网络以及容器互联
    倒计时练习
    会话控制
    XML
    AJAX实现搜索智能提示
    弹窗显示详情练习
    三级联动
  • 原文地址:https://www.cnblogs.com/fsg6/p/14396389.html
Copyright © 2011-2022 走看看