<template>
|
<view class="video-container">
|
<!-- 视频加载 -->
|
<zero-loading v-show="videoLoading" type="circle" color="#0ebd57" text=""></zero-loading>
|
<!-- 视频列表 -->
|
<swiper class="video-swiper" :current="currentIndex" @change="onSwiperChange" :duration="250"
|
easing-function="linear">
|
<swiper-item v-for="(item, index) in videoList" :key="item.updateKey" @touchstart="handleTouchStart"
|
@touchmove="handleTouchMove" @touchend="handleTouchEnd">
|
<view style="width: 100%;height: 100%;" v-if="item.videoContentType === 'video'">
|
<!-- 播放按钮(仅当视频暂停时显示) -->
|
<view class="play-icon" @click="togglePlay(index)" v-show="!currentVideoIsPlaying">
|
<image src="/pages/subComponents/static/video/play.png" style="width: 45px;height: 45px"
|
mode="aspectFit"></image>
|
</view>
|
<video v-if="index >= currentIndex - videoLiveOffset && index <= currentIndex + videoLiveOffset"
|
:id="'video' + index" :ref="'video' + index" :src="item.videoUrl"
|
:autoplay="index === currentIndex" :controls="isControls" :loop="true"
|
:object-fit="item.videoFit" :enable-progress-gesture="false" :show-center-play-btn="false"
|
:show-progress="false" :show-fullscreen-btn="isControls" :show-play-btn="isControls"
|
:show-mute-btn="false" class="video-item" @fullscreenchange="onFullscreenChange"
|
@play="onPlay(item.id, index)" @pause="onPause(index)" @ended="onEnded(index)"
|
@click="togglePlay(index)" @timeupdate="onTimeUpdate($event)"
|
@loadedmetadata="onLoadedMetadata($event)" @waiting="videoWaiting(index)"></video>
|
<!-- <view class="fullscreen-btn">
|
<image class="fullscreen-icon" :src="isFullScreen ? off_screen : on_screen" mode="aspectFit"
|
@click="requestFullScreen('video' + index, item)"></image>
|
</view> -->
|
<!-- 自定义控制条 -->
|
<view @touchstart.stop="handleTouchStart" @touchmove.stop="handleTouchMove"
|
@touchend.stop="handleTouchEnd" :style="{ bottom: marginBottom + 'px' }" class="container">
|
<!-- 进度条 - 整个区域可拖动 -->
|
<view class="process-warp">
|
<!-- 显示当前进度 -->
|
<view v-show="isTouch" class="progress-text">{{ hasPlayTime }}/{{ formartDuration }}</view>
|
<view class="progress-bar" id="progressBar">
|
<!-- 已填充部分 -->
|
<view class="progress-fill" :style="{ width: progress + '%' }"></view>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<view style="width: 100%; height: 100%;" v-else-if="item.videoContentType === 'img'">
|
<uni-swiper-dot :info="item.imgs" :current="currentImgIndex" mode="round"
|
style="width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;"
|
:dots-styles="{ width: 24, bottom: 24, selectedBackgroundColor: 'green', backgroundColor: 'gray' }">
|
<swiper class="swiper-box" @change="imgChange" :autoplay="true" :interval="3000">
|
<swiper-item v-for="img in item.imgs" :key="img">
|
<view class="swiper-item">
|
<!-- 调整 image 样式,使其居中且按比例缩放 -->
|
<image :src="img" mode="aspectFit"
|
style="width: 100%; height: 100%; display: block; margin: 0 auto;"></image>
|
</view>
|
</swiper-item>
|
</swiper>
|
</uni-swiper-dot>
|
</view>
|
|
<!-- 视频信息层 -->
|
<view class="video-info" :style="{ bottom: marginBottom + 20 + 'px' }">
|
<view style="width: 100%; position: relative;">
|
<text class="video-author">@{{ item.authorName }}</text>
|
</view>
|
<view style="width: 100%;word-wrap: break-word;white-space: normal;overflow-wrap: break-word;">
|
<text class="video-title">{{ item.title }}</text>
|
<text class="video-tag" v-for="(tag, index) in item.tagList" :key="tag.id">#{{ tag.tagName
|
}}</text>
|
</view>
|
</view>
|
|
<!-- 右侧互动按钮 -->
|
<view class="action-buttons">
|
<view class="avatar-container">
|
<image class="avatar" @click="jumpToHomePage(item.authorId)" @touchend.stop
|
:src="item.authorAvatar" mode="aspectFill"></image>
|
<!-- 关注图标 - 使用绝对定位 -->
|
<view v-if="!item.subscribeThisAuthor" class="follow-icon"
|
@click="subscribeAuth(index, item.authorId)" @touchend.stop>
|
<text class="iconfont"></text>
|
</view>
|
</view>
|
<view class="action-item" @click="toggleThumbsUp(item, index)">
|
<text class="iconfont" v-if="item.thumbsUp"></text>
|
<text class="iconfont" v-else></text>
|
<text style="font-size: 10px;font-weight: lighter;">{{ item.thumbsUpNum }}</text>
|
</view>
|
<view class="action-item" @click="showComments(item)">
|
<text class="iconfont"></text>
|
<text style="font-size: 10px;font-weight: lighter;">{{ item.commentNum }}</text>
|
</view>
|
<view class="action-item" @click="toggleCollect(item, index)">
|
<text class="iconfont" v-if="item.collected"></text>
|
<text class="iconfont" v-else></text>
|
<text style="font-size: 10px;font-weight: lighter;">{{ item.collectNum }}</text>
|
</view>
|
<view class="action-item">
|
<button open-type="share" class="custom-share-btn" :data-obj="item">
|
<text class="iconfont"></text>
|
</button>
|
</view>
|
</view>
|
|
</swiper-item>
|
</swiper>
|
</view>
|
</template>
|
|
<script>
|
import { getGoodsSimilarlyVideos, savePlayRecord, changeCollect, changeThumbsUp } from "@/api/video.js"
|
|
export default {
|
data() {
|
return {
|
videoList: [], // 视频列表
|
currentIndex: 0, // 当前播放的视频索引
|
videoLoading: false, // 视频加载状态
|
currentVideoIsPlaying: false, // 当前视频是否正在播放
|
isControls: false, // 是否显示视频控制条
|
videoLiveOffset: 1, // 预加载视频数量
|
isFullScreen: false, // 是否全屏
|
on_screen: require('@/pages/tabbar/index/static/on_screen.png'),
|
off_screen: require('@/pages/tabbar/index/static/off_screen.png'),
|
marginBottom: 0, // 底部边距
|
progress: 0, // 视频播放进度
|
duration: 0, // 视频总时长
|
currentTime: 0, // 当前播放时间
|
formartDuration: "00:00", // 格式化后的总时长
|
hasPlayTime: "00:00", // 已播放时间
|
isDragging: false, // 是否正在拖动进度条
|
isTouch: false, // 是否触摸进度条
|
startX: 0, // 触摸开始X坐标
|
startProgress: 0, // 触摸开始时的进度
|
barWidth: 0, // 进度条宽度
|
processHidenTimer: null, // 进度条隐藏定时器
|
currentImgIndex: 0, // 当前图片索引
|
playRecord: { // 播放记录
|
videoId: null,
|
viewDuration: 0, // 观看时长
|
playAt: 0, // 播放位置
|
startPlayTime: 0 // 开始播放时间
|
},
|
totalHidenTime: 0, // 总隐藏时间
|
totalPauseTime: 0, // 总暂停时间
|
startPauseTime: 0, // 开始暂停时间
|
// 右滑返回首页相关
|
touchStartX: 0,
|
touchEndX: 0,
|
minSwipeDistance: 100 // 最小滑动距离
|
}
|
},
|
onLoad(options) {
|
// 获取传递的商品信息
|
if (options.goodsId) {
|
this.loadSimilarVideos(options.goodsId, options.currentVideoId);
|
}
|
},
|
onShow() {
|
// 页面显示时恢复视频播放
|
if (this.videoList.length > 0) {
|
const videoContext = uni.createVideoContext(`video${this.currentIndex}`, this);
|
videoContext.play();
|
}
|
},
|
onHide() {
|
// 页面隐藏时暂停视频
|
if (this.videoList.length > 0) {
|
const videoContext = uni.createVideoContext(`video${this.currentIndex}`, this);
|
videoContext.pause();
|
}
|
},
|
methods: {
|
// 加载相似视频
|
loadSimilarVideos(goodsId, currentVideoId) {
|
this.videoLoading = true;
|
const query = {
|
pageNumber: 1,
|
pageSize: 10,
|
currentVideoId: 0,
|
videoFrom: 'goodsSimilarly',
|
goodsIds: [goodsId]
|
};
|
|
getGoodsSimilarlyVideos(query).then(res => {
|
if (res.data.code === 200) {
|
if (res.data.data.length === 0) {
|
uni.navigateBack();
|
}
|
this.videoList = res.data.data;
|
this.videoLoading = false;
|
}
|
}).catch(err => {
|
console.error('加载相似视频失败', err);
|
this.videoLoading = false;
|
});
|
},
|
|
// 滑动切换视频
|
onSwiperChange(e) {
|
this.videoLoading = false;
|
// 如果视频处于暂停状态往下刷视频,那么需要再计算一次暂停时间
|
if (!this.currentVideoIsPlaying) {
|
if (this.startPauseTime !== 0) {
|
const duration = Date.now() - this.startPauseTime;
|
this.totalPauseTime += duration;
|
}
|
}
|
// 保存上一个视频的播放记录
|
this.savePlayRecord();
|
const oldIndex = this.currentIndex;
|
this.currentIndex = e.detail.current;
|
|
// 处理右滑到第一个视频时返回首页
|
if (e.detail.current === -1) {
|
uni.navigateBack();
|
return;
|
}
|
|
const videoContext = uni.createVideoContext(`video${oldIndex}`, this);
|
// 暂停上一个视频
|
videoContext.pause();
|
this.startPauseTime = 0;
|
|
// 设置当前播放视频的总时长
|
this.duration = this.videoList[this.currentIndex].videoDuration;
|
this.formartDuration = this.sliderFormatTime(this.duration);
|
|
// 播放当前视频
|
const videoContext1 = uni.createVideoContext(`video${this.currentIndex}`, this);
|
videoContext1.play();
|
},
|
|
// 处理触摸开始事件
|
handleTouchStart(e) {
|
this.touchStartX = e.touches[0].clientX;
|
},
|
|
// 处理触摸移动事件
|
handleTouchMove(e) {
|
this.touchEndX = e.touches[0].clientX;
|
},
|
|
// 处理触摸结束事件
|
handleTouchEnd() {
|
// 计算水平滑动距离
|
const swipeDistance = this.touchEndX - this.touchStartX;
|
|
// 如果是右滑且当前是第一个视频,则返回首页
|
if (swipeDistance > this.minSwipeDistance && this.currentIndex === 0) {
|
uni.navigateBack();
|
}
|
|
// 重置触摸坐标
|
this.touchStartX = 0;
|
this.touchEndX = 0;
|
},
|
|
// 返回上一页
|
goBack() {
|
uni.navigateBack();
|
},
|
|
// 单击屏幕:暂停或继续播放
|
togglePlay(index) {
|
const videoContext = uni.createVideoContext(`video${index}`, this);
|
if (this.currentVideoIsPlaying) {
|
videoContext.pause();
|
} else {
|
videoContext.play();
|
}
|
},
|
|
// 视频播放事件
|
onPlay(id, index) {
|
if (index === this.currentIndex) {
|
this.currentVideoIsPlaying = true;
|
if (!this.duration) {
|
// 设置当前播放视频的总时长
|
this.duration = this.videoList[this.currentIndex].videoDuration;
|
this.formartDuration = this.sliderFormatTime(this.duration);
|
}
|
}
|
this.getBarRect();
|
this.progress = 0;
|
this.playRecord.videoId = id;
|
// 没初始化才赋值,因为一个视频重复播放onPlay会重复触发
|
if (this.playRecord.startPlayTime === 0) {
|
this.playRecord.startPlayTime = Date.now();
|
}
|
if (this.startPauseTime !== 0) {
|
const duration = Date.now() - this.startPauseTime;
|
this.totalPauseTime += duration;
|
}
|
this.videoLoading = false;
|
},
|
|
// 视频暂停事件
|
onPause(index) {
|
if (index === this.currentIndex) {
|
this.currentVideoIsPlaying = false;
|
this.startPauseTime = Date.now();
|
}
|
},
|
|
// 视频结束事件
|
onEnded(index) {
|
// 可以添加视频结束后的逻辑
|
},
|
|
// 记录播放时长
|
onTimeUpdate(e) {
|
this.videoLoading = false;
|
this.playRecord.playAt = e.detail.currentTime;
|
this.currentTime = e.detail.currentTime;
|
this.progress = (e.detail.currentTime / this.duration) * 100;
|
this.hasPlayTime = this.sliderFormatTime(e.detail.currentTime);
|
},
|
|
// 获取进度条宽度
|
getBarRect() {
|
const query = uni.createSelectorQuery().in(this);
|
query.select('#progressBar').boundingClientRect(data => {
|
if (data) {
|
this.barWidth = data.width;
|
}
|
}).exec();
|
},
|
|
// 格式化时间
|
sliderFormatTime(time) {
|
if (typeof time !== 'number' || isNaN(time)) {
|
return '00:00';
|
}
|
const minutes = Math.floor(time / 60);
|
const seconds = Math.floor(time % 60);
|
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
},
|
|
// 保存播放记录
|
savePlayRecord() {
|
if (!this.playRecord.videoId) return;
|
|
const data = {
|
videoId: this.playRecord.videoId,
|
viewDuration: Date.now() - this.playRecord.startPlayTime - this.totalHidenTime - this.totalPauseTime,
|
playAt: this.playRecord.playAt
|
};
|
this.playRecord = {
|
videoId: null,
|
viewDuration: 0,
|
playAt: 0,
|
startPlayTime: 0
|
};
|
this.totalHidenTime = 0;
|
this.totalPauseTime = 0;
|
savePlayRecord(data);
|
},
|
|
// 全屏切换
|
onFullscreenChange(e) {
|
this.isFullScreen = e.detail.fullScreen;
|
},
|
|
// 请求全屏
|
requestFullScreen(videoId, item) {
|
const videoContext = uni.createVideoContext(videoId, this);
|
if (this.isFullScreen) {
|
videoContext.exitFullScreen();
|
} else {
|
videoContext.requestFullScreen();
|
}
|
},
|
|
// 图片轮播变化
|
imgChange(e) {
|
this.currentImgIndex = e.detail.current;
|
},
|
|
// 视频缓冲
|
videoWaiting(index) {
|
if (index === this.currentIndex) {
|
this.videoLoading = true;
|
}
|
},
|
|
// 获取视频总时长
|
onLoadedMetadata(e) {
|
// 可以在这里获取视频总时长
|
},
|
|
// 收藏/取消收藏
|
toggleCollect(item, index) {
|
let data = {
|
refId: item.id,
|
collectType: 'video'
|
};
|
const beforeCollected = item.collected;
|
const beforeCollectNum = item.collectNum;
|
if (item.collected) {
|
this.videoList[index].collected = false;
|
this.videoList[index].collectNum -= 1;
|
} else {
|
this.videoList[index].collected = true;
|
this.videoList[index].collectNum += 1;
|
}
|
changeCollect(data).then(res => {
|
if (res.data.code !== 200) {
|
this.videoList[index].collected = beforeCollected;
|
this.videoList[index].collectNum = beforeCollectNum;
|
}
|
});
|
},
|
|
// 点赞/取消点赞
|
toggleThumbsUp(item, index) {
|
let data = {
|
refId: item.id,
|
thumbsUpType: 'video'
|
};
|
const beforeThumbsUp = item.thumbsUp;
|
const beforeThumbsUpNum = item.thumbsUpNum;
|
if (item.thumbsUp) {
|
this.videoList[index].thumbsUp = false;
|
this.videoList[index].thumbsUpNum -= 1;
|
} else {
|
this.videoList[index].thumbsUp = true;
|
this.videoList[index].thumbsUpNum += 1;
|
}
|
changeThumbsUp(data).then(res => {
|
if (res.data.code !== 200) {
|
this.videoList[index].thumbsUp = beforeThumbsUp;
|
this.videoList[index].thumbsUpNum = beforeThumbsUpNum;
|
}
|
});
|
},
|
|
// 跳转到作者主页
|
jumpToHomePage(authorId) {
|
uni.navigateTo({
|
url: `/pages/video/author-home?authorId=${authorId}`
|
});
|
},
|
|
// 关注作者
|
subscribeAuth(index, authorId) {
|
// 实现关注作者的逻辑
|
uni.showToast({
|
title: '关注成功',
|
icon: 'none'
|
});
|
this.videoList[index].subscribeThisAuthor = true;
|
},
|
|
// 显示评论
|
showComments(item) {
|
// 实现显示评论的逻辑
|
uni.showToast({
|
title: '评论功能开发中',
|
icon: 'none'
|
});
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.video-container {
|
width: 100%;
|
height: 100vh;
|
background-color: #000;
|
}
|
|
.video-swiper {
|
width: 100%;
|
height: calc(100% - 50px);
|
}
|
|
.video-item {
|
width: 100%;
|
height: 100%;
|
}
|
|
.play-icon {
|
position: absolute;
|
top: 50%;
|
left: 50%;
|
transform: translate(-50%, -50%);
|
width: 45px;
|
height: 45px;
|
z-index: 10;
|
opacity: 0.6;
|
}
|
|
.fullscreen-btn {
|
position: absolute;
|
right: 45rpx;
|
bottom: 70rpx;
|
width: 60rpx;
|
height: 60rpx;
|
border-radius: 50%;
|
background-color: rgba(0, 0, 0, 0.4);
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
z-index: 999;
|
backdrop-filter: blur(10rpx);
|
border: 1rpx solid rgba(255, 255, 255, 0.2);
|
}
|
|
.fullscreen-icon {
|
width: 36rpx;
|
height: 36rpx;
|
opacity: 0.9;
|
}
|
|
.container {
|
position: absolute;
|
bottom: 0;
|
left: 0;
|
width: 100%;
|
z-index: 10;
|
}
|
|
.process-warp {
|
position: relative;
|
width: 100%;
|
padding: 0 20rpx;
|
box-sizing: border-box;
|
}
|
|
.progress-text {
|
position: absolute;
|
top: -40rpx;
|
right: 20rpx;
|
color: #fff;
|
font-size: 24rpx;
|
}
|
|
.progress-bar {
|
width: 100%;
|
height: 4rpx;
|
background-color: rgba(255, 255, 255, 0.3);
|
border-radius: 2rpx;
|
overflow: hidden;
|
}
|
|
.progress-fill {
|
height: 100%;
|
background-color: #0ebd57;
|
}
|
|
.video-info {
|
width: 100%;
|
position: absolute;
|
bottom: 20px;
|
left: 20px;
|
color: #f8f8f8;
|
z-index: 10;
|
letter-spacing: 1px;
|
}
|
|
.video-author {
|
font-size: 1.2em;
|
}
|
|
.video-title {
|
font-size: 1em;
|
margin-top: 10rpx;
|
display: block;
|
}
|
|
.video-tag {
|
margin-left: 5px;
|
font-weight: bold;
|
color: #eeeeee;
|
}
|
|
.action-buttons {
|
position: absolute;
|
right: 20px;
|
bottom: 150px;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
z-index: 10;
|
}
|
|
.action-item {
|
margin-bottom: 18px;
|
display: flex;
|
flex-direction: column;
|
justify-content: center;
|
align-items: center;
|
color: #fff;
|
}
|
|
.avatar-container {
|
margin-bottom: 27px;
|
position: relative;
|
width: 40px;
|
height: 40px;
|
display: inline-block;
|
}
|
|
.avatar {
|
border: 2px solid #FFFFFF;
|
box-sizing: border-box;
|
width: 100%;
|
height: 100%;
|
border-radius: 50%;
|
overflow: hidden;
|
display: block;
|
}
|
|
.follow-icon {
|
position: absolute;
|
bottom: 0;
|
left: 50%;
|
transform: translate(-50%, 50%);
|
width: 18px;
|
height: 18px;
|
background-color: #FF5A5F;
|
border-radius: 50%;
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
}
|
|
.custom-share-btn {
|
background: transparent;
|
padding: 0;
|
line-height: 1;
|
border: none;
|
color: #fff;
|
font-size: inherit;
|
}
|
|
.custom-share-btn::after {
|
border: none;
|
}
|
|
.back-button {
|
position: absolute;
|
top: 40px;
|
left: 20px;
|
width: 40px;
|
height: 40px;
|
border-radius: 50%;
|
background-color: rgba(0, 0, 0, 0.4);
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
z-index: 999;
|
color: #fff;
|
}
|
</style>
|