绿满眶商城微信小程序-uniapp
peng
2025-07-02 be80b22a4a0fcd33e1b17ebdb86eba91cc7de4d2
pages/video/video-play.vue
@@ -1,32 +1,43 @@
<template>
  <view class="video-container">
   <!-- 视频加载 -->
   <zero-loading v-show="videoLoading" type="circle" color="#0ebd57" text=""></zero-loading>
    <!-- 视频列表 -->
    <swiper 
      class="video-swiper" 
      vertical 
      circular
      :current="currentIndex"
      @change="onSwiperChange"
     :duration="250"
     easing-function="linear"
    >
      <swiper-item v-for="(item, index) in videoList" :key="item.id">
         <view style="width: 100%;height: 100%;" v-if="item.videoContentType === 'video'">
      <swiper-item
      v-for="(item, index) in videoList"
      :key="item.id"
      @touchstart="handleSwiperStart"
      @touchmove="handleSwiperMove"
      @touchend="handleSwiperEnd(item)"
       >
         <view :style="{width: '100%', height: windowHeight - marginBottom + 'px'}" v-if="item.videoContentType === 'video'">
              <!-- 播放按钮(仅当视频暂停时显示) -->
              <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>
              <video
            v-if="index >= currentIndex - videoLiveOffset && index <= currentIndex + videoLiveOffset"
               :id="'video'+index"
               :ref="'video'+index"
               :src="item.videoUrl"
               :autoplay="currentIndex === index"
               :autoplay="index === currentIndex"
               :controls="false"
               :loop="true"
               :object-fit="item.objectFit"
               :object-fit="item.videoFit"
               :enable-progress-gesture="false"
            :show-center-play-btn="false"
               class="video-item"
               @play="onPlay(item.id, index)"
               @pause="onPause(index)"
@@ -34,13 +45,13 @@
               @click="togglePlay(index)"
               @timeupdate="onTimeUpdate($event)"
               @loadedmetadata="onLoadedMetadata($event)"
               @waiting="videoWaiting(index)"
              ></video>
              <!-- 自定义控制条 -->
              <view 
               @touchstart="handleTouchStart"
               @touchmove="handleTouchMove"
               @touchend="handleTouchEnd"
               @touchstart.stop="handleTouchStart"
               @touchmove.stop="handleTouchMove"
               @touchend.stop="handleTouchEnd"
               class="container">
               <!-- 进度条 - 整个区域可拖动 -->
               <view class="process-warp" :style="{ opacity: showProcess ? 1 : 0 }">
@@ -116,31 +127,35 @@
           </view>
          </view>
          
          <!-- 右侧互动按钮 -->
         <view class="action-buttons">
            <view class="avatar-container">
               <image class="avatar" @click="jumpToHomePage(item.authorId)" :src="item.authorAvatar" mode="aspectFill"></image>
               <!-- 关注图标 - 使用绝对定位 -->
               <view v-if="!item.subscribeThisAuthor" class="follow-icon" @click="subscribeAuth(index, item.authorId)">
                <text class="iconfont">&#xe629;</text>
               </view>
            </view>
            <view class="action-item" @click="toggleCollect(item, index)">
            <text class="iconfont" v-if="item.collected">&#xe605;</text>
            <text class="iconfont" v-else>&#xe601;</text>
            <text style="font-size: 10px;font-weight: lighter;">{{item.collectNum}}</text>
            </view>
           <view class="action-item" @click="showComments(item)">
              <text class="iconfont">&#xe7f7;</text>
              <text style="font-size: 10px;font-weight: lighter;">{{item.commentNum}}</text>
            </view>
           <view class="action-item">
              <button open-type="share" class="custom-share-btn" :data-obj="item">
                    <text class="iconfont">&#xe602;</text>
                  </button>
           </view>
          </view>
        <!-- 右侧互动按钮 -->
        <view class="action-buttons">
         <view class="avatar-container">
            <image class="avatar" @click="jumpToHomePage(item.authorId)" :src="item.authorAvatar" mode="aspectFill"></image>
            <!-- 关注图标 - 使用绝对定位 -->
            <view v-if="!item.subscribeThisAuthor" class="follow-icon" @click="subscribeAuth(index, item.authorId)">
             <text class="iconfont">&#xe629;</text>
            </view>
         </view>
           <view class="action-item" @click="toggleThumbsUp(item, index)">
                 <text class="iconfont" v-if="item.thumbsUp">&#xe605;</text>
                 <text class="iconfont" v-else>&#xe601;</text>
                 <text style="font-size: 10px;font-weight: lighter;">{{item.thumbsUpNum}}</text>
           </view>
          <view class="action-item" @click="showComments(item)">
             <text class="iconfont">&#xe7f7;</text>
             <text style="font-size: 10px;font-weight: lighter;">{{item.commentNum}}</text>
           </view>
           <view class="action-item" @click="toggleCollect(item, index)">
             <text class="iconfont" v-if="item.collected">&#xeb9d;</text>
             <text class="iconfont" v-else>&#xe603;</text>
             <text style="font-size: 10px;font-weight: lighter;">{{item.collectNum}}</text>
            </view>
           <view class="action-item">
              <button open-type="share" class="custom-share-btn" :data-obj="item">
               <text class="iconfont">&#xe602;</text>
              </button>
           </view>
         </view>
         
        </swiper-item>
      </swiper>
@@ -228,7 +243,16 @@
</template>
<script>
import { getRecommendVideos, savePlayRecord, subscribe, getVideoComments, addVideoComment, thubmsUpComment, cancelThubmsUpComment } from "@/api/video.js";
import {
   getRecommendVideos,
   savePlayRecord,
   subscribe,
   getVideoComments,
   addVideoComment,
   thubmsUpComment,
   cancelThubmsUpComment,
   changeThumbsUp,
} from "@/api/video.js";
import { changeCollect } from "@/api/collect.js";
import { saveShare } from "@/api/share.js";
import storage from "@/utils/storage.js";
@@ -291,24 +315,31 @@
         startPlayTime: 0 // 这个视频从什么时候开始播放的
      },
      currentVideoIsPlaying: true, // 当前视频是否正在播放
      isFullScreen: false,
      windowHeight: 0,
      currentIndex: 0, // 当前播放的视频索引
      videoList: [
      ],   // 视频列表数据
      videoContexts: [], // 视频上下文对象集合
      videoLoading: false, // 视频缓冲中
      videoList: [],   // 视频列表数据
      videoBufferOffset: 0.1 ,// 视频预加载参数
      videoLiveOffset: 2, // 保留当前视频前后各多少个视频上下文
      touchXY: {  // 监听左滑右滑
         startX: 0,
         endX: 0,
         startY: 0,
         endY: 0
      },
      loading: false,  // 是否正在加载
      videoQuery: {
         pageNumber: 1,
         pageSize: 6,
         pageSize: 10,
         authorId: '',
         videoFrom: ''
      }
         videoFrom: '',
         keyword: ''
      },
      marginBottom: 0, // 底部安全区域
      windowHeight: 0 // 可使用屏幕高度
    }
  },
  onShow() {
     this.loadVideos()
     // this.loadVideos()
     // 如果视频按下暂停后切换页面再回到页面时,只算暂停时间(因为暂停时间和离开页面时间是重复的,只算一个)
     if(this.startHidenTime !== 0 && this.currentVideoIsPlaying) {
        const duration = Date.now() - this.startHidenTime
@@ -320,25 +351,44 @@
  },
  onUnload() {
     uni.removeStorageSync("playInfo");
     uni.removeStorageSync("searchPlayInfo");
  },
  onReady() {
  },
  onLoad(option) {
     const playInfo = uni.getStorageSync("playInfo", playInfo);
     this.marginBottom = uni.getSystemInfoSync().safeAreaInsets.bottom
     this.windowHeight = uni.getSystemInfoSync().windowHeight
     const playInfo = uni.getStorageSync("playInfo");
     const searchPlayInfo = uni.getStorageSync("searchPlayInfo");
     if(playInfo) {
        this.currentIndex = playInfo.playIndex;
        this.videoList = playInfo.videoList;
        console.log("拿到数据了",playInfo);
        this.videoQuery.pageNumber = playInfo.pageNumber;
        this.videoNoMore = playInfo.nomore;
        this.videoQuery.authorId = option.authorId;
        this.videoQuery.videoFrom = option.videoFrom;
        this.currentIndex = playInfo.playIndex;
        this.currentVideoIsPlaying = true;
        this.$nextTick(() => {
           const videoContext = uni.createVideoContext(`video${this.currentIndex}`, this);
           videoContext.play()
        })
     } else if (searchPlayInfo) { // 搜索页跳转过来的数据略有不同
        this.currentIndex = searchPlayInfo.playIndex;
        this.videoList = searchPlayInfo.videoList;
        this.videoQuery.pageNumber = searchPlayInfo.pageNumber;
        this.videoNoMore = searchPlayInfo.nomore;
        this.videoQuery.keyword = searchPlayInfo.keyword;
        this.videoQuery.videoFrom = option.videoFrom;
        this.currentVideoIsPlaying = true;
        this.$nextTick(() => {
                   const videoContext = uni.createVideoContext(`video${this.currentIndex}`, this);
                   videoContext.play()
        })
     } else {
        this.videoQuery.videoFrom = 'recommend';
        this.loadVideos();
     }
  },
  onReady() {
    // 初始化视频上下文
    this.initVideoContexts();
  },
  onShareAppMessage(e) {
     const userInfo = storage.getUserInfo();
@@ -623,13 +673,6 @@
         }
      })
     },
    // 初始化视频上下文
    initVideoContexts() {
      this.videoContexts = this.videoList.map((_, index) => {
        let videoContent = uni.createVideoContext(`video${index}`, this);
        return videoContent;
      });
    },
    
    // 加载视频数据
    async loadVideos() {
@@ -647,9 +690,6 @@
            ),
          ];
        }
        this.$nextTick(() => {
          this.initVideoContexts();
        });
        this.loading = false;
        if(res.data.data.length < this.videoQuery.pageSize) {
           this.videoNoMore = true;
@@ -661,29 +701,75 @@
    
    // 滑动切换视频
    onSwiperChange(e) {
      // 如果视频处于暂停状态往下刷视频,那么需要再计算一次暂停时间
      if(!this.currentVideoIsPlaying) {
         if(this.startPauseTime !== 0) {
            const duration = Date.now() - this.startPauseTime
            this.totalPauseTime += duration
         }
      }
      // 保存上一个视频的播放记录
      this.savePlayRecord()
      const oldIndex = this.currentIndex;
      this.currentIndex = e.detail.current;
      // 暂停上一个视频
      if (this.videoContexts[oldIndex]) {
         this.videoContexts[oldIndex].pause();
      this.videoLoading = false
       // 如果视频处于暂停状态往下刷视频,那么需要再计算一次暂停时间
       if(!this.currentVideoIsPlaying) {
          if(this.startPauseTime !== 0) {
             const duration = Date.now() - this.startPauseTime
             this.totalPauseTime += duration
          }
       }
       // 保存上一个视频的播放记录
       this.savePlayRecord()
       const oldIndex = this.currentIndex;
       this.currentIndex = e.detail.current;
       const videoContext = uni.createVideoContext(`video${oldIndex}`, this);
       // 暂停上一个视频
       videoContext.pause();
       this.startPauseTime = 0;
       // 设置当前播放视频的总时长
       this.duration = this.videoList[this.currentIndex].videoDuration;
       this.formartDuration = this.sliderFormatTime(this.duration);
       // 播放当前视频
       const videoContext1 = uni.createVideoContext(`video${this.currentIndex}`, this);
       videoContext1.play()
       // 如果剩余视频不足,触发请求获取更多视频
       if (this.videoList.length - 1 < this.currentIndex + this.videoLiveOffset) {
          this.loadVideos()
       }
    },
   // 开始触摸
   handleSwiperStart(e) {
       this.touchXY.startX = e.touches[0].pageX
       this.touchXY.startY = e.touches[0].pageY
   },
   // 触摸中
   handleSwiperMove(e) {
       this.touchXY.endX = e.touches[0].pageX
       this.touchXY.endY = e.touches[0].pageY
   },
   // 结束触摸
   handleSwiperEnd(item) {
      // 防止滑动滚动条也触发跳转
      if (this.showProcess) {
         return
      }
      
      this.startPauseTime = 0;
      // 播放当前视频
      if (this.videoContexts[this.currentIndex]) {
         this.videoContexts[this.currentIndex].play();
       const diffX = this.touchXY.endX - this.touchXY.startX
       const diffY = this.touchXY.endY - this.touchXY.startY
       // 判断是否是横向滑动(X轴变化大于Y轴变化)
       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
      }
    },
   },
   
   // 获取进度条的位置和尺寸
   getBarRect() {
@@ -703,7 +789,8 @@
     this.startProgress = this.progress; // 记录开始时的进度
     this.startX = e.touches[0].pageX;
     console.log("记录开始时的进度", this.startProgress);
     this.videoContexts[this.currentIndex].pause()
     const videoContext = uni.createVideoContext(`video${this.currentIndex}`, this);
     videoContext.pause()
     // this.updateProgress(e);
   },
   
@@ -711,7 +798,6 @@
   handleTouchMove(e) {
     if (!this.isDragging || !this.barWidth) return;
     clearTimeout(this.processHidenTimer)
     this.videoContexts[this.currentIndex].pause()
     this.updateProgress(e);
   },
   
@@ -719,11 +805,12 @@
   handleTouchEnd() {
     this.isDragging = false;
     console.log("滑动结束", this.duration * this.progress);
     this.videoContexts[this.currentIndex].seek(this.duration * this.progress / 100)
     this.videoContexts[this.currentIndex].play()
     const videoContext = uni.createVideoContext(`video${this.currentIndex}`, this);
     videoContext.seek(this.duration * this.progress / 100)
     videoContext.play()
     this.processHidenTimer = setTimeout(() => {
        this.showProcess = false;
      }, 1000);
             this.showProcess = false;
           }, 1000);
   },
   
   // 更新进度
@@ -736,7 +823,6 @@
      
      // 将像素距离转换为进度增量
      const deltaProgress = (deltaX / this.barWidth) * 100;
      console.log("进度增量", deltaProgress);
      // 计算新进度 = 开始时的进度 + 滑动增量
      let newProgress = this.startProgress + deltaProgress;
      
@@ -768,18 +854,40 @@
        }
     })
    },
   // 点赞/取消点赞
   toggleThumbsUp(item, index) {
     let data = {
             refId: item.id,
             thumbsUpType: 'video'
     }
     const beforeThumbsUp = item.thumbsUp
     const beforeThumbsUpNum = item.thumbsUpNum
     if(item.thumbsUp) {
             this.videoList[index].thumbsUp = false
             this.videoList[index].thumbsUpNum -= 1
     } else {
             this.videoList[index].thumbsUp = true
             this.videoList[index].thumbsUpNum += 1
     }
     changeThumbsUp(data).then(res => {
             if(res.data.code !== 200) {
                this.videoList[index].thumbsUp = beforeThumbsUp
                this.videoList[index].thumbsUpNum = beforeThumbsUpNum
             }
     })
   },
    // 单击屏幕:暂停或继续播放
   togglePlay(index) {
      console.log("单击视频", index);
      const videoContext = uni.createVideoContext(`video${index}`, this);
      if(this.currentVideoIsPlaying) {
         this.videoContexts[index].pause();
         videoContext.pause();
      } else {
         this.videoContexts[index].play();
         videoContext.play();
      }
   },
    // 视频播放事件
    onPlay(id, index) {
      this.getBarRect()
      this.progress = 0
      if(index === this.currentIndex) {
         this.currentVideoIsPlaying = true;
         if(! this.duration) {
@@ -788,9 +896,11 @@
            this.formartDuration = this.sliderFormatTime(this.duration);
         }
      } else {
         this.currentVideoIsPlaying = false;
         return
      }
      this.getBarRect()
      this.progress = 0
      console.log(id, index, "触发播放");
      this.playRecord.videoId = id;
      // 没初始化才赋值,因为一个视频重复播放onPlay会重复触发
      if(this.playRecord.startPlayTime === 0) {
@@ -800,6 +910,7 @@
         const duration = Date.now() - this.startPauseTime
         this.totalPauseTime += duration
      }
      this.videoLoading = false
    },
    
    // 视频暂停事件
@@ -807,11 +918,8 @@
      console.log(index, "触发暂停");
      if(index === this.currentIndex) {
         this.currentVideoIsPlaying = false;
      } else {
         this.currentVideoIsPlaying = true;
         return
         this.startPauseTime = Date.now()
      }
     this.startPauseTime = Date.now()
    },
    
    // 视频结束事件
@@ -821,9 +929,17 @@
   
   // 记录播放时长
   onTimeUpdate(e) {
      this.videoLoading = false
      this.playRecord.playAt = e.detail.currentTime
      this.currentTime = e.detail.currentTime;
      this.progress = (e.detail.currentTime / this.duration) * 100
   },
   // 视频缓冲
   videoWaiting(index) {
      if (index === this.currentIndex) {
         console.log("视频缓冲中。。。");
         this.videoLoading = true;
      }
   },
   // 获取视频总时长
   onLoadedMetadata(e) {
@@ -872,7 +988,7 @@
   .video-item {
     width: 100%;
     height: 100%;
     object-fit: cover;
     /* object-fit: cover; */
   }
   .play-icon {
     position: absolute;
@@ -888,7 +1004,7 @@
   .video-info {
     width: 70%;
     position: absolute;
     bottom: 20px;
     bottom: 40px;
     left: 20px;
     color: #f8f8f8;
     z-index: 10;
@@ -1231,7 +1347,7 @@
     flex-direction: column;
     align-items: center;
     position: absolute;
     bottom: 0;
     bottom: 20px;
     width: 100%;
   }
   
@@ -1260,7 +1376,7 @@
   .progress-text {
     margin-top: 10px;
     font-size: 14px;
     color: #666;
     color: #fff;
   }
   .swiper-box {
     width: 100%;