绿满眶商城微信小程序-uniapp
zhanghua
5 天以前 1113721c0e068c57adbc15149cce15563960a7f2
pages/video/video-play.vue
@@ -9,89 +9,141 @@
      @change="onSwiperChange"
    >
      <swiper-item v-for="(item, index) in videoList" :key="item.id">
      <!-- 播放按钮(仅当视频暂停时显示) -->
      <view
        class="play-icon"
        @click="togglePlay(index)"
        v-if="!currentVideoIsPlaying"
      >
        <image src="/static/video/play.png" style="width: 45px;height: 45px" mode="aspectFit"></image>
      </view>
        <video
          :id="'video'+index"
        :ref="'video'+index"
          :src="item.videoUrl"
          :autoplay="currentIndex === index"
          :controls="false"
          :loop="true"
        :object-fit="item.objectFit"
          class="video-item"
          @play="onPlay(item.id, index)"
          @pause="onPause(index)"
          @ended="onEnded(index)"
        @click="togglePlay(index)"
        @timeupdate="onTimeUpdate($event)"
        ></video>
      <!-- 悬挂商品链接层 -->
      <view class="goods-link-warp">
         <view class="goods-link">
           <view class="goods-container">
             <!-- 商品图片 -->
             <image class="goods-image" :src="item.goods.imageUrl" mode="aspectFill"></image>
             <!-- 商品信息 -->
             <view class="goods-info">
               <text class="goods-name">{{item.goods.name}}</text>
               <view class="price-section">
                 <text class="current-price">¥{{item.goods.price}}</text>
                 <text class="original-price" v-if="item.goods.originalPrice">¥{{item.goods.originalPrice}}</text>
               </view>
               <text class="sales-count">{{item.goods.saleNum}}人已购</text>
             </view>
             <!-- 购买按钮 -->
             <view class="buy-button">
               <text>购买</text>
             </view>
           </view>
         </view>
      </view>
        <!-- 视频信息层 -->
        <view class="video-info">
        <view>
           <text class="video-author">@{{item.authorName}}</text>
        </view>
          <view style="width: 100%;word-wrap: break-word;white-space: normal;overflow-wrap: break-word;">
           <text class="video-title">{{item.title}}</text>
           <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">
            <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 style="width: 100%;height: 100%;" v-if="item.videoContentType === 'video'">
              <!-- 播放按钮(仅当视频暂停时显示) -->
              <view
               class="play-icon"
               @click="togglePlay(index)"
               v-if="!currentVideoIsPlaying"
              >
               <image src="/static/video/play.png" style="width: 45px;height: 45px" mode="aspectFit"></image>
              </view>
              <video
               :id="'video'+index"
               :ref="'video'+index"
               :src="item.videoUrl"
               :autoplay="currentIndex === index"
               :controls="false"
               :loop="true"
               :object-fit="item.objectFit"
               :enable-progress-gesture="false"
               class="video-item"
               @play="onPlay(item.id, index)"
               @pause="onPause(index)"
               @ended="onEnded(index)"
               @click="togglePlay(index)"
               @timeupdate="onTimeUpdate($event)"
               @loadedmetadata="onLoadedMetadata($event)"
              ></video>
              <!-- 自定义控制条 -->
              <view
               @touchstart="handleTouchStart"
               @touchmove="handleTouchMove"
               @touchend="handleTouchEnd"
               class="container">
               <!-- 进度条 - 整个区域可拖动 -->
               <view class="process-warp" :style="{ opacity: showProcess ? 1 : 0 }">
                 <!-- 显示当前进度 -->
                 <view class="progress-text">{{ hasPlayTime }}/{{formartDuration}}</view>
                 <view
                  class="progress-bar"
                  id="progressBar"
                 >
                  <!-- 已填充部分 -->
                  <view class="progress-fill" :style="{ width: progress + '%' }"></view>
                 </view>
               </view>
              </view>
         </view>
         <view style="width: 100%; height: 100%;" v-else-if="item.videoContentType === 'img'">
           <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'}"
            >
             <swiper class="swiper-box" @change="imgChange" :autoplay="true" :interval="3000">
               <swiper-item v-for="img in item.imgs" :key="img">
                 <view class="swiper-item">
                   <!-- 调整 image 样式,使其居中且按比例缩放 -->
                   <image
                     :src="img"
                     mode="aspectFit"
                     style="width: 100%; height: 100%; display: block; margin: 0 auto;"
                   ></image>
                 </view>
               </swiper-item>
             </swiper>
           </uni-swiper-dot>
         </view>
         <!-- 悬挂商品链接层 -->
         <view class="goods-link-warp" v-if="item.goodsList.length > 0">
            <view class="goods-link">
              <swiper @change="goodsChange" :autoplay="true" :interval="4000" style="height: 120rpx;">
               <swiper-item v-for="goods in item.goodsList" :key="goods.goodsId">
                 <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>
                    <view class="price-section">
                     <text class="current-price">¥{{goods.price}}</text>
                     <text class="original-price" v-if="goods.originalPrice">¥{{goods.originalPrice}}</text>
                    </view>
                  </view>
                 </view>
               </swiper-item>
              </swiper>
            </view>
         </view>
          <!-- 视频信息层 -->
          <view class="video-info">
           <view>
              <text class="video-author">@{{item.authorName}}</text>
           </view>
            <view style="width: 100%;word-wrap: break-word;white-space: normal;overflow-wrap: break-word;">
              <text class="video-title">{{item.title}}</text>
              <text class="video-tag" v-for="(tag, index) in item.tagList" :key="tag.id">#{{tag.tagName}}</text>
           </view>
          </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 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>
      </swiper-item>
    </swiper>
        </swiper-item>
      </swiper>
   
   <!-- 评论弹窗 -->
   <uni-popup ref="commentPopup" type="bottom" :is-mask-click="true" @maskClick="closeCommentPopup">
@@ -178,9 +230,29 @@
<script>
import { getRecommendVideos, savePlayRecord, subscribe, getVideoComments, addVideoComment, thubmsUpComment, cancelThubmsUpComment } from "@/api/video.js";
import { changeCollect } from "@/api/collect.js";
import { saveShare } from "@/api/share.js";
import storage from "@/utils/storage.js";
export default {
   computed: {
          hasPlayTime() {
            return this.sliderFormatTime(this.progress > 0 ? this.duration * this.progress / 100 : 0);
          }
   },
  data() {
    return {
      currentImgIndex: 0, // 播放到第几张图--索引
      currentGoodsIndex: 0, // 播放到第几个商品--索引
      currentTime: 0,
      formartDuration: '',
      duration: 0,
      startX: 0,
      progress: 0, // 视频进度
      startProgress : 0, // 开始滑动时的进度
      barLeft: 0, // 进度条左边界位置
      barWidth: 0, // 进度条宽度
      isDragging: false, // 是否正在拖动
      processHidenTimer: null, // 进度条隐藏定时器
      showProcess: false, // 是否显示进度条
      videoNoMore: false, // 是否还有更多视频
      commentNoMore: false, // 是否还有更多评论
      commentQuery: {
@@ -253,6 +325,7 @@
     const playInfo = uni.getStorageSync("playInfo", playInfo);
     if(playInfo) {
        this.videoList = playInfo.videoList;
        console.log("拿到数据了",playInfo);
        this.videoQuery.pageNumber = playInfo.pageNumber;
        this.videoNoMore = playInfo.nomore;
        this.videoQuery.authorId = option.authorId;
@@ -267,7 +340,51 @@
    // 初始化视频上下文
    this.initVideoContexts();
  },
  onShareAppMessage(e) {
     const userInfo = storage.getUserInfo();
     if(!userInfo) {
        console.log("未登录不能分享");
        return
     }
     const videoInfo = e.target.dataset.obj;
     // 保存分享记录
     const data = {
        shareType: 'video',
        refId: videoInfo.id,
        shareUser: userInfo.id
     }
     saveShare(data)
     return {
        title: videoInfo.title,
        path: `/pages/tabbar/index/home?videoId=${videoInfo.id}&userId=${userInfo.id}`,
        imageUrl: videoInfo.coverUrl
     }
  },
  methods: {
     // 点击商品跳转
     jumpToPay(videoId) {
           uni.navigateTo({
              url: '/pages/video/video-goods-detail?videoId=' + videoId
           });
     },
     // 轮播图变化
     imgChange(e) {
             this.currentImgIndex = e.detail.current;
     },
     // 商品轮播图变化
     goodsChange(e) {
             this.currentGoodsIndex = e.detail.current;
     },
     // 获取进度条的位置和尺寸
     getBarRect() {
       const query = uni.createSelectorQuery().in(this);
       query.select('#progressBar').boundingClientRect(rect => {
         if (rect) {
           this.barLeft = rect.left;
           this.barWidth = rect.width;
         }
       }).exec();
     },
      // 跳转个人主页
      jumpToHomePage(authorId) {
         uni.navigateTo({
@@ -364,6 +481,12 @@
         const input = this.$refs.commentInput;
         if (input) input.focus();
        });
      },
      // 进度条时间格式化 (00:00)
      sliderFormatTime(seconds) {
        const mins = Math.floor(seconds / 60);
        const secs = Math.floor(seconds % 60);
        return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
      },
      // 格式化时间
       formatTime(time) {
@@ -561,6 +684,67 @@
         this.videoContexts[this.currentIndex].play();
      }
    },
   // 获取进度条的位置和尺寸
   getBarRect() {
     const query = uni.createSelectorQuery().in(this);
     query.select('#progressBar').boundingClientRect(rect => {
       if (rect) {
         this.barLeft = rect.left;
         this.barWidth = rect.width;
       }
     }).exec();
   },
   // 触摸开始
   handleTouchStart(e) {
     this.isDragging = true;
     this.showProcess = true;
     this.startProgress = this.progress; // 记录开始时的进度
     this.startX = e.touches[0].pageX;
     console.log("记录开始时的进度", this.startProgress);
     this.videoContexts[this.currentIndex].pause()
     // this.updateProgress(e);
   },
   // 触摸移动
   handleTouchMove(e) {
     if (!this.isDragging || !this.barWidth) return;
     clearTimeout(this.processHidenTimer)
     this.videoContexts[this.currentIndex].pause()
     this.updateProgress(e);
   },
   // 触摸结束
   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()
     this.processHidenTimer = setTimeout(() => {
        this.showProcess = false;
      }, 1000);
   },
   // 更新进度
   updateProgress(e) {
      // 获取当前触摸点X坐标
      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;
   },
    
    // 收藏/取消收藏
    toggleCollect(item, index) {
@@ -594,9 +778,15 @@
   },
    // 视频播放事件
    onPlay(id, index) {
      console.log(id, index, "触发播放");
      this.getBarRect()
      this.progress = 0
      if(index === this.currentIndex) {
         this.currentVideoIsPlaying = true;
         if(! this.duration) {
            // 设置当前播放视频的总时长
            this.duration = this.videoList[this.currentIndex].videoDuration;
            this.formartDuration = this.sliderFormatTime(this.duration);
         }
      } else {
         this.currentVideoIsPlaying = false;
         return
@@ -632,8 +822,15 @@
   // 记录播放时长
   onTimeUpdate(e) {
      this.playRecord.playAt = e.detail.currentTime
      this.currentTime = e.detail.currentTime;
      this.progress = (e.detail.currentTime / this.duration) * 100
   },
   // 获取视频总时长
   onLoadedMetadata(e) {
     // this.duration = e.detail.duration;
     // this.formartDuration = this.sliderFormatTime(this.duration);
     // console.log("视频总时长", this.duration);
   },
   // 保存播放记录
   async savePlayRecord() {
      console.log(Date.now(), this.playRecord.startPlayTime, this.totalHidenTime);
@@ -669,7 +866,7 @@
   
   .video-swiper {
     width: 100%;
     height: 100%;
     height: calc(100% - 20rpx);
   }
   
   .video-item {
@@ -691,7 +888,7 @@
   .video-info {
     width: 70%;
     position: absolute;
     bottom: 70px;
     bottom: 20px;
     left: 20px;
     color: #f8f8f8;
     z-index: 10;
@@ -765,6 +962,7 @@
   }
   .goods-link {
     position: relative;
     width: 450rpx;
     margin: 20rpx 0;
     padding: 12rpx;
     background-color: rgba(255, 255, 255, 0.9);
@@ -773,6 +971,7 @@
   }
   
   .goods-container {
     width: 100%;
     display: flex;
     align-items: center;
   }
@@ -795,11 +994,13 @@
     font-size: 28rpx;
     color: #333;
     font-weight: bold;
     display: -webkit-box;
     -webkit-line-clamp: 2;
     -webkit-box-orient: vertical;
     overflow: hidden;
     margin-bottom: 8rpx;
     width: 280rpx; /* 需要指定宽度 */
     overflow: hidden;
     white-space: nowrap;
     text-overflow: ellipsis;
   }
   
   .price-section {
@@ -1025,4 +1226,62 @@
   .thumbs-num {
      margin-left: 4rpx;
   }
   .container {
     display: flex;
     flex-direction: column;
     align-items: center;
     position: absolute;
     bottom: 0;
     width: 100%;
   }
   .progress-bar {
     position: relative;
     width: 100%;
     height: 16px;
     background-color: #eee;
     overflow: hidden;
   }
   .progress-fill {
     position: absolute;
     left: 0;
     top: 0;
     height: 100%;
     background-color: lightgray;
     transition: width 0.1s;
   }
   .process-warp {
      width: 100%;
      display: flex;
      flex-direction: column;
      align-items: center;
   }
   .progress-text {
     margin-top: 10px;
     font-size: 14px;
     color: #666;
   }
   .swiper-box {
     width: 100%;
     height: 1200rpx;
   }
   .swiper-item {
     display: flex;
     justify-content: center;
     align-items: center;
     width: 100%;
     height: 100%;
   }
   .custom-share-btn {
     font-size: unset;
     background: none;
     padding: 0;
     margin: 0;
     line-height: normal;
     border: none;
   }
   .custom-share-btn::after {
     border: none;
   }
</style>