FastBee/APP/pages_player/list/VideoProgressBar.vue

306 lines
8.2 KiB
Vue
Raw Permalink Normal View History

2025-07-07 09:21:15 +08:00
<template>
<view class="container">
<!-- 进度条 -->
<view ref="progressBar" class="progress-bar" @click="handleClick" v-if="cardGroups.length>0">
<!-- 滑块及时间显示 -->
<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/home/video/playlist.png" mode="widthFix" />
</view>
<!-- 卡片列表展示 -->
<view v-if="group.cards.length > 0" 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.start }}-{{ card.end }}</view>
<view class="card-info" @click="videoPlay(card)">
<image class="thumbnail" src="../../static/video.png" mode="widthFix" />
<!-- <view class="info-text">录像</view> -->
</view>
</view>
</view>
</view>
</view>
<view class="empty-container">
<u-empty mode="data" :show="cardGroups.length === 0" marginTop="30"></u-empty>
</view>
</view>
</template>
<script>
export default {
name: 'VideoProgressBar',
props: {
dateList: {
type: Array,
default: null,
required: true
},
},
watch: {
dateList: function(newVal, oldVal) {
this.updateCardGroups();
}
},
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();
},
//点击-播放
videoPlay(card) {
let convertedData = {
start: this.timeStringToTimestamp(card.start),
end: this.timeStringToTimestamp(card.end),
};
this.$emit('childClicked', { message: convertedData });
},
// 函数:将时间字符串转换为时间戳(单位:秒)
timeStringToTimestamp(timeString) {
let now = new Date();
let [hours, minutes, seconds] = timeString.split(':');
let date = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes, seconds);
return Math.floor(date.getTime() / 1000); // 转为秒
},
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;
},
// 函数:将时间戳转换为时分秒格式
formatTimestamp(timestamp) {
const date = new Date(timestamp * 1000); // 转换为毫秒
const hours = date.getHours().toString().padStart(2, '0'); // 补零
const minutes = date.getMinutes().toString().padStart(2, '0'); // 补零
const seconds = date.getSeconds().toString().padStart(2, '0'); // 补零
return `${hours}:${minutes}:${seconds}`; // 返回格式化的时间字符串
},
updateCardGroups() {
const progressBarHeight = 2400;
const totalSecondsInDay = 24 * 60 * 60;
const minDistance = 50
const timePoints = this.dateList;
const time = timePoints.map(point => {
return {
start: this.formatTimestamp(point.start),
end: this.formatTimestamp(point.end)
};
});
let cardGroups = [];
time.forEach(point => {
const [hours, minutes, seconds] = point.start.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: column;
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;
}
.empty-container {
display: flex;
justify-content: center;
align-items: center;
margin-left: 50rpx;
height: 300px;
}
</style>