【Vue项目复习笔记】详情页的展示

一、跳转详情页并携带iid

1、点击商品跳转到详情页
当我们点击GoodListItem中的每一个item,就跳转到对应的页面。我们首先要做的就是监听GoodsListItem的点击
在GoodsListItem组件中

 <div class="goods-item" @click="itemClick">

在methods中跳转到对应的详情页

 itemClick(){
      //跳转到详情页
    }

我们可以先给详情页配置一个路由
新建datail文件夹,新建Detail.vue,然后在router的index.js里面配置与路由相关的信息

 itemClick(){
      //跳转到详情页
      this.$router.push('/detail')
    }

但是我们在跳转详情页的时候还需要传递一些参数,需要把点击商品的id拿到,然后根据这个id去请求更加详细的数据。这个id也就是下面对应商品的iid
在这里插入图片描述
我们用动态路由的方式传递这个iid参数

 itemClick(){
      //跳转到详情页
      // this.$router.push('/detail')
      this.$router.push('/detail/'+this.goodsItem.iid)
    }

然后在我们的Detail.vue中就要获取这个iid,根据这个iid去请求数据

 data(){
    return{
      iid:null
      }
      }
 created() {
    //保存传入的id
    this.iid=this.$route.params.iid
    }

二、详情页导航栏的封装

新建childComps,新建DetailNavBar.vue,将我们详情页的导航栏相关内容都写在此处

<template>
<div>
  <nav-bar>
    <div slot="left" @click="backClick" class="back">
      <img src="~assets/img/common/back.svg" alt="" >
    </div>
    <div slot="center" class="title">
      <div v-for="(item,index) in titles" class="title-item"
      :class="{active:index===currentIndex}"  @click="titleClick(index)"
      >{{item}}</div>
    </div>
  </nav-bar>
</div>
</template>

<script>
import navBar from '@/components/common/navbar/NavBar';
export default {
  name: 'DetailNavBar',
  components:{
    navBar
  },
  data(){
    return {
      titles:['商品','参数','评论','推荐'],
      currentIndex:0
    }
  },
  methods:{
    titleClick(index){
      this.currentIndex=index;
      this.$emit('titleClick',index)
    },
    backClick(){
      this.$router.go(-1)
      //go(-1)相当于back
    }
  }
};
</script>

<style scoped>
.title {
  display: flex;
  font-size: 13px;
}

.title-item {
  flex: 1;
}

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

.back img {
  margin-top: 12px;
}
</style>

将该组件导入到Deatail组件中去

二、数据的请求以及轮播图的展示

根据iid去请求数据,在network里新建detail.js

import { request } from "./request";

export function getDetail(iid) {
  return request({
    url: "/detail",
    params: {
      iid,
    },
  });
}

然后将这个getDetail方法导入到Detail.vue中,我们在created中打印请求到达数据

 created() {
    //保存传入的id
    this.iid = this.$route.params.iid
    //根据拿到的iid请求详细数据
    getDetail(this.iid).then(res => {
      console.log(res);
    })
  },

在这里插入图片描述
我们要拿到轮播图的数据,在data()中保存一个 topImages

  data() {
    return {
      iid: null,
      topImages:[]
    }
  },
 getDetail(this.iid).then(res=>{
      console.log(res);
      this.topImages=res.result.itemInfo.topImages
      }

得到下面结果:
在这里插入图片描述

新建DetailSwiper.vue,将轮播图相关的存到里面
先要接收轮播图的数据

 props:{
    topImages:{
      type:Array,
      default(){
        return []
      }
    }
  }

然后是在Detail.vue导入DetailSwiper.vue,注册,引用,并传值

  <detail-swiper :top-images="topImages"></detail-swiper>

DetailSwiper.vue如下:

<template>
    <swiper class="detail-swiper">
      <swiper-item v-for="item in topImages">
        <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>
.detail-swiper {
  height: 300px;
  overflow: hidden;
}
</style>

结果如下:
请添加图片描述
我们会发现一个问题:不管点击哪一个item,轮播图里面的内容没有发生变化
请添加图片描述
之所以造成这样的原因是因为我们的数据没有发生变化,也就是我们每次进入详情页,它里面的内容都keep-alive了,但是我们希望它每次进入都要重新获取iid,重新请求新的数据,所以我们在App.vue里面要将Detail排除在外

<div id="app">
    <keep-alive exclude="Detail">
      <router-view></router-view>
    </keep-alive>
    <main-tab-bar></main-tab-bar>
  </div>

结果如下:
请添加图片描述
有点轮播图只显示一张图片,是因为它就只有一张图片显示。

三、商品基本信息的展示

这部分数据仍然在通过iid拿到的数据里面
在这里插入图片描述
因为需要拿到的数据很多,所以我们需要用一个对象把这些东西做一个整合。然后组件面向这一个对象就可以了。
在我们的detail.js中

//导出一个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.discount = itemInfo.discountDesc;
    this.columns = columns;
    this.services = services;
    this.realPrice = itemInfo.lowNowPrice;
  }
}

然后导入到Detail.vue中,在data()里面定义一个空的goods对象

goods:{}

然后在我们的

created() {
    getDetail(this.iid).then(res=>{
      ...
      // 获取商品信息
      this.goods=new Goods(data.itemInfo,data.columns,data.shopInfo.services)

拿到的都是我们想要的数据
在这里插入图片描述
我们在DetailBaseInfo.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>

然后在Detail.vue中将该组件进行导入,注册,并传值

<detail-base-info :goods="goods"></detail-base-info>

其结果如下:
请添加图片描述
重点是:对数据的整合。

四、店铺信息的解析和展示

在detail.js中也对这些数据进行一个整合

export class Shop {
  constructor(shopInfo) {
    this.logo = shopInfo.shopLogo;
    this.name = shopInfo.name;
    this.fans = shopInfo.cFans;
    this.sells = shopInfo.cSells;
    this.score = shopInfo.score;
    this.goodsCount = shopInfo.cGoods;
  }
}

然后到Detail.vue中创建商铺信息,在这之前,在data里面新建一个shop信息

shop:{},

然后在created里面的getDetail函数里面,创建店铺信息对象

  this.shop=new Shop(data.shopInfo)

在这里插入图片描述
可以看到我们已经拿到了shop对象了,下面我们就可以封装独立的组件了。新建DetailShopInfo.vue

<template>
  <div class="shop-info">
    <div class="shop-top">
      <img :src="shop.logo">
      <span class="title">{{shop.name}}</span>
    </div>
    <div class="shop-middle">
      <div class="shop-middle-item shop-middle-left">
        <div class="info-sells">
          <div class="sells-count">
            {{shop.sells | sellCountFilter}}
          </div>
          <div class="sells-text">总销量</div>
        </div>
        <div class="info-goods">
          <div class="goods-count">
            {{shop.goodsCount}}
          </div>
          <div class="goods-text">全部宝贝</div>
        </div>
      </div>
      <div class="shop-middle-item shop-middle-right">
        <table>
          <tr v-for="(item, index) in shop.score" :key="index">
            <td>{{item.name}}</td>
            <td class="score" :class="{'score-better': item.isBetter}">{{item.score}}</td>
            <td class="better" :class="{'better-more': item.isBetter}"><span>{{item.isBetter ? '高':'低'}}</span></td>
          </tr>
        </table>
      </div>
    </div>
    <div class="shop-bottom">
      <div class="enter-shop">进店逛逛</div>
    </div>
  </div>
</template>

<script>
	export default {
		name: "DetailShopInfo",
    props: {
		  shop: {
		    type: Object,
        default() {
		      return {}
        }
      }
    },
    filters: {
      sellCountFilter: function (value) {
        if (value < 10000) return value;
        return (value/10000).toFixed(1) + '万'
      }
    }
	}
</script>

<style scoped>
  .shop-info {
    padding: 25px 8px;
    border-bottom: 5px solid #f2f5f8;
  }

  .shop-top {
    line-height: 45px;
    /* 让元素垂直中心对齐 */
    display: flex;
    align-items: center;
  }

  .shop-top img {
    width: 45px;
    height: 45px;
    border-radius: 50%;
    border: 1px solid rgba(0,0,0,.1);
  }

  .shop-top .title {
    margin-left: 10px;
    vertical-align: center;
  }

  .shop-middle {
    margin-top: 15px;
    display: flex;
    align-items: center;
  }

  .shop-middle-item {
    flex: 1;
  }

  .shop-middle-left {
    display: flex;
    justify-content: space-evenly;
    color: #333;
    text-align: center;
    border-right: 1px solid rgba(0,0,0,.1);
  }

  .sells-count, .goods-count {
    font-size: 18px;
  }

  .sells-text, .goods-text {
    margin-top: 10px;
    font-size: 12px;
  }

  .shop-middle-right {
    font-size: 13px;
    color: #333;
  }

  .shop-middle-right table {
    width: 120px;
    margin-left: 30px;
  }

  .shop-middle-right table td {
    padding: 5px 0;
  }

  .shop-middle-right .score {
    color: #5ea732;
  }

  .shop-middle-right .score-better {
    color: #f13e3a;
  }

  .shop-middle-right .better span {
    background-color: #5ea732;
    color: #fff;
    text-align: center;
  }

  .shop-middle-right .better-more span {
    background-color: #f13e3a;
  }

  .shop-bottom {
    text-align: center;
    margin-top: 10px;
  }

  .enter-shop {
    display: inline-block;
    font-size: 14px;
    background-color: #f2f5f8;
    width: 150px;
    height: 30px;
    text-align: center;
    line-height: 30px;
    border-radius: 10px;
  }
</style>

将该组件在Detail.vue中注册,导入,传值

<detail-shop-info :shop="shop"></detail-shop-info>

结果:
请添加图片描述
商铺信息就可以正常展示了。

五、加入滚动效果Scroll

在详情页下面的首页导航栏就可以不用显示了。但是为什么它依然在我们的详情页展示呢?因为下面的这个tab-control脱离了标准流,它会覆盖到我们的标准流的上方。为了能让它盖上我们对样式进行调整。
我们给到detail组件一个id值

#detail {
  position: relative;
  z-index: 9999;
  background-color: #fff;
  /*height: 100vh;*/
}

在这里插入图片描述

但是我们又发现我们的头部导航栏没了,因为我们没有对原生的js做一个定位,这个时候我们直接用scroll局部滚动,头部导航栏就不用再动了。
首先再Detail.vue中导入scroll,注册,使用

import Scroll from '@/components/common/scroll/Scroll';
 <scroll class="content" >
  <detail-swiper :top-images="topImages"></detail-swiper>
  <detail-base-info :goods="goods"></detail-base-info>
  <detail-shop-info :shop="shop"></detail-shop-info>
  </scroll>

用scroll将滚动区域包裹,并且设置高度

.content {
  height: calc(100% - 44px);
}

此时这个100%是相对父元素而言的,所以要给父元素一个高度

#detail {
  position: relative;
  z-index: 9999;
  background-color: #fff;
  height: 100vh;
}

此时会发现头部导航栏并没有固定,所以我们还是为其添加样式

.detail-nav {
  position: relative;
  z-index: 9;
  background-color: #fff;
}

最终的结果:
请添加图片描述

六、商品详情数据展示

商品详情的数据都存放在哪呢?可以看出在detailImage的list里面
在这里插入图片描述
我们直接在Detail.vue中的data()里面新建一个数据

detailInfo:{},

在created()里面的 getDetail函数里面保存商品的详情数据

this.detailInfo=data.detailInfo

然后再封装一个独立的组件叫做DetailGoodsInfo.vue

<template>
  <div v-if="Object.keys(detailInfo).length !== 0" class="goods-info">
    <div class="info-desc clear-fix">
      <div class="start">
      </div>
      <div class="desc">{{detailInfo.desc}}</div>
      <div class="end"></div>
    </div>
    <div class="info-key">{{detailInfo.detailImage[0].key}}</div>
    <div class="info-list">
      <img v-for="(item, index) in detailInfo.detailImage[0].list" :key="index" :src="item" @load="imgLoad" alt="">
    </div>
  </div>
</template>

<script>
	export default {
		name: "DetailGoodsInfo",
    props: {
      detailInfo: {
        type: Object
      }
    },
    data() {
			return {
				counter: 0,
        imagesLength: 0
      }
    },
    methods: {
	    imgLoad() {
        // 判断, 所有的图片都加载完了, 那么进行一次回调就可以了.
        if (++this.counter === this.imagesLength) {
          this.$emit('imageLoad');
        }
	    }
    },
    //watch用来监听某个属性的变化
    watch: {
	    detailInfo() {
	      // 获取图片的个数
	    	this.imagesLength = this.detailInfo.detailImage[0].list.length
	    }
    }
	}
</script>

<style scoped>
  .goods-info {
    padding: 20px 0;
    border-bottom: 5px solid #f2f5f8;
  }

  .info-desc {
    padding: 0 15px;
  }

  .info-desc .start, .info-desc .end {
    width: 90px;
    height: 1px;
    background-color: #a3a3a5;
    position: relative;
  }

  .info-desc .start {
    float: left;
  }

  .info-desc .end {
    float: right;
  }

  .info-desc .start::before, .info-desc .end::after {
    content: '';
    position: absolute;
    width: 5px;
    height: 5px;
    background-color: #333;
    bottom: 0;
  }

  .info-desc .end::after {
    right: 0;
  }

  .info-desc .desc {
    padding: 15px 0;
    font-size: 14px;
  }

  .info-key {
    margin: 10px 0 10px 15px;
    color: #333;
    font-size: 15px;
  }

  .info-list img {
    width: 100%;
  }
</style>

将该组件导入到Detail.vue中,注册,并传值

 <detail-goods-info :detail-info="detailInfo"></detail-goods-info>

结果如下:
请添加图片描述
我们会发现详情页滚不动的情况,原因是它在最早的时候better-scroll计算可滚动区域的时候只计算一部分高度,后来我们又加载了很多图片,它并没有把我们的图片算在内。
所以在我们的DetailGoodsInfo.vue里面对图片的加载进行了监听

   methods: {
	    imgLoad() {
        // 判断, 所有的图片都加载完了, 那么进行一次回调就可以了.
        if (++this.counter === this.imagesLength) {
          this.$emit('imageLoad');
        }
	    }
    },

上面的判断条件的意思是当加载之后的counter和我们图片的长度是相等的,也就是图片加载完了,就向外面发送一次imageLoad事件

然后在我们的Detail.vue中

<detail-goods-info :detail-info="detailInfo" @imageLoad="imageLoad"></detail-goods-info>

在方法里面

  imageLoad(){
      this.$refs.scroll.scroll.refresh()
    },

七、商品参数信息展示

在detail.js对参数信息做一个整合

export class GoodsParam {
  constructor(info, rule) {
    // 注: images可能没有值(某些商品有值, 某些没有值)
    this.image = info.images ? info.images[0] : "";
    this.infos = info.set;
    this.sizes = rule.tables;
  }
}

然后导入Detail.vue中.
在data()里面定义一个

paramInfo: {},

在到created里面获取参数信息

 this. paramInfo=new GoodsParam(data.itemParams.info,data.itemParams.rule)

然后新建DetailParamInfo.vue

<template>
  <div class="param-info" v-if="Object.keys(paramInfo).length !== 0">
    <table v-for="(table, index) in paramInfo.sizes"
           class="info-size" :key="index">
      <tr v-for="(tr, indey) in table" :key="indey">
        <td v-for="(td, indez) in tr" :key="indez">{{td}}</td>
      </tr>
    </table>
    <table class="info-param">
      <tr v-for="(info, index) in paramInfo.infos">
        <td class="info-param-key">{{info.key}}</td>
        <td class="param-value">{{info.value}}</td>
      </tr>
    </table>
    <div class="info-img" v-if="paramInfo.image.length !== 0">
      <img :src="paramInfo.image" alt="">
    </div>
  </div>
</template>

<script>
	export default {
		name: "DetailParamInfo",
    props: {
		  paramInfo: {
		    type: Object,
        default() {
		      return {}
        }
      }
    }
	}
</script>

<style scoped>
  .param-info {
    padding: 20px 15px;
    font-size: 14px;
    border-bottom: 5px solid #f2f5f8;
  }

  .param-info table {
    width: 100%;
    border-collapse: collapse;
  }

  .param-info table tr {
    height: 42px;
  }

  .param-info table tr td {
    border-bottom: 1px solid rgba(100,100,100,.1);
  }

  .info-param-key {
    /*当value的数据量比较大的时候, 会挤到key,所以给一个固定的宽度*/
    width: 95px;
  }

  .info-param {
    border-top: 1px solid rgba(0,0,0,.1);
  }

  .param-value {
    color: #eb4868
  }

  .info-img img {
    width: 100%;
  }
</style>

导入到Detail.vue里,注册,传值

<detail-param-info :param-info="paramInfo"></detail-param-info>

最后的结果如下:
请添加图片描述

  • 3
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵有千堆雪与长街

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值