适配小程序的下滑上滑播放视频组件

适配小程序的下滑上滑播放视频组件

提示:总感觉自己写好一点,我这里没有封装组件可以自己封装一下就可以了


文章目录


前言

随着时间流失,我的故事很多,当时人生很短,有一天需要开发一个视频组件,公司的测试妹妹说,我之前是用的别人组件,这里bug,哪里bug, 感觉他非常的讨厌我一样,我像下定了某种决心,我自己弄一个,也是经过了一个下午,也是弄出来了,我自信满满的给测试妹子看,那测试妹子惊呆了,直接就是这眼神,这小眼神,感觉要吃了我一样,时间很快,到了中午,阳光像照进了我心里,暖暖的,当然主要是有妹子陪我吃饭,哈哈哈,这妹子从现在开始就特别崇拜我,慢慢的就过去了,一天有一天,到了放假时间了,她居然约我吃饭,我也是勉强的答应了,我从别人哪里打听居然是富婆,哎呀我去,这下赚了啊,然后我们正常发展中,吃饭吃饭,等去玩了,吃完饭我们在散步,突然,~秀的声,她居然来亲我,我也是没有拒绝我也勉勉强强的配合了,那一时候我们就开始,走到了一次。哈哈哈哈,我从此走上了巅峰。

往下看。。。。。

我和你说哦,都是我想出来的。哈哈哈,假的,还是开始正题吧。


一、代码

html 复制代码
<template>
	<view class="video-container" :style="{ height: screenHeight + 'px' }">
		<!-- 视频列表 -->
		<swiper class="video-swiper" :vertical="true" :circular="false" :current="currentIndex" @change="onSwiperChange"
			:duration="300" :style="{ height: screenHeight + 'px' }" @animationfinish="onAnimationFinish">

			<swiper-item v-for="(video, index) in videoList" :key="getVideoKey(video, index)" class="video-item">
				<view class="video-wrapper">
					<!-- 视频播放器 -->
					<video :id="'video-' + index" :src="video.videoUrl" :poster="getVideoPoster(video.videoUrl)"
						:autoplay="false" :loop="false" :controls="false" :muted="isMuted" :show-play-btn="false"
						:show-center-play-btn="false" :enable-play-gesture="true" :enable-progress-gesture="false"
						object-fit="cover" class="video-player" @play="onVideoPlay(index)" @pause="onVideoPause(index)"
						@ended="onVideoEnded(index)" @error="onVideoError(index, $event)"
						@loadedmetadata="onVideoLoaded(index)" @timeupdate="onVideoTimeUpdate(index, $event)"
						@waiting="onVideoWaiting(index)" @stalled="onVideoStalled(index)">
					</video>

					<!-- 视频封面(播放前显示) -->
					<view v-if="!video.isPlaying || video.loadFailed" class="video-poster" @tap="playVideo(index)">
						<image :src="getVideoPoster(video.videoUrl)" mode="aspectFill" class="poster-image"></image>
						<view class="play-icon-wrapper">
							<u-icon name="play-right-fill" size="60" color="#fff"></u-icon>
						</view>
						<!-- 加载失败提示 -->
						<view v-if="video.loadFailed" class="load-failed-tips">
							<text class="failed-text">视频加载失败,点击重试</text>
						</view>
					</view>

					<!-- 加载状态  -->
					<view v-if="video.loading && !video.loadFailed" class="loading-container">
						<view class="loading-spinner"></view>
						<text class="loading-text">加载中...</text>
					</view>

					<!-- 边界提示 -->
					<view v-if="showBoundaryTips" class="boundary-tips">
						<text v-if="isFirstVideo && isSwipingUp" class="tip-text">已经是第一个视频了</text>
						<text v-if="isLastVideo && isSwipingDown" class="tip-text">没有更多视频了</text>
					</view>

					<!-- 暂停按钮 -->
					<view v-if="video.isPaused" class="pause-overlay" @tap="togglePlay(index)">
						<view class="pause-icon-wrapper">
							<u-icon name="pause" size="70" color="#fff"></u-icon>
						</view>
					</view>

					<!-- 底部信息栏 -->
					<view class="video-info">
						<!-- 用户信息 -->
						<view class="user-info">
							<image :src="video.avatar" class="user-avatar"></image>
							<view class="user-name">{{ video.nickname }}</view>
							<button class="follow-btn" @tap="followUser(video.userId)">关注</button>
						</view>

						<!-- 视频描述 -->
						<view class="video-description">
							<text class="description-text">{{ video.videoContent }}</text>
						</view>

						<!-- 标签 -->
						<view v-if="video.tags" class="tags-container">
							<text v-for="tag in parseTags(video.tags)" :key="tag" class="tag-item"
								@tap="searchByTag(tag)">
								#{{ tag }}
							</text>
						</view>

						<!-- 位置信息 -->
						<view v-if="video.location" class="location-info">
							<u-icon name="map-fill" size="16" color="#fff"></u-icon>
							<text>{{ video.location }}</text>
						</view>

						<!-- 发布时间 -->
						<view class="publish-time">
							<u-icon name="calendar" size="16" color="rgba(255, 255, 255, 0.6)"></u-icon>
							<text>{{ formatRelativeTime(video.createdAt) }}</text>
						</view>
					</view>

					<!-- 右侧互动按钮 -->
					<view class="interaction-sidebar">
						<!-- 头像 -->
						<view class="sidebar-avatar" @tap="goUserProfile(video.userId)">
							<image :src="video.avatar" class="avatar-image" mode="aspectFill"></image>
							<view class="follow-animation">
								<u-icon name="plus" size="12" color="#ff2442"></u-icon>
							</view>
						</view>

						<!-- 点赞 -->
						<view class="interaction-btn" @click.tap="() =>likeVideo(video, index)">
							<u-icon :name="video._liked ? 'heart-fill' : 'heart'" size="28"
								:color="video._liked ? '#ff2442' : '#fff'" class="btn-icon">
							</u-icon>
							<text class="btn-count">{{ video.likeCount || 0 }}</text>

							<!-- 点赞动画 -->
							<view v-if="video.showLikeAnimation" class="like-animation">
								<u-icon name="heart-fill" size="80" color="#ff2442"></u-icon>
							</view>
						</view>

						<!-- 评论 -->
						<view class="interaction-btn" @tap="commentVideo(video)">
							<u-icon name="chat" size="28" color="#fff" class="btn-icon"></u-icon>
							<text class="btn-count">{{ video.commentCount || 0 }}</text>
						</view>

						<!-- 收藏 -->
						<view class="interaction-btn" @click.tap="() => collectVideo(video, index)">
							<u-icon :name="video._collected ? 'star-fill' : 'star'" size="28"
								:color="video._collected ? '#ff9500' : '#fff'" class="btn-icon">
							</u-icon>
							<text class="btn-count">{{ video.collectCount || 0 }}</text>
						</view>



						<view class="interaction-btn" @tap="toggleMute">
							<u-icon v-if="isMuted" name="volume-off" size="24" color="#fff"></u-icon>
							<u-icon v-else name="volume" size="24" color="#fff"></u-icon>
						</view>

						<!-- 分享 -->
						<view class="interaction-btn" @tap="shareVideo(video)">
							<u-icon name="share" size="28" color="#fff" class="btn-icon"></u-icon>
							<text class="btn-count">{{ video.shareCount || 0 }}</text>
						</view>

						<!-- 加载更多提示 -->
						<view v-if="index === videoList.length - 1 && loadingMore" class="load-more-indicator">
							<view class="loading-dots">
								<u-icon name="loading" size="20" color="#fff"></u-icon>
							</view>
						</view>

						<!-- 已到底部提示 -->
						<view v-if="index === videoList.length - 1 && isEndOfList" class="end-of-list">
							<u-icon name="more-dot-fill" size="20" color="rgba(255, 255, 255, 0.6)"></u-icon>
						</view>
					</view>

					<!-- 进度条 -->
					<view class="progress-container">
						<view class="progress-bar">
							<view class="progress-current" :style="{ width: video.progress + '%' }"></view>
						</view>
					</view>

					<!-- 返回按钮 -->
					<view class="back-btn" @tap="goBack">
						<u-icon name="arrow-left" size="24" color="#fff"></u-icon>
					</view>
				</view>
			</swiper-item>
		</swiper>

		<!-- 评论弹窗 -->
		<u-popup :show="showComment" mode="bottom" round="20" @close="closeComment">
			<view class="comment-popup">
				<view class="comment-header">
					<text class="comment-title">评论 {{ currentVideo.commentCount || 0 }}</text>
					<view class="close-btn" @tap="closeComment">
						<u-icon name="close" size="20" color="#999"></u-icon>
					</view>
				</view>
				<scroll-view class="comment-list" scroll-y>
					<!-- 评论列表 -->
					<view v-for="comment in comments" :key="comment.id" class="comment-item">
						<image :src="comment.avatar" class="comment-avatar"></image>
						<view class="comment-content">
							<text class="comment-author">{{ comment.nickname }}</text>
							<text class="comment-text">{{ comment.content }}</text>
							<view class="comment-meta">
								<text>{{ formatRelativeTime(comment.createdAt) }}</text>
								<view class="comment-actions">
									<view class="comment-like" @tap="likeComment(comment)">
										<u-icon :name="comment.liked ? 'thumb-up-fill' : 'thumb-up'" size="16"
											:color="comment.liked ? '#ff2442' : '#999'">
										</u-icon>
										<text>{{ comment.likeCount || 0 }}</text>
									</view>
									<text class="reply-btn" @tap="replyComment(comment)">回复</text>
								</view>
							</view>
						</view>
					</view>
				</scroll-view>
				<view class="comment-input-area">
					<input type="text" v-model="commentInput" placeholder="写下你的评论..." class="comment-input"
						@confirm="sendComment">
					<button class="send-btn" @tap="sendComment">
						<u-icon
							name="https://dm-materials.oss-cn-guangzhou.aliyuncs.com/KHCFDC%E5%8F%91%E9%80%8120251215100911.png"
							size="20" color="#fff"></u-icon>
						<text>发送</text>
					</button>
				</view>
			</view>
		</u-popup>


		<u-popup :show="loginOrNot" mode="center" round="10" @close="loginOrNot = false" :safeAreaInsetBottom="false">
			<view
				style="width: 600rpx; padding: 40rpx 30rpx 30rpx; background: #fff; border-radius: 20rpx; position: relative;">
				<!-- 标题 -->
				<view
					style="font-size: 36rpx; font-weight: bold; text-align: center; margin-bottom: 30rpx; color: #333;">
					请登录</view>

				<!-- 内容区域 -->
				<view style="margin-bottom: 40rpx;">
					<view
						style="font-size: 30rpx; color: #333; text-align: center; margin-bottom: 30rpx; font-weight: 500;">
						「云居库」------ 家具设计与美学灵感社区</view>

					<view style="padding: 0 20rpx;">
						<view
							style="font-size: 26rpx; color: #666; line-height: 1.8; padding: 8rpx 0; position: relative; padding-left: 20rpx;">
							🔍 一站解锁设计核心资源</view>
						<view
							style="font-size: 26rpx; color: #666; line-height: 1.8; padding: 8rpx 0; position: relative; padding-left: 20rpx;">
							✅ 国外奢品家具图鉴 | 原创设计首发</view>
						<view
							style="font-size: 26rpx; color: #666; line-height: 1.8; padding: 8rpx 0; position: relative; padding-left: 20rpx;">
							✅ 巢居美学指南 | 治愈系小物推荐</view>
						<view
							style="font-size: 26rpx; color: #666; line-height: 1.8; padding: 8rpx 0; position: relative; padding-left: 20rpx;">
							✅ 设计师专属社区 | 国外品牌源文件直下</view>
						<view
							style="font-size: 26rpx; color: #666; line-height: 1.8; padding: 8rpx 0; position: relative; padding-left: 20rpx;">
							✨ 在这里,找到设计的灵感与质感,立即登录,开启你的云居库之旅~</view>
					</view>
				</view>

				<!-- 登录按钮 -->
				<view style="margin: 20rpx 0 30rpx;">
					<u-button open-type="getPhoneNumber" @getphonenumber="handleGetPhoneNumber" text="手机号登录"
						color="#007FFF" shape="circle" size="normal"></u-button>
				</view>

				<!-- 底部叉掉按钮 -->
				<button @click="gb()"
					style="position: relative; bottom: -90px; left: 0%; z-index: 9999; background: #fff; border: none; border-radius: 50%; width: 24px; height: 25px; font-size: 16px; display: flex; align-items: center; justify-content: center; cursor: pointer;color: #000;">
					×
				</button>
			</view>
		</u-popup>

	</view>
</template>

<script>
	import {
		getToken,
		getUserId,
		setToken,
		setUserId
	} from "@/utils/data";
	import {
		authweixin,
		likeArticle,
		collectArticle,
		checkUserCollectionStatus,
		getVideoNeighbors
	} from "@/api/index_api.js";

	export default {
		data() {
			return {
				loginOrNot: false,
				screenHeight: 0,
				currentIndex: 0,
				isMuted: false,
				videoList: [],
				loadingMore: false,
				showComment: false,
				currentVideo: {},
				comments: [],
				commentInput: '',
				videoContexts: {},
				progressTimers: {},

				// 初始视频ID(从首页传入)
				initialVideoId: null,
				initialVideoData: null,

				// 边界处理相关
				isEndOfList: false, // 是否已经到底
				noMoreVideos: false, // 是否没有更多视频
				showBoundaryTips: false, // 是否显示边界提示
				isSwipingUp: false, // 是否向上滑动
				isSwipingDown: false, // 是否向下滑动
				lastSwipeDirection: null, // 最后滑动方向
				swipeTimer: null, // 滑动提示计时器

				// 简化:不再缓存视频ID,每次滑动都重新查询
				loadingNeighbors: false, // 是否正在加载相邻视频
				neighborCache: new Map(), // 缓存已查询过的视频ID对应的相邻数据

				// 当前正在播放的视频索引
				currentPlayingIndex: -1,

				// 防止重复播放
				isSwitchingVideo: false,

				// 视频监控定时器
				videoMonitorTimers: {},

				// 视频加载重试次数
				videoRetryCount: {},

				// 预加载视频索引
				preloadIndex: -1
			}
		},
		computed: {
			// 是否是第一个视频
			isFirstVideo() {
				return this.currentIndex === 0;
			},

			// 是否是最后一个视频
			isLastVideo() {
				return this.currentIndex === this.videoList.length - 1;
			},

			// 当前视频
			currentVideoData() {
				return this.videoList[this.currentIndex] || {};
			}
		},
		onLoad(options) {
			// 获取屏幕高度
			const systemInfo = uni.getSystemInfoSync();
			this.screenHeight = systemInfo.windowHeight;

			// 获取传入的视频ID
			if (options.videoId) {
				this.initialVideoId = parseInt(options.videoId);
			}

			// 如果有完整的视频数据
			if (options.videoData) {
				try {
					this.initialVideoData = JSON.parse(decodeURIComponent(options.videoData));
				} catch (e) {
					console.error('解析视频数据失败', e);
				}
			}

			// 初始化数据
			this.initVideos();
		},
		onReady() {
			// 页面准备好后自动播放第一个视频
			// this.$nextTick(() => {
			// 	if (this.videoList.length > 0) {
			// 		this.playVideo(0);
			// 	}
			// });
			this.$nextTick(() => {
				// 关键修复:增加延迟确保video组件完全渲染
				setTimeout(() => {
					if (this.videoList.length > 0) {
						this.playVideo(0);
					}
				}, 500);
			});
		},
		onUnload() {
			// 页面卸载时停止所有定时器和视频
			this.stopAllVideos();
			this.clearAllTimers();
			clearTimeout(this.swipeTimer);

			// 清除监控定时器
			this.clearAllMonitorTimers();
		},
		onHide() {
			// 页面隐藏时暂停当前视频
			if (this.videoList[this.currentIndex]) {
				this.pauseVideo(this.currentIndex);
			}
		},
		onShow() {
			// 页面显示时恢复播放当前视频
			if (this.videoList[this.currentIndex] && !this.videoList[this.currentIndex].loadFailed) {
				this.playVideo(this.currentIndex);
			}
		},
		methods: {
			// 生成唯一的key
			getVideoKey(video, index) {
				return video.videoId ? `video-${video.videoId}-${index}` : `video-index-${index}`;
			},

			// 初始化视频列表 - 简化逻辑
			async initVideos() {
				try {
					if (this.initialVideoId) {
						// 直接加载当前视频及其相邻视频
						await this.loadCurrentVideoData(this.initialVideoId);
					} else if (this.initialVideoData) {
						// 如果有初始视频数据,先添加到列表
						const video = this.createVideoObject(this.initialVideoData, true);
						this.videoList.push(video);
						this.currentIndex = 0;

						// 然后加载相邻视频
						await this.loadCurrentVideoData(video.videoId);
					} else {
						// 没有初始数据
						uni.showToast({
							title: '暂无视频数据',
							icon: 'none'
						});
					}
				} catch (error) {
					console.error('初始化视频失败', error);
					uni.showToast({
						title: '加载失败',
						icon: 'none'
					});
				}
			},

			// 加载当前视频的数据
			async loadCurrentVideoData(videoId) {
				try {
					const response = await getVideoNeighbors(videoId);

					if (!response) {
						console.warn('API返回数据格式错误:', response);
						return;
					}

					const {
						shang,
						cu,
						xia
					} = response;

					// 缓存这次查询的结果
					this.neighborCache.set(videoId, response);

					// 清空视频列表
					this.videoList = [];

					// 添加上一个视频(如果存在)
					if (shang && shang.videoId) {
						const prevVideo = this.createVideoObject(shang);
						this.videoList.push(prevVideo);
						console.log('添加上一个视频:', shang.videoId);
					}

					// 添加当前视频(如果存在)
					if (cu && cu.videoId) {
						const currentVideo = this.createVideoObject(cu);
						this.videoList.push(currentVideo);
						this.currentIndex = this.videoList.length - 1;
						console.log('添加当前视频:', cu.videoId);

						// 如果有初始数据,合并用户交互状态
						if (this.initialVideoData && cu.videoId === this.initialVideoData.id) {
							currentVideo._liked = this.initialVideoData._liked || false;
							currentVideo._collected = this.initialVideoData._collected || false;
						}
					}

					// 添加下一个视频(如果存在)
					if (xia && xia.videoId) {
						const nextVideo = this.createVideoObject(xia);
						this.videoList.push(nextVideo);
						console.log('添加下一个视频:', xia.videoId);

						this.noMoreVideos = false;
						this.isEndOfList = false;
					} else {
						// 没有下一个视频
						this.noMoreVideos = true;
						this.isEndOfList = true;
						console.log('这是最后一个视频');
					}

					console.log('初始化后视频列表:', this.videoList.map(v => v.videoId));

				} catch (error) {
					console.error('加载视频数据失败', error);
					uni.showToast({
						title: '加载失败,请稍后重试',
						icon: 'none'
					});
				}
			},

			// 创建视频对象
			createVideoObject(videoData) {
				return {
					videoId: videoData.videoId || videoData.id,
					videoTitle: videoData.videoTitle || videoData.title,
					videoContent: videoData.videoContent || videoData.content,
					videoUrl: videoData.videoUrl,
					location: videoData.location,
					userId: videoData.userId,
					nickname: videoData.nickname,
					avatar: videoData.avatar,
					viewCount: videoData.viewCount || 0,
					likeCount: videoData.likeCount || 0,
					collectCount: videoData.collectCount || 0,
					commentCount: videoData.commentCount || 0,
					shareCount: videoData.shareCount || 0,
					tags: videoData.tags,
					createdAt: videoData.createdAt,

					// 前端状态
					isPlaying: false,
					isPaused: false,
					loading: false,
					loadFailed: false, // 新增:加载失败状态
					progress: 0,
					_liked: false,
					_collected: false,
					showLikeAnimation: false,
					lastUpdateTime: 0, // 新增:最后更新时间,用于监控视频是否卡住
					bufferState: 'normal', // 缓冲状态:normal/waiting/stalled
					duration: 0, // 视频时长(属性,不是方法)
					currentTime: 0 // 当前播放时间
				};
			},



			async onSwiperChange(e) {
				if (this.isSwitchingVideo) {
					console.log('正在切换视频中,跳过');
					return;
				}

				this.isSwitchingVideo = true;

				const oldIndex = this.currentIndex;
				const newIndex = e.detail.current;

				// 判断滑动方向
				this.lastSwipeDirection = newIndex > oldIndex ? 'down' : 'up';
				this.isSwipingDown = this.lastSwipeDirection === 'down';
				this.isSwipingUp = this.lastSwipeDirection === 'up';

				console.log('滑动切换:', {
					from: oldIndex,
					to: newIndex,
					direction: this.lastSwipeDirection,
					totalVideos: this.videoList.length
				});

				// 停止旧视频的播放(彻底停止,释放资源)
				if (this.videoList[oldIndex]) {
					this.stopVideo(oldIndex, false); // 完全停止旧视频
					// 额外清理:删除旧视频上下文
					if (this.videoContexts[oldIndex]) {
						delete this.videoContexts[oldIndex];
					}
				}

				// 更新当前索引
				this.currentIndex = newIndex;

				// 关键修复:使用nextTick确保DOM更新后再创建视频上下文
				this.$nextTick(() => {
					// 增加延迟兼容小程序渲染机制
					setTimeout(() => {
						this.playVideo(newIndex);
						// 预加载下一个视频
						this.preloadNextVideo(newIndex);
					}, 100);
				});

				// 检查是否需要加载更多视频
				await this.checkAndLoadMoreVideos(newIndex);

				// 重置切换状态(缩短延迟)
				setTimeout(() => {
					this.isSwitchingVideo = false;
				}, 300);
			},

			// 预加载下一个视频(修复prefetch不存在的问题)
			preloadNextVideo(currentIndex) {
				// 取消之前的预加载
				if (this.preloadIndex !== -1 && this.preloadIndex !== currentIndex) {
					this.stopVideo(this.preloadIndex, false);
					this.preloadIndex = -1;
				}

				// 预加载下一个视频(向下滑动)
				if (currentIndex < this.videoList.length - 1) {
					const nextIndex = currentIndex + 1;
					this.preloadIndex = nextIndex;
					setTimeout(() => {
						if (this.videoList[nextIndex] && !this.videoList[nextIndex].isPlaying) {
							try {
								const videoContext = uni.createVideoContext(`video-${nextIndex}`, this);
								if (videoContext) {
									// 微信小程序中没有prefetch方法,改用提前创建上下文
									this.videoContexts[nextIndex] = videoContext;
									console.log('预加载视频上下文创建完成:', nextIndex);
								}
							} catch (e) {
								console.warn('预加载视频失败:', nextIndex, e);
							}
						}
					}, 500);
				}
			},

			// 检查并加载更多视频
			async checkAndLoadMoreVideos(currentIndex) {
				const currentVideo = this.videoList[currentIndex];
				if (!currentVideo) return;

				// 如果向上滑动到第一个视频,尝试加载前面的视频
				if (currentIndex === 0 && this.isSwipingUp) {
					await this.loadPreviousVideo(currentVideo.videoId);
					// 加载完成后立即播放当前视频(修复向上滑动后不自动播放的问题)
					this.$nextTick(() => {
						setTimeout(() => {
							this.playVideo(this.currentIndex);
						}, 200);
					});
				}

				// 如果向下滑动到最后一个视频,尝试加载后面的视频
				if (currentIndex === this.videoList.length - 1 && this.isSwipingDown) {
					await this.loadNextVideo(currentVideo.videoId);
				}
			},

			// 加载前面的视频(向上滑动时)
			async loadPreviousVideo(videoId) {
				if (this.loadingNeighbors || this.loadingMore) {
					console.log('正在加载中,跳过');
					return;
				}

				console.log('尝试加载前面的视频:', videoId);

				try {
					this.loadingNeighbors = true;

					// 先检查缓存
					if (this.neighborCache.has(videoId)) {
						const cachedData = this.neighborCache.get(videoId);
						const {
							shang
						} = cachedData;

						if (shang && shang.videoId) {
							// 检查是否已经在列表中
							const exists = this.videoList.some(v => v.videoId === shang.videoId);
							if (!exists) {
								const prevVideo = this.createVideoObject(shang);
								this.videoList.unshift(prevVideo);

								// 更新当前索引,因为数组前面插入了新元素
								this.currentIndex += 1;

								console.log('从缓存添加前面视频:', shang.videoId);
								return;
							}
						}
					}

					// 缓存中没有或需要重新查询
					const response = await getVideoNeighbors(videoId);

					if (!response) {
						console.warn('加载前面视频返回数据格式错误');
						return;
					}

					// 缓存结果
					this.neighborCache.set(videoId, response);

					const {
						shang
					} = response;

					if (shang && shang.videoId) {
						// 检查是否已经在列表中
						const exists = this.videoList.some(v => v.videoId === shang.videoId);
						if (!exists) {
							const prevVideo = this.createVideoObject(shang);
							this.videoList.unshift(prevVideo);

							// 更新当前索引,因为数组前面插入了新元素
							this.currentIndex += 1;

							console.log('从API添加前面视频:', shang.videoId);
						}
					} else {
						// 没有前面的视频了
						this.showBoundaryTip('已经是第一个视频了');
					}

				} catch (error) {
					console.error('加载前面视频失败', error);
				} finally {
					this.loadingNeighbors = false;
				}
			},

			// 加载后面的视频(向下滑动时)
			async loadNextVideo(videoId) {
				if (this.loadingNeighbors || this.loadingMore) {
					console.log('正在加载中,跳过');
					return;
				}

				console.log('尝试加载后面的视频:', videoId);

				// 如果已经到底了,显示提示
				if (this.isEndOfList) {
					this.showBoundaryTip('没有更多视频了');
					return;
				}

				try {
					this.loadingNeighbors = true;

					// 先检查缓存
					if (this.neighborCache.has(videoId)) {
						const cachedData = this.neighborCache.get(videoId);
						const {
							xia
						} = cachedData;

						if (xia && xia.videoId) {
							// 检查是否已经在列表中
							const exists = this.videoList.some(v => v.videoId === xia.videoId);
							if (!exists) {
								const nextVideo = this.createVideoObject(xia);
								this.videoList.push(nextVideo);

								console.log('从缓存添加后面视频:', xia.videoId);
								return;
							}
						} else {
							// 缓存中没有下一个视频,说明到底了
							this.isEndOfList = true;
							this.showBoundaryTip('没有更多视频了');
							return;
						}
					}

					// 缓存中没有或需要重新查询
					const response = await getVideoNeighbors(videoId);

					if (!response) {
						console.warn('加载后面视频返回数据格式错误');
						return;
					}

					// 缓存结果
					this.neighborCache.set(videoId, response);

					const {
						xia
					} = response;

					if (xia && xia.videoId) {
						// 检查是否已经在列表中
						const exists = this.videoList.some(v => v.videoId === xia.videoId);
						if (!exists) {
							const nextVideo = this.createVideoObject(xia);
							this.videoList.push(nextVideo);

							console.log('从API添加后面视频:', xia.videoId);
							this.isEndOfList = false;
						}
					} else {
						// 没有后面的视频了
						this.isEndOfList = true;
						this.showBoundaryTip('没有更多视频了');
					}

				} catch (error) {
					console.error('加载后面视频失败', error);
					this.isEndOfList = true;
				} finally {
					this.loadingNeighbors = false;
				}
			},

			// 显示边界提示
			showBoundaryTip(message) {
				this.showBoundaryTips = true;
				console.log('边界提示:', message);

				// 2秒后隐藏提示
				clearTimeout(this.swipeTimer);
				this.swipeTimer = setTimeout(() => {
					this.showBoundaryTips = false;
				}, 2000);
			},

			// 滑动动画结束
			onAnimationFinish() {
				// 动画结束后确保当前视频播放
				setTimeout(() => {
					this.playVideo(this.currentIndex);
				}, 100);
				this.isSwipingUp = false;
				this.isSwipingDown = false;
			},



			playVideo(index) {
				if (index < 0 || index >= this.videoList.length) {
					console.warn('播放视频索引越界:', index);
					return;
				}

				const video = this.videoList[index];
				if (!video) return;

				// 如果已经在播放,跳过
				if (video.isPlaying && !video.isPaused) {
					console.log('视频已经在播放中,跳过,索引:', index);
					return;
				}

				console.log('播放视频,索引:', index, '视频ID:', video.videoId);

				// 清除加载失败状态
				if (video.loadFailed) {
					video.loadFailed = false;
					this.videoRetryCount[index] = 0; // 重置重试次数
				}

				// 停止其他所有视频(彻底停止)
				this.videoList.forEach((v, i) => {
					if (i !== index && (v.isPlaying || v.isPaused)) {
						this.stopVideo(i, false); // 完全停止其他视频
					}
				});

				// 设置状态
				video.loading = true;
				video.isPlaying = false;
				video.isPaused = false;
				video.bufferState = 'normal';

				// 关键修复:增加多次重试逻辑 + 兼容小程序自动播放限制
				const playWithRetry = (retryTimes = 0) => {
					try {
						// 先销毁旧的上下文
						if (this.videoContexts[index]) {
							delete this.videoContexts[index];
						}

						// 重新创建video context
						const videoContext = uni.createVideoContext(`video-${index}`, this);
						if (!videoContext) {
							throw new Error('无法创建VideoContext');
						}

						this.videoContexts[index] = videoContext;

						// 设置静音状态(小程序静音视频更容易自动播放)
						videoContext.muted = this.isMuted;

						// 重置视频播放位置
						videoContext.seek(0);

						// 关键修复:多次尝试播放,兼容小程序渲染延迟
						const attemptPlay = () => {
							try {
								videoContext.play();
								console.log('播放命令已发送,索引:', index, '重试次数:', retryTimes);

								// 立即检查播放状态
								setTimeout(() => {
									if (!video.isPlaying && video.loading) {
										if (retryTimes < 3) {
											console.log('播放失败,重试...', index, retryTimes + 1);
											playWithRetry(retryTimes + 1);
										} else {
											console.warn('多次播放失败,标记为加载失败:', index);
											video.loading = false;
											video.loadFailed = true;
										}
									}
								}, 500);
							} catch (e) {
								if (retryTimes < 3) {
									setTimeout(() => playWithRetry(retryTimes + 1), 300);
								} else {
									video.loading = false;
									video.loadFailed = true;
								}
							}
						};

						attemptPlay();

					} catch (error) {
						console.error('视频准备失败:', error);
						if (retryTimes < 3) {
							setTimeout(() => playWithRetry(retryTimes + 1), 500);
						} else {
							video.loading = false;
							video.loadFailed = true;
						}
					}
				};

				// 启动重试播放
				playWithRetry();

				// 记录播放次数
				this.recordVideoView(video.videoId);
			},

			// 重试播放视频
			retryPlayVideo(index) {
				const video = this.videoList[index];
				if (!video || video.loadFailed) return;

				console.log('重试播放视频,索引:', index, '重试次数:', this.videoRetryCount[index] || 0);

				// 限制重试次数
				if (this.videoRetryCount[index] && this.videoRetryCount[index] >= 2) {
					video.loading = false;
					video.loadFailed = true;
					return;
				}

				// 增加重试次数
				this.videoRetryCount[index] = (this.videoRetryCount[index] || 0) + 1;

				// 重新播放
				if (this.videoContexts[index]) {
					try {
						this.videoContexts[index].seek(0);
						setTimeout(() => {
							this.videoContexts[index].play();
						}, 500);
					} catch (e) {
						console.error('重试播放失败:', e);
						video.loading = false;
						video.loadFailed = true;
					}
				}
			},

			// 开始视频卡顿监控
			startVideoMonitor(index) {
				// 清除旧的监控器
				this.stopVideoMonitor(index);

				// 开始新的监控
				this.videoMonitorTimers[index] = setInterval(() => {
					const video = this.videoList[index];
					if (video && video.isPlaying && !video.isPaused) {
						const now = Date.now();
						if (video.lastUpdateTime && (now - video.lastUpdateTime > 5000)) {
							// 超过5秒没有更新,认为视频卡住了
							console.warn('视频可能卡住了,索引:', index);
							this.handleVideoStuck(index);
						}
					}
				}, 2000); // 监控频率降低到2秒一次
			},

			// 停止视频监控
			stopVideoMonitor(index) {
				if (this.videoMonitorTimers[index]) {
					clearInterval(this.videoMonitorTimers[index]);
					delete this.videoMonitorTimers[index];
				}
			},

			// 清除所有监控定时器
			clearAllMonitorTimers() {
				Object.keys(this.videoMonitorTimers).forEach(index => {
					clearInterval(this.videoMonitorTimers[index]);
				});
				this.videoMonitorTimers = {};
			},

			// 处理视频卡住
			handleVideoStuck(index) {
				const video = this.videoList[index];
				if (!video || !video.isPlaying) return;

				console.log('尝试恢复卡住的视频,索引:', index);

				// 强制重置并重新播放
				if (this.videoContexts[index]) {
					try {
						// 暂停并重置
						this.videoContexts[index].pause();
						this.videoContexts[index].seek(video.progress > 0 ? video.progress / 100 * video.duration : 0);

						// 延迟播放
						setTimeout(() => {
							this.videoContexts[index].play();
							video.lastUpdateTime = Date.now();
							video.bufferState = 'normal';
						}, 1000);
					} catch (error) {
						console.error('恢复视频失败:', error);
						video.loadFailed = true;
						video.isPlaying = false;
					}
				}
			},

			// 视频加载完成事件(修复duration获取方式)
			onVideoLoaded(index) {
				console.log('视频元数据加载完成,索引:', index);
				const video = this.videoList[index];
				if (video) {
					video.loading = false;
					video.lastUpdateTime = Date.now();

					// 微信小程序中通过wx.createVideoContext获取的duration是属性,不是方法
					// 这里改为从事件中获取duration
					if (this.videoContexts[index]) {
						// 兼容处理:尝试获取duration
						try {
							// 对于微信小程序,duration是通过video组件的bindtimeupdate事件获取的
							// 这里先标记,等待timeupdate事件
							video.duration = video.duration || 0;
						} catch (e) {
							console.warn('获取视频时长失败:', e);
						}
					}
				}
			},

			// 视频缓冲等待事件
			onVideoWaiting(index) {
				console.log('视频缓冲中,索引:', index);
				const video = this.videoList[index];
				if (video) {
					video.bufferState = 'waiting';
				}
			},

			// 视频加载停滞事件
			onVideoStalled(index) {
				console.log('视频加载停滞,索引:', index);
				const video = this.videoList[index];
				if (video) {
					video.bufferState = 'stalled';
					// 尝试恢复
					setTimeout(() => {
						this.handleVideoStuck(index);
					}, 3000);
				}
			},

			// 视频时间更新事件(修复duration获取)
			onVideoTimeUpdate(index, event) {
				const video = this.videoList[index];
				if (video && video.isPlaying) {
					video.lastUpdateTime = Date.now();
					video.bufferState = 'normal';

					// 计算真实进度
					if (event && event.detail) {
						// 从事件中获取时长和当前时间(修复核心问题)
						video.duration = event.detail.duration || video.duration;
						video.currentTime = event.detail.currentTime || video.currentTime;

						const progress = (video.currentTime / video.duration) * 100;
						video.progress = Math.min(progress, 100);
					}
				}
			},

			// 停止视频(修复版本)
			stopVideo(index, pauseOnly = false) {
				if (index < 0 || index >= this.videoList.length) return;

				const video = this.videoList[index];
				if (!video) return;

				console.log(pauseOnly ? '暂停视频' : '停止视频', '索引:', index);

				// 停止video context
				if (this.videoContexts[index]) {
					try {
						if (pauseOnly) {
							// 只是暂停
							this.videoContexts[index].pause();
						} else {
							// 完全停止并重置
							this.videoContexts[index].pause();
							this.videoContexts[index].seek(0);
						}
					} catch (error) {
						console.error('停止视频上下文失败', error);
					}
				}

				// 停止视频监控
				this.stopVideoMonitor(index);

				// 更新状态
				video.isPlaying = false;
				video.isPaused = pauseOnly; // 如果是暂停,则isPaused为true
				video.loading = false;
				video.bufferState = 'normal';

				if (!pauseOnly) {
					video.progress = 0; // 重置进度
					video.currentTime = 0;
					// 保留duration以便下次播放
					video.loadFailed = false; // 重置加载失败状态
					// 重置重试次数
					if (this.videoRetryCount[index]) {
						delete this.videoRetryCount[index];
					}
				}

				// 停止进度条计时器
				this.stopProgressTimer(index);

				// 如果这是当前播放的视频,更新记录
				if (index === this.currentPlayingIndex) {
					this.currentPlayingIndex = -1;
				}
			},

			// 暂停视频(只是暂停,不重置)
			pauseVideo(index) {
				this.stopVideo(index, true);
			},

			// 停止所有视频
			stopAllVideos() {
				// 停止所有视频上下文
				Object.keys(this.videoContexts).forEach(index => {
					const context = this.videoContexts[index];
					if (context) {
						try {
							context.pause();
							context.seek(0);
						} catch (error) {
							console.error('停止视频失败:', error);
						}
					}
				});

				// 清空上下文对象
				this.videoContexts = {};
				this.videoRetryCount = {};
				this.preloadIndex = -1;

				// 重置所有视频状态
				this.videoList.forEach(video => {
					video.isPlaying = false;
					video.isPaused = false;
					video.progress = 0;
					video.loading = false;
					video.loadFailed = false;
					video.bufferState = 'normal';
					video.currentTime = 0;
				});

				// 重置当前播放索引
				this.currentPlayingIndex = -1;

				// 清除所有定时器
				this.clearAllTimers();
				this.clearAllMonitorTimers();
			},

			// 切换播放状态
			togglePlay(index) {
				if (!this.videoList[index]) return;

				const video = this.videoList[index];

				// 如果视频加载失败,点击重新加载
				if (video.loadFailed) {
					console.log('重新加载失败的视频,索引:', index);
					this.playVideo(index);
					return;
				}

				if (video.isPaused) {
					// 继续播放
					this.playVideo(index);
				} else {
					// 暂停
					this.pauseVideo(index);
				}
			},

			// 开始进度条计时器
			startProgressTimer(index) {
				// 清除旧的计时器
				this.stopProgressTimer(index);

				// 开始新的计时器(作为备用,实际使用视频的timeupdate事件)
				this.progressTimers[index] = setInterval(() => {
					const video = this.videoList[index];
					if (video && video.isPlaying && !video.isPaused && !video.loadFailed && video.bufferState ===
						'normal') {
						// 如果视频没有卡住,更新进度
						const now = Date.now();
						if (!video.lastUpdateTime || (now - video.lastUpdateTime < 5000)) {
							// 只在视频正常播放时更新进度
							if (video.duration && video.currentTime) {
								const progress = (video.currentTime / video.duration) * 100;
								video.progress = Math.min(progress, 100);
							}

							// 如果进度到达100%,触发结束事件
							if (video.progress >= 100) {
								this.onVideoEnded(index);
							}
						}
					}
				}, 500); // 降低更新频率,减少性能消耗
			},

			// 停止进度条计时器
			stopProgressTimer(index) {
				if (this.progressTimers[index]) {
					clearInterval(this.progressTimers[index]);
					delete this.progressTimers[index];
				}
			},

			// 清除所有计时器
			clearAllTimers() {
				Object.keys(this.progressTimers).forEach(index => {
					clearInterval(this.progressTimers[index]);
				});
				this.progressTimers = {};
			},

			// 视频播放事件
			onVideoPlay(index) {
				console.log('视频开始播放,索引:', index);
				const video = this.videoList[index];
				if (video) {
					video.isPlaying = true;
					video.isPaused = false;
					video.loading = false;
					video.loadFailed = false;
					this.currentPlayingIndex = index;
					video.lastUpdateTime = Date.now();

					// 启动视频监控
					this.startVideoMonitor(index);

					// 启动进度条计时器
					this.startProgressTimer(index);

					// 确保其他视频都真正停止
					this.videoList.forEach((v, i) => {
						if (i !== index && v.isPlaying) {
							this.stopVideo(i, false);
						}
					});
				}
			},

			// 视频暂停事件
			onVideoPause(index) {
				console.log('视频暂停,索引:', index);
				const video = this.videoList[index];
				if (video) {
					video.isPaused = true;
					video.isPlaying = false;

					// 停止监控
					this.stopVideoMonitor(index);
					this.stopProgressTimer(index);

					// 如果不是当前显示的视频,完全停止
					if (index !== this.currentIndex) {
						this.stopVideo(index, false);
					}
				}
			},

			// 视频结束事件
			onVideoEnded(index) {
				console.log('视频播放结束,索引:', index);

				// 完全停止当前视频
				this.stopVideo(index, false);

				// 自动播放下一个
				if (index < this.videoList.length - 1) {
					console.log('自动播放下一个视频,新索引:', index + 1);
					setTimeout(() => {
						this.currentIndex = index + 1;
						this.playVideo(index + 1);
					}, 500);
				} else {
					// 如果是最后一个视频,显示结束提示 
					uni.showToast({
						title: '没有更多视频了!',
						icon: 'none'
					});
				}
			},

			// 视频错误事件
			onVideoError(index, event) {
				console.error('视频加载失败,索引:', index, '错误信息:', event);
				const video = this.videoList[index];
				if (video) {
					video.loading = false;
					video.isPlaying = false;
					video.bufferState = 'normal';

					// 增加重试逻辑
					if (!this.videoRetryCount[index] || this.videoRetryCount[index] < 2) {
						this.retryPlayVideo(index);
					} else {
						video.loadFailed = true;
						this.currentPlayingIndex = -1;

						// 延迟显示错误提示,避免频繁提示
						setTimeout(() => {
							if (video.loadFailed) {
								uni.showToast({
									title: '视频加载失败',
									icon: 'none'
								});
							}
						}, 500);
					}
				}
			},

			// 切换静音
			toggleMute() {
				this.isMuted = !this.isMuted;
				console.log('切换静音状态:', this.isMuted);

				// 更新所有视频的静音状态
				Object.values(this.videoContexts).forEach(context => {
					if (context) {
						context.muted = this.isMuted;
					}
				});
			},

			// 点赞视频

			async likeVideo(video, index) {
				try {
					// 1. 先通过索引获取 videoList 中的原对象(关键:操作响应式数组中的原对象)
					const targetVideo = this.videoList[index];
					if (!targetVideo) return;

					// 前端立即响应
					console.log("点赞对象", targetVideo);
					const wasLiked = targetVideo._liked;
					const oldLikeCount = targetVideo.likeCount || 0;

					// 2. 使用 $set 确保响应式更新(解决页面不刷新核心问题)
					this.$set(targetVideo, '_liked', !wasLiked);
					const newLikeCount = wasLiked ? oldLikeCount - 1 : oldLikeCount + 1;
					this.$set(targetVideo, 'likeCount', newLikeCount);

					// 显示点赞动画
					if (!wasLiked) {
						this.$set(targetVideo, 'showLikeAnimation', true);
						setTimeout(() => {
							this.$set(targetVideo, 'showLikeAnimation', false);
						}, 800);
					}

					// 调用API
					const query = {
						increment: !wasLiked
					};

					const response = await likeArticle(query, targetVideo.videoId);

					// 3. 接口返回后,用后端数据更新(同样用 $set 保证响应式)
					if (response && response.likeCount !== undefined) { // 改为更通用的判断
						// 匹配 videoList 中对应 videoId 的视频并更新(兼容列表顺序变化)
						const videoIndex = this.videoList.findIndex(v => v.videoId === targetVideo.videoId);
						if (videoIndex !== -1) {
							this.$set(this.videoList[videoIndex], 'likeCount', response.likeCount);
							// 重新计算点赞状态(避免后端数据和前端临时值不一致)
							const finalLiked = wasLiked ?
								response.likeCount < oldLikeCount :
								response.likeCount > oldLikeCount;
							this.$set(this.videoList[videoIndex], '_liked', finalLiked);
							console.log("点赞成功,更新后数据:", this.videoList[videoIndex]);
							this.$forceUpdate();
						}
					}

				} catch (error) {
					console.error('点赞失败', error);
					// 4. 接口失败时回滚前端状态(提升用户体验)
					const targetVideo = this.videoList[index];
					if (targetVideo) {
						this.$set(targetVideo, '_liked', !targetVideo._liked);
						const oldLikeCount = targetVideo.likeCount || 0;
						this.$set(targetVideo, 'likeCount', targetVideo._liked ? oldLikeCount + 1 : oldLikeCount - 1);
					}
					uni.showToast({
						title: '点赞失败,请重试',
						icon: 'none'
					});
				}
			},




			// 收藏视频
			async collectVideo(video, index) {
				try {
					const isLogin = uni.getStorageSync('isLogin');
					if (!isLogin && getToken() == "") {
						this.loginOrNot = true
						console.log("点击视频");
						return
					}
					// 1. 通过索引获取 videoList 中的原对象(确保操作响应式数据)
					const targetVideo = this.videoList[index];
					if (!targetVideo) return;

					// 前端立即响应
					console.log("收藏对象", targetVideo);
					const wasCollected = targetVideo._collected;
					const oldCollectCount = targetVideo.collectCount || 0;

					// 2. 使用 $set 确保响应式更新(核心修复:解决页面不刷新)
					this.$set(targetVideo, '_collected', !wasCollected);
					const newCollectCount = wasCollected ? oldCollectCount - 1 : oldCollectCount + 1;
					this.$set(targetVideo, 'collectCount', newCollectCount);

					// 调用API
					const query = {
						increment: !wasCollected
					};

					const response = await collectArticle(query, targetVideo.videoId);

					// 3. 接口返回后,按后端结果修正状态(同样用 $set 保证响应式)
					if (response && response.whetherToCollect !== undefined) {
						// 匹配 videoList 中对应 videoId 的视频(兼容列表顺序变化)
						const videoIndex = this.videoList.findIndex(v => v.videoId === targetVideo.videoId);
						if (videoIndex !== -1) {
							if (!response.whetherToCollect) {
								// 操作失败,回滚状态(响应式更新)
								this.$set(this.videoList[videoIndex], '_collected', wasCollected);
								this.$set(this.videoList[videoIndex], 'collectCount', oldCollectCount);
								console.log("收藏操作失败,已回滚状态", this.videoList[videoIndex]);
							} else {
								// 操作成功,同步后端返回的收藏数(如果有)
								if (response.collectCount !== undefined) {
									this.$set(this.videoList[videoIndex], 'collectCount', response.collectCount);
								}
								this.$forceUpdate();
							}
						}
					}

				} catch (error) {
					console.error('收藏失败', error);
					// 4. 接口调用异常时,回滚前端状态(提升用户体验)
					const targetVideo = this.videoList[index];
					if (targetVideo) {
						const wasCollected = targetVideo._collected;
						const oldCollectCount = targetVideo.collectCount || 0;
						// 回滚响应式数据
						this.$set(targetVideo, '_collected', !wasCollected);
						this.$set(targetVideo, 'collectCount', wasCollected ? oldCollectCount + 1 : oldCollectCount -
							1);
					}
					// 提示用户
					uni.showToast({
						title: '收藏失败,请重试',
						icon: 'none'
					});
				}
			},

			// 评论视频
			commentVideo(video) {
				this.currentVideo = video;
				this.showComment = true;
				// 这里可以加载评论列表
			},

			// 关闭评论
			closeComment() {
				this.showComment = false;
				this.commentInput = '';
			},

			// 发送评论
			sendComment() {
				if (!this.commentInput.trim()) return;

				// 这里调用发送评论的API
				console.log('发送评论:', this.commentInput);

				// 模拟添加评论
				this.comments.unshift({
					id: Date.now(),
					nickname: '当前用户',
					avatar: 'https://randomuser.me/api/portraits/men/1.jpg',
					content: this.commentInput,
					createdAt: new Date().toISOString(),
					likeCount: 0,
					liked: false
				});

				// 更新评论数
				this.currentVideo.commentCount = (this.currentVideo.commentCount || 0) + 1;

				// 清空输入框
				this.commentInput = '';

				// 隐藏键盘
				uni.hideKeyboard();
			},

			// 分享视频
			shareVideo(video) {
				uni.showShareMenu({
					withShareTicket: true,
					success: () => {
						console.log('分享成功');
						// 更新分享数
						video.shareCount = (video.shareCount || 0) + 1;
					}
				});
			},

			// 关注用户
			followUser(userId) {
				uni.showToast({
					title: '关注成功',
					icon: 'success'
				});
			},

			// 跳转到用户主页
			goUserProfile(userId) {
				uni.navigateTo({
					url: `/pages/user/profile?userId=${userId}`
				});
			},

			// 返回上一页
			goBack() {
				uni.navigateBack();
			},

			// 记录视频观看
			recordVideoView(videoId) {
				// 这里可以调用API记录观看次数
				console.log('记录观看视频:', videoId);
			},

			// 获取视频封面
			getVideoPoster(videoUrl) {
				if (!videoUrl) return '/static/images/video-poster.jpg';

				// 如果是OSS视频,获取第一帧
				if (videoUrl.includes('oss-cn-')) {
					const separator = videoUrl.includes('?') ? '&' : '?';
					return `${videoUrl}${separator}x-oss-process=video/snapshot,t_0,m_fast`;
				}

				return '/static/images/video-poster.jpg';
			},

			// 解析标签
			parseTags(tagsString) {
				if (!tagsString) return [];
				return tagsString.split(',').filter(tag => tag.trim());
			},

			// 按标签搜索
			searchByTag(tag) {
				uni.showToast({
					title: `搜索标签: ${tag}`,
					icon: 'none'
				});
			},

			// 格式化相对时间
			formatRelativeTime(timeStr) {
				if (!timeStr) return '';

				const createTime = new Date(timeStr.replace(/-/g, '/'));
				const now = new Date();
				const diffInSeconds = Math.floor((now - createTime) / 1000);

				if (diffInSeconds < 60) {
					return '刚刚';
				}

				const diffInMinutes = Math.floor(diffInSeconds / 60);
				if (diffInMinutes < 60) {
					return `${diffInMinutes}分钟前`;
				}

				const diffInHours = Math.floor(diffInMinutes / 60);
				if (diffInHours < 24) {
					return `${diffInHours}小时前`;
				}

				const diffInDays = Math.floor(diffInHours / 24);
				if (diffInDays < 7) {
					return `${diffInDays}天前`;
				}

				const diffInWeeks = Math.floor(diffInDays / 7);
				if (diffInWeeks < 4) {
					return `${diffInWeeks}周前`;
				}

				const diffInMonths = Math.floor(diffInDays / 30);
				if (diffInMonths < 12) {
					return `${diffInMonths}个月前`;
				}

				const diffInYears = Math.floor(diffInDays / 365);
				return `${diffInYears}年前`;
			},

			// 显示音乐信息
			// showMusicInfo(video) {
			// 	uni.showToast({
			// 		title: '音乐: 视频原声',
			// 		icon: 'none'
			// 	});
			// },

			// 点赞评论
			likeComment(comment) {
				comment.liked = !comment.liked;
				comment.likeCount = comment.liked ? (comment.likeCount || 0) + 1 : Math.max(0, (comment.likeCount || 0) -
					1);
			},

			// 回复评论
			replyComment(comment) {
				this.commentInput = `回复 @${comment.nickname}: `;
				uni.pageScrollTo({
					scrollTop: 1000,
					duration: 300
				});
			},
			async handleGetPhoneNumber(e) {
				let that = this
				if (e.detail.code == undefined) {
					that.visible = 'unlogin';
					return
				}
				uni.showLoading({
					icon: 'loading',
					title: "正在登陆",
					mask: true,
					duration: 10000
				});

				that.visible = 'unlogin';
				uni.login({
					async success(res) {
						try {

							let body = {
								"phoneCode": e.detail.code,
								"loginCode": res.code,
								"role": 5,
								"parentId": 0
							}
							let login = await authweixin(body);
							console.log("== 登陆信息 ==", login);
							uni.hideLoading();

							setToken(login.accessToken)
							setUserId(login.userId)

							that.loginOrNot = false
							const systemInfo = uni.getSystemInfoSync();
							that.screenHeight = systemInfo.screenHeight;

							that.$forceUpdate();

						} catch (e) {
							console.log(e)
						} finally {
							uni.hideLoading();
						}
					}
				})
			},
			gb() {
				this.loginOrNot = false
			},
		}
	}
</script>



<style lang="scss" scoped>
	/* 新增样式 */
	.load-failed-tips {
		position: absolute;
		bottom: 150rpx;
		left: 0;
		right: 0;
		text-align: center;
		z-index: 5;
	}

	.failed-text {
		background: rgba(0, 0, 0, 0.7);
		color: #fff;
		font-size: 28rpx;
		padding: 15rpx 30rpx;
		border-radius: 30rpx;
	}

	.loading-text {
		color: #fff;
		font-size: 24rpx;
		margin-top: 15rpx;
	}

	/* 其他样式保持不变 */
	.video-container {
		width: 100%;
		background-color: #000;
		position: relative;
		overflow: hidden;
	}

	.video-swiper {
		width: 100%;
	}

	.video-item {
		width: 100%;
		height: 100%;
	}

	.video-wrapper {
		position: relative;
		width: 100%;
		height: 100%;
		background-color: #000;
	}

	.video-player {
		width: 100%;
		height: 100%;
	}

	.video-poster {
		position: absolute;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
		z-index: 1;
	}

	.poster-image {
		width: 100%;
		height: 100%;
	}

	.play-icon-wrapper {
		position: absolute;
		top: 50%;
		left: 50%;
		transform: translate(-50%, -50%);
		width: 120rpx;
		height: 120rpx;
		background: rgba(0, 0, 0, 0.5);
		border-radius: 50%;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.loading-container {
		position: absolute;
		top: 50%;
		left: 50%;
		transform: translate(-50%, -50%);
		z-index: 2;
		display: flex;
		flex-direction: column;
		align-items: center;
	}

	.loading-spinner {
		width: 60rpx;
		height: 60rpx;
		border: 6rpx solid rgba(255, 255, 255, 0.3);
		border-top-color: #fff;
		border-radius: 50%;
		animation: spin 1s linear infinite;
	}

	@keyframes spin {
		to {
			transform: rotate(360deg);
		}
	}

	.boundary-tips {
		position: absolute;
		top: 50%;
		left: 50%;
		transform: translate(-50%, -50%);
		z-index: 20;
		background: rgba(0, 0, 0, 0.7);
		padding: 20rpx 40rpx;
		border-radius: 40rpx;
		animation: fadeInOut 2s ease;
	}

	@keyframes fadeInOut {
		0% {
			opacity: 0;
			transform: translate(-50%, -50%) scale(0.8);
		}

		20% {
			opacity: 1;
			transform: translate(-50%, -50%) scale(1);
		}

		80% {
			opacity: 1;
			transform: translate(-50%, -50%) scale(1);
		}

		100% {
			opacity: 0;
			transform: translate(-50%, -50%) scale(0.8);
		}
	}

	.tip-text {
		color: #fff;
		font-size: 28rpx;
	}

	.pause-overlay {
		position: absolute;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
		background: rgba(0, 0, 0, 0.3);
		z-index: 3;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.pause-icon-wrapper {
		width: 140rpx;
		height: 140rpx;
		background: rgba(0, 0, 0, 0.6);
		border-radius: 50%;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.mute-btn {
		position: absolute;
		top: 60rpx;
		right: 30rpx;
		width: 60rpx;
		height: 60rpx;
		background: rgba(0, 0, 0, 0.4);
		border-radius: 50%;
		display: flex;
		align-items: center;
		justify-content: center;
		z-index: 10;
	}

	.video-info {
		position: absolute;
		bottom: 120rpx;
		left: 20rpx;
		right: 140rpx;
		z-index: 5;
		color: #fff;
	}

	.user-info {
		display: flex;
		align-items: center;
		margin-bottom: 20rpx;
	}

	.user-avatar {
		width: 80rpx;
		height: 80rpx;
		border-radius: 50%;
		border: 2rpx solid #fff;
		margin-right: 20rpx;
	}

	.user-name {
		font-size: 32rpx;
		font-weight: 600;
		flex: 1;
	}

	.follow-btn {
		background: transparent;
		border: 2rpx solid #fff;
		color: #fff;
		font-size: 24rpx;
		padding: 8rpx 24rpx;
		border-radius: 20rpx;
		line-height: 1;
	}

	.follow-btn::after {
		border: none;
	}

	.video-description {
		font-size: 28rpx;
		line-height: 1.5;
		margin-bottom: 15rpx;
		text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
	}

	.tags-container {
		display: flex;
		flex-wrap: wrap;
		gap: 10rpx;
		margin-bottom: 15rpx;
	}

	.tag-item {
		background: rgba(0, 0, 0, 0.4);
		color: #fff;
		font-size: 24rpx;
		padding: 6rpx 16rpx;
		border-radius: 20rpx;
	}

	.location-info {
		display: flex;
		align-items: center;
		font-size: 24rpx;
		color: rgba(255, 255, 255, 0.8);
		margin-bottom: 10rpx;
		gap: 8rpx;
	}

	.publish-time {
		display: flex;
		align-items: center;
		gap: 8rpx;
		font-size: 22rpx;
		color: rgba(255, 255, 255, 0.6);
	}

	.interaction-sidebar {
		position: absolute;
		right: 20rpx;
		bottom: 120rpx;
		display: flex;
		flex-direction: column;
		align-items: center;
		z-index: 5;
		gap: 30rpx;
	}

	.sidebar-avatar {
		position: relative;
		width: 100rpx;
		height: 100rpx;
		margin-bottom: 10rpx;
	}

	.avatar-image {
		width: 100rpx;
		height: 100rpx;
		border-radius: 50%;
		border: 4rpx solid #fff;
	}

	.follow-animation {
		position: absolute;
		bottom: -4rpx;
		right: -4rpx;
		width: 28rpx;
		height: 28rpx;
		background: #fff;
		border-radius: 50%;
		display: flex;
		align-items: center;
		justify-content: center;
		animation: pulse 2s infinite;
	}

	@keyframes pulse {
		0% {
			transform: scale(1);
			opacity: 1;
		}

		50% {
			transform: scale(1.1);
			opacity: 0.7;
		}

		100% {
			transform: scale(1);
			opacity: 1;
		}
	}

	.interaction-btn {
		position: relative;
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 10rpx;
	}

	.btn-icon {
		transition: transform 0.3s;
	}

	.btn-count {
		font-size: 24rpx;
		color: #fff;
		text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.5);
	}

	.like-animation {
		position: absolute;
		top: -30rpx;
		left: 50%;
		transform: translateX(-50%);
		animation: floatUp 0.8s ease-out forwards;
		z-index: 10;
	}

	@keyframes floatUp {
		0% {
			transform: translateX(-50%) scale(0.5);
			opacity: 1;
		}

		100% {
			transform: translateX(-50%) translateY(-100rpx) scale(1.2);
			opacity: 0;
		}
	}

	.music-container {
		position: relative;
		display: flex;
		align-items: center;
		justify-content: center;
		width: 80rpx;
		height: 80rpx;
	}

	.music-dot {
		position: absolute;
		top: -4rpx;
		right: -4rpx;
		width: 16rpx;
		height: 16rpx;
		background: #ff2442;
		border-radius: 50%;
		animation: musicBeat 1s infinite alternate;
	}

	@keyframes musicBeat {
		from {
			transform: scale(0.8);
		}

		to {
			transform: scale(1.2);
		}
	}

	.load-more-indicator {
		margin-top: 30rpx;
		display: flex;
		flex-direction: column;
		align-items: center;
	}

	.loading-dots {
		display: flex;
		gap: 10rpx;
		margin-bottom: 10rpx;
	}

	.end-of-list {
		margin-top: 30rpx;
		text-align: center;
	}

	.progress-container {
		position: absolute;
		bottom: 0;
		left: 0;
		right: 0;
		height: 4rpx;
		background: rgba(255, 255, 255, 0.2);
		z-index: 10;
	}

	.progress-bar {
		width: 100%;
		height: 100%;
		background: transparent;
	}

	.progress-current {
		height: 100%;
		background: #fff;
		transition: width 0.1s linear;
	}

	.back-btn {
		position: absolute;
		top: 60rpx;
		left: 30rpx;
		width: 60rpx;
		height: 60rpx;
		background: rgba(0, 0, 0, 0.4);
		border-radius: 50%;
		display: flex;
		align-items: center;
		justify-content: center;
		z-index: 10;
	}

	/* 评论弹窗样式 */
	.comment-popup {
		background: #fff;
		border-radius: 40rpx 40rpx 0 0;
		padding-bottom: env(safe-area-inset-bottom);
	}

	.comment-header {
		display: flex;
		align-items: center;
		justify-content: space-between;
		padding: 30rpx;
		border-bottom: 2rpx solid #f0f0f0;
	}

	.comment-title {
		font-size: 32rpx;
		font-weight: 600;
		color: #333;
	}

	.close-btn {
		width: 60rpx;
		height: 60rpx;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.comment-list {
		max-height: 800rpx;
		padding: 20rpx 30rpx;
	}

	.comment-item {
		display: flex;
		padding: 20rpx 0;
		border-bottom: 1rpx solid #f5f5f5;
	}

	.comment-avatar {
		width: 80rpx;
		height: 80rpx;
		border-radius: 50%;
		margin-right: 20rpx;
		flex-shrink: 0;
	}

	.comment-content {
		flex: 1;
		display: flex;
		flex-direction: column;
	}

	.comment-author {
		font-size: 28rpx;
		font-weight: 600;
		color: #333;
		margin-bottom: 8rpx;
	}

	.comment-text {
		font-size: 28rpx;
		color: #333;
		line-height: 1.5;
		margin-bottom: 12rpx;
	}

	.comment-meta {
		display: flex;
		justify-content: space-between;
		font-size: 24rpx;
		color: #999;
	}

	.comment-actions {
		display: flex;
		align-items: center;
		gap: 30rpx;
	}

	.comment-like {
		display: flex;
		align-items: center;
		gap: 8rpx;
	}

	.reply-btn {
		color: #666;
	}

	.comment-input-area {
		display: flex;
		align-items: center;
		padding: 20rpx 30rpx;
		border-top: 2rpx solid #f0f0f0;
		background: #fff;
	}

	.comment-input {
		flex: 1;
		height: 80rpx;
		padding: 0 30rpx;
		background: #f5f5f5;
		border-radius: 40rpx;
		font-size: 28rpx;
	}

	.send-btn {
		margin-left: 20rpx;
		background: #9D7A53;
		color: #fff;
		font-size: 28rpx;
		padding: 0 40rpx;
		height: 80rpx;
		line-height: 80rpx;
		border-radius: 40rpx;
		display: flex;
		align-items: center;
		gap: 8rpx;
	}

	.send-btn::after {
		border: none;
	}
</style>

总结

直接复制就可用。接口改成自己的

{

"code": 0,

"data": {

"shang": {

"videoId": 24,

"videoTitle": "快来看吧",

"videoContent": "人生很短,但是我的故事缺很长",

"videoUrl": "https://linda-video-doc.oss-cn-guangzhou.aliyuncs.com/2GDjHz4588b8dc154f23253ccecb39da2cc120251213110830.mp4",

"location": null,

"userId": 7,

"nickname": "Hannah",

"avatar": "https://randomuser.me/api/portraits/women/17.jpg",

"viewCount": 0,

"likeCount": 0,

"collectCount": 0,

"commentCount": 0,

"shareCount": 0,

"tags": "视频,家具",

"createdAt": "2025-12-13 11:09:08"

},

"cu": {

"videoId": 25,

"videoTitle": "看看这场景非常的奈斯",

"videoContent": "快咯,如果是你的房子你几点回家",

"videoUrl": "https://linda-video-doc.oss-cn-guangzhou.aliyuncs.com/MUvsqOHe64c757a58d7472cc4fe2dbd9ab2c520251213110918.mp4",

"location": null,

"userId": 7,

"nickname": "Hannah",

"avatar": "https://randomuser.me/api/portraits/women/17.jpg",

"viewCount": 0,

"likeCount": 3,

"collectCount": 1,

"commentCount": 0,

"shareCount": 0,

"tags": "视频,家具,沙发,场景",

"createdAt": "2025-12-13 11:10:24"

},

"xia": {

"videoId": 26,

"videoTitle": "开来咯",

"videoContent": "时间大厦及回答哦i哦让你",

"videoUrl": "https://linda-video-doc.oss-cn-guangzhou.aliyuncs.com/AsNL8f0b2d646234d71a4def747fffee69cf520251213111052.mp4",

"location": null,

"userId": 7,

"nickname": "Hannah",

"avatar": "https://randor.me/api/portraits/women/17.jpg",

"viewCount": 0,

"likeCount": 21,

"collectCount": 1,

"commentCount": 0,

"shareCount": 0,

"tags": "视频,家具",

"createdAt": "2025-12-13 11:11:20"

}

},

"msg": ""

}

相关推荐
2501_916008897 小时前
IOScer 证书到底是什么和怎么使用的完整说明
android·ios·小程序·https·uni-app·iphone·webview
鸠摩智首席音效师7 小时前
Apache Prefork 和 Worker 有什么区别 ?
apache
00后程序员张7 小时前
iOS 抓包工具实战指南,从代理到数据流,全流程工具分工解析
android·ios·小程序·https·uni-app·iphone·webview
说私域7 小时前
新零售第一阶段传统零售商的困境突破与二次增长路径——基于定制开发AI智能名片S2B2C商城小程序的实践研究
人工智能·小程序·零售
音视频牛哥7 小时前
C#实战:如何开发设计毫秒级延迟、工业级稳定的Windows平台RTSP/RTMP播放器
人工智能·机器学习·机器人·c#·音视频·rtsp播放器·rtmp播放器
Blossom.1188 小时前
基于时序大模型+强化学习的虚拟电厂储能调度系统:从负荷预测到收益最大化的实战闭环
运维·人工智能·python·决策树·机器学习·自动化·音视频
JS-s18 小时前
【无标题】
音视频
山海青风20 小时前
语音合成 - 用 Python 合成藏语三大方言语音
开发语言·python·音视频
coding-fun1 天前
电脑音频录制工具(语音聊天录音软件)
音视频