ruoyi-geek-App/uni_modules/uview-plus/components/u-short-video/u-short-video.vue

463 lines
12 KiB
Vue
Raw Normal View History

2025-11-24 14:57:53 +00:00
<template>
<view class="u-short-video">
<!-- 顶部导航区域 -->
<view class="u-short-video__header">
<slot name="menu">
<view class="u-short-video__header__menu">
<up-icon name="grid" size="24"></up-icon>
</view>
</slot>
<up-tabs
:list="tabsList"
:current="currentTab"
lineColor="#ddd"
:activeStyle="{
color: '#ddd',
fontWeight: 400,
transform: 'scale(1)'
}"
:inactiveStyle="{
color: '#bbb',
transform: 'scale(1)'
}"
@change="handleTabChange"
class="u-short-video__header__tabs"
></up-tabs>
<slot name="search">
<view class="u-short-video__header__search">
<up-icon name="search" size="24"></up-icon>
</view>
</slot>
</view>
<!-- 视频内容区域 -->
<swiper
:vertical="true"
:autoplay="false"
@change="handleSwiperChange"
:current="currentVideo"
class="u-short-video__content"
>
<swiper-item v-for="(item, index) in videoList" :key="index">
<view class="u-short-video__content__item">
<!-- 视频播放区域 -->
<view class="u-short-video__content__video">
<video
:id="'video-' + index"
:src="item.videoUrl"
:autoplay="index === currentVideo"
:controls="false"
:show-fullscreen-btn="false"
:show-play-btn="false"
:show-center-play-btn="false"
:enable-progress-gesture="true"
:loop="true"
:playback-rate="item.playbackRate || 1.0"
@play="onVideoPlay"
@pause="onVideoPause"
@ended="onVideoEnded"
@timeupdate="onTimeUpdate"
@loadedmetadata="onLoadedMetadata"
style="width: 100%; height: 100%;"
></video>
<!-- 倍速设置按钮 -->
<!-- <view class="u-short-video__content__video__speed" @click="showSpeedOptions(index)">
<text class="speed-text">{{ item.playbackRate || 1.0 }}x</text>
<up-icon name="arrow-down" size="12" color="#fff"></up-icon>
</view> -->
</view>
<!-- 作者信息 -->
<view class="u-short-video__content__author">
<view class="u-short-video__content__author__avatar">
<u-avatar :src="item.author.avatar" size="50px"></u-avatar>
</view>
<view class="u-short-video__content__author__info">
<text class="u-short-video__content__author__name">{{ item.author.name }}</text>
<text class="u-short-video__content__author__desc">{{ item.author.desc }}</text>
</view>
<view class="u-short-video__content__author__follow">
<up-button type="primary" size="mini">关注</up-button>
</view>
</view>
<!-- 右侧操作区域 -->
<view class="u-short-video__content__actions">
<slot name="actions" :item="item" :index="index">
<view class="u-short-video__content__actions__item" @click="handleLike(item, index)">
<up-icon color="#eee" :name="item.isLiked ? 'thumb-up-fill' : 'thumb-up'" size="32px"></up-icon>
<text class="u-short-video__content__actions__text">{{ item.likeCount }}</text>
</view>
<view class="u-short-video__content__actions__item" @click="handleComment(item, index)">
<up-icon color="#eee" name="chat" size="32px"></up-icon>
<text class="u-short-video__content__actions__text">{{ item.commentCount }}</text>
</view>
<view class="u-short-video__content__actions__item" @click="handleShare(item, index)">
<up-icon color="#eee" name="share" size="32px"></up-icon>
<text class="u-short-video__content__actions__text">{{ item.shareCount }}</text>
</view>
<view class="u-short-video__content__actions__item" @click="handleCollect(item, index)">
<up-icon color="#eee" :name="item.isCollected ? 'bookmark-fill' : 'bookmark'" size="32px"></up-icon>
<text class="u-short-video__content__actions__text">{{ item.collectCount }}</text>
</view>
</slot>
</view>
</view>
</swiper-item>
</swiper>
<!-- 倍速选择弹窗 -->
<up-action-sheet
:show="showSpeedSheet"
:actions="speedOptions"
title="播放速度"
@close="showSpeedSheet = false"
@select="selectSpeed"
></up-action-sheet>
<!-- 底部导航栏 -->
<view class="u-short-video__footer">
<!-- 进度条 -->
<view class="u-short-video__progress" style="z-index: 999;">
<up-slider
:value="videoList[currentVideo]?.progress"
:min="0"
:max="100"
:step="1"
:show-value="false"
:innerStyle="{padding: 0}"
activeColor="rgba(255,255,255,0.32)"
inactive-color="rgba(255,255,255,0.3)"
block-size="6px"
block-color="rgba(255,255,255,0.5)"
height="1px"
@changing="onProgressChanging"
@change="onProgressChange"
></up-slider>
</view>
<slot name="tabbar">
<up-tabbar
:fixed="true"
:placeholder="true"
:safeAreaInsetBottom="true"
borderColor="rgba(255,255,255,0.25) !important"
backgroundColor="rgba(255,255,255,0.05)"
>
<up-tabbar-item
@click="goNext"
text="首页"
icon="home"
>
</up-tabbar-item>
<up-tabbar-item
text="放映厅"
icon="photo"
></up-tabbar-item>
<up-tabbar-item
text="直播"
icon="play-right"
></up-tabbar-item>
<up-tabbar-item
text="我的"
icon="account"
></up-tabbar-item>
</up-tabbar>
</slot>
</view>
</view>
</template>
<script>
export default {
name: 'u-short-video',
props: {
// tabs标签列表
tabsList: {
type: Array,
default: () => [
{ name: '推荐' },
{ name: '关注' },
{ name: '朋友' },
{ name: '本地' }
]
},
// 视频列表数据
videoList: {
type: Array,
default: () => []
},
// 当前选中的tab索引
currentTab: {
type: Number,
default: 0
},
// 当前播放的视频索引
currentVideo: {
type: Number,
default: 0
}
},
data() {
return {
progressValue: 0,
showSpeedSheet: false,
currentSpeedVideoIndex: 0,
speedOptions: [
{ name: '0.5x', value: 0.5 },
{ name: '0.75x', value: 0.75 },
{ name: '1.0x', value: 1.0 },
{ name: '1.25x', value: 1.25 },
{ name: '1.5x', value: 1.5 },
{ name: '2.0x', value: 2.0 }
]
}
},
methods: {
// 处理tab切换
handleTabChange(index) {
this.$emit('tabChange', index);
},
// 处理swiper切换
handleSwiperChange(e) {
const currentIndex = e.detail.current;
// 暂停当前播放的视频
this.pauseCurrentVideo();
// 播放新切换到的视频
this.$nextTick(() => {
this.playVideo(currentIndex);
});
this.$emit('videoChange', currentIndex);
},
// 处理点赞
handleLike(item, index) {
this.$emit('like', { item, index });
},
// 处理评论
handleComment(item, index) {
this.$emit('comment', { item, index });
},
// 处理分享
handleShare(item, index) {
this.$emit('share', { item, index });
},
// 处理收藏
handleCollect(item, index) {
this.$emit('collect', { item, index });
},
// 进度条拖动中
onProgressChanging(value) {
// 更新当前视频的进度值
if (this.videoList[this.currentVideo]) {
this.videoList[this.currentVideo]['progressValue'] = value.detail.value
}
this.$emit('progressChanging', {
progress: value.detail.value,
index: this.currentVideo
});
},
// 进度条值改变
onProgressChange(value) {
// 更新当前视频的进度值
if (this.videoList[this.currentVideo]) {
this.$set(this.videoList[this.currentVideo], 'progressValue', value.detail.value);
}
this.$emit('progressChange', {
progress: value.detail.value,
index: this.currentVideo
});
},
// 显示倍速选项
showSpeedOptions(index) {
this.currentSpeedVideoIndex = index;
this.showSpeedSheet = true;
},
// 选择倍速
selectSpeed(action) {
const videoContext = uni.createVideoContext('video-' + this.currentSpeedVideoIndex, this);
videoContext.playbackRate(action.value);
// 更新视频倍速数据
this.$set(this.videoList[this.currentSpeedVideoIndex], 'playbackRate', action.value);
this.showSpeedSheet = false;
},
// 播放指定索引的视频
playVideo(index) {
const videoContext = uni.createVideoContext('video-' + index, this);
videoContext.play();
},
// 暂停当前视频
pauseCurrentVideo() {
const videoContext = uni.createVideoContext('video-' + this.currentVideo, this);
videoContext.pause();
},
// 视频播放事件
onVideoPlay(e) {
this.$emit('videoPlay', { index: this.currentVideo, event: e });
},
// 视频暂停事件
onVideoPause(e) {
this.$emit('videoPause', { index: this.currentVideo, event: e });
},
// 视频结束事件
onVideoEnded(e) {
this.$emit('videoEnded', { index: this.currentVideo, event: e });
},
// 视频时间更新事件
onTimeUpdate(e) {
const progress = (e.detail.currentTime / e.detail.duration) * 100;
if (this.videoList[this.currentVideo]) {
this.$set(this.videoList[this.currentVideo], 'progress', progress);
}
this.$emit('timeUpdate', { index: this.currentVideo, event: e });
},
// 视频元数据加载完成事件
onLoadedMetadata(e) {
this.$emit('loadedMetadata', { index: this.currentVideo, event: e });
}
}
}
</script>
<style lang="scss" scoped>
.u-short-video {
width: 100%;
height: 100vh;
position: relative;
&__header {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 10;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 10px 15px;
background-color: rgba(255, 255, 255, 0.05);
opacity: 1;
&__menu, &__search {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
}
&__tabs {
flex: 1;
margin: 0 10px;
}
}
&__content {
width: 100%;
height: 100%;
&__item {
width: 100%;
height: 100%;
position: relative;
}
&__video {
width: 100%;
height: 100%;
position: relative;
&__speed {
position: absolute;
top: 15px;
right: 15px;
z-index: 10;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 20px;
padding: 5px 10px;
display: flex;
align-items: center;
.speed-text {
color: #fff;
font-size: 12px;
margin-right: 4px;
}
}
}
&__author {
position: absolute;
left: 15px;
bottom: 100px;
display: flex;
flex-direction: row;
align-items: center;
z-index: 10;
&__info {
margin-left: 10px;
display: flex;
flex-direction: column;
justify-content: center;
}
&__name {
color: #eee;
font-size: 16px;
font-weight: bold;
margin-bottom: 5px;
}
&__desc {
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
}
&__follow {
margin-left: 15px;
}
}
&__actions {
position: absolute;
right: 15px;
bottom: 100px;
display: flex;
flex-direction: column;
align-items: center;
z-index: 10;
&__item {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
color: #fff;
}
&__text {
color: #fff;
font-size: 12px;
margin-top: 5px;
}
}
}
&__footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 10;
}
&__progress {
}
}
</style>