购物街项目(3)

四、开始完成商品详情页的功能

4.1 创建商品详情页组件和路由,并实现跳转

  1. 创建 detail 组件
  2. 配置动态路由,由于点击每个 goodsitem 进入的商品详情页不一样,要带上商品标记 iid
  3. 点击是在 GoodsItem 组件上触发的,需要在组件中设置点击事件,来跳转到对应的商品详情页
goodsItemClick() {
      this.$router.push("/detail/" + this.goodsItem.iid);
    }
  1. detail 组件中获取到从路由中传过来的 iid,并保存下来
this.iid = this.$route.params.iid

4.2 完成详情页头部的导航组件

  1. 使用公共组件 navbar 并再次封装成详情页的头部 DetailNavBar 组件
  2. v-for 遍历 title 里面的数据,让头部内容显示正确
  3. 通过绑定 :class=“{active: currentIndex === index}” 属性对象语法,来让判断哪个标题高亮显示
  4. 通过点击 title 名来切换高亮显示不同的标签,绑定点击事件:@click=“titleClick(index)”
  5. 使用左侧的插槽实现箭头返回的功能

4.3 进入详情页请求详情页数据

  1. 配置详情页的请求和方法的配置文件 detail.js
     import { request } from './request'
       
     export function getDetailData(iid) {
       return request({
         url: "/detail",
         params: {
           iid
         }
       })
     }
    
  2. Detail 组件中导入详情页请求方法 getDetailData()
  3. method 中配置好调用该请求的方法 getDetailData
  4. 在生命周期函数 created 中调用该请求的方法 getDetailData,并查看可否请求到返回的数据

注意: 由于 App 组件中用了 导致每次进入 Detail 组件页面时 created() 函数不会执行,也就意味着不会去获取新的 iid ,从而无法拿到详情页新的数据。因此需要在 App 组件的 标签上采用 exclude=“Detail” 来将 Detail 组件排除在外,这样在用户每次进入详情页时,就会重新执行 created() 生命周期函数中的代码去获取并存储新的 iid

4.4 完成详情页轮播组件

  1. 初始化轮播图片的数据 topImages: [] ,并从 getDetailData 方法中获取到轮播的数据,将其赋给 this.topImages
  2. 创建详情页轮播的子组件 DetailSwiper.vue ,并在 Detail 父组件中导入、注册和使用
  3. 通过在 组件上绑定轮播图片属性,传给子组件的 props
  4. 使用 Vant ui组件中的 swipe 轮播
    • 安装 Vant 组件
      npm i vant -S
    • 在 main.js 中引入 swiper 轮播功能
     import { Swipe, SwipeItem } from 'vant';
     import 'vant/lib//index.css';
         
     Vue.use(Swipe);
     Vue.use(SwipeItem);
    
  5. topImages 中的数据用 swipe 组件进行遍历实现,并调试样式使轮播正常
     
       
         
       
     
    

4.5 完成详情页基本信息(标题、价格、销量等)

  1. 创建商品详情信息组件 DetailBaseInfo.vue ,并引入、注册和使用
  2. 创建获取商品信息的 goodsInfo 对象
    • 使用构造函数new一个 Goods 的对象
    • 通过创建类对象 class Goods {} 来将所有需要的商品信息拼凑到一起
    • 通过构造方法 constructor(){} 初始化 class 创建的对象
       export class Goods {
         constructor(itemInfo, columns, services) {
           this.title = itemInfo.title
           this.desc = itemInfo.desc
           this.newPrice = itemInfo.price
           this.oldPrice = itemInfo.oldPrice
           this.realPrice = itemInfo.lowNowPrice
           this.discount = itemInfo.discountDesc
           this.columns = columns
           this.services = services
         }
       }
    
  3. 再将拼凑好的商品信息对象 goodsInfo 通过组件上的自定义属性传递给子组件中 props 中的商品信息 goodsInfo 对象
  4. template 中创建商品信息的结构,将 goodsInfo 中的数据显示在对应的位置,并调试好样式
  5. 在组件最外层的 div 标签上通过 v-if 指令判断对象中 keys 的长度,来判断 goodsInfo 是否有数据,
       v-if="Object.keys(goodsInfo).length !== 0"
    

4.6 完成详情页店铺信息

  1. 创建组件 DetailShopInfo.vue 引入、注册并使用
  2. 创建获取商店信息的数据对象 shopInfo
    • 使用构造函数 new 一个 Shop 对象
    • 通过创建一个 class Shop{} 对象来将所有的数据拼凑到一起
    • 通过构造方法 constructor(){} 初始化 class 创建的对象
         export class Shop {
           constructor(shopInfo) {
             this.name = shopInfo.name
             this.shopLogo = shopInfo.shopLogo
             this.cSells = shopInfo.cSells
             this.cGoods = shopInfo.cGoods
             this.score = shopInfo.score
             this.cFans = shopInfo.cFans
           }
         }
      
  3. 在将创建好的的 Shop 对象赋值给 this.shopInfo 后,在 data(){} 中初始化一个 shopInfo 对象
  4. 将父组件 shopInfo 对象通过在组件标签中绑定属性的方式将数据传递给子组件中的 props
  5. template 中创建商品信息的结构,将 goodsInfo 中的数据显示在对应的位置,并调试好样式
    • 其中总销量是 5.2 万,需要用 vue 的过滤器将实际的52157转换成 5.2万
    • 商店评价部分的内容可以用表格 标签和 v-for 遍历数据,来实现结构样式

      4.7 完成详情页商品信息穿着效果

      1. 创建组件 DetailGoodsInfo.vue 引入、注册和使用
      2. 在详情页 Detail.vue 组件中获取详情页请求中的商品穿着效果图信息数据 this.detailInfo ,并在 data 中初始化自定义一个 detailInfo 对象,用来存储获取到的数据
      3. 通过 prop 在父组件 Detail.vue 的子组件标签上定义一个 detail-info=“”属性来将,父组件的数据传递给子组件
      4. 在子组件 DetailGoodsInfo 中的 props 中自定义一个 detailInfo ,并拿到 detailInfo 中的详细数据
      5. 完成 DetailGoodsInfo 组件中解构数据样式的渲染
        • 注意:需要使用 v-if 指令先判断子组件是否有获取到数据,再进行页面解构样式的渲染 v-if="Object.keys(detailInfo).length !== 0
      6. 引入 batter-scroll 组件来使页面可以手动滚动
        • 需要在 scroll 组件标签上设置 content 属性
        • content 上设置样式,采用 .content { height: calc(100% - 44px) }
      7. 防止用户在滚动时图片还未加载出来的情况
        • 监听传过来的 detailInfo 对象中图片长度的变化,将最终获取到的图片长度,保存到 imagesLength
        • 判断图片是否加载完成,之后再进行一次回调

      4.8 完成商品参数信息的内容

      1. 创建组件 DetailParamsInfo.vue 引入、注册和使用
      2. 在父组件 Detail.vue 中获取参数信息数据 this.paramsInfo ,并将其保存到 data
        • 使用构造函数 new 一个 Params 对象
        • 通过创建一个 class Params{} 对象来将所有的数据拼凑到一起
        • 通过构造方法 constructor(){} 初始化 class创建的对象
             export class Params {
               constructor(paramInfo, paramRule) {
                 this.rulekey = paramRule.key
                 this.tables = paramRule.tables
                 this.infokey = paramInfo.key
                 this.set = paramInfo.set
                 this.image = paramInfo.image ? paramInfo.images[0] : '';
               }
             }
          
      3. 通过 prop 父子组件传值的方式将 paramsInfo 参数数据传递给子组件 DetailParamsInfo
      4. 在组件 DetailParamsInfo 中完成结构样式的实现

      4.9 完成商品评论部分的内容

      1. 创建组件 DetailCommentInfo.vue 引入、注册和使用
      2. 在父组件 Detail 中获取评价部分的数据 commentInfo ,并将其保存到 data
      3. 通过 prop 父子组件传值的方式将 commentInfo 参数数据传递给子组件 DetailCommentInfo
      4. 在组件 DetailCommentInfo 中渲染数据和样式
      5. 由于用户评价部分有涉及到时间的数据,需要采用过滤器将时间戳转换成年月日
        • commen 文件夹中的 utils.js 中实现将时间戳转换成 yyyy-MM-dd hh:mm:ss 的字符串
        • 将其引入组件 DetailCommentInfo
        • 在组件中的 filters 过滤器中实现转换功能
        • 将实际的时间戳转成 Date 对象
        • date 进行格式化
        • 再在时间数据后接入 {{ commentInfo.created | showDate }}
             filters: {
               showDate(value) {
                 // 1.将时间戳转成Date对象
                 const date = new Date(value * 1000);
           
                 // 2. 将 date 进行格式化
                 return formatDate(date, "yyyy-MM-dd hh:mm:ss");
               }
             }
          

      4.10 完成商品详情页推荐部分的内容

      1. 在详情页请求数据文件 detail.js 中创建推荐请求 getRecommd 方法
      2. 在父组件 Detail 中调用 getRecommd 方法,来获取推荐数据 recommendsInfo
      3. recommendsInfodata 中初始化,并将获取到的数据存储到 recommendsInfo
      4. 由于推荐数据的样式结构和首页 GoodsList 组件的样式结构一致,因此可以直接复用该组件
      5. 引入 GoodsList 组件,注册、使用
      6. 采用 prop 父子组件传值的方法将 recommendsInfo 数据传给 GoodsList 子组件中的 goods

      4.11 解决商品详情页图片加载刷新影响首页的问题

      1. 由于商品详情推荐信息部分使用的是 GoodsListGoodsItem 组件,而组件中有事件总线方法 $bus 来监听并将图片加载完的事件 itemImageLoad 发送给首页 Home ,导致商品详情页中的图片刷新时会影响首页
      2. 因此需要在首页 deactivated() 函数中,取消全局事件监听 this.$bus.$off()
      3. 先在 data 中初始化一个 itemImgListener 事件监听方法名,并将首页监听事件 $on() 中的刷新方法保存到 itemImgListener
            const newRefresh = debounce(this.$refs.scroll.refresh, 100);    
            // 监听事件总线中的事件变化,即 item 中图片加载完成
               // 对监听事件进行保存, 保存到 itemImgListener 中
               this.itemImgListener = () => {
                 newRefresh();
               };
               this.$bus.$on("itemImageLoad", this.itemImgListener);
        
      4. 由于上面的方法在组件 Home 和 Detail 中的 mounted(){} 中都有使用,因此需要采用混入 mixin 方式将这些方法内容抽离合并到 mixin.js 文件中
        全局混入: https://cn.vuejs.org/v2/guide/mixins.html
           import { debounce } from "../common/utils";
           
           export const itemListenerMixin = {
             data() {
               return {
                 itemImgListener: null,
                 newRefresh: null
               }
             },
             mounted() {
               // 1.图片加载完的事件监听
               // 通过防抖函数 this.$refs.scroll.refresh 的调用来防止 newRefresh() 函数被频繁调用
               this.newRefresh = debounce(this.$refs.scroll.refresh, 100);
               // 监听事件总线中的事件变化,即 item 中图片加载完成
               // 对监听事件进行保存, 保存到 itemImgListener 中
               this.itemImgListener = () => {
                 this.newRefresh();
               };
               this.$bus.$on("itemImageLoad", this.itemImgListener);
             },
           }
        
      5. 然后在 Home 和 Detail 组件中引入混入方法
           import { itemListenerMixin } from "../../common/mixin";
           
           export default {
             mixins: [itemListenerMixin],
           }
        

      4.12 完成点击头部标题跳转对应模块位置

      1. 在子组件 DetailNavBar 中通过 $emit 方法把点击标题的动作和参数 titleClick 传递给父组件 Detail
      2. 在父组件 Detail 中的 DetailNavBar 标签上绑定点击该事件的动作 @titleClick 的方法 titleClick(index) 来监控点击的是哪个标题
      3. titleClick 方法需要调用 scrollTo 方法来实现跳转到标题对应的组件标签所在的 y 轴的位置 this.$refs.scroll.scrollTo(0, -y, 100)
        • data 中给 y 值定义一个初始化属性值叫 titleTopYs: [0, 1000, 2000, 3000]
        • 然后点击详情页顶部标题,看检验是否能正常跳转
      4. 然后获取每个标题对应的实际组件所在位置的 offsetTop ,在替代 titleTopYs 数组中的具体值(即将每个 offsetTop 值通过 push 存入 titleTopYs 中)
      5. 通过给每个组件绑定一个 ref 属性来获取到每个组件的 offsetTop
      6. 由于在获取 offsetTop 时需要先拿到数据,并且也在页面渲染完了数据,否则获取到的 offsetTop 会出现 undefined 的情况。因此需要在 updated() 钩子函数中去进行。
        • 方法二:在获取数据的方法中去调用 this.$nextTick(() => {}) 方法,并在该方法中去获取这些组件的 offsetTop
        • 注意: 方法二中根据最新的数据,对应的DOM是已经被渲染出来,但图片依然图片依然是没有加载完完成,图片的高度没有完成包含在其中

      4.13 滚动页面后头部标题自动切换到当前位置对应的标题

      1. 需要知道滚动的位置,当 probeType 为 3 的时候,不仅在屏幕滑动的过程中,而且在 momentum 滚动动画运行过程中实时派发 scroll 事件,即在 Scroll 组件上绑定属性 probe-type
      2. Scroll 组件中监听事件和位置参数 position 发送给父组件 Detail 中的绑定的 scroll 属性上的方法 contentScroll
      3. 获取滚动的高度 positionY 的值
      4. positionY 值和 titleTopYs 值进行对比,来判断当前是处在哪个标题的位置
        • positionY 在0到10762之间时, index = 0
        • positionY 在10762到11573之间时, index = 1
        • positionY 在10762到11762之间时, index = 2
        • positionY 在大于11762之间时, index = 3
      5. 完成判断逻辑,并将滚动位置的 currentIndex 和标题的 currentIndex 同步
      6. 方法一:普通判断方法
           let titleLength = this.titleTopYs.length;
           for (let i = 0; i < titleLength; i++) {
             if (
               this.currentIndex !== i &&
               ((i < titleLength - 1 &&
                 positionY >= this.titleTopYs[i] &&
                 positionY < this.titleTopYs[i + 1]) ||
                 (i === titleLength - 1 && positionY >= this.titleTopYs[i]))
             ) {
               this.currentIndex = i;
               console.log(this.currentIndex);
               // 将滚动判断的currentIndex赋值给DetailNavBar组件中的currentIndex来改变标题的切换
               this.$refs.detailnavbar.currentIndex = this.currentIndex;
             }
           }
        
      7. 方法二:在 titleTopYs 最后加一个最大值,将最后一个大于判断变成区间判断
           let titleLength = this.titleTopYs.length;
           for (let i = 0; i < titleLength; i++) {
             if (
               this.currentIndex !== i &&
               i < titleLength - 1 &&
                 positionY >= this.titleTopYs[i] &&
                 positionY < this.titleTopYs[i + 1]
             ) {
               this.currentIndex = i;
               console.log(this.currentIndex);
               // 将滚动判断的currentIndex赋值给DetailNavBar组件中的currentIndex来改变标题的切换
               this.$refs.detailnavbar.currentIndex = this.currentIndex;
             }
           }
        

      4.14 完成详情页底部商品动作栏

      1. 采用 Vant 组件中的 (GoodsAction 商品导航) 组件来完成
      2. 先引入该UI组件对应的库
      3. 创建组件 DetailGoodsAction.vue 并在 Detail 组件中导入、注册和使用
      4. 调整 DetailGoodsAction.vue 组件中的样式参数
      5. 并在加入购物车的标签上绑定点击事件方法

      4.15 完成详情页回到顶部的按钮

      1. 使用之前已经封装好的 BackTop.vue 组件
      2. 导入、注册并使用
      3. 由于首页也有该组件和功能,所有采用混入的方式来实现
      4. mixin.js 中定义一个混入对象 backTopMixin
      5. Detail.vueHome.vue 组件中用到的对象
        • data 中定义的对象 isShowBackTop
        • methods 中用到的方法 backClick(){}listenShowBackTop(positon){}
        • mixin.js 中同样要引入 BackTop 组件
        • 同样要在 components 中注册 BackTop
      6. 再在 Detail.vueHome.vue 组件中引用混入,并使用 mixin: [backTopMixin]

      4.16 完成添加购物车的功能

      1. 在组件 DetailGoodsAction 中的添加购物的标签上绑定点击事件 @click=“onClickButton”
      2. 通过 $emitaddCart() 方法发送到父组件 Detail
      3. 并在父组件的 DetailGoodsAction 标签上绑定 addCart 方法来调用 addToCart()
      4. 通过 addToCart 方法将商品数据添加到购物车
      5. 先获取添加到购物车的商品信息 products
      6. 由于 Detail 组件与购物车组件 Cart 组件直接没有任何父子关系,其组件直接的传值需要使用 vuex 来进行全局的状态管理
        • 先安装 vuex 插件工具 npm i vuex --save
        • 创建 store 文件夹,并新建一个 index.js 的文件,其中用来配置 vuex 中初始化一个 Store 对象
             import Vue from 'vue'
             import Vuex from 'vuex'
             
             // 1.使用Vuex
             Vue.use(Vuex)
             // 2.初始化Store实例
             const store = new Vuex.Store({
               state: {
                 cartList: []
               },
               mutations: {},
               getters: {},
               actions: {}
             })
             // 3.将store导出给vue中去挂载
             export default store 
          
        • 在 main.js 中引入 store ,并将其挂载到 vue 实例上
             import store from './store'
             
             new Vue({
               render: h => h(App),
               store, // 4.挂载 store
             }).$mount('#app')
          
      7. products 购物车数据,提交到 vuex 中的 state 上的商品列表 cartList 进行存储
        • 通过 this.$store.commit 提交 addToCartList 方法给 store 中的 mutation
        • mutation 中通过提交的方法 addToCartList 来变更 state 中的状态

      你可能感兴趣的:(购物街项目(3))