detail.vue
<template>
<div id="detail">
<detail-nav-bar @titleClick="selectIndex" :current-index="currentIndex"/>
<scroll class="content"
ref="scroll"
@scroll="contentScroll"
:probe-type="3"
:data="[topImages, goods, shop, detailInfo, paramInfo, goodsList]">
<detail-swiper :images="topImages"/>
<detail-base-info :goods="goods"/>
<detail-shop-info :shop="shop"/>
<detail-goods-info :detail-info="detailInfo" @imageLoad="imageLoad"/>
<detail-param-info ref="param" :param-info="paramInfo"/>
<detail-comment-info ref="comment" :comment-info="commentInfo"/>
<goods-list ref="recommend" :goods="goodsList"/>
</scroll>
<back-top v-show="showBackTop" @click.native="backTop"/>
<!--<cart-button @click.native="cartClick"/>-->
<detail-bottom-bar @addToCart="addToCart"/>
<toast ref="toast"/>
</div>
</template>
<script>
import DetailNavBar from './childComps/DetailNavBar'
import DetailSwiper from './childComps/DetailSwiper'
import DetailBaseInfo from './childComps/DetailBaseInfo'
import DetailShopInfo from './childComps/DetailShopInfo'
import DetailGoodsInfo from './childComps/DetailGoodsInfo'
import DetailParamInfo from './childComps/DetailParamInfo'
import DetailCommentInfo from './childComps/DetailCommentInfo'
import DetailBottomBar from './childComps/DetailBottomBar'
import CartButton from './childComps/CartButton'
import GoodsList from 'components/content/goods/GoodsList'
import Scroll from 'components/common/scroll/Scroll'
import {getDetail, getRecommend, Goods, Shop, GoodsParam} from "network/detail";
import {backTopMixin} from "common/mixin";
import {mapActions} from 'vuex'
import Toast from 'components/common/toast/Toast'
export default {
name: "Detail",
components: {
DetailParamInfo,
DetailNavBar,
DetailSwiper,
DetailBaseInfo,
DetailShopInfo,
DetailGoodsInfo,
DetailCommentInfo,
CartButton,
GoodsList,
DetailBottomBar,
Scroll,
Toast
},
mixins: [backTopMixin],
data() {
return {
iid: '',
topImages: [],
goods: {},
shop: {},
detailInfo: {},
paramInfo: {},
commentInfo: {},
goodsList: [],
themeTops: [],
currentIndex: 0
}
},
created() {
// 1.取出iid
this.iid = this.$route.query.iid
// 2.发送商品请求
this._getDetail(this.iid)
// 3.请求推荐请求
this._getRecommend()
},
methods: {
...mapActions({
addCart: 'addToCart'
}),
imageLoad() {
this.$refs.scroll.refresh()
// 获取对应的offsetTop
this.themeTops = []
this.themeTops.push(0)
this.themeTops.push(this.$refs.param.$el.offsetTop)
this.themeTops.push(this.$refs.comment.$el.offsetTop)
this.themeTops.push(this.$refs.recommend.$el.offsetTop)
this.themeTops.push(Number.MAX_VALUE)
},
selectIndex(index) {
this.$refs.scroll.scrollTo(0, -this.themeTops[index], 500)
},
contentScroll(position) {
// 决定backTop按钮是否显示
this.showBackTop = position.y <= -1000
// 监听滚动到某个主题
this._listenScrollTheme(-position.y)
},
_listenScrollTheme(position) {
let length = this.themeTops.length;
for (let i = 0; i < length; i++) {
let iPos = this.themeTops[i];
/**
* 判断的方案:
* 方案一:
* 条件: (i < (length-1) && currentPos >= iPos && currentPos < this.themeTops[i+1]) || (i === (length-1) && currentPos >= iPos),
* 优点: 不需要引入其他的内容, 通过逻辑解决
* 缺点: 判断条件过长, 并且不容易理解
* 方案二:
* 条件: 给themeTops最后添加一个很大的值, 用于和最后一个主题的top进行比较.
* 优点: 简洁明了, 便于理解
* 缺点: 需要引入一个较大的int数字
* 疑惑: 在第一个判断中, 为什么不能直接判断(currentPos >= iPos)即可?
* 解答: 比如在某一个currentPos大于第0个时, 就会break, 不会判断后面的i了.
*/
if (position >= iPos && position < this.themeTops[i+1]) {
if (this.currentIndex !== i) {
this.currentIndex = i;
}
break;
}
}
},
cartClick() {
this.$router.push('/cart')
},
addToCart() {
// 2.将商品信息添加到Store中
const obj = {}
obj.iid = this.iid
obj.imgURL = this.topImages[0]
obj.title = this.goods.title
obj.desc = this.goods.desc
obj.price = this.goods.realPrice
// this.$store.dispatch('addToCart', obj).then(() => {
// this.$toast({message: '加入购物车成功'})
// })
this.addCart(obj).then(() => {
this.$toast({message: '加入购物车成功'})
})
},
_getDetail(iid) {
getDetail(iid).then(res => {
// 1.获取数据
const data = res.result
console.log(data);
// 2.获取顶部的图片数据
this.topImages = data.itemInfo.topImages
// 3.获取商品信息
this.goods = new Goods(data.itemInfo, data.columns, data.shopInfo.services)
// 4.获取店铺信息
this.shop = new Shop(data.shopInfo)
// 5.获取商品详细信息
this.detailInfo = data.detailInfo
// 6.保存参数信息
this.paramInfo = new GoodsParam(data.itemParams.info, data.itemParams.rule)
// 7.保存评论数据
if (data.rate.list) {
this.commentInfo = data.rate.list[0];
}
})
},
_getRecommend() {
getRecommend().then(res => {
this.goodsList = res.data.list
})
}
}
}
</script>
<style scoped>
#detail {
height: 100vh;
position: relative;
z-index: 1;
background-color: #fff;
}
.content {
position: absolute;
top: 44px;
bottom: 58px;
left: 0;
right: 0;
}
</style>
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.js
import {request} from "./request";
export function getDetail(iid) {
return request({
url: '/detail',
params: {
iid
}
})
}
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
}
}
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
}
}
export class GoodsParam {
constructor(info, rule) {
// 注: images可能没有值(某些商品有值, 某些没有值)
this.image = info.images ? info.images[0] : '';
this.infos = info.set;
this.sizes = rule.tables;
}
}