feat(fittingRoom): 替换轮播D堆叠效果

- 引图组件为3入新的 `hbxw-stack-carousel` 轮播图组件,支持3D堆叠、自动播放和手势滑动
- 更新页面结构与样式以适配新组件
- 增加点击事件处理函数 `handleItemClick` 和轮播切换事件 `handleChange`
- 添加动态更新轮播数据的功能,通过 `dian` 方法测试数据变更
- 调整图片展示模式为 `aspectFill` 并设定固定宽高
-优化样式布局,设置相对定位及层级控制

docs(readme): 添加接口文档链接

- 在 README 中补充项目相关接口文档的访问地址
```
This commit is contained in:
guigongli
2025-09-20 14:42:27 +08:00
parent 657631f5bf
commit f8dd924fa4
8 changed files with 759 additions and 26 deletions
+6
View File
@@ -23,3 +23,9 @@ password: 4b28ffea
feastfu@qq.com Fuyan831111~ https://cloud.tencent.com/login?s_url=https%3A%2F%2Fconsole.cloud.tencent.com%2Fdeveloper
接口: https://i8ar7va0x1.feishu.cn/docx/R6Nod8bcPohRh2xOYbAceof4nxd
https://www.kdocs.cn/l/cdrvvpi9CKNe?f=301
@@ -31,7 +31,7 @@
const sizeType = ['original'];
const imageStyle = {
width: 200,
height: 200,
height: 50,
};
const fittingRoom = sheep.$store('fittingRoom');
@@ -60,6 +60,7 @@
overflow: hidden;
height: 76rpx;
transition: height 0.3s ease-in-out;
z-index: 200;
}
.unfold {
height: 700rpx;
@@ -105,6 +106,7 @@
left: 0;
width: 100%;
height: 100%;
opacity: 0;
}
.item-image {
+95 -25
View File
@@ -1,29 +1,31 @@
<template>
<view v-if="template">
<uni-swiper-dot :current="state.current" :info="state.info" :mode="'default'" field="content">
<view class="uni-margin-wrap">
<swiper
:autoplay="true"
:duration="500"
:interval="5000"
circular
class="swiper"
@change="swiperChange"
>
<swiper-item v-for="(item, index) in state.info" :key="index">
<view class="swiper-item uni-bg-red">
<image class="swiper-image" mode="heightFix" src="/static/mote.png"></image>
</view>
</swiper-item>
</swiper>
<s-select-mote />
</view>
</uni-swiper-dot>
<view class="uni-margin-wrap">
<!-- https://ext.dcloud.net.cn/plugin?id=22930-->
<hbxw-stack-carousel
@click="handleItemClick"
:interval="15000"
:list="state.info"
:width="430"
:height="680"
loop
:key="state.info"
indicatorStyle="line"
@change="handleChange"
>
<template #default="{ item, index }">
<view class="custom-item">
<image class="swiper-image" :src="item.image" mode="aspectFill" />
</view>
</template>
</hbxw-stack-carousel>
<s-select-mote />
</view>
<uv-sticky :customNavHeight="0" :offset-top="0">
<s-tabs />
</uv-sticky>
<s-grid-clothes />
<view class="height1"></view>
<view class="height1" @click="dian"></view>
</view>
</template>
@@ -36,26 +38,73 @@
import SGridClothes from './components/s-grid-clothes.vue'; // 隐藏原生tabBar
const fittingRoom = sheep.$store('fittingRoom');
sheep.$helper.toast('请选择是否同意协议');
// 隐藏原生tabBar
uni.hideTabBar({
fail: () => {},
});
function handleItemClick(index, item) {
console.log(index, item, '12');
}
const carouselList = [
{
image: 'https://picsum.photos/600/300?random=1',
},
{
image: 'https://picsum.photos/600/300?random=2',
},
{
image: 'https://picsum.photos/600/300?random=3',
},
{
image: 'https://picsum.photos/600/300?random=4',
},
{
image: 'https://picsum.photos/600/300?random=5',
},
];
const state = reactive({
info: [
{
content: '内容 A',
image: 'https://picsum.photos/600/300?random=1',
},
{
content: '内容 B',
image: 'https://picsum.photos/600/300?random=2',
},
{
content: '内容 C',
},
image: 'https://picsum.photos/600/300?random=3',
}
],
current: 0,
});
function handleChange(index, oldIndex) {
// state.current = index;
console.log('---- change ----', index, oldIndex)
};
function dian() {
state.info = [
{
image: 'https://picsum.photos/600/300?random=1',
},
{
image: 'https://picsum.photos/600/300?random=2',
},
{
image: 'https://picsum.photos/600/300?random=3',
},
{
image: 'https://picsum.photos/600/300?random=4',
},
{
image: 'https://picsum.photos/600/300?random=5',
},
]
}
const swiperChange = (e) => {
state.current = e.detail.current;
};
@@ -102,6 +151,7 @@
<style lang="scss" scoped>
.uni-margin-wrap {
width: 100%;
position: relative;
}
.swiper {
@@ -116,11 +166,31 @@
background: #ffffff;
}
.swiper-image {
.custom-item {
width: 100%;
height: 100%;
}
.swiper-image {
width: 430rpx;
height: 680rpx;
}
.swiper-item-card {
//width: 92% !important;
height: 100%;
margin: 0 4%;
overflow: hidden;
box-sizing: border-box;
display: flex;
align-items: flex-start;
justify-content: center;
}
.swiper-item-card-margin {
padding: 4% 0;
}
.height1 {
height: 2000px;
}
@@ -0,0 +1,10 @@
## 1.0.42025-04-07
修改说明文挡
## 1.0.32025-04-06
增加插槽和补充使用说明
## 1.0.22025-04-06
增加插槽
## 1.0.12025-04-06
更新示例动图
## 1.0.02025-04-06
新增3d堆叠轮播图
@@ -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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

@@ -0,0 +1,86 @@
{
"id": "hbxw-stack-carousel",
"displayName": "堆叠轮播图",
"version": "1.0.4",
"description": "一个支持3D堆叠效果的轮播图组件,具有自动播放、无缝循环、触摸滑动等功能,适用于各种图片展示场景",
"keywords": [
"3d轮播图",
"3D堆叠轮播图",
"轮播图"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "u",
"app-nvue": "u",
"app-uvue": "u",
"app-harmony": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
+222
View File
@@ -0,0 +1,222 @@
# hbxw-stack-carousel 3D堆叠轮播图
## 介绍
一个支持3D堆叠效果的轮播图组件,具有自动播放、无缝循环、触摸滑动等功能,适用于各种图片展示场景。
## 特性
- 3D堆叠效果:轮播图以3D堆叠的方式展示,提供独特的视觉体验
- 自动播放:支持自动轮播功能,可自定义播放间隔时间
- 无缝循环:支持首尾衔接的无缝循环播放
- 手势滑动:支持左右滑动切换图片
- 自定义指示器:支持圆点和线条两种指示器样式
- 自定义尺寸:支持自定义组件的宽度和高度
## 使用示例
推荐先直接复制示例代码到工程中看效果了解下使用方法再投入项目使用。
```vue
<template>
<view class="container">
<view class="demo-item">
<text class="title">3D堆叠轮播</text>
<hbxw-stack-carousel :list="carouselList" :width="600" :height="300" @change="handleChange" @click="handleClick" />
</view>
<view class="demo-item">
<text class="title">3D堆叠轮播-无缝滚动</text>
<hbxw-stack-carousel :list="carouselList" :width="600" :height="300" loop indicatorStyle="line" />
</view>
<view class="demo-item">
<text class="title">插槽使用示例</text>
<hbxw-stack-carousel :list="carouselList" :width="600" :height="300">
<template #default="{item}">
<view class="custom-item">
<image :src="item.image" mode="aspectFill" />
<text class="item-title">自定义内容</text>
</view>
</template>
<template #indicator="{currentIndex, total, handleClick}">
<view class="custom-indicators">
<view
v-for="i in total"
:key="i"
class="custom-indicator"
:class="{'active': currentIndex === i-1}"
@click="handleClick(i-1)"
>{{ i }}</view>
</view>
</template>
</hbxw-stack-carousel>
</view>
</view>
</template>
<script>
export default {
data() {
return {
carouselList: [
{
image: 'https://picsum.photos/600/300?random=1'
},
{
image: 'https://picsum.photos/600/300?random=2'
},
{
image: 'https://picsum.photos/600/300?random=3'
},
{
image: 'https://picsum.photos/600/300?random=4'
},
{
image: 'https://picsum.photos/600/300?random=5'
}
]
}
},
methods: {
handleChange(index, oldIndex) {
console.log('---- change ----', index, oldIndex)
},
handleClick(index, item) {
console.log('---- click ----', index, item)
}
}
}
</script>
<style>
.container {
padding: 20rpx 0;
width: 100%;
box-sizing: border-box;
overflow-x: hidden;
}
.demo-item {
margin-bottom: 30rpx;
}
.title {
font-size: 28rpx;
color: #666;
margin-bottom: 20rpx;
display: block;
}
.custom-item {
position: relative;
width: 600rpx;
height: 300rpx;
}
.custom-item image {
width: 100%;
height: 100%;
}
.custom-item .item-title {
position: absolute;
bottom: 20rpx;
left: 20rpx;
color: #ccc;
font-size: 26rpx;
text-shadow: 0 2rpx 4rpx rgba(0,0,0,0.5);
}
.custom-indicators {
display: flex;
width: 100%;
gap: 20rpx;
justify-content: center;
}
.custom-indicator {
width: 30rpx;
height: 30rpx;
background: #ccc;
border-radius: 6rpx;
font-size: 24rpx;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
}
.custom-indicator.active {
background: #007AFF;
}
</style>
```
## API
### Props 属性说明
| 属性名 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| list | Array | [] | 轮播图数据列表,每个元素必须包含image属性 |
| width | String/Number | '750rpx' | 组件宽度,支持数字(单位rpx)或字符串 |
| height | String/Number | '400rpx' | 组件高度,支持数字(单位rpx)或字符串 |
| autoplay | Boolean | true | 是否自动播放 |
| interval | Number | 3000 | 自动播放间隔时间(毫秒) |
| loop | Boolean | false | 是否启用无缝循环 |
| showIndicator | Boolean | true | 是否显示指示器 |
| indicatorStyle | String | 'dot' | 指示器样式,支持dot(圆点)和line(线条) |
### Events 事件说明
| 事件名 | 说明 | 回调参数 |
| --- | --- | --- |
| change | 轮播图切换时触发 | (index: number, oldIndex: number) index: 当前索引,oldIndex: 上一个索引 |
| click | 点击轮播图时触发 | (index: number, item: object) index: 当前点击的轮播图索引,item: 当前点击的轮播图数据 |
## 注意事项
1. list数组中的每个对象必须包含image属性
2. 组件宽高支持数字(自动添加rpx单位)或带单位的字符串格式
3. 启用无缝循环时,建议list数组至少包含2个元素
4. 使用line样式指示器时,建议搭配较大的组件宽度,以获得更好的视觉效果
## 插槽使用说明
### 默认插槽
用于自定义轮播图内容,接收以下作用域参数:
- `item`: 当前轮播项数据
- `index`: 当前项索引
示例:
```vue
<hbxw-stacked-carousel :list="carouselList">
<template #default="{ item, index }">
<view class="custom-content">
<image :src="item.image" mode="aspectFill" />
<text class="title">{{index + 1}}张图片</text>
</view>
</template>
</hbxw-stacked-carousel>
```
### 指示器插槽
用于自定义指示器样式,接收以下作用域参数:
- `current-index`: 当前激活的轮播索引
- `total`: 轮播项总数
- `handle-click`: 点击切换轮播的方法
示例:
```vue
<hbxw-stacked-carousel :list="carouselList">
<template #indicator="{ currentIndex, total, handleClick }">
<view class="custom-indicator">
<text>{{currentIndex + 1}}/{{total}}</text>
<view
v-for="i in total"
:key="i"
class="indicator-item"
:class="{ 'active': currentIndex === i - 1 }"
@click="handleClick(i - 1)"
/>
</view>
</template>
</hbxw-stacked-carousel>
```