在学习了 vue 之后,决定做一个小练习,仿写了一个有关购物商城的小项目。下面就对项目做一个简单的介绍。
项目源码: github

项目的目录结构

-assets                            与项目有关的静态资源,包括 css,以及一些 images

-common                            公共的工具类方法

-components						   公共组件
	-common						     与项目耦合度较低的组件
	-content					     与项目耦合度较高的组件

-network						   网络请求相关

-router 						   路由相关

-store							   vuex 相关

-views							   主要展示的页面
	-home								首页
		-childcomps
	-detail								详情页
		-childcomps
	-cart							    购物车页面
		-childcomps
	-profile						    个人信息页面
		-childcomps

划分好目录结构后就可以对每个模块进行独立开发

主要实现的功能

首页
  1. 顶部轮播图的展示
  2. 中间选项卡点击可进行商品的切换以及吸顶效果
  3. 点击商品进入商品详情页
  4. 上拉展示更多商品
  5. 点击按钮回到顶部
详情页
  1. 根据首页中用户的点击进行每个商品的独立展示
  2. 点击商品信息对应的标题跳转到对应的内容区域
  3. 滑动页面过程中与顶部商品信息的标题联动效果
  4. 点击按钮回到顶部
  5. 加入购物车跳出弹窗
购物车页面
  1. 展示各种商品的数量
  2. 底部工具栏展示选中商品的总价格
  3. 全选与取消全选
  4. 结算时的两种弹窗
我的页面
  1. 个人信息的展示
  2. 点击我的购物车进入到购物车页面

主要思路介绍

底部 tabbar 的封装

移动端中比较常见的一种导航栏,需要根据用户的点击进行跳转到相应的页面,所以直接封装一个公共的组件。布局一般是图片加文字,图片一般有两种,需要根据当前是否处于活跃状态的路由和用户的点击来判断展示哪张。内部预留插槽,用户根据实际需求选择底部导航的个数。每个导航为其配置路由。

顶部导航 navbar 的封装

顶部的导航基本会出现在每个页面中,所以也直接封装为一个公共组件,布局通常是左中右三栏布局,所以组件内部预留三个插槽,采用 flex 布局。

数据请求

对 axios 再封装为一个 request.js 文件,让所有的页面都基于这个文件发送请求,这样做可以避免重复操作,提高了项目后期的可维护性。
在这个项目中将每个页面需要发送请求的部分又进行了一层封装,即每个页面都有独立的与之相对应的发送请求的文件,首页发送请求只需要面向 home.js,详情页发送请求面向 detail.js。

滚动组件 Scroll
  • 引入 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 带来的问题
  • 引入 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 来决定什么时候显示,这样就实现了吸顶的效果。
Logo

前往低代码交流专区

更多推荐