feat: 完成前端业务闭环与后端联调
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user