vue3项目-小兔鲜儿笔记-商品详情页01
作者:互联网
1. 基础布局
完成商品详情基础布局,路由配置,搭好页面架子
2. 渲染面包屑
编写一个钩子函数useGoods.js,将面包屑获取数据的逻辑抽取出来。
// 拿到商品信息
import { findGoods } from '@/api/product'
import { nextTick, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
const useGoods = () => {
const route = useRoute()
const goods = ref(null)
watch(
() => route.params.id,
(newVal) => {
// 先判断路由是否为/product/id,是这个路由才发送查找商品详情的请求
if (newVal && `/product/${newVal}` === route.path) {
findGoods(newVal).then(({ result }) => {
// 先置空,让依赖于goods的组件(v-if=goods)这些组件可以先销毁再创建
goods.value = null
nextTick(() => {
goods.value = result
})
})
}
},
{ immediate: true }
)
return {
goods
}
}
export default useGoods
// 面包屑组件中使用:
// goods-bread:
const { goods } = useGoods()
3.图片放大镜
-
首先准备大图容器和遮罩容器
-
使用@vueuse/core的useMouseInElement方法获取基于容器的偏移量elementX和elementY,以及是否鼠标在容器范围外isOutside
-
通过调整遮罩容器的left和top定位以及调整大图容器的背景图片的backgroundPositionX和backgroundPositionY定位,实现图片放大镜的功能。
// 大图容器
.large {
position: absolute;
top: 0;
left: 412px;
width: 400px;
height: 400px;
box-shadow: 0 0 10px rgba(0, 8, 8, 0.1);
background-repeat: no-repeat;
background-size: 800px 800px;
background-color: #f8f8f8;
}
// 遮罩容器
.layer {
position: absolute;
left: 0;
top: 0;
width: 200px;
height: 200px;
background: rgba(0, 0, 0, 0.2);
}
抽取图片放大镜逻辑到usePreviewImage.js中,使得图片预览组件没有那么臃肿
初始数据:
// 要观察的容器
const target = ref(null)
// 是否显示遮罩层和大图背景
const show = ref(false)
// 遮罩层坐标(左上角),因为遮罩层是绝对定位,设置成left、top
const layerPosition = reactive({
left: 0,
top: 0
})
// 大图背景坐标(左上角),因为大图是背景定位,设置成backgroundPositionX
const largeImagePosition = reactive({
backgroundPositionX: 0,
backgroundPositionY: 0
})
大致步骤:
-
明确数据的含义
-
elementX 鼠标基于容器X轴的偏移量
-
elementY 鼠标基于容器Y轴的偏移量
-
isOutside 鼠标是否在容器外部
-
这三个数据都是响应式变量
-
-
监听这三个数据,让遮罩容器的坐标和大图背景坐标随着偏移量的变化而变化
-
如何让鼠标位于遮罩容器的中心?遮罩容器左移和上移自身的一半,这里是减去100
-
因为遮罩容器不能超出图片容器的范围,所以遮罩容器的左上角X轴最多移动距离:0~200
-
因为大图背景显示的就是鼠标指的位置,且受限遮罩容器X轴的范围:0~200,导致鼠标最多移动范围:100~300。而大图背景移动方向是与鼠标相反的,鼠标往右,背景往左移,所以大图背景坐标移动的范围:-300~-100
-
// elementX:鼠标基于容器X轴的偏移量
// elementY: 鼠标基于容器Y轴的偏移量
// isOutside: 鼠标是否在容器外部,这三个数据都是响应式变量
const { elementX, elementY, isOutside } = useMouseInElement(target)
// 监听这三个数据,让遮罩层坐标和大图背景坐标随着偏移量的变化而变化
watch([elementX, elementY, isOutside], () => {
show.value = !isOutside.value
// 让鼠标位于遮罩层中心,就是让遮罩层左移和上移
// 遮罩层的坐标范围:0~200,大图背景坐标范围:-300~-100,因为大图背景随着鼠标移动而移动,鼠标往右,背景往左移
layerPosition.left = elementX.value - 100 + 'px'
layerPosition.top = elementY.value - 100 + 'px'
largeImagePosition.backgroundPositionX = -elementX.value + 'px'
largeImagePosition.backgroundPositionY = -elementY.value + 'px'
if (elementX.value <= 100) {
layerPosition.left = 0
largeImagePosition.backgroundPositionX = '-100px'
}
if (elementY.value <= 100) {
layerPosition.top = 0
largeImagePosition.backgroundPositionY = '-100px'
}
if (elementX.value >= 300) {
layerPosition.left = '200px'
largeImagePosition.backgroundPositionX = '-300px'
}
if (elementY.value >= 300) {
layerPosition.top = '200px'
largeImagePosition.backgroundPositionY = '-300px'
}
})
4. 城市组件的封装
初始数据:
const target = ref(null)
const visible = ref(false) // 控制窗口是否显示
const loading = ref(false) // 数据加载中状态
const cityData = ref([]) // 地区数组
// 根据点击的地区列表的按钮动态更改选择的地区结果
const changeResult = reactive({
provinceCode: '',
provinceName: '',
cityCode: '',
cityName: '',
countyCode: '',
countyName: '',
fullLocation: ''
})
// 获取地址数据
// 1.地址在窗口打开的时候才获取
// 2.获取了地址之后要做全局缓存,因为数据量太大
// 3.获取了缓存之后下次打开窗口就直接从缓存中取,因此数据有可能是异步也有可能是同步获取,需要使用promise判断
const getCityData = () => {
return new Promise((resolve, reject) => {
if (window.cityData) {
resolve(window.cityData)
} else {
const url =
'https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json'
axios.get(url).then(({ data }) => {
window.cityData = data
resolve(data)
})
}
})
}
开关窗口的逻辑
const open = () => {
visible.value = true
// 获取地址数据
loading.value = true
getCityData().then((res) => {
cityData.value = res
loading.value = false
})
}
const close = () => {
visible.value = false
}
// 控制窗口是否显示
const toggleDialog = () => {
visible.value ? close() : open()
}
// 当鼠标点击外部,关闭窗口
onClickOutside(target, () => {
close()
})
点击地区组件的逻辑
// 点击地区列表的按钮时,根据点的是省还是市还是区做判断
const changeItem = (item) => {
if (item.level === 0) {
// 如果点击的是省,就拿到这个省的代码
changeResult.provinceCode = item.code
changeResult.provinceName = item.name
}
if (item.level === 1) {
// 如果是点击的是市,就拿到这个市的代码
changeResult.cityCode = item.code
changeResult.cityName = item.name
}
// 如果已经点到区了,就说明是地址已经全部点完,可生成完整地址,并通知父组件地址已改变
if (item.level === 2) {
changeResult.countyCode = item.code
changeResult.countyName = item.name
changeResult.fullLocation = `${changeResult.provinceName} ${changeResult.cityName} ${changeResult.countyName}`
// 把点击完生成的地址结果传给父组件
emit('change', changeResult)
close()
}
}
地区列表的显示:
// 地区列表中显示的不一定是省的数据,可能会是市和区
// 但是会依赖于cityData来动态显示,根据已动态更改的地区结果改变渲染的地区列表
const currList = computed(() => {
let list = cityData.value // 一开始默认展示的省列表
// 可能是市,如果点击了某个省,就通过省代码找到这个省,获得这个省下的市列表进行展示
if (changeResult.provinceCode) {
list = list.find(
(province) => province.code === changeResult.provinceCode
).areaList
}
// 可能是区,如果点击了某个市,就通过市代码找到这个市,获得这个市下的区列表进行展示
if (changeResult.cityCode) {
list = list.find((city) => city.code === changeResult.cityCode).areaList
}
return list
})
标签:遮罩,01,const,鼠标,容器,鲜儿,value,详情页,changeResult 来源: https://www.cnblogs.com/jzhFlash/p/16656487.html