优化,完善代码

This commit is contained in:
2026-05-14 14:42:51 +08:00
parent 79a21ff0a5
commit 16c91facac
26 changed files with 1792 additions and 1322 deletions

View File

@@ -1,51 +1,55 @@
<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">
<view class="carousel" :style="{width: boxWidth, height: boxHeight}">
<!-- Swiper 轮播 -->
<swiper :current="current" :autoplay="autoplay ? interval : 0" :interval="interval" :circular="true"
:indicator-dots="false" :display-multiple-items="1" @change="onSwiperChange" class="swiper-container">
<swiper-item v-for="(item, index) in normalizedList" :key="index" class="slide">
<!-- 图片 -->
<image v-if="item.mediaType === 'image'" :src="item.src" mode="aspectFill" />
<image v-if="item.mediaType==='image'" :src="item.src"
:class="['img', imageModeMap[index]==='contain'?'vertical':'']" @load="onImageLoad($event,index)"
@tap="onImageTap(index)" />
<!-- 视频 -->
<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 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"
class="video" />
<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 v-else-if="item.mediaType==='vr'" class="vr-wrapper">
<image class="vr-cover" :src="item.src" mode="aspectFit" />
<image class="vr-icon" :src="vrIcon" mode="aspectFit" @tap.stop="enterVR()" />
</view>
</view>
</swiper-item>
</swiper>
<!-- 自定义点指示器 -->
<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 === '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="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 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 class="index-display">{{current+1}} / {{normalizedList.length}}</view>
<!-- 双击放大 -->
<view v-if="zoomed" class="zoom-overlay" @tap="zoomed=false">
<image :src="zoomSrc" mode="widthFix" class="zoom-img" />
</view>
</view>
</template>
@@ -56,7 +60,7 @@
list: {
type: Array,
required: true
}, // [{src:'',type:''}]
},
indicator: {
type: String,
default: 'dot'
@@ -93,38 +97,26 @@
data() {
return {
current: 0,
containerWidth: 0,
startX: 0,
offsetX: 0,
timer: null,
playingIndex: null
playingIndex: null,
imageModeMap: {},
zoomed: false,
zoomSrc: '',
lastTap: 0
}
},
computed: {
/* VR 永远在最前 */
normalizedList() {
const vr = []
const other = []
this.list.forEach(item => {
item.mediaType === 'vr' ? vr.push(item) : other.push(item)
})
const vr = [],
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
let startIndex = 0,
bizType = list[0].bizType
for (let i = 1; i <= list.length; i++) {
if (i === list.length || list[i].bizType !== bizType) {
result.push({
@@ -140,77 +132,38 @@
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() {},
watch: {
normalizedList(list) {
if (this.current >= list.length) this.current = 0
},
current() {
this.stopVideo()
return this.autoTypeIndicateList.length ? 100 / this.autoTypeIndicateList.length : 100
}
},
methods: {
calcWidth() {
uni.createSelectorQuery()
.in(this)
.select('.carousel')
.boundingClientRect(rect => {
if (rect) this.containerWidth = rect.width
})
.exec()
onSwiperChange(e) {
this.current = e.detail.current
this.stopVideo()
},
start() {
if (!this.autoplay) return
this.stop()
this.timer = setInterval(this.next, this.interval)
// 图片模式判断
onImageLoad(e, index) {
const {
width,
height
} = e.detail
if (!width || !height) return
this.$set(this.imageModeMap, index, width >= height ? 'cover' : 'contain')
},
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()
// 双击放大
onImageTap(index) {
const now = new Date().getTime()
if (now - this.lastTap < 300) {
this.zoomed = true
this.zoomSrc = this.normalizedList[index].src
}
this.offsetX = 0
this.start()
this.lastTap = now
},
/* 视频 */
// 视频
playVideo(index) {
if (this.playingIndex === index) return
this.stopVideo()
this.playingIndex = index
this.stop()
this.$nextTick(() => {
uni.createVideoContext('video-' + index, this).play()
})
uni.createVideoContext('video-' + index, this).play()
},
stopVideo() {
if (this.playingIndex !== null) {
@@ -218,31 +171,30 @@
this.playingIndex = null
}
},
onVideoPlay() {
this.stop()
},
onVideoPause() {
this.start()
},
/* VR */
onVideoPlay() {},
onVideoPause() {},
// VR
enterVR() {
this.stop()
this.stopVideo()
this.$u.route({
url: this.vrViewPage,
params: {
title: "vr看资产",
title: 'vr看资产',
id: this.assetsId
}
})
},
/* 分区跳转 */
// 分区跳转
jumpToType(item) {
this.stop()
this.stopVideo()
this.current = item.startIndex
this.$nextTick(this.start)
},
prev() {
const len = this.normalizedList.length
this.current = (this.current - 1 + len) % len
},
next() {
const len = this.normalizedList.length
this.current = (this.current + 1) % len
}
}
}
@@ -250,28 +202,77 @@
<style scoped>
.carousel {
overflow: hidden;
position: relative;
width: 100%;
height: 100%;
}
.track {
display: flex;
.swiper-container,
swiper-item {
width: 100%;
height: 100%;
}
.slide {
width: 100%;
flex-shrink: 0;
}
.slide image,
.video-wrapper,
.vr-wrapper,
video {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
.img {
width: 100%;
height: 100%;
object-fit: cover;
}
.img.vertical {
object-fit: contain;
}
.video-wrapper {
width: 100%;
height: 100%;
position: relative;
}
.video {
width: 100%;
height: 100%;
}
.play-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
background: rgba(0, 0, 0, 0.5);
color: #fff;
border-radius: 50%;
font-size: 40rpx;
}
.vr-wrapper {
position: relative;
}
.vr-icon {
position: absolute;
top: 50%;
left: 12%;
transform: translate(-50%, -50%);
width: 100rpx;
height: 100rpx;
background: rgba(80, 80, 80, 0.45);
border-radius: 50%;
padding: 12rpx;
}
/* dots */
.dots {
position: absolute;
@@ -285,7 +286,7 @@
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: rgba(255, 255, 255, .5);
background: rgba(255, 255, 255, 0.5);
margin: 0 6rpx;
}
@@ -302,7 +303,7 @@
height: 60rpx;
line-height: 60rpx;
text-align: center;
background: rgba(0, 0, 0, .4);
background: rgba(0, 0, 0, 0.4);
color: #fff;
font-size: 40rpx;
border-radius: 50%;
@@ -316,62 +317,25 @@
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 */
.type-bar {
position: absolute;
bottom: 80rpx;
left: 50%;
transform: translateX(-50%);
height: 40rpx;
display: flex;
height: 40rpx;
background: rgba(0, 0, 0, 0.4);
overflow: hidden;
min-width: 300rpx;
overflow: hidden;
z-index: 10;
}
.type-segment {
display: flex;
justify-content: center;
align-items: center;
color: #fff;
/* 保持文字白色 */
font-size: 20rpx;
cursor: pointer;
padding: 0 10rpx;
@@ -380,7 +344,6 @@
.type-segment.active {
background: rgba(255, 47, 49, 0.6);
/* 仅背景高亮,不覆盖文字 */
}
.index-display {
@@ -393,4 +356,23 @@
font-size: 24rpx;
border-radius: 12rpx;
}
/* zoom */
.zoom-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
}
.zoom-img {
max-width: 100%;
max-height: 100%;
}
</style>

View File

@@ -85,7 +85,7 @@ export default {
open(val) {
this.visible = true;
const currentYear = new Date().getFullYear();
this.years = Array.from({ length: currentYear - 1980 + 1 }, (_, i) => 1980 + i);
this.years = Array.from({ length: currentYear - 1980 + 11 }, (_, i) => 1980 + i);
const date = val ? new Date(val) : new Date();
const y = date.getFullYear();
const m = date.getMonth() + 1;