# Vue_Program
**Repository Path**: chen-hongxin/Vue_Program
## Basic Information
- **Project Name**: Vue_Program
- **Description**: 一个适用于手机端的 Vue 项目
- **Primary Language**: JavaScript
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 0
- **Created**: 2021-05-12
- **Last Updated**: 2022-05-01
## Categories & Tags
**Categories**: Uncategorized
**Tags**: Vue
## README
# 购物街
## 介绍
一个适用于手机端的 Vue 小项目,旨在锻炼开发能力
该项目所用的各种数据来自于“蘑菇街”
## 使用说明
命令行输入
- `npm install` 安装所需要的包
- `npm run serve` 运行程序
**注意:不要使用鼠标滚动滚动,而应该使用点击拖动(与手机端手指点击拖动类似)**
## 接口文档
| 路径 | 方法 | get 参数 | 备注 |
| --------- | ---- | -------- | ---------------- |
| /home/multidata | GET | | 请求首页轮播图和推荐行的数据 |
| /home/data | GET | type, page | 请求首页商品数据 |
| /detail | GET | iid | 请求商品详情页的商品详情数据 |
| /recommend | GET | | 请求商品详情页的推荐模块的商品数据 |
| /category | GET | | 请求分类页面的商品数据 |
## 项目基本设置
### 1.1 目录结构
- assets
- common
- components -> common/content
- network
- router
- store
- views -> home/category/cart/profile/detail
### 1.2 设置 CSS 初始化和全局样式(assets -> css)
- base.css
- initialize.css
### 1.3 axios 的封装(network -> request.js)
- 创建 axios 实例
- 拦截响应,仅返回其中的 data 数据
- 根据传入的 config 发送请求,调用者通过.then 获取结果
.jpg")
## 项目开发细节
## 一、公共组件(components -> common/content)
### 1.1 tabbar 底部导航栏的封装
- 封装 TabBar
- 封装 TabBarItem
- 响应点击切换的设计
- MainTabBar 组件对 TabBar 和 TabBarItem 重新包装
### 1.2 navbar 顶部展示栏的封装
- 封装的 navbar 组件包含三个插槽:left、center、right
- 设置 navbar 相关的样式
- 直接使用 navbar 实现首页的顶部展示栏
.jpg")
### 1.3. swiper 轮播图的封装
- 封装 Swiper
- 封装 SwiperItem
- HomeSwiper 组件对 Swiper 和 SwiperItem 重新包装
- 使用 HomeSwiper 组件时,传入 banners 数据进行展示
### 1.4 滚动的封装 Scroll
- 学习 BetterScroll 的使用
- 安装 better-scroll
- 封装一个独立的组件,用于作为滚动组件:Scroll
- 组件内代码的封装:
- 1. 创建 BetterScroll 对象,并且传入 DOM 和选项(probeType、click、pullUpLoad)
.jpg")
- 2. 监听 scroll 事件,该事件会返回一个 position(监听滚动)
* `probeType: 0/1/2(手指滚动) / 3(只要是滚动)`
* `scroll.on('scroll', (position) => {})`
* Home.vue 和 Scroll.vue 之间进行通信
* Home.vue 将 `probeType` 设置为 3
* Scroll.vue 需要通过 `$emit`, 实时将事件发送到 Home.vue
- 3. `click: false`
* button 可以监听点击
* div 不可以
- 4. 监听 pullingUp 事件,监听到该事件进行上拉加载更多
* `pullUpLoad: true`
* `scroll.on('pullingUp', () => {})`
- 5. 封装刷新的方法:this.scroll.refresh()
- 6. 封装滚动的方法:this.scroll.scrollTo(x, y, time)
- 7. 封装完成刷新的方法:this.scroll.finishedPullUp
.jpg")
### 1.5 封装 tabControl
- 独立组件的封装
- 使用 TabControl 组件时,通过 props 传入 titles 数据进行展示
- 监听点击,通过 `currentIndex` 判断当前选中是哪一个 tab, 则该 tab 的文字与 `border-bottom` 样式颜色均变色
### 1.6 goods 商品展示列表的封装
- 展示商品列表,封装 GoodsList 组件
* `props: goods`
* `v-for`: `goods` 取出数据,传入 GoodListItem 组件进行展示
.jpg")
- 列表中每一个商品,封装 GoodsListItem 组件
* `props: goodsItem `
*` goodsItem` 取出数据, 并且使用正确的 `div/span/img` 基本标签进行展示
.jpg")
- 设置相关样式
- 使用 GoodsList 组件时,传入相关的商品数据进行展示
### 1.7 返回顶部
- 封装 BackTop 组件
- 定义一个常量,用于决定在什么数值下显示 BackTop 组件
- 监听滚动,决定 BackTop 的显示和隐藏
- 监听 BackTop 的点击,点击时,调用 scrollTo 返回顶部
## 二、首页界面(views -> home)
### 1.1 请求首页数据
- 封装请求首页数据的方法(network -> home.js)
.jpg")
- 发送数据请求
- 将 banner 数据放在 banners 变量中
- 将 recommend 数据放在 recommends 变量中
- 将不同 type 的 data 数据放在 goods 变量的不同对象中(根据 type 和 page 请求商品数据)

.jpg")
### 1.2 封装 RecommendView(home -> childComps)
- 传入 recommends 数据,进行展示
### 1.3 封装 FeatureView(home -> childComps)
- 独立组件封装FeatureView
- 展示一张图片即可
### 1.4 tabControl 的吸顶效果
#### 1.4.1 获取到 tabControl 的 `offsetTop`
* 必须知道滚动到多少时, 开始有吸顶效果, 这个时候就需要获取 tabControl 的 `offsetTop`
* 但是, 如果直接在 `mounted` 中获取 tabControl 的 `offsetTop`, 那么值是不正确(因为此时可能在图片还没加载完的情况下就计算出了高度)
* 如何获取正确的值了?
* 监听 HomeSwiper 中 img 的加载完成
* 加载完成后, 发出事件, 在 Home.vue 中, 获取正确的值
* 为了不让 HomeSwiper 多次发出事件,可以使用 `isLoad` 的变量进行状态的记录
* 注意: 区分这里不进行多次调用和 debounce(防抖)的区别
#### 1.4.2 监听滚动, 动态的改变 tabControl 的样式
* 问题:动态的改变 tabControl 的样式时, 会出现两个问题:
* 问题一: 下面的商品内容, 会突然上移(tabControl 设置了 fixed后,脱离了标准流)
* 问题二: tabControl 虽然设置了 `fixed`, 但是也随着 Better-Scroll 一起滚出去了
(Better-Scroll 通过改变 `translate` 属性进行滚动,所以设置了 `fixed` 属性的标签也会滚动出去)
* 其他方案来解决停留问题:
* 在 scroll 标签上面, 多复制了一份 tabControl(新) 组件对象, 利用它来实现停留效果(需要设置定位,否则会被盖住)
* 当用户滚动超过 offsetTop 时, tabControl(新) 显示出来
* 否则, tabControl(新) 隐藏起来
### 1.5 backTop 回到顶部
#### 1.5.1 封装 BackTop 组件
#### 1.5.2 BackTop 组件的显示和隐藏
* Home 组件的 `data` 中 新建属性 `isShowBackTop: false`
* 监听滚动, 拿到滚动的位置:
* 若 `-position.y > 1000`,则 `isShowBackTop: true`
* 即 `isShowBackTop = -position.y > 1000`
#### 1.5.3 如何监听组件的点击
* 不可以直接监听 back-top 的点击, 必须添加修饰.native

* 回到顶部
* scroll 对象, `scroll.scrollTo(x, y, time)`

* `this.$refs.scroll.scrollTo(0, 0, 300)`

### 1.6 上拉加载更多
- 通过 Scroll 监听上拉加载更多

- 在 Home 中加载更多的数据
- 请求数据完成后,调动 finishedPullUp
.jpg")
.jpg")
.jpg")
### 1.7 解决首页中可滚动区域的问题
* Better-Scroll 在决定有多少区域可以滚动时, 是根据 `scrollerHeight` 属性决定
* `scrollerHeight` 属性是根据放 Better-Scroll 的 content 中的子组件的高度
* 但是我们的首页中, 刚开始在计算 `scrollerHeight` 属性时, 是没有将图片计算在内的
* 所以计算出来的高度是错误的(1300+)
* 后来图片加载进来之后有了新的高度, 但是 scrollerHeight 属性并没有进行更新.
* 所以滚动出现了问题
* 如何解决这个问题了?
* 监听每一张图片是否加载完成, 只要有一张图片加载完成了, 执行一次 refresh()
* 如何监听图片加载完成了?
* 原生的 js 监听图片: `img.onload = function() {}`
* Vue 中监听: `@load='方法'`
.jpg")
* 调用 scroll 的 `refresh()`
* 如何将 GoodsListItem.vue 中的事件传入到 Home.vue 中
* 因为涉及到非父子组件的通信, 所以这里我们选择了**事件总线**
* bus -> 总线
* `Vue.prototype.$bus = new Vue()`
* `this.bus.emit('事件名称', 参数)`
.jpg")
* `this.bus.on('事件名称', 回调函数(参数))`
.jpg")
* 问题一: refresh 找不到的问题
* 第一: 在 Scroll.vue 中, 调用 this.scroll 的方法之前, 判断 this.scroll 对象是否有值
eg: `this.scroll && this.scroll.scrollTo(x, y, time);`
* 第二: 在 mounted 生命周期函数中使用 this.$refs.scroll 而不是 created 中
(因为 created 在模板渲染成html前调用,组件还未挂载,this.scroll 可能为空;而 mounted 在模板渲染成html后调用)
* 问题二: 对于refresh非常频繁的问题, 进行防抖操作
* 防抖 debounce / 节流 throttle(研究一下)
* 防抖函数起作用的过程:
* 如果我们直接执行 refresh, 那么 refresh 函数会被执行 30 次.
* 可以将 refresh 函数传入到 debounce 函数中, 生成一个新的函数.
* 之后在调用非常频繁的时候, 就使用新生成的函数.
* 而新生成的函数, 并不会非常频繁的调用, 如果下一次执行来的非常快, 那么会将上一次取消掉
```js
debounce(func, delay) {
let timer = null
return function (...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, delay)
}
},
```
### 1.8 让 Home 保持原来的状态
#### 1.8.1 让 Home 不要随意销毁掉
* keep-alive
#### 1.8.2 让 Home 中的内容保持原来的位置
* 离开时, 保存一个位置信息 saveY

* 进来时, 将位置设置为原来保存的位置 saveY 信息即可
.jpg")
* 注意: 最好回来时, 进行一次 refresh()
## 项目界面(仅展示部分)
### 1. 首页

### 2. 商品详情

### 3. 分类界面

### 4. 购物车

### 5. 个人中心
