1 初始化项目
项目环境:node.js : v10.15.3,webpack,npm
1.1 搭建本地项目
安装 Vue CLI3,初始化项目时候可以选择先把 vuex 和 vue router安装。
1 | vue create mall |
1.2 github 托管项目
在 github 新建一个远程仓库,将远程仓库绑定到本地项目的仓库
1 | git remote add origin 远程仓库地址 |
2 项目基本设置
2.1 划分目录结构
1 | - network (网络相关) |
2.2 设置CSS初始化和全局样式
- normalize.css
- base.css
css 文件中也可以定义变量,这个变量在整个项目中的css部分都可以直接用,比如组件内部的 css 代码
1 | /* --- 变量名 定义一个变量 */ |
导入 css 依赖:在 app.vue 中导入base.css;在base.css 中导入 normalize.css
1 | @import "./normalize.css"; //在css 文件中导入xiefa |
- 在 js 代码部分导入所有文件,用 import / require 都可
- 在 css 代码部分导入文件,用 @import
- 在 html 中导入css 文件,图片,需要
~
2.3 .editorconfig 和 配置别名
.editorconfig 和整个代码的风格相关,可以沿用公司的代码该文件
配置别名需要新建一个 vue.config.js 文件,configureWebpack 和 chainWebpack 都可配置别名
1 | module.exports = { |
3 项目模块划分
3.1 初划分
将封装好的 tabBar 组件复制到项目中,项目的模块分为 4大部分:首页,分类,购物车,我的。
配置路由关系
安装 vue-router,vue-router 的使用
- 导入 vue 和 vue-router,vue.use 安装 vue-router
- 创建 vue-router 实例并导出,配置 routers,mode 属性
- routers 配置路由关系。懒加载导入组件,给组件配置 path
- 挂载带 vue 实例上面,在 main.js 中挂载
<%= BASE_URL %> 是 jsp 语法,用于动态获取当前文件的路径
1 | <link rel="icon" href="<%= BASE_URL %>favicon.ico"> |
3.2 细化分
之后每增加一个视图,就是一个模块。比如 detail
4 首页模块
4.1封装导航栏
4.2 网络数据请求
1)请求数据,封装网络请求模块,利用 axios 框架。
- 安装 axios 框架,网络请求封装到 request 函数中
- axios.create() 实例化一个 axios
- require 和 response 拦截过滤器
- 返回请求结果(Promise 对象)
2)为每个模块再封装一层网络请求,比如 home 模块封装一个 home.js ,之后在 app.vue 中导入方法。在数据的请求 在created 里完成
1 | --- home.js --- |
3) 通过 dev-tool 工具可以查看数据是否获取成功,组件是否导入成功。

4.3 轮播图
封装轮播图组件,使用 van UI库的 Swipe 轮播。
问题:van UI 需要引入组件,开始是采用手动导入组件,但是组件的 css 样式一直导入不上,建了一个全局 css样式 在 main.js 中引入也不能解决。后来安装babel-plugin-import 插件自动引入组件,很方便。
Rem 适配 之后加上
使用轮播图遇到的问题:
1.图片懒加载之后,当第一次轮播图左滑的时候是空白,因为最后一张图片还没加载出来
2.图片下面有空隙,display:block; 或者 vertical-align:「top,text-top,bottom,text-bottom」
3.如果想获取当前了轮播图的 index ,可以使用 change 事件,change 事件每次都会获取当前图片的index,从0 开始
1 | onChange(index) { |
4.轮播图的indicator没有点击事件
4.4 首页推荐
首页的组件,在home文件夹下面新建一个childComponents ,里面存放首页的组件。
封装一个 HomeRecomandView 组件,将首页的 recommends 数据传递到组件展示数据。
4.5 本周流行
封装 HomeFeatureView 组件
4.6选项卡模块
1)分析数据结构,请求来数据之后如何存储?
比如这个模块需要请求 3 种不一样的数据 ,同时数据也会存在懒加载,因此将请求的数据放在一个对象里面,每种数据也是一个对象,page 指的当前数据的处于第几页,list 保存的是请求来的所有数据。
1 | //goods: (流行/新款/精选) |
2)组件 TabControl ,组件GoodsList 组件GoodsListItem。点击不同的菜单,激活 tabControl 菜单 active 属性,并且显示不同的 GoodsList数据。
1 | 在数据传递中,往往要找到数据跟各个组件之间的联系,可以从一个事件的发生过程分析。 |
4.7 页面滑动
使用 better-scroll 插件,封装一个 scroll 组件,在 scroll 组件里面要有 wrapper 和 content 的dom 结构,然后在 mounted 函数里面创建一个BScroll 对象
1 | <template> |
在首页导入组件,但是要确保首页中的 wrapper 的高度小于 content的高度。
1 | //wrapper |
4.6 返回顶部按钮
思想:点击按钮,获取 better-scroll 的 scroll 对象,让 position变为(0,0)
封装一个 BackTop 组件 ,在 组件上监听点击事件,调用 scroll 的 scrollTo方法:
- 组件监听事件,native 修饰符
- 父组件中获取子组件对象,$refs
- 在 scroll 组件中封装 scrollTo 方法,home 组件直接调用该方法。
显示和隐藏 BackTop
显示和隐藏 BackTop 需要获取屏幕滚动的距离,better-scroll 中的 on 方法监听滚动位置,可以返回 position。
Scroll 组件 emit 出 position ,Home 组件接受 事实位置
BackTop 添加 v-show 指令,默认为false,当 -position > distance 显示
4.8 better-scroll 可滚动区域问题
bug是滑动页面,流行新款这些图片突然划不动了,等一下又可以滑动。原因是这些图片未加载玩, Better-Scroll 就已经计算出 scrollerHeight 属性了。但是现在没有这个 bug 了。
Better-Scroll在决定有多少区域可以滚动时, 是根据scrollerHeight属性决定(dev-tool 查看 Better-Scroll对象)
scrollerHeight属性是根据放Better-Scroll的content中的子组件的高度。但是我们的首页中, 刚开始在计算scrollerHeight属性时, 可能会因为网络的延迟,没有将图片计算在内的所以, 计算出来的高度是错误的(1300+)。后来图片加载进来之后有了新的高度, 但是scrollerHeight属性并没有进行更新。所以滚动出现了问题
如何解决这个问题了?
1.监听每一张图片是否加载完成, 只要有一张图片加载完成了, 执行一次refresh()
2.如何监听图片加载完成了?
- 原生的js监听图片: img.onload = function() {}
- Vue中监听: @load=’方法’
3.调用scroll的refresh()
如何将 GoodsListItem 中的监听图片加载事件传入到 Home 中
因为涉及到非父子组件的通信, 所以这里我们选择了事件总线
在Vue中可以使用
EventBus
(事件总线)来作为沟通桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件。但是现在 vuex 更好的替代它了
EventBus
的基本使用:
1.在man.js 中初始化的 EventBus
,且它是一个 全局的事件总线
2.发送事件
3.接收事件
1 | Vue.prototype.$bus = new Vue() |
优化
问题一: refresh找不到的问题
第一: 在Scroll.vue中, 调用this.scroll的方法之前, 判断this.scroll对象是否有值
1 | this.scroll && this.scroll.refresh(); |
第二: 在mounted生命周期函数中使用 this.$refs.scroll而不是created中
问题二: 对于refresh非常频繁的问题, 进行防抖操作
防抖debounce/节流throttle
防抖就是让代码等一会执行,防抖函数起作用的过程:
- 如果我们直接执行refresh, 那么refresh函数会被执行30次.
- 可以将refresh函数传入到debounce函数中, 生成一个新的函数.
- 之后在调用非常频繁的时候, 就使用新生成的函数.
- 而新生成的函数, 并不会非常频繁的调用, 如果下一次执行来的非常快, 那么会将上一次取消掉
1 | /** |
4.8 上拉加载更多
上拉加载更多需要监听什么时候滑动到底部并且上拉 —- better-scroll 的 pullingUp 事件
- 在 Scroll.vue 中监听 pullingUp 事件,并传递给 Home.vue
- Home.vue 在该方法内调用 getHomeGoods 方法请求数据
- 当一次上拉加载完毕,需要在getHomeGoods 方法内调用finishPullUp(),表示此次加载数据完毕。
4.9 TabBar 吸顶效果
首先必须知道滚动到多少时, 开始有吸顶效果, 这个时候就需要获取tabControl的offsetTop。但是, 如果直接在 mounted 中获取tabControl的offsetTop, 那么值是不正确,图片可能未加载完全,尤其轮播图,此时获得的 offsetTop 偏小。
1)如何获取正确的值了?
- 监听 Swiper 中img的加载完成.
- 加载完成后, 发出事件, 在Home.vue中, 获取正确的值.
- 补充:
- 为了不让 Swiper 多次发出事件,
- 可以使用isLoad的变量进行状态的记录.
- 通过
$refs
获取 tabControl组件,$el
获取组件下的根元素
1 | this.tabControlOffsetTop = this.$refs.tabControl.$el.offsetTop; |
2)监听滚动, 动态的改变tabControl的样式
1 | contentScroll(position) { |
3)问题:动态的改变tabControl的样式时, 会出现两个问题:
- 问题一: 当滚动到tabControl的时候, 下面的商品内容, 会突然上移。—》因为tabControl此刻脱离文档流,下面的商品就会上移动。
- 问题二: tabControl虽然设置了fixed, 但是也随着Better-Scroll一起滚出去了。—》因为 Better-Scroll 滚动的原理是改变translate 即使脱离文档也会随着translate 而滚动
4)解决问题
在 Scroll组件上,NavBar组价下面复制了一份TabControl组件对象, 利用它来实现停留效果。
- 当用户滚动到一定位置时,复制的TabControl显示出来.
- 当用户滚动没有达到一定位置时, 复制的TabControl隐藏起来.
小 bug :TabControlCopy 和 TabControl 菜单激活按钮没有同步,比如还未滑动到 TabControl 菜单选中了新款,但是滑动到 TabControl 菜单默认选中流行,而下面的图片信息都是新品。
在 tabControl 的点击事件下面。
1 | this.$refs.tabControl.currentIndex = index |
4.10 让Home保持原来的状态
当从 Home 切换到其他页面,再回到 Home 页面,页面不会保存之间的浏览痕迹,而是回到最顶部—》因为在切换路由的时候,路由会销毁相应的对象,下一次跳转则重新创建对象。
keep-alive 可以解决这一问题,在 router-view 包裹上 keep-alive。
让Home中的内容保持原来的位置(这个项目暂时没有这个 bug,参考借鉴思想)
路由离开时, 保存一个位置信息saveY ——> deactivated()
路由进来时, 将位置设置为原来保存的位置saveY信息即可 ——> activated()。注意: 最好回来时, 进行一次refresh(),避免出现 better-scroll 无法滚动。
1 | activated() { |
总结
通常一些 bug 由于插件的版本问题,图片未加载完全,高度问题
1.父传子数据时候,在父组件内如果传的是字符串,不需要 v-bind 绑定,其他变量或者对象类型需要动态绑定
1 | <home-recomand-view :recommends="recommends"></home-recomand-view> |
2.v-for 遍历数组时,需要动态绑定 key 属性
3.document.querySelector只会获取第一个匹配的元素,如果想获取唯一的元素,要么给元素绑定 id ,要么给元素绑定一个ref(Vue)
ref 如果是绑定在组件中的, 那么通过this.$refs.refname获取到的是一个组件对象.
ref 如果是绑定在普通的元素中, 那么通过this.$refs.refname获取到的是一个元素对象.
5.ES6 语法
1 | //参数可以赋默认值 |
6.如果某个函数执得非常频繁,可能会给服务器造成压力,此时可以封装一个防抖函数,限制这种频繁执行的行为。
7.使用动态路由的 params 传递参数的时候
1 | this.$router.push('/detail/'+this.goodsItem.iid) // 在 detail 后面不可添加 / |