209 lines
5.7 KiB
Vue
209 lines
5.7 KiB
Vue
<template>
|
|
<view v-if="visible" class="vertical-video-viewer">
|
|
<view class="vertical-video-viewer__header">
|
|
<button aria-label="关闭上下滑动播放" @click="close">返回</button>
|
|
<text>{{ title }}</text>
|
|
<text>{{ activeIndex + 1 }}/{{ videos.length }}</text>
|
|
</view>
|
|
|
|
<swiper
|
|
class="vertical-video-viewer__swiper"
|
|
vertical
|
|
:current="activeIndex"
|
|
:duration="260"
|
|
@change="changeVideo"
|
|
>
|
|
<swiper-item v-for="(video, index) in videos" :key="video.id">
|
|
<view class="vertical-video-viewer__slide">
|
|
<video
|
|
:id="videoElementId(video)"
|
|
class="vertical-video-viewer__player"
|
|
:src="video.videoFile.accessUrl"
|
|
:poster="video.coverFile?.accessUrl || ''"
|
|
:autoplay="visible && index === activeIndex"
|
|
loop
|
|
controls
|
|
object-fit="contain"
|
|
/>
|
|
<view class="vertical-video-viewer__summary">
|
|
<text class="vertical-video-viewer__title">{{ video.title }}</text>
|
|
<text v-if="video.description" class="vertical-video-viewer__description">{{ video.description }}</text>
|
|
<view class="vertical-video-viewer__actions">
|
|
<button :disabled="actionBusy" @click="requestLike(video)">
|
|
{{ video.likedByCurrentUser ? "已赞" : "点赞" }} {{ video.likeCount || 0 }}
|
|
</button>
|
|
<button :disabled="actionBusy" @click="requestComments(video)">
|
|
评论 {{ video.commentCount || 0 }}
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</swiper-item>
|
|
</swiper>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { nextTick, ref, watch } from "vue";
|
|
|
|
const props = defineProps({
|
|
visible: { type: Boolean, default: false },
|
|
videos: { type: Array, default: () => [] },
|
|
initialVideoId: { type: [String, Number], default: "" },
|
|
title: { type: String, default: "视频播放" },
|
|
actionBusy: { type: Boolean, default: false },
|
|
});
|
|
const emit = defineEmits(["close", "like", "comments"]);
|
|
const activeIndex = ref(0);
|
|
|
|
const videoElementId = (video) => `vertical-video-${String(video.id)}`;
|
|
const pauseVideo = (index) => {
|
|
const video = props.videos[index];
|
|
if (!video) return;
|
|
uni.createVideoContext(videoElementId(video))?.pause();
|
|
};
|
|
const playVideo = (index) => {
|
|
const video = props.videos[index];
|
|
if (!video) return;
|
|
uni.createVideoContext(videoElementId(video))?.play();
|
|
};
|
|
const resolveInitialIndex = () => {
|
|
const requestedId = String(props.initialVideoId || "");
|
|
const requestedIndex = props.videos.findIndex(
|
|
(video) => String(video.id) === requestedId,
|
|
);
|
|
return requestedIndex >= 0 ? requestedIndex : 0;
|
|
};
|
|
|
|
watch(
|
|
() => props.visible,
|
|
async (visible) => {
|
|
if (!visible) {
|
|
pauseVideo(activeIndex.value);
|
|
return;
|
|
}
|
|
activeIndex.value = resolveInitialIndex();
|
|
await nextTick();
|
|
playVideo(activeIndex.value);
|
|
},
|
|
);
|
|
|
|
const changeVideo = async (event) => {
|
|
const nextIndex = Number(event?.detail?.current ?? 0);
|
|
if (!Number.isInteger(nextIndex) || nextIndex < 0 || nextIndex >= props.videos.length) return;
|
|
pauseVideo(activeIndex.value);
|
|
activeIndex.value = nextIndex;
|
|
await nextTick();
|
|
playVideo(activeIndex.value);
|
|
};
|
|
const close = () => {
|
|
pauseVideo(activeIndex.value);
|
|
emit("close");
|
|
};
|
|
const requestLike = (video) => emit("like", video);
|
|
const requestComments = (video) => {
|
|
pauseVideo(activeIndex.value);
|
|
emit("comments", video);
|
|
};
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.vertical-video-viewer {
|
|
position: fixed;
|
|
z-index: 40;
|
|
inset: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
color: #fff;
|
|
background: #080808;
|
|
}
|
|
.vertical-video-viewer__header {
|
|
display: grid;
|
|
min-height: 88rpx;
|
|
padding: calc(12rpx + env(safe-area-inset-top)) 24rpx 12rpx;
|
|
grid-template-columns: 120rpx 1fr 120rpx;
|
|
align-items: center;
|
|
background: rgba(0, 0, 0, 0.82);
|
|
text-align: center;
|
|
}
|
|
.vertical-video-viewer__header button,
|
|
.vertical-video-viewer__actions button {
|
|
min-height: 64rpx;
|
|
border: 1rpx solid rgba(255, 255, 255, 0.35);
|
|
border-radius: 32rpx;
|
|
color: #fff;
|
|
background: rgba(0, 0, 0, 0.42);
|
|
font-size: 26rpx;
|
|
line-height: 1;
|
|
}
|
|
.vertical-video-viewer__header button {
|
|
padding: 0 20rpx;
|
|
justify-self: start;
|
|
}
|
|
.vertical-video-viewer__header text:nth-child(2) {
|
|
overflow: hidden;
|
|
font-weight: 700;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.vertical-video-viewer__header text:last-child {
|
|
justify-self: end;
|
|
color: rgba(255, 255, 255, 0.72);
|
|
}
|
|
.vertical-video-viewer__swiper,
|
|
.vertical-video-viewer__slide {
|
|
min-height: 0;
|
|
flex: 1;
|
|
}
|
|
.vertical-video-viewer__slide {
|
|
position: relative;
|
|
display: flex;
|
|
height: 100%;
|
|
align-items: center;
|
|
background: #080808;
|
|
}
|
|
.vertical-video-viewer__player {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.vertical-video-viewer__summary {
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
padding: 80rpx 30rpx calc(28rpx + env(safe-area-inset-bottom));
|
|
background: linear-gradient(transparent, rgba(0, 0, 0, 0.86));
|
|
pointer-events: none;
|
|
}
|
|
.vertical-video-viewer__summary text {
|
|
display: block;
|
|
}
|
|
.vertical-video-viewer__title {
|
|
font-size: 34rpx;
|
|
font-weight: 700;
|
|
}
|
|
.vertical-video-viewer__description {
|
|
display: -webkit-box !important;
|
|
overflow: hidden;
|
|
margin-top: 12rpx;
|
|
color: rgba(255, 255, 255, 0.82);
|
|
font-size: 26rpx;
|
|
line-height: 1.55;
|
|
-webkit-box-orient: vertical;
|
|
-webkit-line-clamp: 2;
|
|
}
|
|
.vertical-video-viewer__actions {
|
|
display: flex;
|
|
margin-top: 22rpx;
|
|
gap: 16rpx;
|
|
pointer-events: auto;
|
|
}
|
|
.vertical-video-viewer__actions button {
|
|
min-width: 150rpx;
|
|
padding: 0 24rpx;
|
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.vertical-video-viewer__swiper { scroll-behavior: auto; }
|
|
}
|
|
</style>
|