<template>
|
<view class="video-container">
|
<!-- 视频列表 -->
|
<swiper
|
class="video-swiper"
|
vertical
|
circular
|
:current="currentIndex"
|
@change="onSwiperChange"
|
>
|
<swiper-item v-for="(item, index) in videoList" :key="item.id">
|
<!-- 播放按钮(仅当视频暂停时显示) -->
|
<view
|
class="play-icon"
|
@click="togglePlay(index)"
|
v-if="currentVideoIsPlaying != null && !currentVideoIsPlaying"
|
>
|
<image src="/static/video/play.png" style="width: 45px;height: 45px" mode="aspectFit"></image>
|
</view>
|
<video
|
:id="'video'+index"
|
:ref="'video'+index"
|
:src="item.videoUrl"
|
:autoplay="currentIndex === index"
|
:controls="false"
|
:loop="true"
|
:object-fit="item.objectFit"
|
class="video-item"
|
@play="onPlay(item.id, index)"
|
@pause="onPause(index)"
|
@ended="onEnded(index)"
|
@click="togglePlay(index)"
|
@timeupdate="onTimeUpdate($event)"
|
></video>
|
|
<!-- 悬挂商品链接层 -->
|
<view class="goods-link-warp">
|
<view class="goods-link">
|
<view class="goods-container">
|
<!-- 商品图片 -->
|
<image class="goods-image" :src="item.goods.imageUrl" mode="aspectFill"></image>
|
|
<!-- 商品信息 -->
|
<view class="goods-info">
|
<text class="goods-name">{{item.goods.name}}</text>
|
<view class="price-section">
|
<text class="current-price">¥{{item.goods.price}}</text>
|
<text class="original-price" v-if="item.goods.originalPrice">¥{{item.goods.originalPrice}}</text>
|
</view>
|
<text class="sales-count">{{item.goods.saleNum}}人已购</text>
|
</view>
|
|
<!-- 购买按钮 -->
|
<view class="buy-button">
|
<text>购买</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
|
<!-- 视频信息层 -->
|
<view class="video-info">
|
<view>
|
<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">#{{tag.tagName}}</text>
|
</view>
|
</view>
|
|
<!-- 右侧互动按钮 -->
|
<view class="action-buttons">
|
<view class="avatar-container">
|
<image class="avatar" :src="item.authorAvatar" mode="aspectFill"></image>
|
<!-- 关注图标 - 使用绝对定位 -->
|
<view class="follow-icon">
|
<text class="iconfont"></text>
|
</view>
|
</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" @click="showComments(item)">
|
<text class="iconfont"></text>
|
<text style="font-size: 10px;font-weight: lighter;">{{item.commentNum}}</text>
|
</view>
|
</view>
|
</swiper-item>
|
</swiper>
|
<custom-tabbar bgColor="#333333" selected="index" selectedTextColor="#ffffff"></custom-tabbar>
|
</view>
|
</template>
|
|
<script>
|
import { getRecommendVideos, savePlayRecord } from "@/api/video.js";
|
import { changeCollect } from "@/api/collect.js";
|
export default {
|
data() {
|
return {
|
startHidenTime: 0, // 记录切换至其它页面的时间,用于计算视频观看时间减去的部分
|
totalHidenTime: 0, // 总共隐藏页面的时间
|
startPauseTime: 0, // 开始暂停的时间
|
totalPauseTime: 0, // 总共暂停的时间
|
playRecord: {
|
videoId: null,
|
viewDuration: 0, // 这个视频总共观看了多久
|
playAt: 0 ,// 这个视频播放到哪了
|
startPlayTime: 0 // 这个视频从什么时候开始播放的
|
},
|
currentVideoIsPlaying: null, // 当前视频是否正在播放
|
isFullScreen: false,
|
windowHeight: 0,
|
currentIndex: 0, // 当前播放的视频索引
|
videoList: [
|
|
], // 视频列表数据
|
videoContexts: [], // 视频上下文对象集合
|
loading: false, // 是否正在加载
|
page: 1, // 当前页码
|
pageSize: 10 // 每页数量
|
}
|
},
|
onShow() {
|
// 如果视频按下暂停后切换页面再回到页面时,只算暂停时间(因为暂停时间和离开页面时间是重复的,只算一个)
|
if(this.startHidenTime !== 0 && this.currentVideoIsPlaying) {
|
const duration = Date.now() - this.startHidenTime
|
this.totalHidenTime += duration
|
}
|
},
|
onHide() {
|
this.startHidenTime = Date.now()
|
},
|
onLoad() {
|
this.loadVideos();
|
},
|
onReady() {
|
// 初始化视频上下文
|
this.initVideoContexts();
|
},
|
methods: {
|
// 初始化视频上下文
|
initVideoContexts() {
|
this.videoContexts = this.videoList.map((_, index) => {
|
let videoContent = uni.createVideoContext(`video${index}`, this);
|
return videoContent;
|
});
|
},
|
|
// 加载视频数据
|
async loadVideos() {
|
if (this.loading) return;
|
this.loading = true;
|
|
getRecommendVideos({pageNumber: this.page, pageSize: this.pageSize}).then(res => {
|
console.log(res, "视频数据");
|
if (this.page === 1) {
|
this.videoList = res.data.data;
|
} else {
|
this.videoList = [...this.videoList, ...res.data.data];
|
}
|
|
this.page++;
|
this.$nextTick(() => {
|
this.initVideoContexts();
|
});
|
this.loading = false;
|
})
|
},
|
|
// 滑动切换视频
|
onSwiperChange(e) {
|
// 如果视频处于暂停状态往下刷视频,那么需要再计算一次暂停时间
|
if(!this.currentVideoIsPlaying) {
|
if(this.startPauseTime !== 0) {
|
const duration = Date.now() - this.startPauseTime
|
this.totalPauseTime += duration
|
}
|
}
|
// 保存上一个视频的播放记录
|
this.savePlayRecord()
|
const oldIndex = this.currentIndex;
|
console.log("视频上下文",this.videoContexts[oldIndex]);
|
this.currentIndex = e.detail.current;
|
|
// 暂停上一个视频
|
if (this.videoContexts[oldIndex]) {
|
this.videoContexts[oldIndex].pause();
|
}
|
this.currentVideoIsPlaying = true;
|
this.startPauseTime = 0;
|
// 播放当前视频
|
if (this.videoContexts[this.currentIndex]) {
|
this.videoContexts[this.currentIndex].play();
|
}
|
},
|
|
// 收藏/取消收藏
|
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
|
}
|
})
|
},
|
// 单击屏幕:暂停或继续播放
|
togglePlay(index) {
|
if(this.currentVideoIsPlaying) {
|
this.videoContexts[index].pause();
|
} else {
|
this.videoContexts[index].play();
|
}
|
},
|
// 视频播放事件
|
onPlay(id, index) {
|
this.currentVideoIsPlaying = true;
|
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
|
}
|
},
|
|
// 视频暂停事件
|
onPause(index) {
|
this.currentVideoIsPlaying = false;
|
this.startPauseTime = Date.now()
|
},
|
|
// 视频结束事件
|
onEnded(index) {
|
// this.currentVideoIsPlaying = false;
|
},
|
|
// 记录播放时长
|
onTimeUpdate(e) {
|
this.playRecord.playAt = e.detail.currentTime
|
},
|
|
// 保存播放记录
|
async savePlayRecord() {
|
console.log(Date.now(), this.playRecord.startPlayTime, this.totalHidenTime);
|
|
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)
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
::v-deep .custom-tabbar {
|
border-top: none !important;
|
}
|
.video-container {
|
width: 100%;
|
height: 100vh;
|
background-color: #000;
|
}
|
|
.video-swiper {
|
width: 100%;
|
height: 100%;
|
}
|
|
.video-item {
|
width: 100%;
|
height: 100%;
|
object-fit: cover;
|
}
|
.play-icon {
|
position: absolute;
|
top: 50%;
|
left: 50%;
|
transform: translate(-50%, -50%);
|
width: 45px;
|
height: 45px;
|
z-index: 10;
|
opacity: 0.6;
|
}
|
|
.video-info {
|
width: 70%;
|
position: absolute;
|
bottom: 70px;
|
left: 20px;
|
color: #f8f8f8;
|
z-index: 10;
|
letter-spacing: 1px;
|
}
|
|
.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%; /* 关键属性,设置为50%即可实现圆形 */
|
overflow: hidden; /* 确保图片不会超出圆形边界 */
|
display: block;
|
}
|
.follow-icon {
|
position: absolute;
|
bottom: 0; /* 定位到底部 */
|
left: 50%; /* 水平居中开始位置 */
|
transform: translate(-50%, 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); /* 轻微阴影 */
|
}
|
.video-tag {
|
margin-left: 5px;
|
font-weight: bold;
|
color: #eeeeee;
|
}
|
.video-author {
|
font-size: 1.2em;
|
}
|
/* 商品链接悬挂层样式 */
|
.goods-link-warp {
|
position: absolute;
|
bottom: 160px;
|
left: 20px;
|
color: #f8f8f8;
|
z-index: 10;
|
}
|
.goods-link {
|
position: relative;
|
margin: 20rpx 0;
|
padding: 12rpx;
|
background-color: rgba(255, 255, 255, 0.9);
|
border-radius: 12rpx;
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
}
|
|
.goods-container {
|
display: flex;
|
align-items: center;
|
}
|
|
.goods-image {
|
width: 120rpx;
|
height: 120rpx;
|
border-radius: 8rpx;
|
margin-right: 20rpx;
|
}
|
|
.goods-info {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
justify-content: center;
|
}
|
|
.goods-name {
|
font-size: 28rpx;
|
color: #333;
|
font-weight: bold;
|
display: -webkit-box;
|
-webkit-line-clamp: 2;
|
-webkit-box-orient: vertical;
|
overflow: hidden;
|
margin-bottom: 8rpx;
|
}
|
|
.price-section {
|
display: flex;
|
align-items: center;
|
margin-bottom: 6rpx;
|
}
|
|
.current-price {
|
font-size: 32rpx;
|
color: #ff2e4d;
|
font-weight: bold;
|
margin-right: 12rpx;
|
}
|
|
.original-price {
|
font-size: 24rpx;
|
color: #999;
|
text-decoration: line-through;
|
}
|
|
.sales-count {
|
font-size: 22rpx;
|
color: #999;
|
}
|
|
.buy-button {
|
background: linear-gradient(to right, #ff5a5f, #ff2e4d);
|
color: white;
|
padding: 10rpx 24rpx;
|
border-radius: 20rpx;
|
font-size: 26rpx;
|
font-weight: bold;
|
}
|
|
</style>
|