265 lines
7.4 KiB
Vue
265 lines
7.4 KiB
Vue
|
<template>
|
||
|
<view class="container">
|
||
|
<!-- 进度条 -->
|
||
|
<view ref="progressBar" class="progress-bar" @click="handleClick">
|
||
|
<!-- 滑块及时间显示 -->
|
||
|
<view
|
||
|
class="slider-indicator-wrapper"
|
||
|
:style="{ top: sliderPosition + 'rpx', transition: isSliding ? 'none' : 'top 0.2s ease' }"
|
||
|
@touchstart="handleSlideStart"
|
||
|
@touchmove="handleSlideMove"
|
||
|
@touchend="handleSlideEnd"
|
||
|
>
|
||
|
<view class="time-label">
|
||
|
{{ selectedTime }}
|
||
|
</view>
|
||
|
<view class="slider-indicator"></view>
|
||
|
</view>
|
||
|
|
||
|
<!-- 卡片和标识展示 -->
|
||
|
<view v-for="(group, index) in cardGroups" :key="index" class="info-card-wrapper" :style="{ top: group.position + 'rpx', zIndex: group.showCards ? 10 : 1 }">
|
||
|
<!-- 判断是否需要显示悬浮标识 -->
|
||
|
<view v-if="group.cards.length > 1" class="hover-indicator" @click="toggleCardGroup(index)">
|
||
|
<image class="icon-indicator" src="../../static/more.png" mode="widthFix" />
|
||
|
</view>
|
||
|
|
||
|
<!-- 卡片列表展示 -->
|
||
|
<view v-if="group.showCards || group.cards.length === 1" class="card-list" :class="{ 'highlighted-card': group.showCards }">
|
||
|
<view v-for="(card, cardIndex) in group.cards" :key="cardIndex" class="info-card">
|
||
|
<view class="card-time">{{ card.time }}</view>
|
||
|
<view class="card-info">
|
||
|
<image class="thumbnail" src="../../static/video.png" mode="widthFix" />
|
||
|
<view class="info-text">{{ card.info }}</view>
|
||
|
</view>
|
||
|
</view>
|
||
|
</view>
|
||
|
</view>
|
||
|
</view>
|
||
|
</view>
|
||
|
</template>
|
||
|
|
||
|
<script>
|
||
|
export default {
|
||
|
name: 'VideoProgressBar',
|
||
|
data() {
|
||
|
return {
|
||
|
selectedTime: null,
|
||
|
sliderPosition: 0,
|
||
|
isSliding: false,
|
||
|
cardGroups: []
|
||
|
};
|
||
|
},
|
||
|
mounted() {
|
||
|
this.updateCardGroups();
|
||
|
},
|
||
|
methods: {
|
||
|
handleClick(e) {
|
||
|
const query = uni.createSelectorQuery().in(this);
|
||
|
query.select('.progress-bar').boundingClientRect(data => {
|
||
|
if (data) {
|
||
|
let clickY = e.detail.y - data.top;
|
||
|
if (clickY < 0) clickY = 0;
|
||
|
if (clickY > data.height) clickY = data.height;
|
||
|
this.updateTime(clickY, data.height);
|
||
|
}
|
||
|
}).exec();
|
||
|
},
|
||
|
handleSlideStart() {
|
||
|
this.isSliding = true;
|
||
|
},
|
||
|
handleSlideMove(e) {
|
||
|
if (!this.isSliding) return;
|
||
|
const query = uni.createSelectorQuery().in(this);
|
||
|
query.select('.progress-bar').boundingClientRect(data => {
|
||
|
if (data) {
|
||
|
let touchY = e.touches[0].clientY - data.top;
|
||
|
if (touchY < 0) touchY = 0;
|
||
|
if (touchY > data.height) touchY = data.height;
|
||
|
this.sliderPosition = (touchY / data.height) * 2400;
|
||
|
}
|
||
|
}).exec();
|
||
|
},
|
||
|
handleSlideEnd(e) {
|
||
|
this.isSliding = false;
|
||
|
const query = uni.createSelectorQuery().in(this);
|
||
|
query.select('.progress-bar').boundingClientRect(data => {
|
||
|
if (data) {
|
||
|
let touchY = e.changedTouches[0].clientY - data.top;
|
||
|
if (touchY < 0) touchY = 0;
|
||
|
if (touchY > data.height) touchY = data.height;
|
||
|
this.updateTime(touchY, data.height);
|
||
|
}
|
||
|
}).exec();
|
||
|
},
|
||
|
updateTime(position, progressBarHeight) {
|
||
|
const totalSecondsInDay = 24 * 60 * 60;
|
||
|
const secondsAtPosition = (position / progressBarHeight) * totalSecondsInDay;
|
||
|
const hours = Math.floor(secondsAtPosition / 3600);
|
||
|
const minutes = Math.floor((secondsAtPosition % 3600) / 60);
|
||
|
const seconds = Math.floor(secondsAtPosition % 60);
|
||
|
|
||
|
this.sliderPosition = (position / progressBarHeight) * 2400;
|
||
|
this.selectedTime = `${this.padTime(hours)}:${this.padTime(minutes)}:${this.padTime(seconds)}`;
|
||
|
},
|
||
|
padTime(time) {
|
||
|
return time < 10 ? `0${time}` : time;
|
||
|
},
|
||
|
toggleCardGroup(index) {
|
||
|
this.cardGroups[index].showCards = !this.cardGroups[index].showCards;
|
||
|
},
|
||
|
updateCardGroups() {
|
||
|
const progressBarHeight = 2400;
|
||
|
const totalSecondsInDay = 24 * 60 * 60;
|
||
|
const minDistance = 50;
|
||
|
|
||
|
const timePoints = [
|
||
|
{ time: "03:30:00", info: "录像具体的时长1" },
|
||
|
{ time: "03:32:00", info: "测试1" },
|
||
|
{ time: "06:10:00", info: "测试2" },
|
||
|
{ time: "18:45:00", info: "录像具体的时长2" }
|
||
|
];
|
||
|
|
||
|
let cardGroups = [];
|
||
|
timePoints.forEach(point => {
|
||
|
const [hours, minutes, seconds] = point.time.split(":").map(Number);
|
||
|
const totalSeconds = hours * 3600 + minutes * 60 + seconds;
|
||
|
|
||
|
const position = (totalSeconds / totalSecondsInDay) * progressBarHeight;
|
||
|
|
||
|
const existingGroup = cardGroups.find(group => Math.abs(group.position - position) < minDistance);
|
||
|
if (existingGroup) {
|
||
|
existingGroup.cards.push(point);
|
||
|
} else {
|
||
|
cardGroups.push({
|
||
|
position,
|
||
|
showCards: false,
|
||
|
cards: [point]
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this.cardGroups = cardGroups;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
</script>
|
||
|
|
||
|
<style lang="scss" scoped>
|
||
|
.container {
|
||
|
padding-left: 200rpx;
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
align-items: flex-start;
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
justify-content: center;
|
||
|
background-color: rgba(255, 255, 255, 0.2);
|
||
|
}
|
||
|
|
||
|
.progress-bar {
|
||
|
margin: 40rpx;
|
||
|
width: 20rpx;
|
||
|
height: 2400rpx;
|
||
|
background: linear-gradient(200deg, #d4f1f9, #fef9e7);
|
||
|
border-radius: 10rpx;
|
||
|
position: relative;
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
justify-content: space-between;
|
||
|
align-items: center;
|
||
|
box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.3);
|
||
|
}
|
||
|
|
||
|
.slider-indicator-wrapper {
|
||
|
position: absolute;
|
||
|
left: -150rpx;
|
||
|
display: flex;
|
||
|
align-items: center;
|
||
|
height: 5rpx;
|
||
|
}
|
||
|
|
||
|
.slider-indicator {
|
||
|
width: 35rpx;
|
||
|
height: 10rpx;
|
||
|
background-color: #0055ff;
|
||
|
border-radius: 20rpx;
|
||
|
box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.25);
|
||
|
}
|
||
|
|
||
|
.time-label {
|
||
|
font-size: 24rpx;
|
||
|
color: #fff;
|
||
|
margin-right: 20rpx;
|
||
|
background-color: #0088ff;
|
||
|
padding: 8rpx;
|
||
|
border-radius: 8rpx;
|
||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
|
||
|
}
|
||
|
|
||
|
.info-card-wrapper {
|
||
|
position: absolute;
|
||
|
left: 70rpx;
|
||
|
z-index: 1;
|
||
|
}
|
||
|
|
||
|
.hover-indicator {
|
||
|
width: 50rpx;
|
||
|
height: 50rpx;
|
||
|
background-color: transparent;
|
||
|
display: flex;
|
||
|
align-items: center;
|
||
|
justify-content: center;
|
||
|
}
|
||
|
|
||
|
.icon-indicator {
|
||
|
width: 30rpx;
|
||
|
height: 30rpx;
|
||
|
}
|
||
|
|
||
|
.card-list {
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
background-color: transparent;
|
||
|
}
|
||
|
|
||
|
.highlighted-card {
|
||
|
background-color: #ffffff; /* 设置展开卡片的背景色 */
|
||
|
padding: 12rpx;
|
||
|
border-radius: 10rpx;
|
||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.3);
|
||
|
z-index: 10; /* 提升层级 */
|
||
|
}
|
||
|
|
||
|
.info-card {
|
||
|
display: flex;
|
||
|
flex-direction: row;
|
||
|
background: radial-gradient(circle, #000000, #1b2e40, #3d4e5b, #5a6d78, #728896);
|
||
|
padding: 12rpx;
|
||
|
border-radius: 10rpx;
|
||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.3);
|
||
|
min-width: 300rpx;
|
||
|
margin-top: 10rpx;
|
||
|
}
|
||
|
|
||
|
.thumbnail {
|
||
|
width: 100rpx;
|
||
|
border-radius: 6rpx;
|
||
|
display: flex;
|
||
|
margin: 30rpx;
|
||
|
}
|
||
|
|
||
|
.info-text {
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
justify-content: center;
|
||
|
font-size: 22rpx;
|
||
|
color: #ffffff;
|
||
|
}
|
||
|
|
||
|
.card-time {
|
||
|
font-size: 20rpx;
|
||
|
font-weight: bold;
|
||
|
color: #fff;
|
||
|
}
|
||
|
</style>
|