CNCEC_APP/uni_modules/uview-pro/components/u-swiper/u-swiper.vue

309 lines
9.3 KiB
Vue
Raw Normal View History

2026-03-25 14:54:15 +08:00
<template>
<view
class="u-swiper-wrap"
:style="$u.toStyle({ borderRadius: `${borderRadius}rpx` }, customStyle)"
:class="customClass"
>
<swiper
:current="elCurrent"
@change="change"
@animationfinish="animationfinish"
:interval="interval"
:circular="circular"
:duration="duration"
:autoplay="autoplay"
:previous-margin="effect3d ? effect3dPreviousMargin + 'rpx' : '0'"
:next-margin="effect3d ? effect3dPreviousMargin + 'rpx' : '0'"
:style="{ height: height + 'rpx', backgroundColor: bgColor }"
>
<swiper-item class="u-swiper-item" v-for="(item, index) in list" :key="index">
<view
class="u-list-image-wrap"
@tap.stop.prevent="listClick(index)"
:class="[uCurrent != index ? 'u-list-scale' : '']"
:style="{
borderRadius: `${borderRadius}rpx`,
transform: effect3d && uCurrent != index ? 'scaleY(0.9)' : 'scaleY(1)',
margin: effect3d && uCurrent != index ? '0 20rpx' : 0
}"
>
<image class="u-swiper-image" :src="item[name] || item" :mode="imgMode"></image>
<view
v-if="title && item.title"
class="u-swiper-title u-line-1"
:style="[{ 'padding-bottom': titlePaddingBottom }, titleStyle]"
>
{{ item.title }}
</view>
</view>
</swiper-item>
</swiper>
<view
class="u-swiper-indicator"
:style="{
top:
indicatorPos == 'topLeft' || indicatorPos == 'topCenter' || indicatorPos == 'topRight'
? '12rpx'
: 'auto',
bottom:
indicatorPos == 'bottomLeft' || indicatorPos == 'bottomCenter' || indicatorPos == 'bottomRight'
? '12rpx'
: 'auto',
justifyContent: justifyContent,
padding: `0 ${effect3d ? '74rpx' : '24rpx'}`
}"
>
<block v-if="mode == 'rect'">
<view
class="u-indicator-item-rect"
:class="{ 'u-indicator-item-rect-active': index == uCurrent }"
v-for="(item, index) in list"
:key="index"
></view>
</block>
<block v-if="mode == 'dot'">
<view
class="u-indicator-item-dot"
:class="{ 'u-indicator-item-dot-active': index == uCurrent }"
v-for="(item, index) in list"
:key="index"
></view>
</block>
<block v-if="mode == 'round'">
<view
class="u-indicator-item-round"
:class="{ 'u-indicator-item-round-active': index == uCurrent }"
v-for="(item, index) in list"
:key="index"
></view>
</block>
<block v-if="mode == 'number'">
<view class="u-indicator-item-number">{{ uCurrent + 1 }}/{{ list.length }}</view>
</block>
</view>
</view>
</template>
<script lang="ts">
export default {
name: 'u-swiper',
options: {
addGlobalClass: true,
// #ifndef MP-TOUTIAO
virtualHost: true,
// #endif
styleIsolation: 'shared'
}
};
</script>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { SwiperProps } from './types';
import { $u } from '../..';
/**
* swiper 轮播图
* @description 该组件一般用于导航轮播广告展示等场景,可开箱即用
* @tutorial https://uviewpro.cn/zh/components/swiper.html
* @property {Array} list 轮播图数据见官网"基本使用"说明
* @property {Boolean} title 是否显示标题文字需要配合list参数见官网说明默认false
* @property {String} mode 指示器模式见官网说明默认round
* @property {String|Number} height 轮播图组件高度单位rpx默认250
* @property {String} indicator-pos 指示器的位置默认bottomCenter
* @property {Boolean} effect3d 是否开启3D效果默认false
* @property {Boolean} autoplay 是否自动播放默认true
* @property {String|Number} interval 自动轮播时间间隔单位ms默认2500
* @property {Boolean} circular 是否衔接播放见官网说明默认true
* @property {String} bg-color 背景颜色默认var(--u-bg-color)
* @property {String|Number} border-radius 轮播图圆角值单位rpx默认8
* @property {Object} title-style 自定义标题样式
* @property {String|Number} effect3d-previous-margin mode = true模式的情况下激活项与前后项之间的距离单位rpx默认50
* @property {String} img-mode 图片的裁剪模式详见image组件裁剪模式默认aspectFill
* @event {Function} click 点击轮播图时触发
* @example <u-swiper :list="list" mode="dot" indicator-pos="bottomRight"></u-swiper>
*/
const props = defineProps(SwiperProps);
const emit = defineEmits(['click', 'change']);
// 当前活跃的swiper-item的index
const uCurrent = ref(Number(props.current));
// 监听list变化重置uCurrent值避免溢出
watch(
() => props.list,
(nVal, oVal) => {
if (nVal.length !== oVal.length) uCurrent.value = 0;
}
);
// 监听外部current的变化实时修改内部依赖于此测uCurrent值
watch(
() => props.current,
n => {
uCurrent.value = Number(n);
}
);
// 容器 justifyContent
const justifyContent = computed(() => {
if (props.indicatorPos == 'topLeft' || props.indicatorPos == 'bottomLeft') return 'flex-start';
if (props.indicatorPos == 'topCenter' || props.indicatorPos == 'bottomCenter') return 'center';
if (props.indicatorPos == 'topRight' || props.indicatorPos == 'bottomRight') return 'flex-end';
return 'center';
});
// 标题下边距
const titlePaddingBottom = computed(() => {
if (props.mode == 'none') return '12rpx';
if (['bottomLeft', 'bottomCenter', 'bottomRight'].includes(props.indicatorPos) && props.mode == 'number') {
return '60rpx';
} else if (['bottomLeft', 'bottomCenter', 'bottomRight'].includes(props.indicatorPos) && props.mode != 'number') {
return '40rpx';
} else {
return '12rpx';
}
});
// swiper组件current参数只接受Number类型
const elCurrent = computed(() => Number(props.current));
/**
* 点击轮播图项
*/
function listClick(index: number) {
emit('click', index);
}
/**
* swiper change事件
*/
function change(e: any) {
const current = e.detail.current;
uCurrent.value = current;
emit('change', current);
}
/**
* swiper animationfinish事件
* 头条小程序不支持animationfinish事件改由change事件
* 暂不监听此事件因为不再给swiper绑定uCurrent属性
*/
function animationfinish(e: any) {
// #ifndef MP-TOUTIAO
// uCurrent.value = e.detail.current
// #endif
}
defineExpose({ listClick, change, animationfinish });
</script>
<style lang="scss" scoped>
@import '../../libs/css/style.components.scss';
.u-swiper-wrap {
position: relative;
overflow: hidden;
transform: translateY(0);
}
.u-swiper-image {
width: 100%;
will-change: transform;
height: 100%;
/* #ifndef APP-NVUE */
display: block;
/* #endif */
/* #ifdef H5 */
pointer-events: none;
/* #endif */
}
.u-swiper-indicator {
padding: 0 24rpx;
position: absolute;
@include vue-flex;
width: 100%;
z-index: 1;
}
.u-indicator-item-rect {
width: 26rpx;
height: 8rpx;
margin: 0 6rpx;
transition: all 0.5s;
background-color: rgba(0, 0, 0, 0.3);
}
.u-indicator-item-rect-active {
background-color: rgba(255, 255, 255, 0.8);
}
.u-indicator-item-dot {
width: 14rpx;
height: 14rpx;
margin: 0 6rpx;
border-radius: 20rpx;
transition: all 0.5s;
background-color: rgba(0, 0, 0, 0.3);
}
.u-indicator-item-dot-active {
background-color: rgba(255, 255, 255, 0.8);
}
.u-indicator-item-round {
width: 14rpx;
height: 14rpx;
margin: 0 6rpx;
border-radius: 20rpx;
transition: all 0.5s;
background-color: rgba(0, 0, 0, 0.3);
}
.u-indicator-item-round-active {
width: 34rpx;
background-color: rgba(255, 255, 255, 0.8);
}
.u-indicator-item-number {
padding: 6rpx 16rpx;
line-height: 1;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 100rpx;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
}
.u-list-scale {
transform-origin: center center;
}
.u-list-image-wrap {
width: 100%;
height: 100%;
flex: 1;
transition: all 0.5s;
overflow: hidden;
box-sizing: content-box;
position: relative;
}
.u-swiper-title {
position: absolute;
background-color: rgba(0, 0, 0, 0.3);
bottom: 0;
left: 0;
width: 100%;
font-size: 28rpx;
padding: 12rpx 24rpx;
color: rgba(255, 255, 255, 0.9);
}
.u-swiper-item {
@include vue-flex;
overflow: hidden;
align-items: center;
}
</style>