zoukankan      html  css  js  c++  java
  • 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 {
        42px;
       height: 42px;
      }
      </style>
    2. 在Home首页组件导入并注册:(需要注意的是这个组件是不需要随着滚动的)

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

      ![1587051148799](C:UsersNBAppDataRoamingTypora ypora-user-images1587051148799.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 {
      100%;
     border-bottom: 2px solid #333;
     height: 160px;
     margin: 20px 0;
     position: fixed;
     z-index: 10;
    }
    .nav {
      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 {
      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 {
      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

  • 相关阅读:
    解决com.xpand.. starter-canal 依赖引入问题
    缓存预热加入二级缓存
    缓存预热的实现
    ShardingSphere 中有哪些分布式主键实现方式?
    ShardingSphere 如何实现系统的扩展性
    如何系统剖析 ShardingSphere 的代码结构?
    SharingSphere的数据脱敏
    ShardingSphere的分布式事务
    Qt 事件过滤器原理(installEventFilter函数)
    Qt Event 以及 Event Filter 事件处理
  • 原文地址:https://www.cnblogs.com/doumian/p/12585904.html
Copyright © 2011-2022 走看看