当前位置:   article > 正文

Vue笔记三——Vue项目开发_this.$emit('close', isrefresh)

this.$emit('close', isrefresh)

Vue项目开发

小贴士:

1.若想查询vue/cli的版本号,在终端输入:

vue -V
  • 1

2.在cmd命令行重新安装了3.2.1的Vue:

npm install @vue/cli@3.2.1 -g
  • 1

3.安装了3.0.2版本的vue-router

npm install vue-router@3.0.2 
  • 1

4.使用 li{分类列表$}*100 ,可以得到分类列表1~分类列表100

5.用户片段

  • vue的用户片段
	"Print to console": {
		"prefix": "vue",
		"body": [
		  // "<script src=\"../js/vue.js\"></script>",
      "<template>",
      "",
      "</template>",
			"<script>",
			"\texport default {",
				"\t\tname: '$TM_FILENAME_BASE'",
      "\t}",
		  "</script>",
      "<style scoped>",
      "",
      "</style>"
		],
		"description": "Log output to console"
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • html中的Vue片段
	"Print to console": {
		"prefix": "vue",
		"body": [
		  "<div id='app'>",
		  "</div>",
		  "<script src=\"../js/vue.js\"></script>",
		  "<script>",
			"\tconst app = new Vue({",
			"\t\tel: '#app',",
			"\t\tdata: {",
			  "\t\t\tmessage: '你好呀'",
			  "\t\t}",
			"\t})",
		  "</script>",
		],
		"description": "Log output to console"
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

6.关于VScode标签页的一些问题

  • 这是因为你单击文件名的缘故,这个是“预览模式”,所以再单击其他文件时,会覆盖当前打开的文件。

  • 如果你要每次都打开新tab,那就双击文件名好了。这个逻辑和sublime是一样的。不知道你是不是问的这个事情

  • 预览模式是现在各类编辑器的默认功能,如果你实在不喜欢,可以关掉的。给你配置settings.json里加一条:

    "workbench.editor.enablePreview": false,
    
    • 1

7.判断一个对象是否为空的方法

利用Object.keys(obj)可以获得对象的关键字构成的数组,利用if语句判断。若为length为0,则该对象为空。

Object.key(obj).length === 0
  • 1

1.git基本使用

  • 在github上new respository,复制https地址
  • 在终端运行git clone https://github.com/This-is-Leon/supermall.git 命令
  • 复制VueCLI3脚手架文件(node_modules除外)到clone的文件夹下
  • 在终端中输入git status查看文件状态,若为红色则说明未被加入仓库中
  • 在终端中输入git add . 将所以文件添加进仓库中
  • 在终端中输入git commit -m ‘初始化项目’
  • 在终端中输入git push 进行提交
  • 在终端输入git push origin master: 新分支名

2.git命令行

  • 全局配置:git config --global --list
  • 设置用户名和邮箱:
git config --global user.email lz4135@qq.com
git config --global user.name This-is-Leon
  • 1
  • 2
  • 初始化一个仓库:git init
  • 添加远程仓库地址
git remote add origin 远程仓库地址
  • 1

在这里插入图片描述

  • 修改远程仓库地址

    git remote set-url origin [url]
    
    • 1
  • 查看当前远程仓库地址

    git remote -v
    
    • 1

3.CSS文件

1.安装normalize.css文件:

方法一:直接通过命令行下载

npm install normalize.css
  • 1

方法二:

2.创建自定义的base.css文件

3.在App.vue文件中导入base.css

<style>
  @import "./assets/css/base.css";
</style>
  • 1
  • 2
  • 3

4.分析base.css代码

  • 定义可以重复使用的变量
/* :root -> 获取根元素html */
:root {
  --color-text: #666;
  --color-high-text: #ff5777;
  --color-tint: #ff8198;
  --color-background: #fff;
  --font-size: 14px;
  --line-height: 1.5;
  /* 这里定义了一些可以使用的变量 */
  --large-seize: 50px;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 使用方式

body {
  background: var(--color-background);
  color: var(--color-text);
}
  • 1
  • 2
  • 3
  • 4
  • 5

5.对于CLI3和CLI4,若想修改配置,则需要先创建一个vue.config.js文件

  • 系统已经设置了@表示src路径
module.exports = {
  configureWebpack: {
    resolve: {
      extensions: [],
      alias: {
        'assets': '@/assets',
        'common': '@/common',
        'compoents': '@/components',
        'network': '@/network',
        'views': '@/views'
      }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

6.创建CLI2的时候会自动生成一个.editorconfig文件,但CLI3之后便不会生成了。该文件里是关于缩进、空格等的一些配置文件。往往同一个项目组内会保持一个相同的编程格式。

即便是不同的项目也会选择使用相同的配置文件,所以这里将前一个axios的.editorconfig文件复制到supermall文件中。

4.安装Vue-router

1.在项目文件夹下安装vue-router:

npm install vue-router --save
  • 1

2.创建router文件夹

创建index.js文件->导入Vue和Vue-router->使用use方法进行调用

如下是index.js

import Vue from 'vue'
import VueRouter from 'vue-router'

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

// 2.创建router
const router = new VueRouter({

})

export default router
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.在main.js文件中进行调用:

若是cli2,则代码如下:

import router from './router'
new Vue({
  el: '#app',
  router,
  render: h => h(App)
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

若是cli3,则代码如下:

import router from './router'
createApp(App).use(router).mount('#app')
  • 1
  • 2

5.小图标的修改

  1. 打开public文件夹
  2. 替换里面的favicon.ico文件即可

6.创建NavBar组件

1.在components文件夹的common文件夹中创建导航栏的组件

2.设置具名插槽,用于之后的替换(注意每个插槽都要单独用div封装起来,否则容易出现bug)

<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;
    height: 44px;
    line-height: 44px; 
    text-align: center;
    box-shadow: 0 1px 1px rgba(100, 100, 100, 0.1);
    /* background-color: pink; */
  }
  .left, .right {
    width: 60px;
    /* background-color: skyblue; */
  }
  .center {
    flex: 1;
    /* 因为left和right设置了width,那么剩余部分均会被flex占据 */
  }
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

3.在views的Home.vue文件中进行引用

<template>
  <div id="home">
    <nav-bar class="home-nav">
      <span slot="center">购物街</span>
    </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: white;
  }
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

7.设置Network文件夹

免费接口:

淘宝商品接口:http://suggest.taobao.com/sug?code=utf-8&q=商品关键字&callback=cb

8.设置轮播图

  1. 复制轮播图组件swiper到components文件夹的common文件夹中

  2. 在Home.vue中导入

    import {Swiper, SwiperItem} from '@/components/common/swiper'
    
    • 1
  3. 在components中进行注册

    export default {
        name: 'Home',
        components: {
          NavBar,
          Swiper,
          SwiperItem
        },
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  4. 在template中进行调用(注意:因为swiper-item标签是自定义的,所以在使用v-for循环进行遍历时,需要添加:key=“index”,否则会不停报错。)

    <template>
      <div id="home">
        <nav-bar class="home-nav">
          <span slot="center">购物街</span>
        </nav-bar>
        <swiper>
          <!-- 自定义属性要绑定key -->
          <swiper-item v-for="(item, index) in banners" :key="index">
            <a :href="item.link">
              <img :src="item.image" alt="">
            </a>
          </swiper-item>
        </swiper>
      </div>
    </template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

9.设置TabControl

1.因为是公共组件,所以将组件放在components/common/content目录下:

2.注意要点如下:

  • 利用父子组件的通信书写props属性。
  • 因为是让文字底部出现横线,所以css属性要添加到span
  • 使用颜色时,直接使用颜色变量。颜色变量存在assets/css/base.css文件下
/*TabControl.vue*/
<template>
  <div class="tab-control">
    <div v-for="(item, index) in titles" :key="index" class="tab-control-item" :class="{active:index === currentIndex}" @click="itemClick(index)">
      <span>{{item}}</span>
    </div>
  </div>
</template>
<script>
  export default {
    name: 'TabControl',
    props: {
      titles: {
        type: Array,
        default() {
          return []
        }
      }
    },
    data() {
      return {
        currentIndex: 0
      }
    },
    methods: {
      itemClick(index) {
        this.currentIndex = index;
      }
    }
  }
</script>
<style scoped>
  .tab-control {
    display: flex;
    text-align: center;
    font-size: 15px;
    height: 40px;
    line-height: 40px;
  }

  .tab-control-item {
    flex: 1;
  }

  .active {
    color: var(--color-high-text);
  }

  .active span {
    border-bottom: 3px solid var(--color-tint);
  }
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

导入组件后,使用动态属性输入titles

为了达到向下滑动时,可以起到固定效果。使用position:sticky属性,还可以设置top值,表示在top的距离上固定住

为了防止底部文字穿过组件,设置背景颜色。

 /*Home.vue的template*/
 <tab-control class="tab-control" :titles="['流行', '新款', '精选']"></tab-control>

  • 1
  • 2
  • 3
 /*Home.vue的style*/
  .tab-control {
    position: sticky;
    top: 44px;
    /* 防止透明 */
    background-color: #fff; 
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

10.代码的再次封装

将home.vue中的created方法代码封装在methods中,这样created属性的代码就变得简洁明了了。

    //全生命周期函数,在组件创建后获取接口数据
    created() {
      // 1.请求多个数据
      this.getHomeMultidata()

      // 2.请求商品数据
      this.getHomeGoods()


    },

    methods: {
      getHomeMultidata() {
        getHomeMultidata().then(res => {
        // this.banners = res.jokes;
        // this.recommends = res.jokes;

        this.banners = res.data.banner.list;
        this.recommends = res.data.recommend.list;
        // console.log(res);
        })
      },
      getHomeGoods(type) {
        const page = this.goods[type].page + 1; //当进行复次调用的时候page就会不断增加
        getHomeGoods(type, page).then(res => {
          //运用扩展运算符把list数据一个一个push进去,相当于for循环的效果
          this.goods[type].list.push(...res.data.list);
          this.goods[type].page += 1;
        })
      }

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

getHomeGoods()方法的关键点:

运用扩展运算符将从接口拿到的数据一个一个放到data的goods中:

 getHomeGoods(type, page).then(res => {
          //运用扩展运算符把list数据一个一个push进去,相当于for循环的效果
          this.goods[type].list.push(...res.data.list);
          this.goods[type].page += 1;
        })
  • 1
  • 2
  • 3
  • 4
  • 5

11.CSS布局设置

GoodsList.vue中的goods属性设置:flex-wrap:wrap表示根据宽度多行显示。 justify-content: space-around表示均等分布。

 .goods {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-around;
  }
  • 1
  • 2
  • 3
  • 4
  • 5

GoodsListItem.vue中的goods-item属性宽度设置为50%,则一行显示两个商品。

.goods-item {
    padding-bottom: 40px;
    position: relative;
    width: 50%;
  }
  • 1
  • 2
  • 3
  • 4
  • 5

11.betterScroll的安装和使用

1.安装betterscroll

npm install better-scroll@1.32.2 --save
  • 1

2.可以使用css自带的滚动,设置一个高度height,再设置overflow:auto或者overflow:scroll。但这种方案的缺点是在移动端是非常卡顿的。

  .content {
    height: 150px;
    background-color: pink;
    /* 设置溢出隐藏,即可实现在height=150px范围内的列表滚动 */
    overflow: scroll; 
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3.betterscroll的使用,在script标签中,记住是在mounted这个生命周期函数中进行调用,因为mounted之后才将挂载的模板渲染到页面上。

import BScroll from 'better-scroll'

  export default {
    name: 'Category',
    data() {
      return{
        scroll: null
      }
    },
    mounted() {
      // console.log(document.querySelector('.wrapper'));
      this.scroll = new BScroll(document.querySelector('.wrapper'), {

      })
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

注意:要刷新的元素的格式如下,wrapper的元素只能包含一个元素content(类名可以随便取)。但content元素内部可以包含很多元素。设置style的class是最外层包裹的类。

<style>
    .wrapper {
      height: 200px;
      background-color: pink;
      overflow: hidden;
    }
</style>

<body>
 <div class="wrapper">
    <ul class="content">
      <button class="btn">按钮</button>
      <li>列表1</li>
      <li>列表2</li>
      <li>列表3</li>
    </ul>
  </div>
</body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

4.bscroll的监听事件

bscroll.on表示监听事件,当需要上拉加载更多时。参数pullUpLoad参数设为true,并进行事件监听。click属性设置为true表示滚动部分的按钮标签是可以监听到的。

<script src="./bscroll.js"></script>
  <script>
    // 默认情况下BScroll是不可以实时的监听滚动位置
    // probe表示侦测,其参数0、1都是不侦测实时位置。2则是在手指滚动的过程中侦测,手指离开后的惯性滚动过程不侦测。3表示只要是滚动,都侦测。
    const bscroll = new BScroll(document.querySelector('.wrapper'), {
      probeType: 2,
      click: true,
      pullUpLoad: true
    });

    bscroll.on('scroll', (position) => {
      // console.log(position);
    })

    bscroll.on('pullingUp', () => {
      console.log('上拉加载更多');
      // 发送网络请求,请求更多页的数据

      // 等数据请求完成,并且将新的数据展示出来后
      setTimeout(() => {
        bscroll.finishPullUp()
      }, 2000)
    })

    document.querySelector('.btn').addEventListener('click', function() {
      console.log('-------');
    })
  </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

12.BetterScroll的封装

为了防止betterscroll有一天不再进行维护,需要对其进行一个封装处理。当之后需要更换其他的库时,便会方便很多。

在components的common文件夹下创建Scroll.vue:

<template>
  <div class="wrapper" ref="wrapper">
    <div class="content">
      <slot></slot>
    </div>
  </div>
</template>
<script>
  import BScroll from 'better-scroll'

  export default {
    name: 'Scroll',
    data() {
      return {
        scroll: null
      }
    },
    mounted() {
      this.scroll = new BScroll(this.$refs.wrapper, {

      })
    }
  }
</script>
<style scoped>
 
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

问题1:当new一个BScroll时,不建议使用document.querySelector去获取类对象,因为一个项目里类会出现多次。

解答: 在Vue中建议使用ref属性对标签进行绑定。

ref如果是绑定在组件中的,那么通过this.$refs.refname获取到的是一个组件对象。

ref如果是绑定在普通元素中,那么如果this.$refs.refname获取到的是一个元素对象。

问题2:Vue中style标签中的scoped是什么意思?

解答:当一个style标签拥有scoped属性时,它的CSS样式就只能作用于当前的组件,也就是说,该样式只能适用于当前组件元素。 通过该属性,可以使得组件之间的样式不互相污染。 如果一个项目中的所有style标签全部加上了scoped,相当于实现了样式的模块化。

问题3:vh单位表示的含义?

解答:vh表示viewport height,即视口高度。

**问题4:**出现上拉加载的bug的解决方案?

**解答:**如果是2.0以上版本,在better-scroll的配置对象里面加入observeDom:true,observeImage:true就可以了

  • 在home.vue中导入该组件,并进行使用。使用标签把需要滚动的标签包裹起来。

  • 给content设置高度,例如这里设置height: calc(100vh - 93px)

  • 给scroll组件的父组件设置高度,例如这里设置height: 100vh;

    //script
    import Scroll from '@/components/common/scroll/Scroll'
    
    export default {
        components: {
        	Scroll
        }
    }
    
    //template
    <scroll class="content">
        <home-swiper :banners="banners" />
        <recommend-view :recommends="recommends" />
        <feature-view/>
        <tab-control class="tab-control" :titles="['流行', '新款', '精选']" @tabClick="tabClick" />
        <goods-list :goods="showGoods" />
    </scroll>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

13.添加返回顶部按钮

1.在components的content文件夹下创建backTop文件夹,并在文件夹内创建BackTop.vue文件

<template>
  <div class="back-top">
    <img src="@/assets/img/common/top.png" alt="">
  </div>
</template>
<script>
  export default {
    name: 'BackTop'
  }
</script>
<style scoped>
  .back-top {
    position: fixed;
    right: 8px;
    bottom: 55px;
  }
  .back-top img {
    width: 43px;
  }
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2.在home.vue文件夹中进行导入,添加到components中,再进行使用。

关键点:对于自定义组件标签设置点击事件需要添加修饰符,例如:

  • .stop - 调用 event.stopPropagation()。

  • .prevent - 调用 event.preventDefault()。

  • .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。

  • .native - 监听组件根元素的原生事件。

  • .once - 只触发一次回调。

<back-top @click.native="backClick"></back-top>
  • 1

3.定义btnClick()方法

backClick() {
  this.$refs.scroll.scroll.scrollTo(0, 0, 500)
}
  • 1
  • 2
  • 3

4.设置对返回顶部按钮的显示隐藏

  • 在mounted()中对滚动事件进行监听,当进行滚动时,通过emit将scroll事件和参数position传递给父组件。

     this.scroll.on('scroll', (position) => {
            // console.log(position);
            this.$emit('scroll', position)
          })
    
    • 1
    • 2
    • 3
    • 4
  • 父组件中可以得到传出的事件scroll,通过@scroll进行事件监听并调用方法contentScroll。父组件中contentScroll不需要写参数,参数可以在methods方法中写

    <scroll 
        class="content" 
        ref="scroll" 
        :probe-type="3" 
        @scroll="contentScroll">
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 书写contentScroll()方法,判断条件很重要。这里的position.y是一个负数,所以取其绝对值,赋值时等式右边是一个表达式

    contentScroll(position) {
        //根据等号右侧条件判断true or false
        this.isShowBackTop = Math.abs(position.y) > 1000
    }
    
    • 1
    • 2
    • 3
    • 4

14.解决首页中Better-Scroll可滚动区域的问题

  • ​ Better-Scroll在决定有多少区域可以滚动时,是根据scrollHeight属性决定

    • scrollHeight属性是根据放Better-Scroll的content中的子组件的高度
    • 但是我们的首页中,刚开始计算scrollHeight属性时,是没有将图片计算在内
    • 所以,计算出来的高度时错误的(1300+)
    • 后来图片加载进来之后有了新的高度,但是scrollHeight属性并没有进行更新
    • 所以滚动出现了问题
  • 如何解决该问题呢?

    • 监听每一张图片是否加载完成,只要有一张图片加载完成了,执行一次refresh()
    • 如何监听图片加载完成呢?
      • 原生的js监听图片:img.onload = function() {}
      • Vue中监听;@load=‘方法’
    • 调用scroll的refresh
  • 事件总线的概念:

    • EventBus 又称为事件总线。在Vue中可以使用 EventBus 来作为沟通桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件,但也就是太方便所以若使用不慎,就会造成难以维护的“灾难”,因此才需要更完善的Vuex作为状态管理中心,将通知的概念上升到共享状态层次。

    • 如何将GoodListItem.vue中的事件传入到home.vue中

      • 因为涉及到非父子组件的通信,所以这里我们选择了事件总线
    • 事件总线使用方式:

      • 首先打开main.js文件,在Vue的原型链上创建一个Vue实例,用来作为事件总线

        // 需要创建事件总线的实例
        Vue.prototype.$bus = new Vue()

        
        
        
        - 在GoodListItem.vue中使用对图片的加载进行监听,并通过$bus.$emit进行发送。
        
        
        • 1
        • 2
        • 3
        • 4
        • 5

        <img :src=“goodsItem.show.img” alt="" @load=“imageLoad”>

            imageLoad() {
              this.$bus.$emit('itemImageLoad')
            }
          
          • 1
          • 2
          • 3
          • 在home.vue的created()中对发送的事件进行监听并调用对scroll的refresh()进行刷新

            this.$bus.$on('itemImageLoad', () => {
              this.$refs.scroll.scroll.refresh()
            })
            
            
            • 1
            • 2
            • 3
            • 4
            
            
            • 1
      • 若出现切换首先、分类出现函数报错问题时

        • 在页面销毁的destroy(){}生命周期事件中移除事件线就好

          destroyed() {
                this.$bus.$off()
          },
          
          • 1
          • 2
          • 3

      15.刷新频繁的防抖函数处理

      • 对于refresh非常频繁的问题,进行防抖操作

        • 防抖debounce/节流throttle

        • 防抖函数起作用的过程:

          • 如果我们直接执行refresh,那么refresh函数会被执行30次

          • 可以将refresh函数传入到debounce函数中,生成一个新的函数

          • debounce函数有两个参数,一个是函数,一个是延迟时间。记住传入函数是不要加(),因为我们需要传入的是函数本身而不是函数的返回值。

          • debounce函数的返回值是一个函数,声明一个变量令其等于返回值,再对该函数进行调用

          • if(timer) clearTimeout(timer) 这个判断语句表示当debounce函数在执行的过程中又被调用,则清除调之间的计时,重新再开始计时。

          • …args表示传入的函数的参数,…表示扩展运算符,表示你可以传入多个参数。

            //methods中定义函数
            debounce(func, delay) {
                    let timer = null
            
                    return function(...args) {
                      if (timer) clearTimeout(timer)
            
                      timer = setTimeout(() => {
                        func.apply(this, args)
                      }, delay)
                    }
                  }
            
            • 1
            • 2
            • 3
            • 4
            • 5
            • 6
            • 7
            • 8
            • 9
            • 10
            • 11
            • 12
            //mounted对函数进行调用
            const refresh = this.debounce(this.$refs.scroll.refresh, 500)
            this.$bus.$on('itemImageLoad', () => {
              refresh()
            })
            
            • 1
            • 2
            • 3
            • 4
            • 5
      • 对debounce函数进行封装

        • 在common文件夹下的utils.js组件中导出debounce函数

          export function debounce(func, delay) {
            let timer = null
          
            return function(...args) {
              if (timer) clearTimeout(timer)
          
              timer = setTimeout(() => {
                func.apply(this, args)
              }, delay)
            }
          }
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
        • 在Home.vue中导入debounce函数并进行使用

          import {debounce} from '@/common/utils.js'
          
          • 1

      16.上拉加载更多

      • 在props属性中设置pullUpLoad属性

        pullUpLoad: {
                type: Boolean,
                default: false
        }
        
        • 1
        • 2
        • 3
        • 4
      • 在mounted函数中进行事件监听,监听pullingUp,并同$emit将事件传递出去,注意这里的事件名最好不要与pullingUp同名(这里取名pulling)

        this.scroll.on('pullingUp', () => {
                this.$emit('pulling')
        })
        
        • 1
        • 2
        • 3
      • 在scroll标签中设置监听传入的pulling事件,并调用loadMore方法

        <scroll @pulling="loadMore"></scroll>
        
        • 1
      • 在methods中定义loadMore方法,loadMore函数内部调用之间定义好的getHomeGoods方法

        loadMore() {
            this.getHomeGoods(this.currentType)
        }
        
        • 1
        • 2
        • 3
      • 为了能够进行多次上拉加载更多,在Scroll.vue中定义finishPullUp方法

        finishPullUp() {
           this.scroll && this.scroll.finishPullUp()
        }
        
        • 1
        • 2
        • 3
      • 在Home.vue中对getHomeGoods方法增添一行代码,这样每次加载图片后均为设定完成一次上拉加载更多

        this.$refs.scroll.finishPullUp()
        
        • 1

      17.tabControl的吸顶效果

      • 方案总结:

        • 在最上面,多复制了一份TabControl组件对象,利用它来实现停留效果。
        • 当用户滚动到一定位置时,TabControl显示出来
        • 当用户滚动没有达到一定位置时,TabControl隐藏起来
      • 获取tabControl的offsetTop,知识点:所有的组件都有一个属性$el:用于获取组件中的元素

      • 在mounted()中直接打印offsetTop,得到的值偏小。主要原因是轮播图的加载需要时间

      • 所以,我们可以在HomeSwiper.vue组件中对图片的加载进行监听

        <img :src="item.image" alt="" @load="imageLoad">
        
        • 1
      • 定义imageLoad方法,为了防止重复加载,在data中设置isLoad的bool值用作节流阀。isLoad默认为false,加载完成后设置为true

        imageLoad() {
            // 添加节流阀
            if(!this.isLoad) {
            this.$emit('swiperImageLoad')
            this.isLoad = true
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
      • 在Home.vue的home-swiper标签中对轮播图的图片加载进行监听,并赋值给tabOffsetTop

        <home-swiper :banners="banners" @swiperImageLoad="swiperImageLoad" />
        
        • 1
        swiperImageLoad() {
            this.taboffsetTop = this.$refs.tabControl2.$el.offsetTop
        }
        
        • 1
        • 2
        • 3
      • 小技巧:偷天换日。在nav-bar组件下再设置一个tab-control组件。这样等于在scroll组件内外各有一个tab-control组件,两者均设置v-show属性,当滚动到一定距离时,前者显示后者隐藏。

        //前者
        <tab-control :titles="['流行', '新款', '精选']" @tabClick="tabClick" 
            ref="tabControl1"
            class="tab-control" v-show="isTabFixed" />
        //后者    
        <tab-control :titles="['流行', '新款', '精选']" @tabClick="tabClick" 
              ref="tabControl2"
              v-show="!isTabFixed" />
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
      • 在data中定义isTabFixed变量,默认为false。因为contentScroll方法对页面滚动进行监听,所以在方法内部添加一行代码,用于改变isTabFixed。当position>offsetTop时为true,否则为false

        contentScroll(position) {
             this.isTabFixed = Math.abs(position.y) > this.taboffsetTop
        },
        
        • 1
        • 2
        • 3
        
        
        • 1
      • 此时还会出现一个bug,即两个tab-control组件的标签不同步。解决方案:首先两个组件分别取名ref=“tab-Control1"和"tab-Control2”,然后给tabClick()添加两行代码,修改两者的currentIndex

        this.$refs.tabControl1.currentIndex = index;
        this.$refs.tabControl2.currentIndex = index;
        
        • 1
        • 2

      18.让Home保持原来的状态

      1.首先让Home不要随意销毁掉:

      **解决方案:**打开App.vue,使用keep-alive标签包裹住router-view标签

      <keep-alive>
            <router-view></router-view>
      </keep-alive>
      
      • 1
      • 2
      • 3

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

      **解决方案:**因为不进行销毁了,所以多了两个生命周期函数,即activated和deactived。步骤:

      1.首先在data中创建saveY变量用来存储位置信息,默认为0。

      2.在activated函数中设置scroll的跳转位置,保险起见可以添加一个scroll的刷新。

      activated() {
          this.$refs.scroll.refresh()
          this.$refs.scroll.scrollTo(0, this.saveY, 0)
            
      },
      
      • 1
      • 2
      • 3
      • 4
      • 5

      3.在deactived函数中存储当前的位置,scroll对象中有一个y属性可以用来的得到位置信息

      deactivated() {
            this.saveY = this.$refs.scroll.scroll.y
      },
      
      • 1
      • 2
      • 3

      19.设计商品详情页

      1.在views文件夹中创建detail文件夹,并在文件夹下创建Detail.vue组件

      <template>
        <div>{{iid}}</div>
      </template>
      <script>
        export default {
          name: 'Detail',
          data() {
            return {
              iid: null
            }
          },
          created() {
            this.iid = this.$route.params.iid
          }
        }
      </script>
      <style scoped>
      
      </style>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

      2.打开router文件夹的index.js文件,对detail组件进行懒加载,并在路由中进行配置。因为每件商品对应的详情页不同,故配置的是一个动态路由。

      const Detail = () => import('@/views/detail/Detail')
      const routes = [
        {
          path: '/detail/:iid',
          component: Detail
        }
      ]
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      3.在GoodsListItem.vue文件中利用图片对点击进行监听,并定义itemClick()方法,让其通过路由进行跳转

      itemClick() {
          this.$router.push('/detail/' + this.goodsItem.iid)
      }
      
      • 1
      • 2
      • 3

      4.开始编写Detail.vue组件,总体流程跟写Home.vue差不多,将页面的各个部分封装到外面。

      • 首先编写DetailNavBar组件存储childComps文件夹里,导入NavBar组件。对中间插槽插入标题,这里可以参考tabControl组件的书写方式,先利用v-for遍历titles,定义样式和点击事件。
      • 然后在左边插槽插入图片

      5.从接口获取商品详情的数据

      • 在network文件夹创建detail.js文件,创建getDetail()方法并进行导出

        import {request} from './request';
        
        export function getDetail(iid) {
          return request({
            url: '/detail',
            params: {
              iid
            }
          })
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
      • Detail.vue组件:导入getDetail方法,在created()生命周期函数中进行调用

      import {getDetail} from '@/network/detail'
      
      getDetail(this.iid).then(res => {
              console.log(res);
            })
      
      • 1
      • 2
      • 3
      • 4
      • 5

      同理,商品轮播图也是采取同样的做法。

      • 在childComps文件夹中创建DetailSwiper.vue组件,导入Swiper、SwiperItem组件。

      • 在props对象中,定义topImages属性,用来接收父组件Detail.vue的图片数据,最后定义css属性

      • 在Detail.vue中导入DetailSwiper组件,并利用动态属性传入图片数据

        <template>
            <swiper>
              <swiper-item class="swiper-item" v-for="(item, index) in topImages" :key="index">
                <img :src="item" alt="">
              </swiper-item>
            </swiper>
        </template>
        <script>
          import {Swiper, SwiperItem} from '@/components/common/swiper'
        
          export default {
            name: 'DetailSwiper',
            components: {
              Swiper,
              SwiperItem
            },
            props: {
              topImages: {
                type: Array,
                default() {
                  return []
                }
              }
            }
          }
        </script>
        <style scoped>
          .swiper-item {
            height: 300px;
            overflow: hidden;
          }
        </style>
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        <detail-swiper :topImages="topImages"></detail-swiper>
        
        • 1

      6.针对接口数据分布在不同位置,我们这里提出一个方法将需调用的数据进行整合

      • 打开network文件夹下的detail.js文件,定义一个商品类,在构造函数中传入相应数据

        export class Goods {
          constructor(itemInfo, columns, services) {
            this.title = itemInfo.title
            this.desc = itemInfo.desc
            this.newPrice = itemInfo.price
            this.oldPrice = itemInfo.oldPrice
            this.discount = itemInfo.discountDesc
            this.columns = columns
            this.services = services
            this.realPrice = itemInfo.lowNowPrice
          }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
      • 在Detail.vue中:在data中定义goods属性。导入定义的类,并在使用getDetail()方法时通过new Goods传入数据

          //data中定义goods属性
          data() {
              return {
                  iid: null,
                  res: null,
                  topImages: [],
                  goods: {},
              }
          },
         
         getDetail(this.iid).then(res => {
             // 1.获取顶部的图片轮播数据
             console.log(res);
             const data = res.result;
             this.topImages = data.itemInfo.topImages;
        
            // 2.获取商品信息
            this.goods = new Goods(data.itemInfo, data.columns, data.shopInfo.services)
                
        })
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
      • 数据整合完成之后,便可以开始定义DetailBaseInfo组件,在props对象中定义goods属性。goods属性用来接收从父组件Detail.vue传递过来的数据。

        <template>
          <div v-if="Object.keys(goods).length !== 0" class="base-info">
            <div class="info-title">{{goods.title}}</div>
            <div class="info-price">
              <span class="n-price">{{goods.newPrice}}</span>
              <span class="o-price">{{goods.oldPrice}}</span>
              <span v-if="goods.discount" class="discount">{{goods.discount}}</span>
            </div>
            <div class="info-other">
              <span>{{goods.columns[0]}}</span>
              <span>{{goods.columns[1]}}</span>
              <span>{{goods.services[goods.services.length-1].name}}</span>
            </div>
            <div class="info-service">
              <span class="info-service-item" v-for="index in goods.services.length-1" :key="index">
                <img :src="goods.services[index-1].icon">
                <span>{{goods.services[index-1].name}}</span>
              </span>
            </div>
          </div>
        </template>
        <script>
        
          export default {
            name: 'DetailBaseInfo',
            props: {
                goods: {
                  type: Object,
                  default() {
                    return {}
                  }
                }
            }
          }
        </script>
        <style scoped>
          .base-info {
            margin-top: 15px;
            padding: 0 8px;
            color: #999;
            border-bottom: 5px solid #f2f5f8;
          }
        
          .info-title {
            color: #222
          }
        
          .info-price {
            margin-top: 10px;
          }
        
          .info-price .n-price {
            font-size: 24px;
            color: var(--color-high-text);
          }
        
          .info-price .o-price {
            font-size: 13px;
            margin-left: 5px;
            text-decoration: line-through;
          }
        
          .info-price .discount {
            font-size: 12px;
            padding: 2px 5px;
            color: #fff;
            background-color: var(--color-high-text);
            border-radius: 8px;
            margin-left: 5px;
        
            /*让元素上浮一些: 使用相对定位即可*/
            position: relative;
            top: -8px;
          }
        
          .info-other {
            margin-top: 15px;
            line-height: 30px;
            display: flex;
            font-size: 13px;
            border-bottom: 1px solid rgba(100,100,100,.1);
            justify-content: space-between;
          }
        
          .info-service {
            display: flex;
            justify-content: space-between;
            line-height: 60px;
          }
        
          .info-service-item img {
            width: 14px;
            height: 14px;
            position: relative;
            top: 2px;
          }
        
          .info-service-item span {
            font-size: 13px;
            color: #333;
          }
        </style>
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 44
        • 45
        • 46
        • 47
        • 48
        • 49
        • 50
        • 51
        • 52
        • 53
        • 54
        • 55
        • 56
        • 57
        • 58
        • 59
        • 60
        • 61
        • 62
        • 63
        • 64
        • 65
        • 66
        • 67
        • 68
        • 69
        • 70
        • 71
        • 72
        • 73
        • 74
        • 75
        • 76
        • 77
        • 78
        • 79
        • 80
        • 81
        • 82
        • 83
        • 84
        • 85
        • 86
        • 87
        • 88
        • 89
        • 90
        • 91
        • 92
        • 93
        • 94
        • 95
        • 96
        • 97
        • 98
        • 99
        • 100
        • 101
        • 102

      7.给Detail组件,即商品详情页添加better-scroll

      • 首先导入封装好的Scroll组件

        import Scroll from '@/components/common/scroll/Scroll'
        
        • 1
      • 使用scroll标签包住要滑动的部分,接下来是关键步骤:给scroll标签定义一个类,类样式中利用计算属性添加高度。最后整个大标签也要添加一个高度(大标签的高度设置为100vh,即100%的视图高度)。

        <div id="detail">
            <detail-nav-bar class="detail-nav" />
            <scroll class="content">
              <detail-swiper :topImages="topImages" />
              <detail-base-info :goods="goods" />
              <detail-shop-info :shop="shop" />
            </scroll>
         </div>
        
        
        <style scoped>
          #detail {
            position: relative;
            z-index: 9;
            background-color: #fff;
            height: 100vh;
          }
        
          .content {
            height: calc(100% - 44px); /* 减号两边记得留空格!否则无效 */
          }
        </style>
            
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
      • 固定导航标签,给导航标签定义detail-nav的类,设置相对定位,背景色定位白色,z-index设置为9

          .detail-nav {
            position: relative;
            z-index: 9;
            background-color: #fff;
          }
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6

      8.导入其他组件,包括店铺信息、商品的详细展示信息、商品的尺码参数

      • 方法同DetailBaseInfo.vue子组件的方法相同
      • 总结如下:
        • 首先,定义公共类
        • 第二,复制粘贴子组件
        • 第三,在Detail父组件中导入公共类和子组件,利用props的父传子的功能将数据导入子组件中,即可完成子组件在网页的渲染。

      9.给详情页添加推荐的子组件

      • 首先,因为推荐的数据接口不同,所以先打开detail.js文件。写一个getCommend()函数

        export function getRecommend() {
          return request({
            url: '/recommend'
          })
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
      • 第二,在Detail组件中导入getCommend()方法获取recommends数据

          import {getDetail, getRecommend, Goods, Shop, GoodsParam} from '@/network/detail'
          
          //在created周期函数中获取数据
          getRecommend().then(res => {
              console.log(res);
              this.recommends = res.data.list;
          })
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
      • 第三,推荐商品的展示可以直接复用之前写过的GoodsList组件,但存在一个问题。即两者对于图片的数据结构不一致,所以会出现报错。解决方案是:在GoodsListItem组件中添加一个计算属性,用来获取图片数据。

         <img :src="showImage" alt="" @load="imageLoad" @click="itemClick">
        
        • 1
        computed: {
            showImage() {
            	return this.goodsItem.image || this.goodsItem.show.img
            }
        },
        
        • 1
        • 2
        • 3
        • 4
        • 5

      20.如何将时间戳转换成Date对象

      时间戳:1535694719(秒)

      1.将时间转成Date对象,因为时间戳是秒,乘以1000将其转化成毫秒

      const date = new Date(1535694719*1000)
      
      • 1

      2.将date进行格式化,转成对应的字符串

      *date.getYear() + date.getMonth()+1
      //date -> FormatString(太常用)
      fmt.format(date, 'yyyy-MM-dd hh:mm:ss')
      
      
      • 1
      • 2
      • 3
      • 4

      21.mixin对象

      混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

      问题1:我自己测试的时候发现一个bug,当你点开商品详情页之后。再回到主页,向上滑动时会发现无法进行上拉加载更多了。

      解决方案:我认为问题产生的原因时,home.vue停止监听的缘故。所以我把mixin.js中的mounted改成activated,问题就解决了。

      问题2

      因为详情页的推荐部分复用了GoodsListItem组件,而该组件在图片进行加载时会通过事件总线向home.vue发送itemImgLoad事件。而详情页的加载又不需要home.vue进行监听,这就导致了代码的性能下降。

      解决方案:

      在Detail和Home组件的mounted函数中,分别使用debounce防抖函数进行刷新。然后在destroyed函数中将监听关闭。

      因为两者在mounted中的代码相同,故可以利用mixin对象将相同的代码部分混合进mixin.js中。

      首先,在mixin.js中定义itemListenerMixin对象。

      import {debounce} from './utils';
      
      export const itemListenerMixin = {
        mounted() {
          let newrefresh = debounce(this.$refs.scroll.refresh, 100)
          this.itemImgListener = () => {
            newrefresh()
          } 
          this.$bus.$on('itemImgLoad', this.itemImgListener)
          console.log('我是混入中的内容');
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      第二,在Home/Detail中进行导入,并添加进mixins属性数列的数组中。

      import {itemListenerMixin} from '@/common/mixin.js'
      
      
       mixins: [itemListenerMixin],
      
      
      • 1
      • 2
      • 3
      • 4
      • 5

      22.标题和内容的联动效果

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

      • 在detail组件中监听主题的点击,获取index

        itemClick(index) {
                this.currentIndex = index;
                this.$emit('titleClick', index);
        },
        
        • 1
        • 2
        • 3
        • 4
        <detail-nav-bar class="detail-nav" @titleClick="titleClick" />
        
        • 1
      • 滚动到对应的主题

        titleClick(index) {
            // 第一个参数是x值,第二个参数是y值,第三个参数是时间200ms
            this.$refs.scroll.scrollTo(0, -this.themeTopYs[index], 200)
        }
        
        • 1
        • 2
        • 3
        • 4
        • 获取所有主题的offsetTop

        • 关键点:在哪里才能获取到正确的offsetTop

          • 1.created肯定不行,因为在created中压根不能获取元素

          • 2.mounted也不行,因为数据还没有获取到

          • 3.利用获取完数据的回调函数也不行,因为DOM还没有渲染完

          • 4.$nextTick也不行,因为图片的高度没有被计算在内

            /*
            //当数据加载完成时会触发一个nextTick函数
            this.$nextTick(() => {
                //根据最新的数据,对应的DOM是已经被渲染出来
                // 但是图片依然是没有加载完(目前获取到offsetTop不包含其中的图片)
                this.themeTopYs = [];
                this.themeTopYs.push(0);
                this.themeTopYs.push(this.$refs.params.$el.offsetTop);
                this.themeTopYs.push(this.$refs.comm	ent.$el.offsetTop);
                this.themeTopYs.push(this.$refs.recommend.$el.offsetTop);
                console.log(this.themeTopYs);
            })
            */
            
            • 1
            • 2
            • 3
            • 4
            • 5
            • 6
            • 7
            • 8
            • 9
            • 10
            • 11
            • 12
            • 13
          • 5.在图片加载后,获取的图片高度才是正确。因为会获取多次,所以可以利用防抖函数进行一个简单处理。

            imageLoad() {
                    this.$refs.scroll.refresh();
                    this.themeTopYs = [];
                    this.themeTopYs.push(0);
                    this.themeTopYs.push(this.$refs.params.$el.offsetTop);
                    this.themeTopYs.push(this.$refs.comment.$el.offsetTop);
                    this.themeTopYs.push(this.$refs.recommend.$el.offsetTop);
                    console.log(this.themeTopYs);
            },
            
            • 1
            • 2
            • 3
            • 4
            • 5
            • 6
            • 7
            • 8
            • 9
          • 防抖函数处理流程:

            • 1.在data中创建getThemeTopY:null

              getThemeTopY: null
              
              • 1
            • 2.在created中声明debounce函数

              this.getThemeTopY = debounce(() => {
                  this.themeTopYs = [];
                  this.themeTopYs.push(0);
                  this.themeTopYs.push(this.$refs.params.$el.offsetTop);
                  this.themeTopYs.push(this.$refs.comment.$el.offsetTop);
                  this.themeTopYs.push(this.$refs.recommend.$el.offsetTop);
                  console.log(this.themeTopYs);
              })
              
              • 1
              • 2
              • 3
              • 4
              • 5
              • 6
              • 7
              • 8
            • 3.在methods的imageLoad()方法中进行函数调用,这样就可以只输出一次themeTopYs

              imageLoad() {
                  this.$refs.scroll.refresh();
                  this.getThemeTopY();
              },
              
              • 1
              • 2
              • 3
              • 4

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

      1.普通做法

      this.currentIndex !== i && ((i < length - 1 && positionY >= this.themeTopYs[i] && positionY < this.themeTopYs[i+1]) || (i === length - 1 && positionY >= this.themeTopYs[i]))
      条件成立:this.currentIndex = i
      条件一:防止赋值的过程过于频繁
      条件二:((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.themeTopY[i])
      *判断大于等于:i === length - 1
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      23.底部工具栏的封装

      1.创建DetailBottomBar的子组件,对子组件进行封装

      <template>
        <div id="detail_bottom_bar">
          <div class="bottom_left">
            <div class="service">
              <i class="icon"></i>
              <span>客服</span>
            </div>
            <div class="shop">
              <i class="icon"></i>
              <span>店铺</span>
            </div>
            <div class="collect">
              <i class="icon"></i>
              <span>收藏</span>
            </div>
          </div>
          <div class="bottom_right">
            <div class="cart" @click="addToCart">
              加入购物车
            </div>
            <div class="buy">
              购买
            </div>
          </div>
        </div>
      </template>
      <script>
        export default {
          name: 'DetailBottomBar',
          methods: {
            addToCart() {
              this.$emit("addEvent");
            }
          }
        }
      </script>
      <style scoped>
        #detail_bottom_bar {
          font-size: 0.65rem;
          display: flex;
          position: fixed;
          background-color: #fff;
          bottom: 0px;
          left: 0;
          right: 0;
          height: 2.09rem;
          text-align: center;
          box-shadow: 0 -0.04rem 0.4rem gray;
        }
        .bottom_left {
          display: flex;
          flex: 1;
        }
        .bottom_left > div {
          flex: 1;
          border-right: 0.04rem solid rgba(128, 128, 128, 0.2);
        }
        .bottom_left .icon {
          display: block;
          background: url("./detail_bottom.png") 0 0/100%;
          /* background: url("@/assets/img/detail/detail_bottom.png") 0 0/100%; */
          width: 1rem;
          height: 1rem;
          margin: 0.12rem auto;
        }
        .service .icon {
          background-position: 0 -2.4rem;
        }
        .shop .icon {
          background-position: 0 -4.5rem;
        }
        .bottom_right {
          display: flex;
          flex: 1;
        }
        .bottom_right > div {
          flex: 1;
          line-height: 2.09rem;
        }
        .cart {
          background-color: rgb(255, 174, 0);
        }
        .buy {
          background-color: var(--color-tint);
          color: white;
        }
      </style>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87

      2.在Detail中导入子组件,并将元素标签放在与scroll同级的位置

      备注:rem定义

      rem(font size of the root element)是指相对于根元素的字体大小的单位。 1rem 等于根元素 htmfont-size,即只需要设置根元素的 font-size,其它元素使用 rem 单位时,设置成相应的百分比即可。

      24.给详情页添加返回顶部

      24.1常规方法:

      • 1.导入BackTop组件

        import BackTop from '@/components/content/backTop/BackTop'
        
        • 1
      • 2.在scroll组件同级位置添加back-top标签

        <back-top  @click.native="backClick" v-show="isShowBackTop"></back-top>
        
        
        • 1
        • 2
      • 3.定义backClick方法和contentScroll方法

      24.2混合Mixins:

      • 1.因为Home和Detail都有BackTop组件,代码几乎相同,所以可以使用mixins功能。编写mixin.js文件

        import BackTop from '@/components/content/backTop/BackTop'
        
        export const backTopMixin = {
          components: {
            BackTop
          },
          data() {
            return {
              isShowBackTop: false,
            }
          },
          methods: {
            backClick() {
              this.$refs.scroll.scrollTo(0, 0, 500)
            },
          }
        }
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
      • 2.在两个组件中导入backTopMixin

        import {itemListenerMixin, backTopMixin} from '@/common/mixin.js'
        
        mixins: [itemListenerMixin, backTopMixin],
        
        • 1
        • 2
        • 3

      25.加入购物车

      25.1Vuex的准备

      1.首先安装vuex

      npm installl vuex@3.1.0 --dev
      
      • 1

      2.在main.js文件中导入store并添加到Vue对象中

      import store from './store'
      
      new Vue({
        render: h => h(App),
        router,
        store
      }).$mount('#app')
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      3.在store文件夹下创建index.js文件,并将mutations,actions,getters进行一个封装

      import Vue from 'vue'
      import Vuex from 'vuex'
      
      import mutations from './mutations'
      import actions from './actions'
      import getters from './getters'
      
      // 1.安装插件
      Vue.use(Vuex)
      
      // 2.创建Store对象
      const state= {
        cartList: []
      }
      const store = new Vuex.Store({
        state,
        mutations,
        actions,
        getters
      })
      
      // 3.挂载Vue实例上
      export default store
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24

      25.2加入购物车

      通过下述步骤,即可实现监听添加购物车的商品数量

      1.在DetailBottomBar组件的加入购物车的标签中添加点击事件addToCart

       <div class="cart" @click="addToCart">
              加入购物车
       </div>
      
      • 1
      • 2
      • 3

      2.在addToCart方法中利用子传父的$emit将addCart事件导出

      addToCart() {
         this.$emit("addCart");
      }
      
      • 1
      • 2
      • 3

      3.Detail组件的detail-bottom-bar组件对addCart进行事件监听

      <detail-bottom-bar @addCart="addCart" />
      
      • 1

      4.在Detail组件中定义同名的addCart方法,获取需要展示的商品信息并使用commit调用store中的addToCart方法。

      addCart() {
              // 1.获取购物车需要展示的基本信息
              const product = {}
              product.image = this.topImages[0]
              product.title = this.goods.title;
              product.desc = this.goods.desc;
              product.price = this.goods.realPrice;
              // 2.获取iid,商品的唯一标识
              product.iid = this.iid;
      
              // 3.将商品添加到购物车
              this.$store.commit('addToCart', product);
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      5.store文件夹下的mutations文件定义addCounter和addToCart方法用来记录加入购物车的各类商品数量

      export default {
        //mutations唯一的目的就是修改state中的状态
        //mutations中的每个方法尽可能完成的事件比较单一一点
        addCounter(state, payload) {
          payload.count++
        },
        addToCart(state, payload) {
          state.cartList.push(payload)
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      6.store文件夹下的getters文件定义获取数据的计算属性,例如购物的商品种类

      export default {
        cartLength(state) {
          return state.cartList.length
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      25.3购物车界面

      1.进入Cart.vue组件

      2.介绍vuex的mapGetters功能,可以将getters的计算属性直接导入到Cart.vue中

      • 常规写法

        computed: {
          cartLength() {
              return this.$store.getters.cartLength
          }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
      • mapgetters用法

        import {mapGetters} from 'vuex'
        
        computed: {
        	//用法1
            ...mapGetters(['cartLength', 'cartList'])
            //用法2
            ...mapGetters({
            	length: 'cartLength',
            	list: 'cartList'
            })
        }
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12

      ions from ‘./actions’
      import getters from ‘./getters’

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

      // 2.创建Store对象
      const state= {
      cartList: []
      }
      const store = new Vuex.Store({
      state,
      mutations,
      actions,
      getters
      })

      // 3.挂载Vue实例上
      export default store

      
      **25.2加入购物车**
      
      通过下述步骤,即可实现监听添加购物车的商品数量
      
      1.在DetailBottomBar组件的加入购物车的标签中添加点击事件addToCart
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      2.在addToCart方法中利用子传父的$emit将addCart事件导出

      addToCart() {
         this.$emit("addCart");
      }
      
      • 1
      • 2
      • 3

      3.Detail组件的detail-bottom-bar组件对addCart进行事件监听

      <detail-bottom-bar @addCart="addCart" />
      
      • 1

      4.在Detail组件中定义同名的addCart方法,获取需要展示的商品信息并使用commit调用store中的addToCart方法。

      addCart() {
              // 1.获取购物车需要展示的基本信息
              const product = {}
              product.image = this.topImages[0]
              product.title = this.goods.title;
              product.desc = this.goods.desc;
              product.price = this.goods.realPrice;
              // 2.获取iid,商品的唯一标识
              product.iid = this.iid;
      
              // 3.将商品添加到购物车
              this.$store.commit('addToCart', product);
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      5.store文件夹下的mutations文件定义addCounter和addToCart方法用来记录加入购物车的各类商品数量

      export default {
        //mutations唯一的目的就是修改state中的状态
        //mutations中的每个方法尽可能完成的事件比较单一一点
        addCounter(state, payload) {
          payload.count++
        },
        addToCart(state, payload) {
          state.cartList.push(payload)
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      6.store文件夹下的getters文件定义获取数据的计算属性,例如购物的商品种类

      export default {
        cartLength(state) {
          return state.cartList.length
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      25.3购物车界面

      1.进入Cart.vue组件

      2.介绍vuex的mapGetters功能,可以将getters的计算属性直接导入到Cart.vue中

      • 常规写法

        computed: {
          cartLength() {
              return this.$store.getters.cartLength
          }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
      • mapgetters用法

        import {mapGetters} from 'vuex'
        
        computed: {
        	//用法1
            ...mapGetters(['cartLength', 'cartList'])
            //用法2
            ...mapGetters({
            	length: 'cartLength',
            	list: 'cartList'
            })
        }
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
      声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/132395
      推荐阅读
      相关标签
        

      闽ICP备14008679号