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