创建项目

这里我们使用脚手架3进行开发

vue create supermall

划分目录结构

一般在src文件夹下创建几个文件夹

  • assets资源,比如css样式,img图片等
  • views:存放一些大的视图,比如首页视图,分类视图等
  • components:一般存放一些公共组件,比如有的组件既在首页使用,又在分类视图里使用,就存放在’组件’文件夹中。
  • common(公共的组件):指可以在多个项目中重复使用的组件
  • content(与当前项目业务相关的组件):只可在当前项目中使用的
  • router:路由相关的js文件
  • store: vuex公共状态管理相关的文件
  • network:网路相关文件的封装
  • common:公共的js文件,如一些常量(const.js)、封装一些工具的方法(utils.js)等等

css文件的引入

现在大部分项目都会引入一个名为​​normalize.css​​的样式表,它是一个可以定制的CSS文件,它让不同的浏览器在渲染网页元素的时候形式更统一。

Normalize.css 能干什么?

  • 保留有用的默认值,不同于许多 CSS 的重置
  • 标准化的样式,适用范围广的元素。
  • 纠正错误和常见的浏览器的不一致性。
  • 一些细微的改进,提高了易用性。
  • 使用详细的注释来解释代码。

除此之外,一般还会建一个自己的css样式,名为​​base.css​​,比如padding、margin等等。

​:root​​作用是获取根元素html

:root { --color-text: #666; --color-high-text: #ff5777; --color-tint: #ff8198; --color-background: #fff; --font-size: 14px; --line-height: 1.5; }

其中的​​--color-text​​​相当于是定义变量,之后设置颜色等属性可以使用​​color: var(--color-text);​

配置文件

别名:vue.config.js

正如前面所说,引入一些文件时需要提供绝对路径,这会比较麻烦。

别名注意事项

所以我们使用别名,首先需要在src目录下新建自己的​​vue.config.js​​文件。

  • 注意:CLI3可以互相调用别名,比如​​'assets': '@/assets',​
  • CLI4中不支持相互调用别名,比如​​'assets': 'src/assets',​​​;然后在引入时,需要在路径最前面加上​​@/​
module.exports = {
configureWebpack: {
resolve: { // 路径相关
extensions: [], // 后缀名
alias: {
//'@': 'src',
'assets': '@/assets',
'common': '@/common',
'components': '@/components',
'network': '@/network',
'views': '@/views',
},
},
},
}

保存之后,即可直接使用别名。

注意:在css中使用,需要加上​​~​​,并且不要写成字符串

{ background: url(~@/assets/img/04_2.jpg); background: url('~@/assets/img/04_2.jpg');//错了,这里有个坑,不能写成字符串,我就是因为这样写错了 }

html中使用,可以加入​​~​​​也可以不加入​​~​​。

​ <img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404"> ​

代码规范:.editorconfig

用于确保小组代码编写的统一。

root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

tabbar引入和模块划分

像我们之前抽离的两个组件​​MainTabBar​​​和​​tabbar​​,就可以引入其中了,问题来了,这两个组件应该放在common和content哪个文件夹中呢?

上文提过,common是放公共组件的,而content是存放与我们项目业务逻辑相关的组件的。因此,tabbar应该放在common文件夹下,MainTabBar放在content文件夹下。

此时,别名的好处就体现出来了。使用​​@/assets​​​就可以代替​​../../...​​去寻找绝对路径。

此时,在​​App.vue​​​中导入该组件,此时就需要安装vue-router插件,​​npm install vue-router --save​​。

  1. 在router文件夹下创建index.js,安装插件,创建router对象,进行导出
import Vue from 'vue'
import VueRouter from "vue-router";

// 1.安装插件
Vue.use(VueRouter)

// 习惯将路径的映射关系单独创建出来
const routes = [
// 配置对应映射关系
{
path: '',
redirect: '/home',
},
{
path: '/home',
component: Home,
},
{
path: '/category',
component: Category,
},
{
path: '/cart',
component: Cart,
},
{
path: '/profile',
component: Profile,
},
]
// 2.创建router
const router = new VueRouter({
routes,
mode: 'history',
})

// 3.导出
export default
  1. 去main.js中进行挂载
import Vue from 'vue'
import App from './App.vue'
import router from "./router";

Vue.config.productionTip = false

new Vue({
render: h => h(App),
router, // 进行挂载
}).$mount('#app')

网页图标的修改

在public文件夹里加入心仪的图标即可。

此处有一个原始模板语法​​<% BASE_URL %>​​,目的是为了动态获取路径。在打包过后会替换为当前路径。

首页开发

首页导航栏的封装和使用

  • 首先,考虑该组件应该封装到哪个目录下?

因为多个页面都需要用到该组件,所以封装到components文件夹下;并且导航栏大部分项目都会用到,所以放到common目录下。

注意:文件夹一般为小写,组件名称为驼峰式。

此时导航栏我们使用​​flex​​布局。需要使用到具名插槽,另外给每个插槽外包装一个class用于固定其样式和位置。

NavBar.vue如下

<template>
<div class="nav-bar">
<div class="left">
<slot name="left"></slot> <!-- 具名插槽-->
</div>
<div class="center">
<slot name="center"></slot>
</div>
<div class="right">
<slot name="right"></slot>
</div>

</div>
</template>

<script>
export default {
name: "NavBar"
}
</script>

<style scoped>
.nav-bar{
display: flex;
line-height: 44px;
height: 44px;
text-align: center;
box-shadow: 0 1px 1px rgba(100, 100, 100, 0.1); /*阴影*/
}
.left, .right{
width: 60px;
}
.center {
flex: 1;
/*为了将center占据剩余部分,使用该属性
"1"表示的是比例,因为这只有一个比例,所以它就占了剩余空间的百分之百
*/
}
</style>

Home.vue如下

<template>
<div id="home">
<nav-bar class="home-nav">
<div slot="center">购物街</div>
</nav-bar>
</div>
</template>

<script>
import NavBar from "@/components/common/navbar/NavBar";

export default {
name: "Home",
components: {
NavBar
}
}
</script>

<style scoped>
.home-nav {
background-color: var(--color-tint);
color: #fff;
}
</style>

请求首页的多个数据

需要用到我们之前提及的axios插件,这里需要注意的是,我们重新建了一个​​home.js​​文件,用于抽离这些函数,使得函数之间尽可能的分离。

轮播图的组件

调用写好的组件,粘贴到components下common文件夹里。

此处的​​index.js​​目的是为了导入时不需要分开导入

接着,在​​Home.vue​​里调用该组件Swiper

<template>
<div id="home">
<nav-bar class="home-nav"><div slot="center">购物街</div></nav-bar>
<swiper>
<swiper-item v-for="item in banners">
<a :href="item.link">
<img :src="item.image" alt="">
</a>
</swiper-item>
</swiper>


</div>
</template>

但是一般情况下,​​Home.vue​​​都是存放主要组件,类似这种轮播图我们可以抽取到​​views​​​文件夹的​​home​​​文件夹里,创建​​childComps​​​文件夹,创建​​HomeSwiper.vue​​,代码如下:

<template>
<swiper>
<swiper-item v-for="item in banners">
<a :href="item.link">
<img :src="item.image" alt="">
</a>
</swiper-item>
</swiper>
</template>

<script>
import {Swiper, SwiperItem} from '@/components/common/swiper'
export default {
name: "HomeSwiper",
props: {
banners: Array,
default() {
return []
}
},
components: {
Swiper,
SwiperItem
}
}
</script>

<style scoped>
</style>

注意:

  • banners数据需要通过​​props​​属性通过父传子获取数据

这样一来,封装起来后,更加清晰,如果要去修改轮播图的组件,只需要进入​​HomeSwiper.vue​​中进行修改,而不回影响到其它组件

多体会这种封装思想,不要把代码都写到一块。

推荐信息的展示

同样的,在childComps文件夹下新建一个vue组件。

文件命名:可以是​​RecommendView​​​,但有的公司会要求在文件名最前面添加上所在界面名,比如我们要放在Home界面下,就命名成​​HomeRecommendView​​​;有的公司还要求加上公司名缩写,比如​​TXRecommendView​​等等。

通过​​v-for​​等属性进行显示:

<template>
<div class="recommend">
<div v-for="item in recommends" class="recommend-item">
<a :href="item.link">
<img :src="item.image" alt="">
<div>{{item.title}}</div>
</a>
</div>
</div>
</template>

再调试下样式(使用flex布局)。

FeatureView的封装

因为功能相似,所以这里简便处理,使用图片。

<template>
<div class="feature">
<a href="https://act.mogujie.com/zzlx67">
<img src="@/assets/img/home/recommend_bg.jpg" alt="">
</a>
</div>
</template>

TabControl的封装

只需要修改文字的情况下,没有必要设置插槽,可以通过传递数组,来提供文字的元素。

粘性定位:会使组件移动到某个位置后停留不动。

.tab-control { position: sticky; top: 44px; }

保存商品的数据结构设计

根据TabControl的样式,可知我们需要保存三种数据(流行、新款、精选)。我们可以设计如下方式:记录当前页面以及当前list中已经请求了多少条数据。

goods: {
'pop': {page: 5, list: [150条数据]},
'news': {page: 2, list: [60条]},
'sell': {page: 1, list: [30条数据]},
}

首页数据的请求与保存

此时应该去网络处理的文件夹(network)编写​​home.js​​。

export function getHomeGoods(type,) {
return request({
url: '/home/data',
params: {
type,
page
}
})
}

回到​​Home.vue​​中,编写create函数,此时我们会发现一个问题:

  • create函数的作业是:当组件一旦创建完成后所执行的函数。
  • 所以在该函数中做事情的时候,最好只在里面写主要逻辑,详细逻辑不要在里面实现,一般抽取出来。
  • 因此,可以在​​methods​​​属性中编写发送请求的函数,然后在​​create​​中调用她。

将新数组的元素全部存入另一个数组

如上图所示,可以使用push方法

js扩展运算符(spread)是三个点(…)
作用:将一个数组转为用逗号分隔的参数序列

// ES6 的写法 Math.max(...[14, 3, 77]) // 等同于 Math.max(14, 3, 77);

let totalNums = []
const nums1 = [20, 15, 66]
const nums2 = [21, 33, 4]
// for(let n of nums1) {
// totalNums.push(n)
// }

totalNums.push(...nums1) // 将数组nums1中所有的元素解析出来存入totalNums

此时我们可以将请求到的数据保存到我们先前创建的变量中:

由于我们暂时只请求了​​pop​​的数据,所以运行后我们只在pop变量中发现存储了30条数据。

首页商品数据的展示

数据已经获取成功,接下来就是将其展示在首页。

TabControl点击切换商品

子传父事件,使用​​$emit​​​ 当然,需要创建变量,这里我们是​​currentType​

Better-Scroll滚动插件

​BetterScroll​​​ 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件。它的核心是借鉴的 ​​iscroll​​ 的实现,它的 API 设计基本兼容 iscroll,在 iscroll 的基础上又扩展了一些 feature 以及做了一些性能优化。

生命周期回顾

生命周期

是否获取dom节点

是否可以获取data

是否获取methods

beforeCreate




created




beforeMount




mounted




created: 在模块渲染成html调用,即通常初始化某些属性值,然后再渲染成视图。

mounted: 在模块渲染成html调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。

因此,需要注意的点是,因为在 Vue 模板中列表渲染还没完成时,是没有生成列表 DOM 元素的,所以需要在确保列表渲染完成以后,才能创建 BScroll 实例,因此在 Vue 中,初始化 BScroll 的最佳时机是 mouted 的 nextTick。

安装和使用

​npm install better-scroll –save​​​进行安装,官方文档​​指路​​​,笔记​​在这​

我们用分类界面做演示:

  • 由生命周期可知,我们要在mounted中才能获取dom节点。
<template>
<div class="wrapper" ref="aaa">
<ul class="content">
<li>分类列表1</li>
......
<li>分类列表100</li>
</ul>
</div>
</template>
<script>
import BScroll from 'better-scroll'
export default {
name: "Category",
data() {
return {
scroll: null
}
},
// mounted: 在模块渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作
mounted() {
console.log(this.$refs.aaa);
this.scroll = new BScroll(document.querySelector('.wrapper'), {

})
},
// created: 在模块渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图
// created() {
// new BScroll(this.$ref.aaa, {
// })
// }
}
</script>

<style scoped>
.wrapper {
height: 150px;
background-color: red;
overflow: hidden;
/*overflow-y: scroll;*/
}
</style>

效果如下:具有弹簧效果,更加顺滑。

BScroll的封装以及使用

如果直接用BScroll插件在源码上更改,当改插件不再进行维护时我们就需要对每一个使用了该插件的源代码进行修改,会非常麻烦。所以我们进行封装,需要修改时只需要对一个文件进行修改即可。

因为多个页面都可以用该组件,所以将其放置在公共组件文件夹下

注意

  1. 尽量避免直接查找dom元素,比如用​​document.querySelector('.wrapper')​​去查找wrapper,原因是多个页面里可能会有多个类名为wrapper的,会导致不能明确使用的是哪一个。

在Vue中想要明确拿到哪个组件,给组件绑定​​ref​​。

回顾:

  • ref如果是绑定在组件中的,那么通过​​this.$refs.refName​​获取的是一个组件对象
  • ref如果是绑定在普通的元素中,那么通过​​this.$refs.refName​​获取的是一个元素对象
  1. 样式中vh: viewport height(视口高度);

封装后的文件如下:

注意此处scroll标签的类标签名也是"content",与​​Scroll.vue​​​中的类标签名并不会冲突,因为它的样式表为​​scoped​

因为现在滚动利用的是BScroll而不是原生的滚动,系统是无法检查tabControl所在的位置,所以​​.tab-control​​中的sticky是无效的了,

回到顶部的按钮

因为和我们的业务有关,所以封装到components下的content文件夹下,命名为​​backTop​​。

如果要监听某个组件的点击,需要加上修饰符​​.native​

  • 在我们需要监听一个组件的原生事件时,必须给对应的事件加上​​.native​​修饰符,才能进行监听。

这里我们需要使用到的是​​scrollTo​​方法

scrollTo(x, y, time, easing, extraTransform)

  • 参数:
  • {number} x 横轴坐标(单位 px)
  • {number} y 纵轴坐标(单位 px)
  • {number} time 滚动动画执行的时长(单位 ms)
  • {Object} easing 缓动函数,一般不建议修改,如果想修改,参考源码中的​​packages/shared-utils/src/ease.ts​​ 里的写法
  • 只有在你想要修改 CSS transform 的一些其他属性的时候,你才需要传入此参数,结构如下:
let extraTransform = {
// 起点的属性
start: {
scale: 0
},
// 终点的属性
end: {
scale: 1.1
}
}

BackTop的显示和隐藏

根据之前的经验,若想要图片根据位置进行显示或隐藏,那么就可以根据​​probeType​​获取当前x,y值。

Scroll.vue中:

export default {
name: "Scroll",
props: {
probeType: { // 父传子,是否跟踪位置却绝育父组件
type: Number,
default: 0,
},
},
data() {
return {
scroll: null,
}
},
mounted() {
// 1.创建BScroll对象
this.scroll = new BScroll(this.$refs.wrapper,{
click: true,
probeType: this.probeType,
})

// 2.监听滚动的位置
this.scroll.on('scroll', (position) => {
this.$emit('scroll', position) // 子传父,传递位置
})
},
...

Home.vue中,

<back-top @click.native="backClick" v-show="isShowBackTop"></back-top>
...
</template>

<script>
export default {
name: "Home",
...
data() {
return {
...
isShowBackTop: false, // 默认为false,即不显示返回顶部的图片
}
},
methods: {
// 事件监听相关的方法
contentScroll(position) {
this.isShowBackTop = (-position.y) > 1000
// 由于position.y是负值,所以取反。当其大于1000(为true)才显示,否则不显示
},
...

完成上拉加载更多

调用pullingUp方法,上拉加载更多。

但是会遇到一个bug,就是上拉拖动不了。

原因在于初始设计的固定高度与最终使用的高度不一致,有出入。

  • 如wrapper高度只有400px,而插入的内容有1000px,因此BScroll会帮助计算滚动的范围,即600px。
  • 但是我们此处因为使用了接口,需要加载数据​​GoodsListItem组件​​(图片标题等)。但可能由于网络或其它原因,图片没有加载出来(异步处理),所以计算插入内容(item)的高度时没有包括图片的高度。
  • 如举的例子,此时计算出来的高度比如只有600px,所以滚动的范围只有200px。

解决方法也很简单:

  • 监听图片加载完成后,在某个地方拿到scroll对象​​refresh​​一下,刷新的作用是重新计算最新可滚动的区域。
  1. 监听图片何时加载完
  2. 调用​​this.$refs.scroll.scroll.refresh()​

一. FeatureView

  • 独立组件封装FeatureView
  • div>a>img

二. TabControl

  • 独立组件的封装
  • props -> titles
  • div>根据titles v-for遍历 div -> span{{title}}
  • css相关
  • 选中哪一个tab, 哪一个tab的文字颜色变色, 下面border-bottom
  • currentIndex

三. 首页商品数据的请求

3.1. 设计数据结构, 用于保存数据

goods: {

pop: page/list

new: page/list

sell: page/list

}

3.2. 发送数据请求

  • 在home.js中封装getHomeGoods(type, page)
  • 在Home.vue中, 又在methods中getHomeGoods(type)
  • 调用getHomeGoods(‘pop’)/getHomeGoods(‘new’)/getHomeGoods(‘sell’)
  • page: 动态的获取对应的page
  • 获取到数据: res
  • this.goods[type].list.push(…res.data.list)
  • this.goods[type].page += 1

goods: {

pop: page1:/list[30]

new: page1/list[30]

sell: page1/list[30]

}

四. 对商品数据进行展示

4.1. 封装GoodsList.vue组件

  • props: goods -> list[30]
  • v-for goods -> GoodsListItem[30]
  • GoodListItem(组件) -> GoodsItem(数据)

4.2. 封装GoodsListItem.vue组件

  • props: goodsItem
  • goodsItem 取出数据, 并且使用正确的div/span/img基本标签进行展示

五. 对滚动进行重构: Better-Scroll

5.1. 在index.html中使用Better-Scroll

  • const bscroll = new BScroll(el, { })
  • 注意: wrapper -> content -> 很多内容
  • 1.监听滚动
  • probeType: 0/1/2(手指滚动)/3(只要是滚动)
  • bscroll .on(‘scroll’, (position) => {})
  • 2.上拉加载
  • pullUpLoad: true
  • bscroll .on(‘pullingUp’, () => {})
  • 3.click: false
  • button可以监听点击
  • div不可以

5.2. 在Vue项目中使用Better-Scroll

  • 在Profile.vue中简单的演示
  • 对Better-Scroll进行封装: Scroll.vue
  • Home.vue和Scroll.vue之间进行通信
  • Home.vue将probeType设置为3
  • Scroll.vue需要通过$emit, 实时将事件发送到Home.vue

六. 回到顶部BackTop

6.1. 对BackTop.vue组件的封装

6.2. 如何监听组件的点击

  • 直接监听back-top的点击, 但是可以直接监听?
  • 不可以, 必须添加修饰.native
  • 回到顶部
  • scroll对象, scroll.scrollTo(x, y, time)
  • this.$refs.scroll.scrollTo(0, 0, 500)

6.3. BackTop组件的显示和隐藏

  • isShowBackTop: false
  • 监听滚动, 拿到滚动的位置:
  • -position.y > 1000 -> isShowBackTop: true
  • isShowBackTop = -position.y > 1000

七. 解决首页中可滚动区域的问题

  • Better-Scroll在决定有多少区域可以滚动时, 是根据scrollerHeight属性决定
  • scrollerHeight属性是根据放Better-Scroll的content中的子组件的高度
  • 但是我们的首页中, 刚开始在计算scrollerHeight属性时, 是没有将图片计算在内的
  • 所以, 计算出来的高度是错误的(1300+)
  • 后来图片加载进来之后有了新的高度, 但是scrollerHeight属性并没有进行更新.
  • 所以滚动出现了问题
  • 如何解决这个问题了?
  • 监听每一张图片是否加载完成, 只要有一张图片加载完成了, 执行一次refresh()
  • 如何监听图片加载完成了?
  • 原生的js监听图片: img.onload = function() {}
  • Vue中监听: @load=‘方法’
  • 调用scroll的refresh()
  • 如何将GoodsListItem.vue中的事件传入到Home.vue中
  • 因为涉及到非父子组件的通信, 所以这里我们选择了事件总线
  • bus ->总线
  • Vue.prototype.$bus = new Vue()
  • this.bus.emit(‘事件名称’, 参数)
  • this.bus.on(‘事件名称’, 回调函数(参数))
  • 问题一: refresh找不到的问题
  • 第一: 在Scroll.vue中, 调用this.scroll的方法之前, 判断this.scroll对象是否有值
  • 第二: 在mounted生命周期函数中使用 this.$refs.scroll而不是created中
  • 需求: 对于refresh非常频繁的问题, 进行防抖操作
  • 防抖debounce/节流throttle
  • 防抖函数起作用的过程:
  • 如果我们直接执行refresh, 那么refresh函数会被执行30次.
  • 可以将refresh函数传入到debounce函数中, 生成一个新的函数.
  • 之后在调用非常频繁的时候, 就使用新生成的函数.
  • 而新生成的函数, 并不会非常频繁的调用, 如果下一次执行来的非常快, 那么会将上一次取消掉
debounce(func,) {
let timer = null
return function (...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, delay)
}
},

八. 上拉加载更多的功能

  • 监听滚到底部

九. tabControl的吸顶效果

9.1. 获取到tabControl的offsetTop

  • 必须知道滚动到多少时, 开始有吸顶效果, 这个时候就需要获取tabControl的offsetTop
  • 但是, 如果直接在mounted中获取tabControl的offsetTop, 那么值是不正确.
  • 如何获取正确的值了?
  • 监听HomeSwiper中img的加载完成.
  • 加载完成后, 发出事件, 在Home.vue中, 获取正确的值.
  • 补充:
  • 为了不让HomeSwiper多次发出事件,
  • 可以使用isLoad的变量进行状态的记录.
  • 注意: 这里不进行多次调用和debounce的区别

9.2. 监听滚动, 动态的改变tabControl的样式

  • 问题:动态的改变tabControl的样式时, 会出现两个问题:
  • 问题一: 下面的商品内容, 会突然上移
  • 问题二: tabControl虽然设置了fixed, 但是也随着Better-Scroll一起滚出去了.
  • 其他方案来解决停留问题.
  • 在最上面, 多复制了一份PlaceHolderTabControl组件对象, 利用它来实现停留效果.
  • 当用户滚动到一定位置时, PlaceHolderTabControl显示出来.
  • 当用户滚动没有达到一定位置时, PlaceHolderTabControl隐藏起来.

十. 让Home保持原来的状态

10.1. 让Home不要随意销毁掉

  • keep-alive

10.2. 让Home中的内容保持原来的位置

  • 离开时, 保存一个位置信息saveY.
  • 进来时, 将位置设置为原来保存的位置saveY信息即可.
  • 注意: 最好回来时, 进行一次refresh()

十一.推荐数据的展示

  • 请求推荐数据
  • GoodsList展示数据

十二.mixin的使用

  • 创建混入对象:const mixin = {}
  • 组件对象中:mixins[mixin]

十四.标题和内容的联动效果

14.1. 点击标题,滚动到对应的主题

  • 在detail中监听标题的点击,获取index
  • 滚动到对应的主题:
  • 获取所有主题的offsetTop
  • 问题:在哪里才能获取到正确的offsetTop
  • 1.created肯定不行,压根不能获取元素
  • 2.mounted也不行,数据还没有获取到
  • 3.获取到数据的回调中也不行,DOM还没渲染完
  • 4.$nextTick也不行,因为图片的高度没有被计算在内
  • 5.在图片加载完成后,获取的高度才是正确

14.2.内容滚动,显示正确的标题

普通做法:

(this.currentIndex != i &&
((i < length - 1 && positionY >= this.themeTopYs[i] &&
positionY < this.themeTopYs[i + 1]) || (i == length - 1 &&
positionY >= this.themeTopYs[i])))
条件成立:this.currentIndex = 1
条件一:防止赋值的过程过于频繁
条件二: ((i < length - 1 && positionY >= this.themeTopYs[i] &&
positionY < this.themeTopYs[i + 1]) || (i == length - 1 &&
positionY >= this.themeTopYs[i]))
条件1:(i < length - 1 && positionY >= this.themeTopYs[i] && positionY < this.themeTopYs[i + 1])
* 判断区间:在0和某个数字之间(i === length -1)
条件2:(i == length - 1 &&positionY >= this.themeTopYs[i])
* 判断大于等于:i === length - 1

hack做法:

(this.currentIndex !== i && (positionY >= this.themeTopYs[i] && positionY < this.themeTopYs[i + 1]))

十五.顶部工具栏的封装

十六.详情页的回到顶部

  • home.vue和detail.vue回到顶部:mixin

十七.点击加入购物车

17.1监听加入购物按钮的点击,并且获取商品信息

  • 监听
  • 获取商品信息:iid/price/image/title/desc

17.2将商品添加到Vuex中

  • 安装Vuex
  • 配置Vuex
  • 定义mutations,将商品添加到state.cartList
  • 重构代码
  • 将mutations中的代码抽取actions中(定义两个mutations)
  • 将mutations/actions单独抽取到文件中

十八.购物车的展示

18.1购物车的导航栏的展示

18.2购物车商品的展示

  • CartList -> Scroll(样式问题)
  • CartListItem -> CheckButton

18.3商品的选中和不选中切换

  • 修改模型对象,改变选中和不选中

18.4底部工具栏的汇总

  • 全选按钮
  • 计算总价格
  • 去计算

十九.购物车全选按钮

  • 显示的转态
  • 判断是否有一个不选中,全选
  • 点击全选按钮
  • 如果原来都是选中,点击一次,全部不选中
  • 如果原来都是不选中(某些不选中),全部选中

二十.添加购物车弹窗

20.1Vuex的补充

  • Actions可以返回一个Promise
  • mapActions的映射关系

Toast(吐司)封装

  • 普通封装方式
  • 插件封装方式

二十一.补充一些细节

21.1 fastClick减少点击延迟

  • 安装fastclick
  • 导入
  • 调用attach函数

22.2图片的懒加载

  • 什么是图片懒加载
  • 图片需要显示在屏幕上时再加载图片
  • 使用vue-lazyload
  • 安装
  • 导入
  • Vue-use
  • 修改img.src -> v-lazy

22.3px2vw插件使用

  • 按照插件
  • postcss.config.js中配置