在学习了 vue 以后,决定作一个小练习,仿写了一个有关购物商城的小项目。下面就对项目作一个简单的介绍。
项目源码: githubcss
项目的目录结构
-assets 与项目有关的静态资源,包括 css,以及一些 images
-common 公共的工具类方法
-components 公共组件
-common 与项目耦合度较低的组件
-content 与项目耦合度较高的组件
-network 网络请求相关
-router 路由相关
-store vuex 相关
-views 主要展现的页面
-home 首页
-childcomps
-detail 详情页
-childcomps
-cart 购物车页面
-childcomps
-profile 我的信息页面
-childcomps
划分好目录结构后就能够对每一个模块进行独立开发vue
主要实现的功能
首页
- 顶部轮播图的展现
- 中间选项卡点击可进行商品的切换以及吸顶效果
- 点击商品进入商品详情页
- 上拉展现更多商品
- 点击按钮回到顶部
详情页
- 根据首页中用户的点击进行每一个商品的独立展现
- 点击商品信息对应的标题跳转到对应的内容区域
- 滑动页面过程当中与顶部商品信息的标题联动效果
- 点击按钮回到顶部
- 加入购物车跳出弹窗
购物车页面
- 展现各类商品的数量
- 底部工具栏展现选中商品的总价格
- 全选与取消全选
- 结算时的两种弹窗
个人页面
- 我的信息的展现
- 点击个人购物车进入到购物车页面
主要思路介绍
底部 tabbar 的封装
移动端中比较常见的一种导航栏,须要根据用户的点击进行跳转到相应的页面,因此直接封装一个公共的组件。布局通常是图片加文字,图片通常有两种,须要根据当前是否处于活跃状态的路由和用户的点击来判断展现哪张。内部预留插槽,用户根据实际需求选择底部导航的个数。每一个导航为其配置路由。ios
顶部导航 navbar 的封装
顶部的导航基本会出如今每一个页面中,因此也直接封装为一个公共组件,布局一般是左中右三栏布局,因此组件内部预留三个插槽,采用 flex 布局。git
数据请求
对 axios 再封装为一个 request.js 文件,让全部的页面都基于这个文件发送请求,这样作能够避免重复操做,提升了项目后期的可维护性。
在这个项目中将每一个页面须要发送请求的部分又进行了一层封装,即每一个页面都有独立的与之相对应的发送请求的文件,首页发送请求只须要面向 home.js,详情页发送请求面向 detail.js。github
- 引入 better-scroll 来替换掉页面的原生滚动,Scroll.vue 是对 better-scroll 的封装,为了提升组件的可复用性,可让 probeType, pullUpLoad,等值从外部传入(项目中 click 的值为 true)。
- Scroll.vue 内代码的封装:
建立一个 Better-Scroll 对象,传入 DOM 和 参数( probeType,click,pullUpLoad等),
监听 scroll 事件,该事件会返回一个 position,
监听 pullUpLoad 事件,表示该事件触发的时候是否上拉加载更多,
封装一个滚动的方法 this.scroll.scrollTo(x,y,time),
封装一个刷新的方法 this.scroll.refresh(),会从新计算可滚动区域的高度,
封装完成上拉加载更多的方法 this.scroll.finishPullUp
全局插件 Toast
- 项目中有两个地方用到了弹窗组件,一个是点击加入购物车时,一个是点击结算时,若是想要使用弹窗组件那么每次都须要导入还要加一些代码,这样作有点麻烦,若是有多个地方都须要使用这个弹窗组件那么此时须要作的重复工做量太大,因此采用 Vue.use() 方法全局注册了一个插件。
- 须要在 main.js 中 import, 而后 Vue.use() 进行注册,Vue.use() 须要传入至少一个参数,该参数只能是 Object 或 Function ,若是是 Object 那么这个对象须要定义一个 install 方法,若是是 Funcion 那么这个函数就被当作 install 方法,在 Vue.use() 执行时 install 方法会默认执行,install 方法调用时会将 Vue 做为参数传入。Vue.use() 除了传入第一个参数外,也可继续传入一个选项对象。这里还要注意的是 Vue.use() 必需要在 new Vue() 以前被调用,这是由于在安装组件时,组件给 Vue 添加全局功能,因此必须写在 new Vue() 以前,不然建立的 Vue 实例就没法获取插件添加的全局功能。
- 因此咱们在项目中使用了 Vue.use() 注册了全局插件,在任何地方想使用只须要 this.$名称 便可。
首页
- 数据处理
banners 数组保存轮播图的数据,recommends 数组保存推荐的数据。
还有其它的即须要展现的每一条商品的数据经过 goods 来保存。
goods: {
'pop': {page: 0, list: [ ] },
'new': {page: 0, list: [ ] },
'sell': {page: 0, list: [ ] }
}
- 最上面的轮播图,能够根据图片数量自动进行轮播,也可手动滑动图片,是一个独立的子组件。
轮播图下面的推荐部分是一个独立的子组件。
- 对 goods 中的数据进行展现,封装 GoodsList 组件,而每个商品又是一个独立的小组件 GoodsListItem.vue。
- 点击选项卡进行数据的切换,监听选项卡的点击事件,经过 $emit() 发出事件(携带 index),在父组件中监听,根据 index 对应的数据类型(swicth(index)),父组件再经过 props 将 currentType 传给 GoodsList 进行展现。
- 滑动过程当中选项卡的吸顶效果,原理是复制一个选项卡(tabControl)在顶部,默认隐藏,经过 v-show 来决定什么时候出现,用一个变量保存原选项卡 (contentControl) 的 offsetTop,在页面滚动的过程当中不断获取滚动的距离,当滚动的距离大于原选项卡 (contentControl) 的 offsetTop时,就显示 tabControl。
- 上拉加载更多,Scroll 组件中的 pullingUp 一触发就会发送一个事件,父组件中进行监听而后当事件触发的时候去请求数据便可。
- 回到顶部组件 BackTop,默认不显示,当页面滚动到设定的距离时显示。点击回到顶部须要监听组件的点击(@click.native),而后触发函数 scrollTo(x,y,time) 返回顶部。
详情页
- 当用户点击商品时进入到详情页,首先要监听每一个 GoodsListItem 的点击,点击时进行路由的跳转,而且携带商品的 id,根据 id 请求数据,再将数据进行展现。
- 底部功能栏的封装也是一个独立的子组件,当点击加入购物车时,将点击事件发送给父组件 detail.vue,父组件中根据商品 id 获取购物车(cart.vue,与 detail.vue 同级)中须要保存的商品数据,提交到 vuex 进行全局状态管理,购物车组件中经过 $store.state 拿到数据进行渲染,加入购物车成功后弹出弹窗提示。
- 将商品添加到购物车其实有两种状况,一种是 vuex 中尚未这个商品的添加,另外一种是 vuex 中已经有了这个商品,因此为了使 mutation 里定义的函数职能单一,这里将点击添加购物车这个操做提交到 action 中管理,再由 action 提交到 mutation 来使 vuex 管理的状态进行更新。
- 点击联动效果,商品详情页面中共有四个部分组成:商品,参数,评论,推荐,每一个部分都是一个独立的子组件,当数据请求完成时去得到每一个组件的 offsetTop ($refs.组件绑定的 ref 值.$el.offsetTop) 保存在一个数组中,监听商品详情栏中的点击,根据 index 触发 scrollTo() 进行跳转
- 滑动联动效果,实时监听滚动位置,经过和上面数组中的值进行比较,在 0 - 参数之间的偏移这个高度时,currentIndex 为 0,在参数的偏移高度 - 评论的偏移高度时 currentIndex 为1,以此类推,这样就能够实如今滑动的过程当中与顶部标题信息的联动。
- 回到顶部,与首页中的同样。
购物车页
- 渲染在详情页添加到购物车的数据,从 vuex 中拿到数据,封装 CartList 组件,而每一条商品又是一个独立的子组件 CartListItem。
- 底部工具栏的封装,主要包括全选按钮,选中商品的总价格,去结算时的弹窗。
- 全选按钮的实现,某商品第一次添加到购物车时,须要给这件商品的数据里定义一个是否选中 (checked)的属性,默认为 true。为了管理选中的状态,只能由 mucation 对状态进行修改,商品最终是否展现也由 mucation 最终修改的状态为准,监听全选按钮的点击,根据商品数组中每一个商品的 checked 属性进行逻辑判断便可。
- 点击去结算组件时,先判断当前购物车的商品列表中是否有数据,若是没有弹出提示信息,让用户选中商品。
个人页面
- 展现用户的一些我的信息,点击“个人购物车”跳转到购物车页面,this.$router.push('/cart')
项目中遇到的一些问题
- 引入 better-scroll 以后,带来了一些问题,好比页面会划不动、scrollTo 跳不许、等等,致使这些问题的缘由,多数状况下是由于请求的数据没回来,或者拿到数据了可是页面还未加载完,而 better-scroll 在这以前就须要计算可滚动区域的高度,但因为图片还没加载完因此这个时候计算的高度并非最终的高度,因此致使滚动出现了问题,怎么解决呢?
- 那么这个时候能够在某些资源加载完成是时来个 scroll.refresh(),让 better-scroll 从新计算可滚动区域的高度,好比项目中咱们能够监听图片加载事件,当有图片加载完成时就触发 scroll.refresh()。但这样的话只要一有图片加载完成就会 scroll.refresh() ,使得 refresh() 的调用太频繁了,在此咱们能够进行一个防抖操做来减小 scroll.refresh() 的调用。
让首页的内容保持原来的位置
- 项目中在浏览到首页某个位置时,用户点击了商品而后进入了商品详情页或者进入了购物车等其它页面,当再次回到首页时发现不是以前浏览的位置。
- 能够在首页的 deactivated() 中记录下离开时的位置信息,activated() 时再将位置设置为原来保存的位置。
选项卡 tabControl 的吸顶效果
- 其实实现这个效果的基本思路就是首先获取 tabControl 的 offsetTop ,怎么获取?this.$refs.tabControl.$el.offsetTop ,而后实时监测选项卡的滚动位置,当滚动位置达到 offsetTop 时将选项卡改成固定定位便可,这里须要注意的是在 mounted() 中获取的值不必定是正确的,由于可能顶部的轮播图或者推荐部分的图片没有加载完,那么如何获取正确的值?监听轮播图的图片加载状况,加载完毕发出事件,而后在首页中再去获取 offsetTop。
- 按照这样的思路实现之后,发现并无达到预期的效果,而是出现了下面的商品部分会忽然上移,并无实现停留的效果,因此换了另外一种方案,就是上文提到的先在顶部复制一份占位选项卡,默认不显示,而后根据滚动位置和 offsetTop 来决定何时显示,这样就实现了吸顶的效果。