四、开始完成商品详情页的功能
4.1 创建商品详情页组件和路由,并实现跳转
- 创建
detail
组件 - 配置动态路由,由于点击每个
goodsitem
进入的商品详情页不一样,要带上商品标记iid
- 点击是在
GoodsItem
组件上触发的,需要在组件中设置点击事件,来跳转到对应的商品详情页
goodsItemClick() {
this.$router.push("/detail/" + this.goodsItem.iid);
}
-
detail
组件中获取到从路由中传过来的iid
,并保存下来
this.iid = this.$route.params.iid
4.2 完成详情页头部的导航组件
- 使用公共组件
navbar
并再次封装成详情页的头部DetailNavBar
组件 -
v-for
遍历title
里面的数据,让头部内容显示正确 - 通过绑定
:class=“{active: currentIndex === index}”
属性对象语法,来让判断哪个标题高亮显示 - 通过点击
title
名来切换高亮显示不同的标签,绑定点击事件:@click=“titleClick(index)”
- 使用左侧的插槽实现箭头返回的功能
4.3 进入详情页请求详情页数据
- 配置详情页的请求和方法的配置文件
detail.js
import { request } from './request' export function getDetailData(iid) { return request({ url: "/detail", params: { iid } }) }
- 在
Detail
组件中导入详情页请求方法getDetailData()
- 在
method
中配置好调用该请求的方法getDetailData
- 在生命周期函数 created 中调用该请求的方法
getDetailData
,并查看可否请求到返回的数据
注意: 由于 App 组件中用了
导致每次进入 Detail
组件页面时 created()
函数不会执行,也就意味着不会去获取新的 iid
,从而无法拿到详情页新的数据。因此需要在 App 组件的
标签上采用 exclude=“Detail”
来将 Detail
组件排除在外,这样在用户每次进入详情页时,就会重新执行 created()
生命周期函数中的代码去获取并存储新的 iid
。
4.4 完成详情页轮播组件
- 初始化轮播图片的数据
topImages: []
,并从getDetailData
方法中获取到轮播的数据,将其赋给this.topImages
- 创建详情页轮播的子组件
DetailSwiper.vue
,并在 Detail 父组件中导入、注册和使用 - 通过在
组件上绑定轮播图片属性,传给子组件的props
中 - 使用
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);
- 安装 Vant 组件
- 将
topImages
中的数据用swipe
组件进行遍历实现,并调试样式使轮播正常
4.5 完成详情页基本信息(标题、价格、销量等)
- 创建商品详情信息组件
DetailBaseInfo.vue
,并引入、注册和使用 - 创建获取商品信息的
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 } }
- 使用构造函数new一个
- 再将拼凑好的商品信息对象
goodsInfo
通过组件上的自定义属性传递给子组件中props
中的商品信息goodsInfo
对象 - 在
template
中创建商品信息的结构,将goodsInfo
中的数据显示在对应的位置,并调试好样式 - 在组件最外层的
div
标签上通过v-if
指令判断对象中keys
的长度,来判断goodsInfo
是否有数据,v-if="Object.keys(goodsInfo).length !== 0"
4.6 完成详情页店铺信息
- 创建组件
DetailShopInfo.vue
引入、注册并使用 - 创建获取商店信息的数据对象
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 } }
- 使用构造函数
- 在将创建好的的
Shop
对象赋值给this.shopInfo
后,在data(){}
中初始化一个shopInfo
对象 - 将父组件
shopInfo
对象通过在组件标签中绑定属性的方式将数据传递给子组件中的props
- 在
template
中创建商品信息的结构,将goodsInfo
中的数据显示在对应的位置,并调试好样式- 其中总销量是 5.2 万,需要用 vue 的过滤器将实际的52157转换成 5.2万
- 商店评价部分的内容可以用表格
标签和
v-for
遍历数据,来实现结构样式4.7 完成详情页商品信息穿着效果
- 创建组件
DetailGoodsInfo.vue
引入、注册和使用 - 在详情页
Detail.vue
组件中获取详情页请求中的商品穿着效果图信息数据this.detailInfo
,并在data
中初始化自定义一个detailInfo
对象,用来存储获取到的数据 - 通过
prop
在父组件Detail.vue
的子组件标签上定义一个detail-info=“”
属性来将,父组件的数据传递给子组件 - 在子组件
DetailGoodsInfo
中的props
中自定义一个detailInfo
,并拿到detailInfo
中的详细数据 - 完成
DetailGoodsInfo
组件中解构数据样式的渲染- 注意:需要使用
v-if
指令先判断子组件是否有获取到数据,再进行页面解构样式的渲染v-if="Object.keys(detailInfo).length !== 0
- 注意:需要使用
- 引入
batter-scroll
组件来使页面可以手动滚动- 需要在
scroll
组件标签上设置content
属性 - 在
content
上设置样式,采用.content { height: calc(100% - 44px) }
- 需要在
- 防止用户在滚动时图片还未加载出来的情况
- 监听传过来的
detailInfo
对象中图片长度的变化,将最终获取到的图片长度,保存到imagesLength
中 - 判断图片是否加载完成,之后再进行一次回调
- 监听传过来的
4.8 完成商品参数信息的内容
- 创建组件
DetailParamsInfo.vue
引入、注册和使用 - 在父组件
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] : ''; } }
- 使用构造函数
- 通过
prop
父子组件传值的方式将paramsInfo
参数数据传递给子组件DetailParamsInfo
- 在组件
DetailParamsInfo
中完成结构样式的实现
4.9 完成商品评论部分的内容
- 创建组件
DetailCommentInfo.vue
引入、注册和使用 - 在父组件
Detail
中获取评价部分的数据commentInfo
,并将其保存到data
中 - 通过
prop
父子组件传值的方式将commentInfo
参数数据传递给子组件DetailCommentInfo
- 在组件
DetailCommentInfo
中渲染数据和样式 - 由于用户评价部分有涉及到时间的数据,需要采用过滤器将时间戳转换成年月日
- 在
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 完成商品详情页推荐部分的内容
- 在详情页请求数据文件
detail.js
中创建推荐请求getRecommd
方法 - 在父组件
Detail
中调用getRecommd
方法,来获取推荐数据recommendsInfo
- 将
recommendsInfo
在data
中初始化,并将获取到的数据存储到recommendsInfo
- 由于推荐数据的样式结构和首页
GoodsList
组件的样式结构一致,因此可以直接复用该组件 - 引入
GoodsList
组件,注册、使用 - 采用
prop
父子组件传值的方法将recommendsInfo
数据传给GoodsList
子组件中的goods
4.11 解决商品详情页图片加载刷新影响首页的问题
- 由于商品详情推荐信息部分使用的是
GoodsList
和GoodsItem
组件,而组件中有事件总线方法$bus
来监听并将图片加载完的事件itemImageLoad
发送给首页 Home ,导致商品详情页中的图片刷新时会影响首页 - 因此需要在首页
deactivated()
函数中,取消全局事件监听this.$bus.$off()
- 先在
data
中初始化一个itemImgListener
事件监听方法名,并将首页监听事件$on()
中的刷新方法保存到itemImgListener
中const newRefresh = debounce(this.$refs.scroll.refresh, 100); // 监听事件总线中的事件变化,即 item 中图片加载完成 // 对监听事件进行保存, 保存到 itemImgListener 中 this.itemImgListener = () => { newRefresh(); }; this.$bus.$on("itemImageLoad", this.itemImgListener);
- 由于上面的方法在组件 Home 和 Detail 中的
mounted(){}
中都有使用,因此需要采用混入mixin
方式将这些方法内容抽离合并到mixin.js
文件中
全局混入: https://cn.vuejs.org/v2/guide/mixins.htmlimport { 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); }, }
- 然后在 Home 和 Detail 组件中引入混入方法
import { itemListenerMixin } from "../../common/mixin"; export default { mixins: [itemListenerMixin], }
4.12 完成点击头部标题跳转对应模块位置
- 在子组件
DetailNavBar
中通过$emit
方法把点击标题的动作和参数titleClick
传递给父组件Detail
- 在父组件
Detail
中的DetailNavBar
标签上绑定点击该事件的动作@titleClick
的方法titleClick(index)
来监控点击的是哪个标题 - 该
titleClick
方法需要调用scrollTo
方法来实现跳转到标题对应的组件标签所在的 y 轴的位置this.$refs.scroll.scrollTo(0, -y, 100)
- 在
data
中给 y 值定义一个初始化属性值叫titleTopYs: [0, 1000, 2000, 3000]
- 然后点击详情页顶部标题,看检验是否能正常跳转
- 在
- 然后获取每个标题对应的实际组件所在位置的
offsetTop
,在替代titleTopYs
数组中的具体值(即将每个offsetTop
值通过push
存入titleTopYs
中) - 通过给每个组件绑定一个
ref
属性来获取到每个组件的offsetTop
- 由于在获取
offsetTop
时需要先拿到数据,并且也在页面渲染完了数据,否则获取到的offsetTop
会出现undefined
的情况。因此需要在updated()
钩子函数中去进行。- 方法二:在获取数据的方法中去调用
this.$nextTick(() => {})
方法,并在该方法中去获取这些组件的offsetTop
值 - 注意: 方法二中根据最新的数据,对应的DOM是已经被渲染出来,但图片依然图片依然是没有加载完完成,图片的高度没有完成包含在其中
- 方法二:在获取数据的方法中去调用
4.13 滚动页面后头部标题自动切换到当前位置对应的标题
- 需要知道滚动的位置,当
probeType
为 3 的时候,不仅在屏幕滑动的过程中,而且在momentum
滚动动画运行过程中实时派发scroll
事件,即在Scroll
组件上绑定属性probe-type
- 将
Scroll
组件中监听事件和位置参数position
发送给父组件 Detail 中的绑定的scroll
属性上的方法contentScroll
- 获取滚动的高度
positionY
的值 - 把
positionY
值和titleTopYs
值进行对比,来判断当前是处在哪个标题的位置-
positionY
在0到10762之间时, index = 0 -
positionY
在10762到11573之间时, index = 1 -
positionY
在10762到11762之间时, index = 2 -
positionY
在大于11762之间时, index = 3
-
- 完成判断逻辑,并将滚动位置的
currentIndex
和标题的currentIndex
同步 - 方法一:普通判断方法
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; } }
- 方法二:在 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 完成详情页底部商品动作栏
- 采用
Vant
组件中的 (GoodsAction 商品导航) 组件来完成 - 先引入该UI组件对应的库
- 创建组件
DetailGoodsAction.vue
并在Detail
组件中导入、注册和使用 - 调整
DetailGoodsAction.vue
组件中的样式参数 - 并在加入购物车的标签上绑定点击事件方法
4.15 完成详情页回到顶部的按钮
- 使用之前已经封装好的
BackTop.vue
组件 - 导入、注册并使用
- 由于首页也有该组件和功能,所有采用混入的方式来实现
- 在
mixin.js
中定义一个混入对象backTopMixin
- 把
Detail.vue
和Home.vue
组件中用到的对象-
data
中定义的对象isShowBackTop
-
methods
中用到的方法backClick(){}
和listenShowBackTop(positon){}
- 在
mixin.js
中同样要引入BackTop
组件 - 同样要在
components
中注册BackTop
-
- 再在
Detail.vue
和Home.vue
组件中引用混入,并使用mixin: [backTopMixin]
4.16 完成添加购物车的功能
- 在组件
DetailGoodsAction
中的添加购物的标签上绑定点击事件@click=“onClickButton”
- 通过
$emit
将addCart()
方法发送到父组件Detail
- 并在父组件的
DetailGoodsAction
标签上绑定addCart
方法来调用addToCart()
- 通过
addToCart
方法将商品数据添加到购物车 - 先获取添加到购物车的商品信息
products
- 由于
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')
- 先安装
- 将
products
购物车数据,提交到vuex
中的state
上的商品列表cartList
进行存储- 通过
this.$store.commit
提交addToCartList
方法给store
中的mutation
- 在
mutation
中通过提交的方法addToCartList
来变更state
中的状态
- 通过
你可能感兴趣的:(购物街项目(3))
- 创建组件