503 lines
12 KiB
Vue
503 lines
12 KiB
Vue
|
<template>
|
|||
|
<view>
|
|||
|
<view class="ui-swiper" :class="[props.mode, props.bg, props.ui]">
|
|||
|
<swiper
|
|||
|
:circular="props.circular"
|
|||
|
:current="state.cur"
|
|||
|
:autoplay="props.autoplay && !state.videoPlaySataus"
|
|||
|
:interval="props.interval"
|
|||
|
:duration="props.duration"
|
|||
|
@transition="transition"
|
|||
|
@animationfinish="animationfinish"
|
|||
|
:style="customStyle"
|
|||
|
@change="swiperChange"
|
|||
|
>
|
|||
|
<swiper-item
|
|||
|
class="swiper-item"
|
|||
|
v-for="(item, index) in props.list"
|
|||
|
:key="index"
|
|||
|
:class="{ cur: state.cur == index }"
|
|||
|
@tap="onSwiperItem(item)"
|
|||
|
>
|
|||
|
<view class="ui-swiper-main">
|
|||
|
<image
|
|||
|
v-if="item.type === 'image'"
|
|||
|
class="swiper-image"
|
|||
|
:mode="props.imageMode"
|
|||
|
:src="item.src"
|
|||
|
width="100%"
|
|||
|
height="100%"
|
|||
|
@load="onImgLoad"
|
|||
|
></image>
|
|||
|
<su-video
|
|||
|
v-else
|
|||
|
:ref="(el) => (refs.videoRef[`video_${index}`] = el)"
|
|||
|
:poster="sheep.$url.cdn(item.poster)"
|
|||
|
:src="sheep.$url.cdn(item.src)"
|
|||
|
:index="index"
|
|||
|
:moveX="state.moveX"
|
|||
|
:initialTime="item.currentTime || 0"
|
|||
|
:height="seizeHeight"
|
|||
|
@videoTimeupdate="videoTimeupdate"
|
|||
|
></su-video>
|
|||
|
</view>
|
|||
|
</swiper-item>
|
|||
|
</swiper>
|
|||
|
<template v-if="!state.videoPlaySataus">
|
|||
|
<view class="ui-swiper-dot" :class="props.dotStyle" v-if="props.dotStyle != 'tag'">
|
|||
|
<view
|
|||
|
class="line-box"
|
|||
|
v-for="(item, index) in props.list"
|
|||
|
:key="index"
|
|||
|
:class="[state.cur == index ? 'cur' : '', props.dotCur]"
|
|||
|
></view>
|
|||
|
</view>
|
|||
|
<view class="ui-swiper-dot" :class="props.dotStyle" v-if="props.dotStyle == 'tag'">
|
|||
|
<view
|
|||
|
class="ui-tag radius-lg"
|
|||
|
:class="[props.dotCur]"
|
|||
|
style="pointer-events: none; padding: 0 10rpx"
|
|||
|
>
|
|||
|
<view style="transform: scale(0.7)">{{ state.cur + 1 }} / {{ props.list.length }}</view>
|
|||
|
</view>
|
|||
|
</view>
|
|||
|
</template>
|
|||
|
</view>
|
|||
|
</view>
|
|||
|
</template>
|
|||
|
|
|||
|
<script setup>
|
|||
|
/**
|
|||
|
* 轮播组件
|
|||
|
*
|
|||
|
* @property {Boolean} circular = false - 是否采用衔接滑动,即播放到末尾后重新回到开头
|
|||
|
* @property {Boolean} autoplay = true - 是否自动切换
|
|||
|
* @property {Number} interval = 5000 - 自动切换时间间隔
|
|||
|
* @property {Number} duration = 500 - 滑动动画时长,app-nvue不支持
|
|||
|
* @property {Array} list = [] - 轮播数据
|
|||
|
* @property {String} ui = '' - 样式class
|
|||
|
* @property {String} mode - 模式
|
|||
|
* @property {String} dotStyle - 指示点样式
|
|||
|
* @property {String} dotCur= 'ui-BG-Main' - 当前指示点样式,默认主题色
|
|||
|
* @property {String} bg - 背景
|
|||
|
* @property {String} height = 300 - 组件高度
|
|||
|
* @property {String} imgHeight = 300 - 图片高度
|
|||
|
*
|
|||
|
* @example list = [{url:'跳转路径',urlType:'跳转方式',type:'轮播类型',src:'轮播内容地址',poster:'视频必传'}]
|
|||
|
*/
|
|||
|
|
|||
|
import { reactive, computed } from 'vue';
|
|||
|
import sheep from '@/sheep';
|
|||
|
import { clone } from 'lodash-es';
|
|||
|
|
|||
|
// 数据
|
|||
|
const state = reactive({
|
|||
|
imgHeight: 0,
|
|||
|
cur: 0,
|
|||
|
moveX: 0,
|
|||
|
videoPlaySataus: false,
|
|||
|
heightList: [],
|
|||
|
});
|
|||
|
|
|||
|
const refs = reactive({
|
|||
|
videoRef: {},
|
|||
|
});
|
|||
|
|
|||
|
// 接收参数
|
|||
|
|
|||
|
const props = defineProps({
|
|||
|
circular: {
|
|||
|
type: Boolean,
|
|||
|
default: true,
|
|||
|
},
|
|||
|
autoplay: {
|
|||
|
type: Boolean,
|
|||
|
default: false,
|
|||
|
},
|
|||
|
interval: {
|
|||
|
type: Number,
|
|||
|
default: 3000,
|
|||
|
},
|
|||
|
duration: {
|
|||
|
type: Number,
|
|||
|
default: 500,
|
|||
|
},
|
|||
|
mode: {
|
|||
|
type: String,
|
|||
|
default: 'default',
|
|||
|
},
|
|||
|
imageMode: {
|
|||
|
type: String,
|
|||
|
default: 'scaleToFill',
|
|||
|
},
|
|||
|
list: {
|
|||
|
type: Array,
|
|||
|
default() {
|
|||
|
return [];
|
|||
|
},
|
|||
|
},
|
|||
|
dotStyle: {
|
|||
|
type: String,
|
|||
|
default: 'long', //default long tag
|
|||
|
},
|
|||
|
dotCur: {
|
|||
|
type: String,
|
|||
|
default: 'ss-bg-opactity-block',
|
|||
|
},
|
|||
|
bg: {
|
|||
|
type: String,
|
|||
|
default: 'bg-none',
|
|||
|
},
|
|||
|
height: {
|
|||
|
type: Number,
|
|||
|
default: 0,
|
|||
|
},
|
|||
|
imgHeight: {
|
|||
|
type: Number,
|
|||
|
default: 0,
|
|||
|
},
|
|||
|
imgTopRadius: {
|
|||
|
type: Number,
|
|||
|
default: 0,
|
|||
|
},
|
|||
|
imgBottomRadius: {
|
|||
|
type: Number,
|
|||
|
default: 0,
|
|||
|
},
|
|||
|
isPreview: {
|
|||
|
type: Boolean,
|
|||
|
default: false,
|
|||
|
},
|
|||
|
seizeHeight: {
|
|||
|
type: Number,
|
|||
|
default: 200,
|
|||
|
},
|
|||
|
});
|
|||
|
|
|||
|
// current 改变时会触发 change 事件
|
|||
|
const swiperChange = (e) => {
|
|||
|
if (e.detail.source !== 'touch' && e.detail.source !== 'autoplay') return;
|
|||
|
state.cur = e.detail.current;
|
|||
|
state.videoPlaySataus = false;
|
|||
|
if (props.list[state.cur].type === 'video') {
|
|||
|
refs.videoRef[`video_${state.cur}`].pausePlay();
|
|||
|
}
|
|||
|
};
|
|||
|
// 点击轮播组件
|
|||
|
const onSwiperItem = (item) => {
|
|||
|
if (item.type === 'video') {
|
|||
|
state.videoPlaySataus = true;
|
|||
|
} else {
|
|||
|
sheep.$router.go(item.url);
|
|||
|
onPreview();
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
const onPreview = () => {
|
|||
|
if (!props.isPreview) return;
|
|||
|
let previewImage = clone(props.list);
|
|||
|
previewImage.forEach((item, index) => {
|
|||
|
if (item.type === 'video') {
|
|||
|
previewImage.splice(index, 1);
|
|||
|
}
|
|||
|
});
|
|||
|
uni.previewImage({
|
|||
|
urls:
|
|||
|
previewImage.length < 1
|
|||
|
? [props.src]
|
|||
|
: previewImage.reduce((pre, cur) => {
|
|||
|
pre.push(cur.src);
|
|||
|
return pre;
|
|||
|
}, []),
|
|||
|
current: state.cur,
|
|||
|
// longPressActions: {
|
|||
|
// itemList: ['发送给朋友', '保存图片', '收藏'],
|
|||
|
// success: function (data) {
|
|||
|
// console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片');
|
|||
|
// },
|
|||
|
// fail: function (err) {
|
|||
|
// console.log(err.errMsg);
|
|||
|
// },
|
|||
|
// },
|
|||
|
});
|
|||
|
};
|
|||
|
//
|
|||
|
|
|||
|
// swiper-item 的位置发生改变时会触发 transition
|
|||
|
const transition = (e) => {
|
|||
|
// #ifdef APP-PLUS
|
|||
|
state.moveX = e.detail.dx;
|
|||
|
// #endif
|
|||
|
};
|
|||
|
|
|||
|
// 动画结束时会触发 animationfinish
|
|||
|
const animationfinish = (e) => {
|
|||
|
state.moveX = 0;
|
|||
|
};
|
|||
|
|
|||
|
const videoTimeupdate = (e) => {
|
|||
|
props.list[state.cur].currentTime = e.detail.currentTime;
|
|||
|
};
|
|||
|
|
|||
|
// 自动计算高度
|
|||
|
const customStyle = computed(() => {
|
|||
|
let height;
|
|||
|
|
|||
|
// 固定高度情况
|
|||
|
if (props.height !== 0) {
|
|||
|
height = props.height;
|
|||
|
}
|
|||
|
|
|||
|
// 自动高度情况
|
|||
|
if (props.height === 0) {
|
|||
|
// 图片预加载占位高度
|
|||
|
if (state.imgHeight !== 0) {
|
|||
|
height = state.imgHeight;
|
|||
|
} else if (props.seizeHeight !== 0) {
|
|||
|
height = props.seizeHeight;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return {
|
|||
|
height: height + 'rpx',
|
|||
|
};
|
|||
|
});
|
|||
|
|
|||
|
// 计算轮播图片最大高度
|
|||
|
function onImgLoad(e) {
|
|||
|
if (props.height === 0) {
|
|||
|
let newHeight = (e.detail.height / e.detail.width) * 750;
|
|||
|
if (state.imgHeight < newHeight) {
|
|||
|
state.imgHeight = newHeight;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</script>
|
|||
|
|
|||
|
<style lang="scss" scoped>
|
|||
|
.ui-swiper {
|
|||
|
position: relative;
|
|||
|
|
|||
|
.ui-swiper-main {
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
}
|
|||
|
|
|||
|
.ui-swiper-main .swiper-image {
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
}
|
|||
|
|
|||
|
.ui-swiper-dot {
|
|||
|
position: absolute;
|
|||
|
width: 100%;
|
|||
|
bottom: 20rpx;
|
|||
|
height: 30rpx;
|
|||
|
display: flex;
|
|||
|
align-items: center;
|
|||
|
justify-content: center;
|
|||
|
|
|||
|
&.default .line-box {
|
|||
|
display: inline-flex;
|
|||
|
border-radius: 50rpx;
|
|||
|
width: 6px;
|
|||
|
height: 6px;
|
|||
|
border: 2px solid transparent;
|
|||
|
margin: 0 10rpx;
|
|||
|
opacity: 0.3;
|
|||
|
position: relative;
|
|||
|
justify-content: center;
|
|||
|
align-items: center;
|
|||
|
|
|||
|
&.cur {
|
|||
|
width: 8px;
|
|||
|
height: 8px;
|
|||
|
opacity: 1;
|
|||
|
border: 0px solid transparent;
|
|||
|
}
|
|||
|
|
|||
|
&.cur::after {
|
|||
|
content: '';
|
|||
|
border-radius: 50rpx;
|
|||
|
width: 4px;
|
|||
|
height: 4px;
|
|||
|
background-color: #fff;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
&.long .line-box {
|
|||
|
display: inline-block;
|
|||
|
border-radius: 100rpx;
|
|||
|
width: 6px;
|
|||
|
height: 6px;
|
|||
|
margin: 0 10rpx;
|
|||
|
opacity: 0.3;
|
|||
|
position: relative;
|
|||
|
|
|||
|
&.cur {
|
|||
|
width: 24rpx;
|
|||
|
opacity: 1;
|
|||
|
}
|
|||
|
|
|||
|
&.cur::after {
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
&.line {
|
|||
|
bottom: 20rpx;
|
|||
|
|
|||
|
.line-box {
|
|||
|
display: inline-block;
|
|||
|
width: 30px;
|
|||
|
height: 3px;
|
|||
|
opacity: 0.3;
|
|||
|
position: relative;
|
|||
|
|
|||
|
&.cur {
|
|||
|
opacity: 1;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
&.tag {
|
|||
|
justify-content: flex-end;
|
|||
|
position: absolute;
|
|||
|
bottom: 20rpx;
|
|||
|
right: 20rpx;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
&.card {
|
|||
|
.swiper-item {
|
|||
|
width: 610rpx !important;
|
|||
|
left: 70rpx;
|
|||
|
box-sizing: border-box;
|
|||
|
padding: 20rpx 0rpx 60rpx;
|
|||
|
overflow: initial;
|
|||
|
}
|
|||
|
|
|||
|
.swiper-item .ui-swiper-main {
|
|||
|
width: 100%;
|
|||
|
display: block;
|
|||
|
height: 100%;
|
|||
|
transform: scale(0.9);
|
|||
|
transition: all 0.2s ease-in 0s;
|
|||
|
position: relative;
|
|||
|
background-size: cover;
|
|||
|
|
|||
|
.swiper-image {
|
|||
|
height: 100%;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
.swiper-item .ui-swiper-main::before {
|
|||
|
content: '';
|
|||
|
display: block;
|
|||
|
background: inherit;
|
|||
|
filter: blur(5px);
|
|||
|
position: absolute;
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
top: 10rpx;
|
|||
|
left: 10rpx;
|
|||
|
z-index: -1;
|
|||
|
opacity: 0.3;
|
|||
|
transform-origin: 0 0;
|
|||
|
transform: scale(1, 1);
|
|||
|
}
|
|||
|
|
|||
|
.swiper-item.cur .ui-swiper-main {
|
|||
|
transform: scale(1);
|
|||
|
transition: all 0.2s ease-in 0s;
|
|||
|
}
|
|||
|
|
|||
|
.ui-swiper-dot.tag {
|
|||
|
position: absolute;
|
|||
|
bottom: 85rpx;
|
|||
|
right: 75rpx;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
&.hotelCard {
|
|||
|
.swiper-item {
|
|||
|
width: 650rpx !important;
|
|||
|
left: 30rpx;
|
|||
|
box-sizing: border-box;
|
|||
|
padding: 0rpx 0rpx 50rpx;
|
|||
|
overflow: initial;
|
|||
|
}
|
|||
|
|
|||
|
.swiper-item .ui-swiper-main {
|
|||
|
width: 100%;
|
|||
|
display: block;
|
|||
|
height: 100%;
|
|||
|
transform: scale(0.9);
|
|||
|
opacity: 0.8;
|
|||
|
transition: all 0.2s ease-in 0s;
|
|||
|
position: relative;
|
|||
|
background-size: cover;
|
|||
|
|
|||
|
.swiper-image {
|
|||
|
width: 100%;
|
|||
|
height: 400rpx;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
.swiper-item .ui-swiper-main::before {
|
|||
|
content: '';
|
|||
|
display: block;
|
|||
|
background: inherit;
|
|||
|
filter: blur(5px);
|
|||
|
position: absolute;
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
top: 10rpx;
|
|||
|
left: 10rpx;
|
|||
|
z-index: -1;
|
|||
|
opacity: 0.3;
|
|||
|
transform-origin: 0 0;
|
|||
|
transform: scale(1, 1);
|
|||
|
}
|
|||
|
|
|||
|
.swiper-item.cur .ui-swiper-main {
|
|||
|
transform: scale(1);
|
|||
|
transition: all 0.2s ease-in 0s;
|
|||
|
opacity: 1;
|
|||
|
}
|
|||
|
|
|||
|
.ui-swiper-dot {
|
|||
|
display: none;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
&.hotelDetail {
|
|||
|
.swiper-item {
|
|||
|
width: 690rpx !important;
|
|||
|
left: 30rpx;
|
|||
|
box-sizing: border-box;
|
|||
|
padding: 20rpx 0rpx;
|
|||
|
overflow: initial;
|
|||
|
}
|
|||
|
|
|||
|
.swiper-item .ui-swiper-main {
|
|||
|
width: 100%;
|
|||
|
display: block;
|
|||
|
height: 100%;
|
|||
|
transform: scale(0.96);
|
|||
|
transition: all 0.2s ease-in 0s;
|
|||
|
position: relative;
|
|||
|
background-size: cover;
|
|||
|
|
|||
|
.swiper-image {
|
|||
|
height: 100%;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
.swiper-item.cur .ui-swiper-main {
|
|||
|
transform: scale(0.96);
|
|||
|
transition: all 0.2s ease-in 0s;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</style>
|