|
|
<template> <view class="u-swiper" :style="{ backgroundColor: bgColor, height: $u.addUnit(height), borderRadius: $u.addUnit(radius) }" > <view class="u-swiper__loading" v-if="loading" > <u-loading-icon mode="circle"></u-loading-icon> </view> <swiper v-else class="u-swiper__wrapper" :style="{ height: $u.addUnit(height), }" @change="change" :circular="circular" :interval="interval" :duration="duration" :autoplay="autoplay" :current="current" :currentItemId="currentItemId" :previousMargin="$u.addUnit(previousMargin)" :nextMargin="$u.addUnit(nextMargin)" :acceleration="acceleration" :displayMultipleItems="displayMultipleItems" :easingFunction="easingFunction" > <swiper-item class="u-swiper__wrapper__item" v-for="(item, index) in list" :key="index" > <view class="u-swiper__wrapper__item__wrapper" :style="[itemStyle(index)]" > <!-- 在nvue中,image图片的宽度默认为屏幕宽度,需要通过flex:1撑开,另外必须设置高度才能显示图片 --> <image class="u-swiper__wrapper__item__wrapper__image" v-if="getItemType(item) === 'image'" :src="getSource(item)" :mode="imgMode" @tap="clickHandler(index)" :style="{ height: $u.addUnit(height), borderRadius: $u.addUnit(radius) }" ></image> <video class="u-swiper__wrapper__item__wrapper__video" v-if="getItemType(item) === 'video'" :id="`video-${index}`" :enable-progress-gesture="false" :src="getSource(item)" :poster="getPoster(item)" :title="showTitle && $u.test.object(item) && item.title ? item.title : ''" :style="{ height: $u.addUnit(height) }" controls @tap="clickHandler(index)" ></video> <text v-if="showTitle && $u.test.object(item) && item.title && $u.test.image(getSource(item))" class="u-swiper__wrapper__item__wrapper__title u-line-1" >{{ item.title }}</text> </view> </swiper-item> </swiper> <view class="u-swiper__indicator" :style="[$u.addStyle(indicatorStyle)]"> <slot name="indicator"> <u-swiper-indicator v-if="!loading && indicator && !showTitle" :indicatorActiveColor="indicatorActiveColor" :indicatorInactiveColor="indicatorInactiveColor" :length="list.length" :current="currentIndex" :indicatorMode="indicatorMode" ></u-swiper-indicator> </slot> </view> </view> </template>
<script> import props from './props.js'; /** * Swiper 轮播图 * @description 该组件一般用于导航轮播,广告展示等场景,可开箱即用, * @tutorial https://www.uviewui.com/components/swiper.html
* @property {Array} list 轮播图数据 * @property {Boolean} indicator 是否显示面板指示器(默认 false ) * @property {String} indicatorActiveColor 指示器非激活颜色(默认 '#FFFFFF' ) * @property {String} indicatorInactiveColor 指示器的激活颜色(默认 'rgba(255, 255, 255, 0.35)' ) * @property {String | Object} indicatorStyle 指示器样式,可通过bottom,left,right进行定位 * @property {String} indicatorMode 指示器模式(默认 'line' ) * @property {Boolean} autoplay 是否自动切换(默认 true ) * @property {String | Number} current 当前所在滑块的 index(默认 0 ) * @property {String} currentItemId 当前所在滑块的 item-id ,不能与 current 被同时指定 * @property {String | Number} interval 滑块自动切换时间间隔(ms)(默认 3000 ) * @property {String | Number} duration 滑块切换过程所需时间(ms)(默认 300 ) * @property {Boolean} circular 播放到末尾后是否重新回到开头(默认 false ) * @property {String | Number} previousMargin 前边距,可用于露出前一项的一小部分,nvue和支付宝不支持(默认 0 ) * @property {String | Number} nextMargin 后边距,可用于露出后一项的一小部分,nvue和支付宝不支持(默认 0 ) * @property {Boolean} acceleration 当开启时,会根据滑动速度,连续滑动多屏,支付宝不支持(默认 false ) * @property {Number} displayMultipleItems 同时显示的滑块数量,nvue、支付宝小程序不支持(默认 1 ) * @property {String} easingFunction 指定swiper切换缓动动画类型, 只对微信小程序有效(默认 'default' ) * @property {String} keyName list数组中指定对象的目标属性名(默认 'url' ) * @property {String} imgMode 图片的裁剪模式(默认 'aspectFill' ) * @property {String | Number} height 组件高度(默认 130 ) * @property {String} bgColor 背景颜色(默认 '#f3f4f6' ) * @property {String | Number} radius 组件圆角,数值或带单位的字符串(默认 4 ) * @property {Boolean} loading 是否加载中(默认 false ) * @property {Boolean} showTitle 是否显示标题,要求数组对象中有title属性(默认 false ) * @event {Function(index)} click 点击轮播图时触发 index:点击了第几张图片,从0开始 * @event {Function(index)} change 轮播图切换时触发(自动或者手动切换) index:切换到了第几张图片,从0开始 * @example <u-swiper :list="list4" keyName="url" :autoplay="false"></u-swiper> */ export default { name: 'u-swiper', mixins: [uni.$u.mpMixin, uni.$u.mixin, props], data() { return { currentIndex: 0 } }, watch: { current(val, preVal) { if(val === preVal) return; this.currentIndex = val; // 和上游数据关联上
} }, computed: { itemStyle() { return index => { const style = {} // #ifndef APP-NVUE || MP-TOUTIAO
// 左右流出空间的写法不支持nvue和头条
// 只有配置了此二值,才加上对应的圆角,以及缩放
if (this.nextMargin && this.previousMargin) { style.borderRadius = uni.$u.addUnit(this.radius) if (index !== this.currentIndex) style.transform = 'scale(0.92)' } // #endif
return style } } }, methods: { getItemType(item) { if (typeof item === 'string') return uni.$u.test.video(this.getSource(item)) ? 'video' : 'image' if (typeof item === 'object' && this.keyName) { if (!item.type) return uni.$u.test.video(this.getSource(item)) ? 'video' : 'image' if (item.type === 'image') return 'image' if (item.type === 'video') return 'video' return 'image' } }, // 获取目标路径,可能数组中为字符串,对象的形式,额外可指定对象的目标属性名keyName
getSource(item) { if (typeof item === 'string') return item if (typeof item === 'object' && this.keyName) return item[this.keyName] else uni.$u.error('请按格式传递列表参数') return '' }, // 轮播切换事件
change(e) { // 当前的激活索引
const { current } = e.detail this.pauseVideo(this.currentIndex) this.currentIndex = current this.$emit('change', e.detail) }, // 切换轮播时,暂停视频播放
pauseVideo(index) { const lastItem = this.getSource(this.list[index]) if (uni.$u.test.video(lastItem)) { // 当视频隐藏时,暂停播放
const video = uni.createVideoContext(`video-${index}`, this) video.pause() } }, // 当一个轮播item为视频时,获取它的视频海报
getPoster(item) { return typeof item === 'object' && item.poster ? item.poster : '' }, // 点击某个item
clickHandler(index) { this.$emit('click', index) } }, } </script>
<style lang="scss" scoped> @import "../../libs/css/components.scss";
.u-swiper { @include flex; justify-content: center; align-items: center; position: relative; overflow: hidden;
&__wrapper { flex: 1;
&__item { flex: 1;
&__wrapper { @include flex; position: relative; overflow: hidden; transition: transform 0.3s; flex: 1;
&__image { flex: 1; }
&__video { flex: 1; }
&__title { position: absolute; background-color: rgba(0, 0, 0, 0.3); bottom: 0; left: 0; right: 0; font-size: 28rpx; padding: 12rpx 24rpx; color: #FFFFFF; flex: 1; } } } }
&__indicator { position: absolute; bottom: 10px; } } </style>
|