```
feat(fittingRoom): 替换轮播D堆叠效果 - 引图组件为3入新的 `hbxw-stack-carousel` 轮播图组件,支持3D堆叠、自动播放和手势滑动 - 更新页面结构与样式以适配新组件 - 增加点击事件处理函数 `handleItemClick` 和轮播切换事件 `handleChange` - 添加动态更新轮播数据的功能,通过 `dian` 方法测试数据变更 - 调整图片展示模式为 `aspectFill` 并设定固定宽高 -优化样式布局,设置相对定位及层级控制 docs(readme): 添加接口文档链接 - 在 README 中补充项目相关接口文档的访问地址 ```
This commit is contained in:
+337
@@ -0,0 +1,337 @@
|
||||
<template>
|
||||
<view class="stacked-carousel">
|
||||
<view class="carousel-container" :style="containerStyle" @touchstart="handleTouchStart" @touchend="handleTouchEnd">
|
||||
<view v-for="(item, index) in displayList" :key="index" class="carousel-item"
|
||||
:class="{ 'active': index === activeIndex, 'no-transition': isResetting }" :style="getItemStyle(index)"
|
||||
@click="handleItemClick(index)" @transitionend="handleTransitionEnd">
|
||||
<slot :item="item" :index="index">
|
||||
<image :src="item.image" mode="aspectFill" />
|
||||
</slot>
|
||||
</view>
|
||||
<view v-if="showIndicator" class="carousel-indicators">
|
||||
<slot name="indicator" :current-index="getRealIndex(activeIndex)" :total="list.length"
|
||||
:handle-click="handleIndicatorClick">
|
||||
<view v-for="(_, index) in list" :key="index" :class="[
|
||||
indicatorStyle === 'dot' ? 'indicator-dot' : 'indicator-line',
|
||||
{ 'active': getRealIndex(activeIndex) === index }
|
||||
]" @click="handleIndicatorClick(index)" />
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 3D堆叠轮播图组件
|
||||
* @description 一个支持3D堆叠效果的轮播图组件,具有自动播放、无缝循环、触摸滑动等功能
|
||||
*/
|
||||
export default {
|
||||
name: 'hbxw-stacked-carousel',
|
||||
props: {
|
||||
// 轮播图数据列表,每个元素必须包含image属性
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 组件宽度,支持数字(单位rpx)或字符串
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: '750rpx'
|
||||
},
|
||||
// 组件高度,支持数字(单位rpx)或字符串
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: '400rpx'
|
||||
},
|
||||
// 是否自动播放
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 自动播放间隔时间(毫秒)
|
||||
interval: {
|
||||
type: Number,
|
||||
default: 3000
|
||||
},
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否启用无缝循环
|
||||
loop: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示指示器
|
||||
showIndicator: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 指示器样式,支持dot(圆点)和line(线条)
|
||||
indicatorStyle: {
|
||||
type: String,
|
||||
default: 'dot',
|
||||
validator: function (value) {
|
||||
return ['dot', 'line'].includes(value)
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeIndex: 0, // 当前激活的轮播图索引
|
||||
timer: null, // 自动播放定时器
|
||||
touchStartX: 0, // 触摸开始位置
|
||||
touchEndX: 0, // 触摸结束位置
|
||||
displayList: [], // 实际显示的轮播图列表(包含首尾过渡图片)
|
||||
isResetting: false // 是否正在重置位置
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 计算容器样式,处理宽高单位
|
||||
containerStyle() {
|
||||
return {
|
||||
width: typeof this.width === 'number' ? this.width + 'rpx' : this.width,
|
||||
height: typeof this.height === 'number' ? this.height + 'rpx' : this.height
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initDisplayList()
|
||||
this.startAutoplay()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stopAutoplay()
|
||||
},
|
||||
methods: {
|
||||
// 初始化显示列表,处理无缝循环逻辑
|
||||
initDisplayList() {
|
||||
if (!this.list.length) return
|
||||
|
||||
if (this.loop && this.list.length > 1) {
|
||||
// 在首尾添加额外的图片副本以实现无缝效果
|
||||
this.displayList = [
|
||||
this.list[this.list.length - 1],
|
||||
...this.list,
|
||||
this.list[0]
|
||||
]
|
||||
this.activeIndex = 1 // 从真实的第一张图片开始
|
||||
} else {
|
||||
this.displayList = [...this.list]
|
||||
this.activeIndex = 0
|
||||
}
|
||||
},
|
||||
// 计算每个轮播图项的样式,实现3D堆叠效果
|
||||
getItemStyle(index) {
|
||||
const diff = index - this.activeIndex
|
||||
const total = this.displayList.length
|
||||
let scale = 1
|
||||
let zIndex = 1
|
||||
let translateX = '0'
|
||||
|
||||
if (diff === 0) {
|
||||
scale = 1
|
||||
zIndex = 3
|
||||
} else if (Math.abs(diff) === 1) {
|
||||
scale = 0.8
|
||||
zIndex = 2
|
||||
translateX = diff > 0 ? '60%' : '-60%'
|
||||
} else {
|
||||
scale = 0.6
|
||||
zIndex = 1
|
||||
translateX = diff > 0 ? '120%' : '-120%'
|
||||
}
|
||||
|
||||
return {
|
||||
transform: `translateX(${translateX}) scale(${scale})`,
|
||||
zIndex: zIndex
|
||||
}
|
||||
},
|
||||
// 处理轮播图过渡结束事件,实现无缝循环
|
||||
handleTransitionEnd() {
|
||||
if (!this.loop || this.displayList.length <= 1) return
|
||||
|
||||
// 处理无缝轮播的首尾过渡
|
||||
if (this.activeIndex === 0 || this.activeIndex === this.displayList.length - 1) {
|
||||
this.isResetting = true
|
||||
|
||||
// 立即重置位置
|
||||
const targetIndex = this.activeIndex === 0 ? this.displayList.length - 2 : 1
|
||||
this.activeIndex = targetIndex
|
||||
|
||||
// 在下一帧恢复过渡效果
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.isResetting = false
|
||||
}, 50)
|
||||
})
|
||||
}
|
||||
},
|
||||
// 处理轮播图项点击事件
|
||||
handleItemClick(index) {
|
||||
this.$emit('click', this.getRealIndex(index), this.displayList[index])
|
||||
// if (index === this.activeIndex) return
|
||||
// const oldIndex = this.getRealIndex(this.activeIndex)
|
||||
// this.activeIndex = index
|
||||
// this.$emit('change', this.getRealIndex(index), oldIndex)
|
||||
// this.resetAutoplay()
|
||||
},
|
||||
// 处理轮播图切换,direction: 1向后,-1向前
|
||||
handleSlideChange(direction) {
|
||||
if (!this.displayList.length) return
|
||||
|
||||
const oldIndex = this.getRealIndex(this.activeIndex)
|
||||
let newIndex
|
||||
|
||||
if (this.loop && this.displayList.length > 1) {
|
||||
// 处理向前切换时的特殊情况
|
||||
if (direction === -1 && this.activeIndex === 1) {
|
||||
this.isResetting = true
|
||||
this.activeIndex = this.displayList.length - 2
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.isResetting = false
|
||||
}, 50)
|
||||
})
|
||||
} else {
|
||||
newIndex = (this.activeIndex + direction + this.displayList.length) % this.displayList.length
|
||||
this.activeIndex = newIndex
|
||||
}
|
||||
} else {
|
||||
newIndex = (this.activeIndex + direction + this.list.length) % this.list.length
|
||||
this.activeIndex = newIndex
|
||||
}
|
||||
|
||||
this.$emit('change', this.getRealIndex(this.activeIndex), oldIndex)
|
||||
},
|
||||
// 开始自动播放
|
||||
startAutoplay() {
|
||||
if (!this.autoplay) return
|
||||
this.timer = setInterval(() => {
|
||||
this.handleSlideChange(1)
|
||||
}, this.interval)
|
||||
},
|
||||
// 停止自动播放
|
||||
stopAutoplay() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
},
|
||||
// 重置自动播放定时器
|
||||
resetAutoplay() {
|
||||
this.stopAutoplay()
|
||||
this.startAutoplay()
|
||||
},
|
||||
// 处理触摸开始事件
|
||||
handleTouchStart(e) {
|
||||
this.touchStartX = e.touches[0].clientX
|
||||
this.stopAutoplay()
|
||||
},
|
||||
// 处理触摸结束事件,判断滑动方向
|
||||
handleTouchEnd(e) {
|
||||
this.touchEndX = e.changedTouches[0].clientX
|
||||
const diff = this.touchEndX - this.touchStartX
|
||||
|
||||
if (Math.abs(diff) > 50) { // 设置最小滑动距离阈值
|
||||
if (diff > 0) {
|
||||
// 向右滑,显示上一张
|
||||
this.handleSlideChange(-1)
|
||||
} else {
|
||||
// 向左滑,显示下一张
|
||||
this.handleSlideChange(1)
|
||||
}
|
||||
}
|
||||
|
||||
this.startAutoplay()
|
||||
},
|
||||
|
||||
// 获取真实的轮播图索引(考虑无缝循环的情况)
|
||||
getRealIndex(displayIndex) {
|
||||
if (this.loop && this.list.length > 1) {
|
||||
if (displayIndex === 0) return this.list.length - 1
|
||||
if (displayIndex === this.displayList.length - 1) return 0
|
||||
return displayIndex - 1
|
||||
}
|
||||
return displayIndex
|
||||
},
|
||||
|
||||
// 处理指示器点击事件
|
||||
handleIndicatorClick(index) {
|
||||
const currentRealIndex = this.getRealIndex(this.activeIndex)
|
||||
if (currentRealIndex === index) return
|
||||
|
||||
const targetDisplayIndex = this.loop ? index + 1 : index
|
||||
this.handleItemClick(targetDisplayIndex)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.stacked-carousel {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.carousel-container {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.carousel-item {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: all 0.5s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.carousel-item.no-transition {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.carousel-item image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.carousel-indicators {
|
||||
position: absolute;
|
||||
bottom: 20rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.indicator-dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
transition: all 0.5s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.indicator-dot.active {
|
||||
background-color: #ffffff;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.indicator-line {
|
||||
width: 24rpx;
|
||||
height: 8rpx;
|
||||
border-radius: 4rpx;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
transition: all 0.5s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.indicator-line.active {
|
||||
width: 32rpx;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user