mirror of
http://36.133.248.69:3088/admin/RentWeAppFront.git
synced 2026-03-07 17:32:25 +08:00
402 lines
8.2 KiB
Vue
402 lines
8.2 KiB
Vue
|
|
<template>
|
|||
|
|
<view class="carousel" :style="{width:boxWidth,height:boxHeight}" @touchstart="touchStart" @touchmove="touchMove"
|
|||
|
|
@touchend="touchEnd">
|
|||
|
|
<!-- 轮播轨道 -->
|
|||
|
|
<view class="track" :style="trackStyle">
|
|||
|
|
<view class="slide" v-for="(item, index) in normalizedList" :key="index">
|
|||
|
|
<!-- 图片 -->
|
|||
|
|
<image v-if="item.mediaType === 'image'" :src="item.src" mode="aspectFill" />
|
|||
|
|
|
|||
|
|
<!-- 视频 -->
|
|||
|
|
<view v-else-if="item.mediaType === 'video'" class="video-wrapper" @click.stop="playVideo(index)">
|
|||
|
|
<video :id="'video-' + index" :src="item.src" :poster="item.poster"
|
|||
|
|
:controls="playingIndex === index" :show-center-play-btn="false" object-fit="cover"
|
|||
|
|
@play="onVideoPlay" @pause="onVideoPause" />
|
|||
|
|
<view v-if="playingIndex !== index" class="play-btn">▶</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- VR -->
|
|||
|
|
<view v-else-if="item.mediaType === 'vr'" class="vr-wrapper">
|
|||
|
|
<image class="vr-cover" :src="item.src" mode="aspectFill" />
|
|||
|
|
<image class="vr-icon" mode="aspectFit" :src="vrIcon" @tap.stop="enterVR()" />
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 点指示器 -->
|
|||
|
|
<view v-if="indicator === 'dot'" class="dots">
|
|||
|
|
<view v-for="(item, index) in normalizedList" :key="index" class="dot"
|
|||
|
|
:class="{ active: index === current }" />
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 箭头指示器 -->
|
|||
|
|
<view v-if="indicator === 'arrow'" class="arrow left" @click.stop="prev">‹</view>
|
|||
|
|
<view v-if="indicator === 'arrow'" class="arrow right" @click.stop="next">›</view>
|
|||
|
|
|
|||
|
|
<!-- 底部分区长条指示器(固定分区长度,跳动游标) -->
|
|||
|
|
<view v-if="autoTypeIndicateList.length" class="type-bar">
|
|||
|
|
<view v-for="(item, index) in autoTypeIndicateList" :key="index" class="type-segment"
|
|||
|
|
:class="{ active: current >= item.startIndex && current <= item.endIndex }"
|
|||
|
|
:style="{ width: getSegmentWidth + '%' }" @click.stop="jumpToType(item)">
|
|||
|
|
{{ item.name }}
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
<!-- 右下角索引 -->
|
|||
|
|
<view class="index-display">
|
|||
|
|
{{ current + 1 }} / {{ normalizedList.length }}
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
export default {
|
|||
|
|
name: 'Carousel',
|
|||
|
|
props: {
|
|||
|
|
list: {
|
|||
|
|
type: Array,
|
|||
|
|
required: true
|
|||
|
|
}, // [{src:'',type:''}]
|
|||
|
|
indicator: {
|
|||
|
|
type: String,
|
|||
|
|
default: 'dot'
|
|||
|
|
}, // dot | arrow | none
|
|||
|
|
autoplay: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: false
|
|||
|
|
},
|
|||
|
|
interval: {
|
|||
|
|
type: Number,
|
|||
|
|
default: 3000
|
|||
|
|
},
|
|||
|
|
vrIcon: {
|
|||
|
|
type: String,
|
|||
|
|
default: ''
|
|||
|
|
},
|
|||
|
|
boxWidth: {
|
|||
|
|
type: String,
|
|||
|
|
default: '100%'
|
|||
|
|
},
|
|||
|
|
boxHeight: {
|
|||
|
|
type: String,
|
|||
|
|
default: '500rpx'
|
|||
|
|
},
|
|||
|
|
vrViewPage: {
|
|||
|
|
type: String,
|
|||
|
|
default: ''
|
|||
|
|
},
|
|||
|
|
vrList:{
|
|||
|
|
type: Array,
|
|||
|
|
default: []
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
current: 0,
|
|||
|
|
containerWidth: 0,
|
|||
|
|
startX: 0,
|
|||
|
|
offsetX: 0,
|
|||
|
|
timer: null,
|
|||
|
|
playingIndex: null
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
computed: {
|
|||
|
|
/* VR 永远在最前 */
|
|||
|
|
normalizedList() {
|
|||
|
|
const vr = []
|
|||
|
|
const other = []
|
|||
|
|
this.list.forEach(item => {
|
|||
|
|
item.mediaType === 'vr' ? vr.push(item) : other.push(item)
|
|||
|
|
})
|
|||
|
|
return [...vr, ...other]
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
trackStyle() {
|
|||
|
|
return `width:${this.normalizedList.length * this.containerWidth}px;
|
|||
|
|
transform: translate3d(${-this.current * this.containerWidth + this.offsetX}px,0,0);
|
|||
|
|
transition:${this.offsetX === 0 ? 'transform .3s' : 'none'};`
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/* 自动生成分区指示 */
|
|||
|
|
autoTypeIndicateList() {
|
|||
|
|
const list = this.normalizedList
|
|||
|
|
if (!list.length) return []
|
|||
|
|
|
|||
|
|
const result = []
|
|||
|
|
let startIndex = 0
|
|||
|
|
let bizType = list[0].bizType
|
|||
|
|
for (let i = 1; i <= list.length; i++) {
|
|||
|
|
if (i === list.length || list[i].bizType !== bizType) {
|
|||
|
|
result.push({
|
|||
|
|
type: bizType,
|
|||
|
|
name: bizType,
|
|||
|
|
startIndex,
|
|||
|
|
endIndex: i - 1
|
|||
|
|
})
|
|||
|
|
startIndex = i
|
|||
|
|
bizType = list[i] && list[i].bizType
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return result
|
|||
|
|
},
|
|||
|
|
getSegmentWidth() {
|
|||
|
|
if (!this.autoTypeIndicateList.length) return 100
|
|||
|
|
return 100 / this.autoTypeIndicateList.length
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
mounted() {
|
|||
|
|
this.$nextTick(this.calcWidth)
|
|||
|
|
this.start()
|
|||
|
|
},
|
|||
|
|
beforeUnmount() {
|
|||
|
|
this.stop()
|
|||
|
|
},
|
|||
|
|
onLoad() {
|
|||
|
|
this.fillVrList();
|
|||
|
|
},
|
|||
|
|
watch: {
|
|||
|
|
normalizedList(list) {
|
|||
|
|
if (this.current >= list.length) this.current = 0
|
|||
|
|
},
|
|||
|
|
current() {
|
|||
|
|
this.stopVideo()
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
calcWidth() {
|
|||
|
|
uni.createSelectorQuery()
|
|||
|
|
.in(this)
|
|||
|
|
.select('.carousel')
|
|||
|
|
.boundingClientRect(rect => {
|
|||
|
|
if (rect) this.containerWidth = rect.width
|
|||
|
|
})
|
|||
|
|
.exec()
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
start() {
|
|||
|
|
if (!this.autoplay) return
|
|||
|
|
this.stop()
|
|||
|
|
this.timer = setInterval(this.next, this.interval)
|
|||
|
|
},
|
|||
|
|
stop() {
|
|||
|
|
this.timer && clearInterval(this.timer)
|
|||
|
|
this.timer = null
|
|||
|
|
},
|
|||
|
|
next() {
|
|||
|
|
this.current = (this.current + 1) % this.normalizedList.length
|
|||
|
|
},
|
|||
|
|
prev() {
|
|||
|
|
this.current = (this.current - 1 + this.normalizedList.length) % this.normalizedList.length
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
touchStart(e) {
|
|||
|
|
this.stop()
|
|||
|
|
this.startX = e.touches[0].clientX
|
|||
|
|
},
|
|||
|
|
touchMove(e) {
|
|||
|
|
this.offsetX = e.touches[0].clientX - this.startX
|
|||
|
|
},
|
|||
|
|
touchEnd() {
|
|||
|
|
if (Math.abs(this.offsetX) > this.containerWidth / 4) {
|
|||
|
|
this.offsetX > 0 ? this.prev() : this.next()
|
|||
|
|
}
|
|||
|
|
this.offsetX = 0
|
|||
|
|
this.start()
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/* 视频 */
|
|||
|
|
playVideo(index) {
|
|||
|
|
if (this.playingIndex === index) return
|
|||
|
|
this.stopVideo()
|
|||
|
|
this.playingIndex = index
|
|||
|
|
this.stop()
|
|||
|
|
this.$nextTick(() => {
|
|||
|
|
uni.createVideoContext('video-' + index, this).play()
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
stopVideo() {
|
|||
|
|
if (this.playingIndex !== null) {
|
|||
|
|
uni.createVideoContext('video-' + this.playingIndex, this).pause()
|
|||
|
|
this.playingIndex = null
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
onVideoPlay() {
|
|||
|
|
this.stop()
|
|||
|
|
},
|
|||
|
|
onVideoPause() {
|
|||
|
|
this.start()
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/* VR */
|
|||
|
|
enterVR() {
|
|||
|
|
this.stop()
|
|||
|
|
this.stopVideo()
|
|||
|
|
if(this.vrList.length > 0) {
|
|||
|
|
console.log(this.vrViewPage)
|
|||
|
|
// this.$u.route({
|
|||
|
|
// url: this.vrViewPage,
|
|||
|
|
// params: {
|
|||
|
|
// title:"vr看资产",
|
|||
|
|
// vrList: this.vrList
|
|||
|
|
// }
|
|||
|
|
// })
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
/* 分区跳转 */
|
|||
|
|
jumpToType(item) {
|
|||
|
|
this.stop()
|
|||
|
|
this.stopVideo()
|
|||
|
|
this.current = item.startIndex
|
|||
|
|
this.$nextTick(this.start)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.carousel {
|
|||
|
|
overflow: hidden;
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.track {
|
|||
|
|
display: flex;
|
|||
|
|
height: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.slide {
|
|||
|
|
width: 100%;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.slide image,
|
|||
|
|
.video-wrapper,
|
|||
|
|
.vr-wrapper,
|
|||
|
|
video {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* dots */
|
|||
|
|
.dots {
|
|||
|
|
position: absolute;
|
|||
|
|
bottom: 20rpx;
|
|||
|
|
left: 50%;
|
|||
|
|
transform: translateX(-50%);
|
|||
|
|
display: flex;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dot {
|
|||
|
|
width: 12rpx;
|
|||
|
|
height: 12rpx;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
background: rgba(255, 255, 255, .5);
|
|||
|
|
margin: 0 6rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dot.active {
|
|||
|
|
background: #fff;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* arrows */
|
|||
|
|
.arrow {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 50%;
|
|||
|
|
transform: translateY(-50%);
|
|||
|
|
width: 60rpx;
|
|||
|
|
height: 60rpx;
|
|||
|
|
line-height: 60rpx;
|
|||
|
|
text-align: center;
|
|||
|
|
background: rgba(0, 0, 0, .4);
|
|||
|
|
color: #fff;
|
|||
|
|
font-size: 40rpx;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.arrow.left {
|
|||
|
|
left: 20rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.arrow.right {
|
|||
|
|
right: 20rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* video */
|
|||
|
|
.video-wrapper {
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.play-btn {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 50%;
|
|||
|
|
left: 50%;
|
|||
|
|
transform: translate(-50%, -50%);
|
|||
|
|
width: 80rpx;
|
|||
|
|
height: 80rpx;
|
|||
|
|
line-height: 80rpx;
|
|||
|
|
text-align: center;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
background: rgba(0, 0, 0, .5);
|
|||
|
|
color: #fff;
|
|||
|
|
font-size: 40rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* vr */
|
|||
|
|
.vr-wrapper {
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.vr-icon {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 50%;
|
|||
|
|
left: 12%;
|
|||
|
|
transform: translate(-50%, -50%);
|
|||
|
|
width: 100rpx !important;
|
|||
|
|
height: 100rpx !important;
|
|||
|
|
background: rgba(80, 80,80,0.45);
|
|||
|
|
border-radius: 50%;
|
|||
|
|
padding: 12rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.type-bar {
|
|||
|
|
position: absolute;
|
|||
|
|
bottom: 80rpx;
|
|||
|
|
left: 50%;
|
|||
|
|
transform: translateX(-50%);
|
|||
|
|
height: 40rpx;
|
|||
|
|
display: flex;
|
|||
|
|
background: rgba(0, 0, 0, 0.4);
|
|||
|
|
overflow: hidden;
|
|||
|
|
min-width: 300rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
.type-segment {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: center;
|
|||
|
|
align-items: center;
|
|||
|
|
color: #fff;
|
|||
|
|
/* 保持文字白色 */
|
|||
|
|
font-size: 20rpx;
|
|||
|
|
cursor: pointer;
|
|||
|
|
padding: 0 10rpx;
|
|||
|
|
transition: background 0.3s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.type-segment.active {
|
|||
|
|
background: rgba(255, 47, 49, 0.6);
|
|||
|
|
/* 仅背景高亮,不覆盖文字 */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.index-display {
|
|||
|
|
position: absolute;
|
|||
|
|
bottom: 80rpx;
|
|||
|
|
right: 20rpx;
|
|||
|
|
padding: 6rpx 12rpx;
|
|||
|
|
background: rgba(0, 0, 0, 0.5);
|
|||
|
|
color: #fff;
|
|||
|
|
font-size: 24rpx;
|
|||
|
|
border-radius: 12rpx;
|
|||
|
|
}
|
|||
|
|
</style>
|