Vue仿蘑菇街项目需求记录(B站codewhy老师)

项目需求分析

1 项目前期准备工作

1. cli搭建项目
2.css样式导入 公共资源图片等的导入
3.别名的配置和代码规则
    <script>
   module.exports = {
 configureWebpack: {
   resolve: {
     alias: {
       'assets': '@/assets',
       'common': '@/common',
       'components': '@/components',
       'network': '@/network',
       'views': '@/views',
    }
  }
}
  </script>
<script>
   root = true
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
</script>

2 底部导航栏封装

3 Home 顶部导航栏封装(因我在其他的页面中也有这个组件)

4 Home组件首页轮播图组件导入

5 安装axios 网络封装

    <script>
   import axios from 'axios'
export function request(config) {
 // 1.创建axios的实例
 const instance = axios.create({
   baseURL: 'http://123.207.32.32:8000/api/h8',
   //baseURL: 'http://106.54.54.237:8000/api/v1,
   timeout: 5000
})

 // 2.axios的拦截器
 // 2.1.请求拦截的作用
 instance.interceptors.request.use(config => {
   return config
}, err => {
   // console.log(err);
})

 // 2.2.响应拦截
 instance.interceptors.response.use(res => {
   return res.data
}, err => {
   console.log(err);
})

 // 3.发送真正的网络请求
 return instance(config)
}

6 在组件刚一创建的时候就发送网络请求,在created生命周期函数进行网络请求

轮播图数据展示/推荐数据展示/流行数据展示(feature)

7 Tab'Cont'rol业务分析 因为在其他页面还有使用 应该是在业务组件中

8 请求首页goods数据

点击3个不同的按钮请求不同的数据,首页列表数据请求保存

goods: {
   'pop': {page: 1, list:[]},
   'new': {page: 1, list:[]},
   'sell': {page: 1, list:[]}
}
//发送HomeGoods商品列表数据  需要进行参数传递  
export function getHomeGoods(type, page) {
 return request({
   url: '/home/data',
   params: {
     type,
     page
  }
})
}
//请求数据
getHomeGoods('pop',1).then(res => {
     console.log(res);
})
8.1如何点击不同tabControl 请求不同的数据

 // 2 请求homeGoods商品数据
   this.getHomeGoods('pop')
   this.getHomeGoods('new')
   this.getHomeGoods('sell')

getHomeGoods(type) {
     const page = this.goods[type].page + 1
     getHomeGoods(type,page).then(res => {
     // console.log(res.data.list);
     this.goods[type].list.push(...res.data.list)
     this.goods[type].page += 1
  })
}
8.2对商品列表分析,和业务相关 而不是公共组件了

 

然后把数据传递到goodsList goodsListItem

9 引入better-scroll 解决在移动端滚动卡顿问题

需求: 为了减少对better-sroll的依赖,对better-scroll 进行封装 而且在多数页面需要用到,在公共组件进行创建创建,将引用插件进行封装成一个组件,那里需要进行导入这个组件即可,后期维护也方便,逻辑也i清晰

better-scroll安装:
 1. 终端通过npm安装: npm install better-scroll --save 
<template>
 <div class="wrapper" ref="wrapper">
   <div class="content">
     <slot></slot>
   </div>
 </div>
</template>

<script>
import BScroll from 'better-scroll'
export default {
 data() {
   return {
     scroll: null
  }
},
 props: {
   probeType: {
     type: Number,
     default() {
       return 0
    }
  }
},
 mounted() {
   //1 创建BScroll实力对象
   this.scroll = new BScroll(this.$refs.wrapper, {
     click: true,
     probeType: this.probeType  //默认为不触发scroll滚动事件,那个页面需要进行传值即可,这样性能也大大提高
  })

滚动原理:

 

10 点击backTop按钮返回Home顶部

需求:

 

需求分析:
  1. 首先该功能会在页面滚动某个临界值进行显示或隐藏,其次不会随着页面滚动而滚动,所以简单方法进行fixed 固定定位

  2. 对页面需要进行实时监听滚动位置

  3. 该需求是在Home 首页组件进行监听,也就是需要对backTop这个组件进行监听

下面进行代码实现:
  1. 新建BackTop组件,引入一张箭头图片,如下

    <template>
     <div class="back-top" >
       <img src="~assets/img/common/top.png" alt="">
     </div>
    </template>
    <script>
    export default {
    }
    </script>
    <style scoped>
    .back-top {
     position: fixed;
     right: 8px;
     bottom: 50px;
    }
    .back-top img {
     width: 42px;
     height: 42px;
    }
    </style>
  2. 在Home首页组件导入并注册:(需要注意的是这个组件是不需要随着滚动的)

  3. 对这个组件进行事件点击监听 --------组件监听需要用到事件修饰符native(这里也可以在backTop组件内部进行点击事件,然后把这个事件发送出来在进行监听,这样做不过相对繁琐,还多写了代码)

    ![1587051148799](C:\Users\NB\AppData\Roaming\Typora\typora-user-images\1587051148799.png)

    然后通过ref拿到scroll这个组件调用scrollTo()这个方法

     backTopClick() {
         // console.log(this.$refs.scroll.scroll);
         // this.$refs.scroll.scroll.scrollTo(0, 0, 400);
         this.$refs.scroll.scrollTo(0, 0, 400);
    }

    //scrollTo(x, y, time, easing)
    //参数:
    //{Number} x 横轴坐标(单位 px)
    //{Number} y 纵轴坐标(单位 px)
    //{Number} time 滚动动画执行的时长(单位 ms)
    //{Object} easing 缓动函数,一般不建议修改,如果想修改,参考源码中的 ease.js 里的写法
    //返回值:无
    //作用:滚动到指定的位置,引用:https://ustbhuangyi.github.io/better-scroll/doc/zh-hans/api.html#scrolltox-y-time-easing
    //this.$refs.scroll.scroll.scrollTo(0, 0, 400)
    //此代码不方便阅读,对奇进行封装,把插件scrollTo()封装到组件scrollTo中 看代码吧 这样更清楚

4.对Home页面滚动进行实时监听,而我们封装的BScroll滚动插件是scroll这个组件,这里需要在scroll组件进行监听,然后把这个事件发送出了

 mounted() {
   //1 创建BScroll实力对象
   this.scroll = new BScroll(this.$refs.wrapper, {
     click: true,
     probeType: this.probeType
  }),
   //发送事件监听事件
   this.scroll.on('scroll', (position) => {
     // console.log(position);
     this.$emit('scroll',position)
  })
}

然后在首页接受这个事件,设置一个变量控制backTop显示隐藏

 contentScroll(position) {
     console.log(position);
     this.isShow = -position.y > 1300 ? true : false
}

 目前代码效果:

 

 

11 点击商品列表进入商品详情页

分析: 每个商品都有不同id,所以在点击每个goodslistitem,我们需要拿到这个唯一的商品id,去后台接口请求对应的商品数据,然后在进行展示,而且从home也跳转到详情页,我们使用路由就可以

1 对goodslistitem进行点击事件,并将商品id传过去
goodsItemClick() {
     console.log('111',this.goodsitem.iid);
     this.$router.push('/detail/'+ this.goodsitem.iid)
}

//路由跳转传参 可以有2种方式 1 动态路由 2 query方式
goodsItemClick() {
     // console.log('-----',this.goodsitem.iid)
     //动态路由
     this.$router.push('/detail/'+ this.goodsitem.iid)
     //query方式
     this.$router.push({
       path: '/detail',
       query: {
         iid: this.goodsitem.iid
      }
    })
  }

//路由配置方式也不一样
{
   path: '/detail/:iid', //动态路引
   component: Detail
},
{
   path: '/detail',    //query方式
   component: Detail
}

//获取iid方式
this.iid = this.$route.params.iid
this.iid = this.$route.query.iid
// 另外在url中显示也有差异 如下

2 拿到iid在network请求数据,为了方便后期维护 阅读,我们单独新建详情页网络请求 dedtail.js
import {request} from './request'

export function getDetail(iid) {
 return request({
   url: '/detail',
   params: {
     iid
  }
})
}
之后在Detail组件刚一创建出来created生命周期函数进行网络请求

12 上拉加载更多

在封装好的 BScroll 中 调用上拉加载更多事件,然后把这个事件发送出来,然后在home页 执行并调用finishPullUp()

 //上拉加载更多
   this.scroll.on('pullingUp',() => {
     // console.log('上拉加载更多');
     this.$emit('pullingUp')
     // this.scroll.finishPullUp()
  })

home页进行监听

 //上拉加载更多
   pullUpClick() {
     // console.log('上拉加载');
     this.getHomeGoods(this.currentType)
     this.$refs.scroll.finishPullUp()
  },

13 解决首页 详情页 滚动卡顿问题

  • 分析:
  • 主要原因是图片加载影响 better-scroll计算可滚动区域问题,不能滚动的问题主要是因为图片加载完成后,这个时候better-scroll得到可滚动区域height,没有刷新,所导致

  • 我们对每一张加载图片进行监听 ,只要有一张图片你加载完成,我们就重新计算一次Bscroll的高度

    • 如何监听图片加载完成

      • 原生js监听 img.load = () => {}

      • Vue提供的加载方式 @load=() => {}

    • 然后调用better-scroll的refresh()

      //refresh()
      //参数:无
      //返回值:无
      //作用:重新计算 better-scroll,当 DOM 结构发生变化的时候务必要调用确保滚动的效果正常。
  • 这里涉及到非父子组件事件通信,我们可以使用Vuex或者事件总线 这里我们先使用事件总线

  • 1 事件总线使用方法:
    • main.js入口文件 挂载到Vue原型实例上,全局任何地方都可以使用

    • Vue.prototype.$bus = new Vue()
    • 发送事件

    • this.$bus.$emit('itemImageLoad',params)// 事件函数, 参数
    • 接收事件

    • this.$bus.$on(""callback)  // 事件函数 回调函数  一般是组件运行阶段,mounted函数中执行
    • 取消事件

    • this.$bus.$off(""callback)  // 事件函数 回调函数  取消事件是组件是否有keep-alive  有选择deactivated 否destroyed中
  • 2 使用Vuex进行非父子组件通信
    • 先创建·1一个变量,对这个变量植进行watch监听,每加载一张图片,变量植发生改变

14 实现详情页上下联动联动效果,点击对应标题滚动到对应的位置和内容滚动显示正确标题

  • 效果图:

  • 点击标题跳转到对应位置

    • 每给标题对应一个值,只要我们拿到这个值,在点击标题的时候,调用scrollTo就可以滚 动到对应位置,所以我们新建一个数组,来保存offsetTop

    • 首先对详情页 标题 进行监听

    • 获取 详情页 所有组件的offsetTop

      • 如何获取 不同组件 的offsetTop

        • 首先我们会想到在mounted中获取offsetTop,然而获取的值完全不对,甚至出现了undefined

        • mounted() {
             console.log('-----')
             this.themeTops = [];
             this.themeTops.push(0);
             this.themeTops.push(this.$refs.paramsinfo.$el.offsetTop);
             this.themeTops.push(this.$refs.commentinfo.$el.offsetTop);
             this.themeTops.push(this.$refs.recommend.$el.offsetTop);
             console.log(this.themeTops);
          },
          //[0, undefined, 498, 626, __ob__: Observer]
        • 值获取不正确,首先想到的是图片影响,之前我们有对图片的加载进行监听,图片加载完成后,我们再获取offsetTop值

        • //对详情页图片监听
             detailImageLoad() {
               //获取参数 评论 推荐的offsetTop值
               this.themeTops = [];
               this.themeTops.push(0);
               this.themeTops.push(this.$refs.paramsinfo.$el.offsetTop);
               this.themeTops.push(this.$refs.commentinfo.$el.offsetTop);
               this.themeTops.push(this.$refs.recommend.$el.offsetTop);
               console.log(this.themeTops);
                 
               this.$refs.scroll.refresh();
            },
          // 打印结果 [0, 9289, 10031, 10180, __ob__: Observer]
        • 然后调用scrollTo方法

        • titleClick(index) {
              // console.log(index);
              this.$refs.scroll.scrollTo(0, -this.themeTops[index], 300)
          },
    • 内容滚动显示正确标题

      • 分析: 我们需要实时对页面滚动进行监听 ,在better-scroll中把scroll这个事件发送出来,我们在Detail进行接受这个事件

      • 通过获取到posiiton.y 和之前获取到 this.themeTops这个数组中4个值进行比较 然后动态改变标题

14 Vue原生上下联动或左右联动效果

  • 上下联动或左右联动效果非常常见 但是我们平时大多使用封装好的UI工具库,直接按需导入安装,这里我们不使用插件和UI

  • 这里我们主要简单建立三个组件

  • 1 点击ShopTitles中小titles 让活跃title显示背景颜色 其他不变,这个相对简单

  • 2 点击不同title让右边的Shop组件跳转到不同位置

    • 1 首先要监听SHop组件的滚动,其次调用JS原生滚动方法 scroll

    • 2 对Shop组件添加滚动事件 需要注意的是 addEventListener第三个参数为true  ,

    • 3 父元素 overflow不能是hidden,而是scroll,要不然子元素滚动不了 (当然子元素内容高度高于父元素高度)

//父组件
<template>
 <div class="category">
  <div class="nav-bar" >
    <div class="nav"> 我是顶部的商品展示区域</div>
  </div>
  <shop-titles :titles="titles" @titleClick="titleClick" ref="titles"/>
  <shop ref="shop" @shopOpsitons="shopOpsitons" />
 </div>
</template>

<script>
import Shop from 'components/common/shop/Shop'
import ShopTitles from 'components/common/shop/ShopTitles'
export default {
 name: 'Category',
 data() {
   return {
     titles:['热卖','特色精品','精选热菜','热卖','特色精品','精选热菜'],
     shopScrollTop:[0, 300, 600, 900, 1200, 1500],
     current: 0
  }
},
 components: {
   Shop,
   ShopTitles
},
 mounted() {
 
},
 methods: {
  titleClick(index) {
    this.$refs.shop.$el.scrollTo({left: 0, top: this.shopScrollTop[index],behavior: 'smooth'})
  },
 shopOpsitons(saveY) {
   // console.log(saveY)
   const Max = Number.MAX_VALUE;
   this.shopScrollTop.push(Max);

   const length = this.shopScrollTop.length;
   for( let i = 0; i < length; i++) {
     if((this.current !== i)&&(saveY >= this.shopScrollTop[i] && saveY < this.shopScrollTop[i+ 1])) {
       this.current = i
       this.$refs.titles.currentIndex = this.current
    }
  }
   
}
}
}
</script>
<style scoped>
.category {
 height: 100vh;
}
.nav-bar {
 width: 100%;
 border-bottom: 2px solid #333;
 height: 160px;
 margin: 20px 0;
 position: fixed;
 z-index: 10;
}
.nav {
 width: 90%;
 height: 160px;
 background-color: #ccc;
 position: fixed;
 top: 10px;
 left: 0;
 right: 0;
 padding: 10px 10px;
 margin: 0 auto;
 border-radius: 12px;
}
</style>
<template>
    <div class="shopitem" ref="shopitem">
    <div class="content">
      <ul>
       <li>请设置内容高度</li>
     </ul>
    </div>
   </div>
</template>
<script>
export default {
 data() {
   return {
     currentIndex: 0,
     saveY: null
  }
},
 mounted() {
   window.addEventListener('scroll', this.shopScroll,true)
},
 methods: {
   shopScroll() {
     let shopScrTop = document.querySelector('.shopitem')
     //获取滚动出去的距离
     let saveY = shopScrTop.scrollTop
     // console.log(saveY)
     this.$emit('shopOpsitons', saveY)
  },
},
destoroyed() {  window.removeEventListener('scroll', this.shopScroll) } 
}
</script>

<style scoped>
.shopitem {
 width: 75%;
 background-color: #4ca2cd;
 position: fixed;
 top: 200px;
 right: 0;
 bottom: 49px;
 overflow: scroll;
 border-radius: 10px;
}
</style>
<template>
   <div class="titles">
     <div v-for="(item,index) in titles"
         :key="index"
         class="titles-item"
         @click="titlesClick(index)"
         :class="{active: currentIndex === index}"
         >
       <div>{{item}}</div>
     </div>
   </div>
</template>
<script>
export default {
 data() {
   return {
     currentIndex: 0
  }
},
 props: {
   titles: {
     type: Array,
     default() {
       return []
    }
  }
},
 methods: {
   titlesClick(index) {
     this.currentIndex = index
     this.$emit('titleClick',index)
  }
}
}
</script>

<style scoped>
.titles {
 width: 25%;
 position: fixed;
 top: 200px;
 left: 0;
}
.titles-item {
 padding: 10px 6px;
 border-radius: 10px;
}
.active {
 background-color: #4ca2cd;
 color: #fff
}
</style>

效果: 

但是有bug 还没有找到了。。。。。

15 mixin混入

 

  • 这里主要是对事件监听代码进行抽取

  • 定义mixin.js

  • import {debounce} from './Utils'
    export const itemListenerMixin = {
     data(){
       return {
         itemImgListener: null
      }
    },
     mounted() {
       let refresh = debounce(this.$refs.scroll.refresh, 500)
       this.itemImgListener = () => {
        refresh()
      }
       this.$bus.$on('itemImageLoad',  this.itemImgListener)
       // console.log('我是混入内容')
    }
    }
  • 使用方法 先导入

  • import {itemListenerMixin} from 'common/mixin'
    // import Toast from 'components/common/toast/Toast'
    export default {
     name: 'Detail',
     mixins: [itemListenerMixin],
    }

16 把Toast组件封装成一个插件 (这里主要是通过 Vue.prototype 上实现)

  • 1 在入口文件 main.js

  • //导入toast插件
    import toast from 'components/common/toast'
    //安装toast插件
    Vue.use(toast)
  • 2 在toast文件(里边有Toast组件)中,新建index.js文件

  • import Toast from './Toast.vue'
    const obj = {}
    obj.install = function (Vue) {
     console.log('执行了这个函数')
     //1 创建组件构造器 并传入组件对象
     const toastConstructor = Vue.extend(Toast)
     //2 通过new方式 根据组件构造器,创建出新的组件对象
     const toast = new toastConstructor()
     //3 将组件对象手动挂载在某一元素上
     toast.$mount(document.createElement('div'))
     //4 toast.$el对应是div
     document.body.appendChild(toast.$el)
     //把这个插件挂载到Vue原型上
     Vue.prototype.$toast = toast
    }
    export default obj
  • 使用方法(这里Toast组件已经把方法封装好了)

  • this.$toast.show(res, 1800)

    17 性能优化 频繁发生操做,进行防抖节流操做

    完结 

     

视频地址:https://www.bilibili.com/video/BV15741177Eh?from=search&seid=13265493894652667210

posted @ 2020-04-16 23:55  性感的沙皮  阅读(4215)  评论(1编辑  收藏  举报