文章目录
- 01-商品详情-基础布局
- 02-商品详情-渲染面包屑
- 03-商品详情-图片预览组件
- 04-商品详情-图片放大镜
01-商品详情-基础布局
目的:完成商品详情基础布局,路由配置,搭好页面架子。
大致步骤:
- 准备组件结构容器
- 提取商品推荐组件且使用
- 配置路由和组件
落地代码:
- 页面组件:
src/views/goods/index.vue
<template>
<div class='xtx-goods-page'>
<div class="container">
<!-- 面包屑 -->
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem to="/">手机</XtxBreadItem>
<XtxBreadItem to="/">华为</XtxBreadItem>
<XtxBreadItem to="/">p30</XtxBreadItem>
</XtxBread>
<!-- 商品信息 -->
<div class="goods-info"></div>
<!-- 商品推荐 -->
<GoodsRelevant />
<!-- 商品详情 -->
<div class="goods-footer">
<div class="goods-article">
<!-- 商品+评价 -->
<div class="goods-tabs"></div>
<!-- 注意事项 -->
<div class="goods-warn"></div>
</div>
<!-- 24热榜+专题推荐 -->
<div class="goods-aside"></div>
</div>
</div>
</div>
</template>
<script>
import GoodsRelevant from './components/goods-relevant'
export default {
name: 'XtxGoodsPage',
components: { , GoodsRelevant }
}
</script>
<style scoped lang='less'>
.goods-info {
min-height: 600px;
background: #fff;
}
.goods-footer {
display: flex;
margin-top: 20px;
.goods-article {
width: 940px;
margin-right: 20px;
}
.goods-aside {
width: 280px;
min-height: 1000px;
}
}
.goods-tabs {
min-height: 600px;
background: #fff;
}
.goods-warn {
min-height: 600px;
background: #fff;
margin-top: 20px;
}
</style>
- 商品推荐组件:
src/views/goods/components/goods-relevant.vue
<template>
<div class='goods-relevant'></div>
</template>
<script>
export default {
name: 'GoodsRelevant'
}
</script>
<style scoped lang='less'>
.goods-relevant {
background: #fff;
min-height: 460px;
margin-top: 20px;
}
</style>
- 路由配置:
src/router/index.js
const Goods = () => import('@/views/goods/index')
children: [
{ path: '/', component: Home },
{ path: '/category/:id', component: TopCategory },
{ path: '/category/sub/:id', component: SubCategory },
+ { path: '/product/:id', component: Goods }
]
02-商品详情-渲染面包屑
目的:获取数据,渲染面包屑。
大致步骤:
- 定义获取商品详情API函数
- 在组件setup中获取商品详情数据
- 定义一个useXxx函数处理数据
落地代码:
- API函数
src/api/product.js
import request from '@/utils/request'
/**
* 获取商品详情
* @param {String} id - 商品ID
*/
export const findGoods = (id) => {
return request('/goods', 'get', { id })
}
- useGoods函数
src/views/goods/index.vue
在setup中使用
import GoodsRelevant from './components/goods-relevant'
import { nextTick, ref, watch } from 'vue'
import { findGoods } from '@/api/product'
import { useRoute } from 'vue-router'
export default {
name: 'XtxGoodsPage',
components: { GoodsRelevant },
setup () {
const goods = useGoods()
return { goods }
}
}
// 获取商品详情
const useGoods = () => {
// 出现路由地址商品ID发生变化,但是不会重新初始化组件
const goods = ref(null)
const route = useRoute()
watch(() => route.params.id, (newVal) => {
if (newVal && `/product/${newVal}` === route.path) {
findGoods(route.params.id).then(data => {
// 让商品数据为null让后使用v-if的组件可以重新销毁和创建
goods.value = null
nextTick(() => {
goods.value = data.result
})
})
}
}, { immediate: true })
return goods
}
- 防止报错,加载完成goods再显示所有内容
<div class='xtx-goods-page' v-if="goods">
- 渲染面包屑
<!-- 面包屑 -->
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem :to="'/category/'+goods.categories[0].id">{{goods.categories[0].name}}</XtxBreadItem>
<XtxBreadItem :to="'/category/sub/'+goods.categories[1].id">{{goods.categories[1].name}}</XtxBreadItem>
<XtxBreadItem>{{goods.name}}</XtxBreadItem>
</XtxBread>
03-商品详情-图片预览组件
目的:完成商品图片预览功能和切换
大致步骤:
- 首先准备商品信息区块左右两侧的布局盒子
- 在定义一个商品图片组件,用来实现图片预览
- 首先组件布局,渲染
- 实现切换图片
落地代码:
- 商品信息区块,布局盒子
src/views/goods/index.vue
<!-- 商品信息 -->
<div class="goods-info">
<div class="media"></div>
<div class="spec"></div>
</div>
.goods-info {
min-height: 600px;
background: #fff;
display: flex;
.media {
width: 580px;
height: 600px;
padding: 30px 50px;
}
.spec {
flex: 1;
padding: 30px 30px 30px 0;
}
}
- 商品图片组件,渲染和切换
<template>
<div class="goods-image">
<div class="middle">
<img :src="images[currIndex]" alt="">
</div>
<ul class="small">
<li v-for="(img,i) in images" :key="img" :class="{active:i===currIndex}">
<img @mouseenter="currIndex=i" :src="img" alt="">
</li>
</ul>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'GoodsImage',
props: {
images: {
type: Array,
default: () => []
}
},
setup (props) {
const currIndex = ref(0)
return { currIndex }
}
}
</script>
<style scoped lang="less">
.goods-image {
width: 480px;
height: 400px;
position: relative;
display: flex;
.middle {
width: 400px;
height: 400px;
background: #f5f5f5;
}
.small {
width: 80px;
li {
width: 68px;
height: 68px;
margin-left: 12px;
margin-bottom: 15px;
cursor: pointer;
&:hover,&.active {
border: 2px solid @xtxColor;
}
}
}
}
</style>
04-商品详情-图片放大镜
目的:实现图片放大镜功能
大致步骤:
- 首先准备大图容器和遮罩容器
- 然后使用
@vueuse/core
的useMouseInElement
方法获取基于元素的偏移量 - 计算出 遮罩容器定位与大容器北京定位 暴露出数据给模板使用
落地代码:src/views/goods/components/goods-image.vue
- 准备大图容器
<div class='goods-image'>
+ <div class="large" :style="[{backgroundImage:`url(${images[currIndex]})`}]"></div>
<div class="middle">
.goods-image {
width: 480px;
height: 400px;
position: relative;
display: flex;
+ z-index: 500;
+ .large {
+ position: absolute;
+ top: 0;
+ left: 412px;
+ width: 400px;
+ height: 400px;
+ box-shadow: 0 0 10px rgba(0,0,0,0.1);
+ background-repeat: no-repeat;
+ background-size: 800px 800px;
+ background-color: #f8f8f8;
+ }
- 准备待移动的遮罩容器
<div class="middle" ref="target">
<img :src="images[currIndex]" alt="">
+ <div class="layer"></div>
</div>
.middle {
width: 400px;
height: 400px;
+ position: relative;
+ cursor: move;
+ .layer {
+ width: 200px;
+ height: 200px;
+ background: rgba(0,0,0,.2);
+ left: 0;
+ top: 0;
+ position: absolute;
+ }
}
- 使用vueuse提供的API获取鼠标偏移量
import { reactive, ref, watch } from 'vue'
import { useMouseInElement } from '@vueuse/core'
const usePreviewImg = () => {
const target = ref(null)
const show = ref(false)
// elementX 鼠标基于容器左上角X轴偏移
// elementY 鼠标基于容器左上角Y轴偏移
// isOutside 鼠标是否在模板容器外
const { elementX, elementY, isOutside } = useMouseInElement(target)
const position = reactive({ left: 0, top: 0 })
const bgPosition = reactive({ backgroundPositionX: 0, backgroundPositionY: 0 })
watch([elementX, elementY, isOutside], () => {
// 控制X轴方向的定位 0-200 之间
if (elementX.value < 100) position.left = 0
else if (elementX.value > 300) position.left = 200
else position.left = elementX.value - 100
// 控制Y轴方向的定位 0-200 之间
if (elementY.value < 100) position.top = 0
else if (elementY.value > 300) position.top = 200
else position.top = elementY.value - 100
// 设置大背景的定位
bgPosition.backgroundPositionX = -position.left * 2 + 'px'
bgPosition.backgroundPositionY = -position.top * 2 + 'px'
// 设置遮罩容器的定位
position.left = position.left + 'px'
position.top = position.top + 'px'
// 设置是否显示预览大图
show.value = !isOutside.value
})
return { position, bgPosition, show, target }
}
- 在setup中返回模板需要数据,并使用它
setup () {
const { currIndex, toggleImg } = useToggleImg()
+ const { position, bgPosition, show, target } = usePreviewImg()
+ return { currIndex, toggleImg, position, bgPosition, show, target }
}
<div class="large" v-show="show" :style="[{backgroundImage:`url(${images[currIndex]})`},bgPosition]"></div>
<div class="middle" ref="target">
<img :src="images[currIndex]" alt="">
<div class="layer" v-show="show" :style="position"></div>
</div>