From 818fbcd2dc3073ab4dacdff2b19cea56552f32fc Mon Sep 17 00:00:00 2001 From: peng <peng.com> Date: 星期一, 23 六月 2025 15:33:25 +0800 Subject: [PATCH] Merge remote-tracking branch 'origin/dev' into dev --- api/video.js | 4 pages/tabbar/index/home.vue | 315 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 217 insertions(+), 102 deletions(-) diff --git a/api/video.js b/api/video.js index df1681f..d3dddd1 100644 --- a/api/video.js +++ b/api/video.js @@ -26,7 +26,7 @@ * * @param params */ - export function getRecommendVideos(params) { + export async function getRecommendVideos(params) { return http.request({ url: "/lmk/video/recommend", method: Method.GET, @@ -235,4 +235,4 @@ method: Method.POST, needToken: true }); -} \ No newline at end of file +} diff --git a/pages/tabbar/index/home.vue b/pages/tabbar/index/home.vue index ade53fe..1c56703 100644 --- a/pages/tabbar/index/home.vue +++ b/pages/tabbar/index/home.vue @@ -1,20 +1,26 @@ <template> <view class="video-container"> <!-- 瑙嗛鍒楄〃 --> - <swiper - class="video-swiper" - vertical - circular + <swiper + class="video-swiper" + vertical :current="currentIndex" @change="onSwiperChange" + easing-function="linear" > - <swiper-item v-for="(item, index) in videoList" :key="item.id"> + <swiper-item + v-for="(item, index) in videoList" + :key="item.id" + @touchstart="handleSwiperStart" + @touchmove="handleSwiperMove" + @touchend="handleSwiperEnd(item)" + > <view style="width: 100%;height: 100%;" v-if="item.videoContentType === 'video'"> <!-- 鎾斁鎸夐挳锛堜粎褰撹棰戞殏鍋滄椂鏄剧ず锛� --> - <view - class="play-icon" + <view + class="play-icon" @click="togglePlay(index)" - v-if="!currentVideoIsPlaying" + v-show="!currentVideoIsPlaying" > <image src="/static/video/play.png" style="width: 45px;height: 45px" mode="aspectFit"></image> </view> @@ -22,7 +28,7 @@ :id="'video'+index" :ref="'video'+index" :src="item.videoUrl" - :autoplay="currentIndex === index" + :autoplay="false" :controls="false" :loop="true" :object-fit="item.objectFit" @@ -34,10 +40,9 @@ @click="togglePlay(index)" @timeupdate="onTimeUpdate($event)" @loadedmetadata="onLoadedMetadata($event)" - ></video> <!-- 鑷畾涔夋帶鍒舵潯 --> - <view + <view @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd" @@ -46,11 +51,11 @@ <view class="process-warp" :style="{ opacity: showProcess ? 1 : 0 }"> <!-- 鏄剧ず褰撳墠杩涘害 --> <view class="progress-text">{{ hasPlayTime }}/{{formartDuration}}</view> - <view - class="progress-bar" + <view + class="progress-bar" id="progressBar" > - + <!-- 宸插~鍏呴儴鍒� --> <view class="progress-fill" :style="{ width: progress + '%' }"></view> </view> @@ -58,10 +63,10 @@ </view> </view> <view style="width: 100%; height: 100%;" v-else-if="item.videoContentType === 'img'"> - <uni-swiper-dot - :info="item.imgs" - :current="currentImgIndex" - mode="round" + <uni-swiper-dot + :info="item.imgs" + :current="currentImgIndex" + mode="round" style="width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;" :dots-styles="{width: 24, bottom: 24,selectedBackgroundColor: 'green', backgroundColor: 'gray'}" > @@ -69,9 +74,9 @@ <swiper-item v-for="img in item.imgs" :key="img"> <view class="swiper-item"> <!-- 璋冩暣 image 鏍峰紡锛屼娇鍏跺眳涓笖鎸夋瘮渚嬬缉鏀� --> - <image - :src="img" - mode="aspectFit" + <image + :src="img" + mode="aspectFit" style="width: 100%; height: 100%; display: block; margin: 0 auto;" ></image> </view> @@ -79,8 +84,8 @@ </swiper> </uni-swiper-dot> </view> - - + + <!-- 鎮寕鍟嗗搧閾炬帴灞� --> <view class="goods-link-warp" v-if="item.goodsList.length > 0"> <view class="goods-link"> @@ -89,7 +94,7 @@ <view class="goods-container" @click="jumpToPay(item.id)"> <!-- 鍟嗗搧鍥剧墖 --> <image class="goods-image" :src="goods.thumbnail" mode="aspectFill"></image> - + <!-- 鍟嗗搧淇℃伅 --> <view class="goods-info"> <text class="goods-name">{{goods.goodsName}}</text> @@ -103,8 +108,8 @@ </swiper> </view> </view> - - + + <!-- 瑙嗛淇℃伅灞� --> <view class="video-info"> <view> @@ -115,7 +120,7 @@ <text class="video-tag" v-for="(tag, index) in item.tagList" :key="tag.id">#{{tag.tagName}}</text> </view> </view> - + <!-- 鍙充晶浜掑姩鎸夐挳 --> <view class="action-buttons"> <view class="avatar-container"> @@ -138,13 +143,13 @@ <button open-type="share" class="custom-share-btn" :data-obj="item"> <text class="iconfont"></text> </button> - + </view> </view> - + </swiper-item> </swiper> - + <!-- 璇勮寮圭獥 --> <uni-popup ref="commentPopup" type="bottom" :is-mask-click="true" @maskClick="closeCommentPopup"> <view class="comment-popup"> @@ -156,23 +161,23 @@ </view> <text class="iconfont close-icon" @click="closeCommentPopup"></text> </view> - + <scroll-view class="comment-list" scroll-y :show-scrollbar="false" @scrolltolower="getCommentPage"> <view v-if="commentLoading" class="loading"> <uni-load-more status="loading"></uni-load-more> </view> - + <view v-else-if="comments.length === 0" class="empty"> 鏆傛棤璇勮锛屽揩鏉ュ彂琛ㄧ涓�鏉¤瘎璁哄惂~ </view> - + <view v-else class="comment-item" v-for="(comment, index) in comments" :key="comment.id"> <view style="display: flex;"> <image class="comment-avatar" :src="comment.userAvatar || '/static/default-avatar.png'"></image> <view class="comment-content"> <text class="nickname">{{comment.userNickname}}</text> <text class="content">{{comment.commentContent}}</text> - <view style="position: relative;"> + <view style="position: relative;"> <text class="time">{{formatTime(comment.createTime)}}</text> <text @click="openReply(comment)" class="reply-btu time">鍥炲</text> <text v-if="!comment.hasThumbsUp" class="thumbs-up time iconfont" @click="thubmsUp(comment.id, index, null)"><text v-show="comment.thumbsUpNum > 0" class="thumbs-num">{{comment.thumbsUpNum}}</text></text> @@ -212,19 +217,19 @@ </view> </scroll-view> <view class="comment-input-area"> - <input + <input ref="commentInput" - class="comment-input" - v-model="commentForm.commentContent" - :placeholder="commentForm.replyId ? `鍥炲 @${commentForm.replyUserNickname}` : '鍐欎笅浣犵殑璇勮...'" + class="comment-input" + v-model="commentForm.commentContent" + :placeholder="commentForm.replyId ? `鍥炲 @${commentForm.replyUserNickname}` : '鍐欎笅浣犵殑璇勮...'" placeholder-class="placeholder" /> <button class="submit-btn" @click="submitComment" :disabled="!commentForm.commentContent.trim()">鍙戦��</button> </view> </view> </uni-popup> - - + + <custom-tabbar bgColor="#333333" selected="index" selectedTextColor="#ffffff"></custom-tabbar> </view> </template> @@ -298,14 +303,20 @@ isFullScreen: false, windowHeight: 0, currentIndex: 0, // 褰撳墠鎾斁鐨勮棰戠储寮� - videoList: [ - - ], // 瑙嗛鍒楄〃鏁版嵁 + videoList: [], // 瑙嗛鍒楄〃鏁版嵁 videoContexts: [], // 瑙嗛涓婁笅鏂囧璞¢泦鍚� + videoBufferOffset: 0.1 ,// 瑙嗛棰勫姞杞藉弬鏁� + videoLiveOffset: 5, // 淇濈暀褰撳墠瑙嗛鍓嶅悗鍚勫灏戜釜瑙嗛涓婁笅鏂� + touchXY: { // 鐩戝惉宸︽粦鍙虫粦 + startX: 0, + endX: 0, + startY: 0, + endY: 0 + }, loading: false, // 鏄惁姝e湪鍔犺浇 videoQuery: { pageNumber: 1, - pageSize: 6, + pageSize: 10, videoFrom: 'recommend' } } @@ -316,7 +327,7 @@ // this.wxSilentLogin(() => { // this.loadVideos(); // }) - // } else { + // } else { // this.loadVideos(); // } // 濡傛灉瑙嗛鎸変笅鏆傚仠鍚庡垏鎹㈤〉闈㈠啀鍥炲埌椤甸潰鏃讹紝鍙畻鏆傚仠鏃堕棿锛堝洜涓烘殏鍋滄椂闂村拰绂诲紑椤甸潰鏃堕棿鏄噸澶嶇殑锛屽彧绠椾竴涓級 @@ -339,13 +350,9 @@ saveShareClickRecord({refId: option.videoId, shareUserId: option.userId}) } }) - } else { + } else { this.loadVideos(); } - }, - onReady() { - // 鍒濆鍖栬棰戜笂涓嬫枃 - this.initVideoContexts(); }, onShareAppMessage(e) { const userInfo = storage.getUserInfo(); @@ -525,11 +532,11 @@ const date = new Date(time); const now = new Date(); const diff = Math.floor((now - date) / 1000); // 绉� - + if (diff < 60) return '鍒氬垰'; if (diff < 3600) return `${Math.floor(diff / 60)}鍒嗛挓鍓峘; if (diff < 86400) return `${Math.floor(diff / 3600)}灏忔椂鍓峘; - + return `${date.getMonth() + 1}鏈�${date.getDate()}鏃; }, // 鎻愪氦璇勮 @@ -545,7 +552,7 @@ addVideoComment(this.commentForm).then(res => { if(res.data.code === 200) { this.resetCommentForm() - + // 濡傛灉鏄瘎璁哄埆浜虹殑鍥炲锛岄偅涔堝氨灏嗚繖涓彂甯冨埌replies閲岄潰 if(res.data.data.replyId) { for (const [index, item] of this.comments.entries()) { @@ -657,17 +664,60 @@ }, // 鍒濆鍖栬棰戜笂涓嬫枃 initVideoContexts() { - this.videoContexts = this.videoList.map((_, index) => { - let videoContent = uni.createVideoContext(`video${index}`, this); - return videoContent; - }); + const start = Math.max(0, this.currentIndex - this.videoLiveOffset); + const end = Math.min(this.currentIndex + this.videoLiveOffset, this.videoList.length - 1); + let contextsLength = this.videoContexts.length; + if (contextsLength === 0) { + // 绗竴娆″垵濮嬪寲 + for (let i = 0; i < this.videoList.length; i++) { + if (i < start || i > end) { + this.videoContexts.push(null) + } else { + let videoContent = uni.createVideoContext(`video${i}`, this); + videoContent.seek(this.videoBufferOffset); + videoContent.pause(); + this.videoContexts.push(videoContent); + } + } + } else { + for (let i = 0; i < this.videoList.length; i++) { + contextsLength = this.videoContexts.length + if (contextsLength - 1 >= i) { + // 濡傛灉宸茬粡鏄痭ull浜嗗氨涓嶇敤绠★紝鍥犱负瑙嗛鍔犺浇鍙細鍦ㄥ悗闈ush锛屽墠闈㈠凡缁忚缃负null鍒欐棤闇�澶勭悊 + if (this.videoContexts[i] == null) { + continue + } + // 瓒呭嚭鍙鍖栬寖鍥寸殑瑙嗛鐩存帴閲婃斁璧勬簮锛屽苟缃负null + if (i < start || i > end) { + if (this.videoContexts[i]) { + this.videoContexts[i].stop(); + this.videoContexts[i] = null + } + } + } else { + if (i < start || i > end) { + this.videoContexts.push(null); + } else { + let videoContent = uni.createVideoContext(`video${i}`, this); + videoContent.seek(this.videoBufferOffset); + videoContent.pause(); + this.videoContexts.push(videoContent); + } + } + } + } + // 灏嗗綋鍓嶈棰戣缃负鎾斁 + if (this.videoContexts[this.currentIndex]) { + this.videoContexts[this.currentIndex].play() + } + }, - + // 鍔犺浇瑙嗛鏁版嵁 async loadVideos() { if (this.loading || this.videoNoMore) return; this.loading = true; - + getRecommendVideos(this.videoQuery).then(res => { console.log(res, "瑙嗛鏁版嵁"); if (this.videoQuery.pageNumber === 1) { @@ -689,10 +739,10 @@ return; } this.videoQuery.pageNumber++; - + }) }, - + // 婊戝姩鍒囨崲瑙嗛 onSwiperChange(e) { // 濡傛灉瑙嗛澶勪簬鏆傚仠鐘舵�佸線涓嬪埛瑙嗛锛岄偅涔堥渶瑕佸啀璁$畻涓�娆℃殏鍋滄椂闂� @@ -711,17 +761,82 @@ if (this.videoContexts[oldIndex]) { this.videoContexts[oldIndex].pause(); } - + this.startPauseTime = 0; + + // 璁剧疆褰撳墠鎾斁瑙嗛鐨勬�绘椂闀� + this.duration = this.videoList[this.currentIndex].videoDuration; + this.formartDuration = this.sliderFormatTime(this.duration); // 鎾斁褰撳墠瑙嗛 if (this.videoContexts[this.currentIndex]) { this.videoContexts[this.currentIndex].play(); } - // 璁剧疆褰撳墠鎾斁瑙嗛鐨勬�绘椂闀� - this.duration = this.videoList[this.currentIndex].videoDuration; - this.formartDuration = this.sliderFormatTime(this.duration); + this.clearVideoContext() }, - + + // 娓呴櫎瓒呭嚭瑙嗛鍙鍖栧尯鍩熺殑瑙嗛涓婁笅鏂� + async clearVideoContext() { + // 瀵硅秴鍑哄彲瑙嗗寲鍖哄煙鐨勮棰戜笂涓嬫枃鍋氶攢姣佸鐞� + const start = Math.max(0, this.currentIndex - this.videoLiveOffset); + const end = Math.min(this.currentIndex + this.videoLiveOffset, this.videoList.length - 1); + for (let i = 0; i < this.videoContexts.length; i++) { + if (i < start || i > end) { + if (this.videoContexts[i]) { + this.videoContexts[i].stop(); + this.videoContexts[i] = null + } + } else { + if (this.videoContexts[i] == null) { + let videoContent = uni.createVideoContext(`video${i}`, this); + videoContent.seek(this.videoBufferOffset); + videoContent.pause(); + this.videoContexts[i] = videoContent; + } + } + } + // 濡傛灉鍓╀綑瑙嗛涓嶈冻锛岃Е鍙戣姹傝幏鍙栨洿澶氳棰� + if (this.videoList.length - 1 < this.currentIndex + this.videoLiveOffset) { + this.loadVideos() + } + }, + + // 寮�濮嬭Е鎽� + handleSwiperStart(e) { + console.log("寮�濮嬭Е鎽�", e); + this.touchXY.startX = e.touches[0].pageX + this.touchXY.startY = e.touches[0].pageY + }, + // 瑙︽懜涓� + handleSwiperMove(e) { + console.log("瑙︽懜涓�", e); + this.touchXY.endX = e.touches[0].pageX + this.touchXY.endY = e.touches[0].pageY + }, + // 缁撴潫瑙︽懜 + handleSwiperEnd(item) { + const diffX = this.touchXY.endX - this.touchXY.startX + const diffY = this.touchXY.endY - this.touchXY.startY + + // 鍒ゆ柇鏄惁鏄í鍚戞粦鍔紙X杞村彉鍖栧ぇ浜嶻杞村彉鍖栵級 + if (Math.abs(diffX) > Math.abs(diffY)) { + if (diffX > 0) { + console.log('鍙虫粦') + if (item.goodsList && item.goodsList.length > 0) { + this.jumpToPay(item.id) + } + } else { + console.log('宸︽粦') + } + } + // 閲嶇疆鍧愭爣 + this.touchXY = { + startX: 0, + endX: 0, + startY: 0, + endY: 0 + } + }, + // 鏀惰棌/鍙栨秷鏀惰棌 toggleCollect(item, index) { let data = { @@ -778,9 +893,9 @@ const duration = Date.now() - this.startPauseTime this.totalPauseTime += duration } - + }, - + // 瑙嗛鏆傚仠浜嬩欢 onPause(index) { console.log(index, "瑙﹀彂鏆傚仠"); @@ -796,11 +911,11 @@ onEnded(index) { // this.currentVideoIsPlaying = false; }, - + // 璁板綍鎾斁鏃堕暱 onTimeUpdate(e) { this.playRecord.playAt = e.detail.currentTime; - + this.currentTime = e.detail.currentTime; this.progress = (e.detail.currentTime / this.duration) * 100 }, @@ -814,7 +929,7 @@ this.videoContexts[this.currentIndex].pause() // this.updateProgress(e); }, - + // 瑙︽懜绉诲姩 handleTouchMove(e) { if (!this.isDragging || !this.barWidth) return; @@ -822,7 +937,7 @@ this.videoContexts[this.currentIndex].pause() this.updateProgress(e); }, - + // 瑙︽懜缁撴潫 handleTouchEnd() { this.isDragging = false; @@ -833,24 +948,24 @@ this.showProcess = false; }, 1000); }, - + // 鏇存柊杩涘害 updateProgress(e) { // 鑾峰彇褰撳墠瑙︽懜鐐筙鍧愭爣 const currentX = e.touches[0].pageX; - + // 璁$畻婊戝姩璺濈(鍍忕礌) const deltaX = currentX - this.startX; - + // 灏嗗儚绱犺窛绂昏浆鎹负杩涘害澧為噺 const deltaProgress = (deltaX / this.barWidth) * 100; console.log("杩涘害澧為噺", deltaProgress); // 璁$畻鏂拌繘搴� = 寮�濮嬫椂鐨勮繘搴� + 婊戝姩澧為噺 let newProgress = this.startProgress + deltaProgress; - + // 闄愬埗鑼冨洿鍦�0-100涔嬮棿 newProgress = Math.max(0, Math.min(100, newProgress)); - + this.progress = newProgress; }, // 鑾峰彇瑙嗛鎬绘椂闀� @@ -862,7 +977,7 @@ // 淇濆瓨鎾斁璁板綍 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, @@ -891,12 +1006,12 @@ height: 100vh; background-color: #000; } - + .video-swiper { width: 100%; height: calc(100% - 50px); } - + .video-item { width: 100%; height: 100%; @@ -912,7 +1027,7 @@ z-index: 10; opacity: 0.6; } - + .video-info { width: 70%; position: absolute; @@ -922,7 +1037,7 @@ z-index: 10; letter-spacing: 1px; } - + .action-buttons { position: absolute; right: 20px; @@ -932,7 +1047,7 @@ align-items: center; z-index: 10; } - + .action-item { margin-bottom: 18px; display: flex; @@ -962,7 +1077,7 @@ bottom: 0; /* 瀹氫綅鍒板簳閮� */ left: 50%; /* 姘村钩灞呬腑寮�濮嬩綅缃� */ transform: translate(-50%, 50%); /* 姘村钩灞呬腑骞跺悜涓嬬Щ鍔�50% */ - + width: 18px; /* 鍥炬爣澶у皬 */ height: 18px; background-color: #FF5A5F; /* 鍥炬爣鑳屾櫙鑹� */ @@ -997,27 +1112,27 @@ border-radius: 12rpx; box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); } - + .goods-container { width: 100%; 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; @@ -1030,31 +1145,31 @@ white-space: nowrap; text-overflow: ellipsis; } - + .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: 28rpx; color: #999; text-decoration: line-through; } - + .sales-count { font-size: 22rpx; color: #999; } - + .buy-button { background: linear-gradient(to right, #ff5a5f, #ff2e4d); color: white; @@ -1192,16 +1307,16 @@ align-items: center; height: 40rpx; } - + .reply-item { display: flex; margin-bottom: 20rpx; } - + .reply-content { flex: 1; } - + .reply-to { color: #576b95; margin: 0 10rpx; @@ -1213,7 +1328,7 @@ font-size: 28rpx; color: #333; } - + .cancel-reply { margin-left: 20rpx; color: #576b95; @@ -1262,7 +1377,7 @@ bottom: 0; width: 100%; } - + .progress-bar { position: relative; width: 100%; @@ -1270,7 +1385,7 @@ background-color: #eee; overflow: hidden; } - + .progress-fill { position: absolute; left: 0; @@ -1312,4 +1427,4 @@ .custom-share-btn::after { border: none; } -</style> \ No newline at end of file +</style> -- Gitblit v1.8.0