| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- <template>
- <view class="swiper-container">
- <swiper ref="swiperRef" :indicator-dots="indicatorDots" :autoplay="autoplay" :interval="interval"
- :duration="duration" :circular="circular" :current="currentIndex" @change="onSwiperChange"
- @animationfinish="onAnimationFinish" :previous-margin="previousMargin" :next-margin="nextMargin"
- class="custom-swiper" :style="{ height: height }">
- <swiper-item v-for="(item, index) in list" :key="index">
- <view class="swiper-item">
- <!-- 图片内容 -->
- <image v-if="item.image" :src="item.image" mode="aspectFill" class="swiper-image"
- @click="onItemClick(index)" />
- <!-- 自定义内容插槽 -->
- <slot v-else :item="item" :index="index">
- <view class="default-content">{{ item.content || `内容 ${index + 1}` }}</view>
- </slot>
- </view>
- </swiper-item>
- </swiper>
- <!-- 自定义指示器 -->
- <view v-if="showDots" class="swiper-dots">
- <view v-for="(item, index) in list" :key="index" class="dot"
- :class="{ 'dot-active': currentIndex === index }" @click="goToSlide(index)"></view>
- </view>
- <!-- 滚动条 -->
- <view v-if="showScrollbar" class="swiper-scrollbar">
- <view class="swiper-scrollbar-drag" :style="{
- width: scrollbarWidth,
- transform: `translateX(${scrollbarOffset})`
- }"></view>
- </view>
- <!-- 导航按钮 -->
- <view v-if="showNavigation" class="swiper-navigation">
- <view class="swiper-button-prev" :class="{ 'swiper-button-disabled': currentIndex === 0 && !circular }"
- @click="swipePrev">
- <svg class="swiper-navigation-icon" width="11" height="20" viewBox="0 0 11 20" fill="none"
- xmlns="http://www.w3.org/2000/svg">
- <path
- d="M10.4341 0.482966C10.7052 0.754138 10.7052 1.19379 10.4341 1.46497L1.61942 10.2796L10.4341 19.0942C10.7052 19.3654 10.7052 19.805 10.4341 20.0762C10.1629 20.3474 9.72321 20.3474 9.45204 20.0762L0.382867 11.007C-0.0188908 10.6053 -0.0188908 9.9539 0.382867 9.55214L9.45204 0.482966C9.72321 0.211794 10.1629 0.211794 10.4341 0.482966Z"
- fill="currentColor" />
- </svg>
- </view>
- <view class="swiper-button-next"
- :class="{ 'swiper-button-disabled': currentIndex === list.length - 1 && !circular }" @click="swipeNext">
- <svg class="swiper-navigation-icon" width="11" height="20" viewBox="0 0 11 20" fill="none"
- xmlns="http://www.w3.org/2000/svg">
- <path
- d="M0.38296 20.0762C0.111788 19.805 0.111788 19.3654 0.38296 19.0942L9.19758 10.2796L0.38296 1.46497C0.111788 1.19379 0.111788 0.754138 0.38296 0.482966C0.654131 0.211794 1.09379 0.211794 1.36496 0.482966L10.4341 9.55214C10.8359 9.9539 10.8359 10.6053 10.4341 11.007L1.36496 20.0762C1.09379 20.3474 0.654131 20.3474 0.38296 20.0762Z"
- fill="currentColor" />
- </svg>
- </view>
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { ref, computed, watch, onMounted } from 'vue'
- const props = defineProps({
- // 轮播数据
- list: {
- type: Array,
- default: () => []
- },
- // 当前索引
- modelValue: {
- type: Number,
- default: 0
- },
- // 高度
- height: {
- type: String,
- default: '400rpx'
- },
- // 是否自动播放
- autoplay: {
- type: Boolean,
- default: true
- },
- // 自动播放间隔(毫秒)
- interval: {
- type: Number,
- default: 3000
- },
- // 滑动动画时长(毫秒)
- duration: {
- type: Number,
- default: 500
- },
- // 是否采用衔接滑动
- circular: {
- type: Boolean,
- default: true
- },
- // 是否显示面板指示点
- indicatorDots: {
- type: Boolean,
- default: false
- },
- // 是否显示自定义圆点指示器
- showDots: {
- type: Boolean,
- default: false
- },
- // 是否显示滚动条
- showScrollbar: {
- type: Boolean,
- default: true
- },
- // 是否显示导航按钮
- showNavigation: {
- type: Boolean,
- default: true
- },
- // 前边距,可用于露出前一项的一小部分
- previousMargin: {
- type: String,
- default: '0px'
- },
- // 后边距,可用于露出后一项的一小部分
- nextMargin: {
- type: String,
- default: '0px'
- }
- })
- const emit = defineEmits(['update:modelValue', 'change', 'click'])
- // 当前索引
- const currentIndex = ref(props.modelValue)
- // 监听外部变化
- watch(() => props.modelValue, (val) => {
- currentIndex.value = val
- })
- const swiperRef = ref<any>(null)
- // 计算滚动条宽度
- const scrollbarWidth = computed(() => {
- const total = props.list.length
- if (total === 0) return '0%'
- return `${100 / total}%`
- })
- // 计算滚动条偏移
- const scrollbarOffset = computed(() => {
- return `${currentIndex.value * 100}%`
- })
- // 滑动变化
- const onSwiperChange = (e: any) => {
- const { current } = e.detail
- currentIndex.value = current
- emit('update:modelValue', current)
- emit('change', current)
- }
- // 动画完成
- const onAnimationFinish = (e: any) => {
- // 可以在这里处理动画完成后的逻辑
- }
- // 跳转到指定幻灯片
- const goToSlide = (index: number) => {
- currentIndex.value = index
- emit('update:modelValue', index)
- emit('change', index)
- }
- // 上一页
- const swipePrev = () => {
- if (currentIndex.value === 0 && !props.circular) return
- swiperRef.value?.swipePrev?.()
- const newIndex = currentIndex.value === 0 ? props.list.length - 1 : currentIndex.value - 1
- goToSlide(newIndex)
- }
- // 下一页
- const swipeNext = () => {
- if (currentIndex.value === props.list.length - 1 && !props.circular) return
- swiperRef.value?.swipeNext?.()
- const newIndex = currentIndex.value === props.list.length - 1 ? 0 : currentIndex.value + 1
- goToSlide(newIndex)
- }
- // 点击项目
- const onItemClick = (index: number) => {
- emit('click', index)
- }
- </script>
- <style scoped lang="scss">
- @import "@/uni.scss";
- .swiper-container {
- position: relative;
- width: 100%;
- overflow: hidden;
- }
- .custom-swiper {
- width: 100%;
- }
- .swiper-item {
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- .swiper-image {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .default-content {
- font-size: px2rpx(24);
- color: var(--bs-heading-color);
- }
- }
- // 自定义指示点
- .swiper-dots {
- position: absolute;
- bottom: px2rpx(20);
- left: 0;
- right: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: px2rpx(16);
- z-index: 10;
- .dot {
- width: px2rpx(16);
- height: px2rpx(16);
- border-radius: 50%;
- background-color: rgba(255, 255, 255, 0.5);
- transition: all 0.3s;
- &-active {
- width: px2rpx(32);
- border-radius: px2rpx(16);
- background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important;
- }
- }
- }
- // 滚动条
- .swiper-scrollbar {
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- height: px2rpx(4);
- background-color: rgba(0, 0, 0, 0.1);
- z-index: 10;
- .swiper-scrollbar-drag {
- height: 100%;
- background: #00000080;
- border-radius: px2rpx(10);
- transition: transform 0.3s;
- }
- }
- // 导航按钮
- .swiper-navigation {
- position: absolute;
- top: 50%;
- left: 0;
- right: 0;
- transform: translateY(-50%);
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 px2rpx(20);
- z-index: 10;
- pointer-events: none;
- .swiper-button-prev,
- .swiper-button-next {
- width: px2rpx(60);
- height: px2rpx(60);
- background-color: rgba(0, 0, 0, 0.3);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- pointer-events: auto;
- transition: all 0.3s;
- &.swiper-button-disabled {
- opacity: 0.3;
- pointer-events: none;
- }
- &:active {
- background-color: rgba(0, 0, 0, 0.5);
- }
- .swiper-navigation-icon {
- width: px2rpx(24);
- height: px2rpx(40);
- color: var(--color-white);
- }
- }
- }
- // 深色背景适配
- .dark-theme {
- .swiper-dots .dot {
- background-color: rgba(255, 255, 255, 0.3);
- &-active {
- background-color: #ffd700;
- }
- }
- .swiper-scrollbar {
- background-color: rgba(255, 255, 255, 0.2);
- .swiper-scrollbar-drag {
- background-color: #ffd700;
- }
- }
- }
- </style>
|