feat: 商品详情页面交互

This commit is contained in:
duanshuwen
2025-08-02 17:35:57 +08:00
parent 9d6abe3e2a
commit ea5841d594
28 changed files with 1967 additions and 268 deletions

View File

@@ -0,0 +1,239 @@
# ImageSwiper 轮播图组件
一个功能丰富的轮播图组件,支持自定义圆角、缩略图导航和图片描述。
## 功能特性
- 🎨 **可配置圆角**:支持数字(px)或字符串形式的圆角设置
- 🖼️ **缩略图导航**:底部缩略图快速切换,支持左右滑动
- 📱 **响应式设计**:适配不同屏幕尺寸
- 🎯 **自定义数据**:支持传入自定义图片数据
- 📊 **进度指示器**:显示当前图片位置
- 🎭 **选中状态**:缩略图选中时高亮显示,带缩放动画
- 🔄 **自动滚动**:缩略图自动滚动到可视区域
-**性能优化**:使用计算属性优化渲染
## 基础用法
### 默认使用
```vue
<template>
<ImageSwiper />
</template>
<script setup>
import ImageSwiper from '@/components/ImageSwiper/index.vue'
</script>
```
### 自定义圆角
```vue
<template>
<!-- 数字形式 (px) -->
<ImageSwiper :border-radius="12" />
<!-- 字符串形式 -->
<ImageSwiper border-radius="1rem" />
<!-- 无圆角 -->
<ImageSwiper :border-radius="0" />
</template>
```
### 自定义图片数据
```vue
<template>
<ImageSwiper
:border-radius="15"
:images="customImages"
/>
</template>
<script setup>
import { ref } from 'vue'
import ImageSwiper from '@/components/ImageSwiper/index.vue'
const customImages = ref([
{
photoUrl: "https://example.com/image1.jpg",
photoName: "图片描述1"
},
{
photoUrl: "https://example.com/image2.jpg",
photoName: "图片描述2"
}
])
</script>
```
### 缩略图滑动功能
组件支持缩略图左右滑动,当图片数量较多时,缩略图会自动滚动到可视区域:
```vue
<template>
<!-- 多图片展示缩略图支持滑动 -->
<ImageSwiper :images="manyImages" />
</template>
<script setup>
const manyImages = ref([
{ photoUrl: "https://example.com/1.jpg", photoName: "图片1" },
{ photoUrl: "https://example.com/2.jpg", photoName: "图片2" },
{ photoUrl: "https://example.com/3.jpg", photoName: "图片3" },
// ... 更多图片
{ photoUrl: "https://example.com/10.jpg", photoName: "图片10" }
])
</script>
```
## API 文档
### Props
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| borderRadius | Number \| String | 8 | 轮播图圆角大小数字时单位为px |
| images | Array | [] | 图片数据数组,为空时使用默认数据 |
### images 数组结构
```typescript
interface ImageItem {
photoUrl: string; // 图片URL
photoName: string; // 图片名称/描述
}
```
## 样式定制
### 圆角配置示例
```vue
<!-- 小圆角 -->
<ImageSwiper :border-radius="4" />
<!-- 中等圆角 -->
<ImageSwiper :border-radius="12" />
<!-- 大圆角 -->
<ImageSwiper :border-radius="24" />
<!-- 使用rem单位 -->
<ImageSwiper border-radius="0.5rem" />
<!-- 使用百分比 -->
<ImageSwiper border-radius="10%" />
```
### 动态圆角控制
```vue
<template>
<view>
<slider
:value="radius"
:min="0"
:max="50"
@change="handleChange"
/>
<ImageSwiper :border-radius="radius" />
</view>
</template>
<script setup>
import { ref } from 'vue'
const radius = ref(8)
const handleChange = (e) => {
radius.value = e.detail.value
}
</script>
```
## 高级用法
### 响应式圆角
```vue
<template>
<ImageSwiper :border-radius="responsiveRadius" />
</template>
<script setup>
import { computed } from 'vue'
// 根据屏幕宽度动态调整圆角
const responsiveRadius = computed(() => {
const screenWidth = uni.getSystemInfoSync().screenWidth
return screenWidth > 750 ? 16 : 8
})
</script>
```
### 主题适配
```vue
<template>
<ImageSwiper :border-radius="themeRadius" />
</template>
<script setup>
import { computed } from 'vue'
// 根据主题调整圆角
const isDarkMode = ref(false)
const themeRadius = computed(() => {
return isDarkMode.value ? 12 : 8
})
</script>
```
## 注意事项
1. **圆角单位**数字类型自动添加px单位字符串类型直接使用
2. **图片比例**:建议使用相同比例的图片以获得最佳显示效果
3. **性能优化**:大量图片时建议使用懒加载
4. **兼容性**支持微信小程序、H5、App等平台
## 更新日志
### v1.2.0
- ✨ 新增缩略图左右滑动功能
- ✨ 新增缩略图选中状态高亮显示
- ✨ 新增缩略图自动滚动到可视区域
- 🎨 优化缩略图动画效果和交互体验
- 🔧 改进主轮播图与缩略图的联动机制
- 📝 更新文档和演示示例
### v1.1.0
- ✨ 新增 `borderRadius` 属性,支持自定义圆角
- ✨ 新增 `images` 属性,支持自定义图片数据
- 🔧 优化组件结构,使用计算属性提升性能
- 📝 完善文档和示例
### v1.0.0
- 🎉 初始版本发布
- ✨ 基础轮播图功能
- ✨ 缩略图导航
- ✨ 进度指示器
## 技术栈
- Vue 3 Composition API
- SCSS
- uni-app
## 浏览器支持
- 微信小程序
- H5 (Chrome, Firefox, Safari, Edge)
- App (iOS, Android)
## 许可证
MIT License

View File

@@ -0,0 +1,151 @@
<template>
<view class="demo-container">
<view class="demo-title">ImageSwiper 轮播图组件演示</view>
<!-- 示例1: 默认圆角 -->
<view class="demo-section">
<view class="section-title">示例1: 默认圆角 (8px)</view>
<ImageSwiper />
</view>
<!-- 示例2: 无圆角 -->
<view class="demo-section">
<view class="section-title">示例2: 无圆角 (0px)</view>
<ImageSwiper :border-radius="0" />
</view>
<!-- 示例3: 大圆角 -->
<view class="demo-section">
<view class="section-title">示例3: 大圆角 (20px)</view>
<ImageSwiper :border-radius="20" />
</view>
<!-- 示例4: 字符串圆角 -->
<view class="demo-section">
<view class="section-title">示例4: 字符串圆角 (1rem)</view>
<ImageSwiper border-radius="1rem" />
</view>
<!-- 示例5: 自定义图片数据 -->
<view class="demo-section">
<view class="section-title">示例5: 自定义图片数据 + 圆角15px</view>
<ImageSwiper :border-radius="15" :images="customImages" />
</view>
<!-- 示例7: 多图片测试滑动 -->
<view class="demo-section">
<view class="section-title">示例7: 多图片测试缩略图滑动</view>
<ImageSwiper :border-radius="10" :images="manyImages" />
</view>
<!-- 示例6: 动态圆角控制 -->
<view class="demo-section">
<view class="section-title">示例6: 动态圆角控制</view>
<view class="control-panel">
<text>圆角大小: {{ dynamicRadius }}px</text>
<slider
:value="dynamicRadius"
:min="0"
:max="50"
@change="handleRadiusChange"
activeColor="#007AFF"
/>
</view>
<ImageSwiper :border-radius="dynamicRadius" />
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import ImageSwiper from './index.vue'
// 动态圆角控制
const dynamicRadius = ref(8)
// 自定义图片数据
const customImages = ref([
{
photoUrl: "https://fastly.picsum.photos/id/100/654/400.jpg?hmac=lYhMw5jKKjJJKjJKjJKjJKjJKjJKjJKjJKjJKjJKjJK",
photoName: "自定义图片1",
},
{
photoUrl: "https://fastly.picsum.photos/id/200/654/400.jpg?hmac=lYhMw5jKKjJJKjJKjJKjJKjJKjJKjJKjJKjJKjJKjJK",
photoName: "自定义图片2",
},
{
photoUrl: "https://fastly.picsum.photos/id/300/654/400.jpg?hmac=lYhMw5jKKjJJKjJKjJKjJKjJKjJKjJKjJKjJKjJKjJK",
photoName: "自定义图片3",
}
])
// 多图片数据,用于测试缩略图滑动
const manyImages = ref([
{ photoUrl: "https://fastly.picsum.photos/id/10/654/400.jpg", photoName: "风景1" },
{ photoUrl: "https://fastly.picsum.photos/id/20/654/400.jpg", photoName: "风景2" },
{ photoUrl: "https://fastly.picsum.photos/id/30/654/400.jpg", photoName: "风景3" },
{ photoUrl: "https://fastly.picsum.photos/id/40/654/400.jpg", photoName: "风景4" },
{ photoUrl: "https://fastly.picsum.photos/id/50/654/400.jpg", photoName: "风景5" },
{ photoUrl: "https://fastly.picsum.photos/id/60/654/400.jpg", photoName: "风景6" },
{ photoUrl: "https://fastly.picsum.photos/id/70/654/400.jpg", photoName: "风景7" },
{ photoUrl: "https://fastly.picsum.photos/id/80/654/400.jpg", photoName: "风景8" },
{ photoUrl: "https://fastly.picsum.photos/id/90/654/400.jpg", photoName: "风景9" },
{ photoUrl: "https://fastly.picsum.photos/id/110/654/400.jpg", photoName: "风景10" }
])
// 处理圆角变化
const handleRadiusChange = (e) => {
dynamicRadius.value = e.detail.value
}
</script>
<style scoped lang="scss">
.demo-container {
padding: 20rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.demo-title {
font-size: 32rpx;
font-weight: bold;
text-align: center;
margin-bottom: 40rpx;
color: #333;
}
.demo-section {
margin-bottom: 60rpx;
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.section-title {
font-size: 28rpx;
font-weight: 600;
margin-bottom: 20rpx;
color: #333;
border-left: 6rpx solid #007AFF;
padding-left: 16rpx;
}
.control-panel {
margin-bottom: 30rpx;
padding: 20rpx;
background: #f8f9fa;
border-radius: 12rpx;
}
.control-panel text {
display: block;
font-size: 24rpx;
color: #666;
margin-bottom: 16rpx;
}
slider {
width: 100%;
}
</style>

View File

@@ -2,17 +2,19 @@
<view class="image-swiper">
<swiper
class="swiper-box"
:style="borderRadiusStyle"
:autoplay="false"
:interval="3000"
:duration="1000"
:current="active"
@change="handleSwiperChange"
>
<swiper-item
class="swiper-item"
v-for="(item, index) in thumbnails"
:key="index"
>
<image :src="item.url" mode="aspectFill"></image>
<image :src="item.photoUrl" mode="aspectFill"></image>
</swiper-item>
</swiper>
@@ -20,50 +22,122 @@
图片{{ active + 1 }}/{{ thumbnails.length }}
</view>
<!-- 缩略图部分 -->
<view class="thumbnail-box">
<view
v-for="(thumb, index) in thumbnails"
:key="index"
class="thumbnail-item"
@click="handleThumbnailClick(index)"
<scroll-view
class="thumbnail-scroll"
scroll-x="true"
:scroll-left="scrollLeft"
:scroll-with-animation="true"
show-scrollbar="false"
>
<image :src="thumb.url" mode="aspectFill"></image>
<text>{{ thumb.description }}</text>
</view>
<view class="thumbnail-list">
<view
v-for="(thumb, index) in thumbnails"
:key="index"
:class="['thumbnail-item', { active: index === active }]"
:id="`thumbnail-${index}`"
@click="handleThumbnailClick(index)"
>
<image :src="thumb.photoUrl" mode="aspectFill"></image>
<text>{{ thumb.photoName }}</text>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup>
import { ref } from "vue";
import { ref, computed, nextTick } from "vue";
// Props定义
const props = defineProps({
// 轮播图圆角大小,支持数字(px)或字符串
borderRadius: {
type: [Number, String],
default: 8,
},
// 轮播图数据
images: {
type: Array,
default: () => [],
},
});
const active = ref(0);
const scrollLeft = ref(0);
const thumbnails = ref([
// 计算圆角样式
const borderRadiusStyle = computed(() => {
const radius =
typeof props.borderRadius === "number"
? `${props.borderRadius}px`
: props.borderRadius;
return {
borderRadius: radius,
};
});
// 默认图片数据
const defaultImages = [
{
url: "https://fastly.picsum.photos/id/866/654/400.jpg?hmac=z3vI4CYrpnXEgimSlJCDwXRxEa-UDHiRwzGEyB8V-po",
description: "瑶山古寨",
photoUrl:
"https://fastly.picsum.photos/id/866/654/400.jpg?hmac=z3vI4CYrpnXEgimSlJCDwXRxEa-UDHiRwzGEyB8V-po",
photoName: "瑶山古寨",
},
{
url: "https://fastly.picsum.photos/id/284/654/400.jpg?hmac=89XRCJxYTblKIFGLOp6hJ9U0GC8BQrcnJwE5pG21NAk",
description: "民俗表演",
photoUrl:
"https://fastly.picsum.photos/id/284/654/400.jpg?hmac=89XRCJxYTblKIFGLOp6hJ9U0GC8BQrcnJwE5pG21NAk",
photoName: "民俗表演",
},
{
url: "https://fastly.picsum.photos/id/281/654/400.jpg?hmac=hcAJB7y2Xz3DVuz6S4XeQZgzaTJ_QWnxtbnaagZL6Fs",
description: "特色美食",
photoUrl:
"https://fastly.picsum.photos/id/281/654/400.jpg?hmac=hcAJB7y2Xz3DVuz6S4XeQZgzaTJ_QWnxtbnaagZL6Fs",
photoName: "特色美食",
},
{
url: "https://fastly.picsum.photos/id/435/654/400.jpg?hmac=TSVDxfo-zXbunxNQK0erSG_nmKcS20xfhbQsCAXLlHo",
description: "传统服饰",
photoUrl:
"https://fastly.picsum.photos/id/435/654/400.jpg?hmac=TSVDxfo-zXbunxNQK0erSG_nmKcS20xfhbQsCAXLlHo",
photoName: "传统服饰",
},
{
url: "https://fastly.picsum.photos/id/737/654/400.jpg?hmac=VED05oEK3XB0Aa_DUVoZjTAf0bHjAmNYyJky4lq5vVo",
description: "其他",
photoUrl:
"https://fastly.picsum.photos/id/737/654/400.jpg?hmac=VED05oEK3XB0Aa_DUVoZjTAf0bHjAmNYyJky4lq5vVo",
photoName: "其他",
},
]);
];
// 使用传入的图片数据或默认数据
const thumbnails = computed(() => {
return props.images.length ? props.images : defaultImages;
});
const handleThumbnailClick = (index) => {
active.value = index;
scrollToActiveItem(index);
};
// 滚动到选中项
const scrollToActiveItem = async (index) => {
await nextTick();
// 计算每个缩略图项的宽度(包括间距)
const itemWidth = 58; // 48px宽度 + 10px间距
const containerWidth = 300; // 大概的容器宽度
const targetScrollLeft = Math.max(
0,
index * itemWidth - containerWidth / 2 + itemWidth / 2
);
scrollLeft.value = targetScrollLeft;
};
// 监听主轮播图变化,同步缩略图滚动
const handleSwiperChange = (e) => {
const currentIndex = e.detail.current;
active.value = currentIndex;
scrollToActiveItem(currentIndex);
};
</script>

View File

@@ -4,9 +4,9 @@
}
.swiper-box {
border-radius: 8px;
height: 200px;
overflow: hidden;
// 圆角通过内联样式动态设置
}
.swiper-item image {
@@ -31,23 +31,53 @@
left: 12px;
right: 12px;
bottom: 36px;
height: 60px;
}
.thumbnail-scroll {
width: 100%;
height: 100%;
white-space: nowrap;
}
.thumbnail-list {
display: flex;
gap: 5px;
align-items: center;
gap: 10px;
padding: 0 5px;
}
.thumbnail-item {
flex-shrink: 0;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
&.active {
image {
border: 1px solid #fff;
}
}
}
.thumbnail-item image {
width: 48px;
height: 38px;
border-radius: 4px;
border: 1px solid #fff;
box-sizing: border-box;
border: 1px solid transparent;
transition: all 0.3s ease;
display: block;
}
.thumbnail-item text {
color: #fff;
font-size: 8px;
display: block;
margin-top: 4px;
transition: all 0.3s ease;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 48px;
}