绿满眶商城微信小程序-uniapp
zxl
2025-08-25 c1626f0e66bb1301668698c71c490a0bdc1c6a0e
pages/tabbar/index/home.vue
@@ -1,67 +1,96 @@
<template>
  <view class="video-container">
   <top-bar selectedTitleIndex="home" textColor="white" @changeTab="topBarChange" class="topBar"></top-bar>
   <!-- 视频加载 -->
   <zero-loading v-show="videoLoading" type="circle" color="#0ebd57" text=""></zero-loading>
    <!-- 视频列表 -->
    <swiper
      class="video-swiper"
      vertical
      circular
    <swiper
      class="video-swiper"
      vertical
      :current="currentIndex"
      @change="onSwiperChange"
     :duration="250"
     easing-function="linear"
    >
      <swiper-item v-for="(item, index) in videoList" :key="item.id">
      <swiper-item
      v-for="(item, index) in videoList"
      :key="item.updateKey"
      @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>
            <image src="/pages/subComponents/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"
            :controls="false"
            :autoplay="index === currentIndex"
            :controls="isControls"
            :loop="true"
            :object-fit="item.objectFit"
            :object-fit="item.videoFit"
            :enable-progress-gesture="false"
            :show-center-play-btn="false"
            :show-progress="false"
            :show-fullscreen-btn="isControls"
            :show-play-btn="isControls"
            :show-mute-btn="false"
            class="video-item"
            @fullscreenchange="onFullscreenChange"
            @play="onPlay(item.id, index)"
            @pause="onPause(index)"
            @ended="onEnded(index)"
            @click="togglePlay(index)"
            @timeupdate="onTimeUpdate($event)"
            @loadedmetadata="onLoadedMetadata($event)"
            @waiting="videoWaiting(index)"
           ></video>
            <view class="fullscreen-btn">
                 <image
                   class="fullscreen-icon"
                   :src="isFullScreen ? off_screen : on_screen"
                   mode="aspectFit"
                 @click="requestFullScreen('video'+index,item)"
                 ></image>
             </view>
           <!-- 自定义控制条 -->
           <view
            @touchstart="handleTouchStart"
            @touchmove="handleTouchMove"
            @touchend="handleTouchEnd"
           <view
            @touchstart.stop="handleTouchStart"
            @touchmove.stop="handleTouchMove"
            @touchend.stop="handleTouchEnd"
            :style="{bottom: marginBottom + 'px'}"
            class="container">
            <!-- 进度条 - 整个区域可拖动 -->
            <view class="process-warp" :style="{ opacity: showProcess ? 1 : 0 }">
            <!-- <view class="process-warp" :style="{ opacity: showProcess ? 1 : 0 }"> -->
            <view class="process-warp">
              <!-- 显示当前进度 -->
              <view class="progress-text">{{ hasPlayTime }}/{{formartDuration}}</view>
              <view
               class="progress-bar"
              <view v-show="isTouch" 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"
        <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 +98,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 +108,8 @@
          </swiper>
        </uni-swiper-dot>
      </view>
      <!-- 悬挂商品链接层 -->
      <view class="goods-link-warp" v-if="item.goodsList.length > 0">
         <view class="goods-link">
@@ -89,7 +118,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,48 +132,54 @@
           </swiper>
         </view>
      </view>
        <!-- 视频信息层 -->
        <view class="video-info">
        <view>
        <view class="video-info" :style="{bottom: marginBottom + 20 + 'px'}">
        <view style="width: 100%; position: relative;">
           <text class="video-author">@{{item.authorName}}</text>
           <text class="iconfont" @click="jumpToSearch" style="position: absolute;right: 42px;bottom: 50rpx;">&#xe64e;</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>
            <image class="avatar" @click="jumpToHomePage(item.authorId)" @touchend.stop :src="item.authorAvatar" mode="aspectFill"></image>
            <!-- 关注图标 - 使用绝对定位 -->
            <view v-if="!item.subscribeThisAuthor" class="follow-icon" @click="subscribeAuth(index, item.authorId)">
            <view v-if="!item.subscribeThisAuthor" class="follow-icon" @click="subscribeAuth(index, item.authorId)" @touchend.stop>
             <text class="iconfont">&#xe629;</text>
            </view>
         </view>
          <view class="action-item" @click="toggleCollect(item, index)">
         <text class="iconfont" v-if="item.collected">&#xe605;</text>
          <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.collectNum}}</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>
   <!-- 评论弹窗 -->
   <uni-popup ref="commentPopup" type="bottom" :is-mask-click="true" @maskClick="closeCommentPopup">
     <view class="comment-popup">
@@ -156,23 +191,25 @@
        </view>
         <text class="iconfont close-icon" @click="closeCommentPopup">&#xe675;</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;">
         <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 @click="replyClick(comment)">
                  <text class="nickname">{{comment.userNickname}} <text v-if="userId===comment.userId">(我)</text> </text>
                  <text class="content">{{comment.commentContent}}</text>
               </view>
              <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)">&#xe614;<text v-show="comment.thumbsUpNum > 0" class="thumbs-num">{{comment.thumbsUpNum}}</text></text>
@@ -183,11 +220,11 @@
         <!-- 回复列表 -->
           <view class="reply-list" v-if="comment.replies && comment.replies.length > 0">
            <view class="reply-item" v-for="(reply, replyIndex) in comment.replies" :key="reply.id">
              <view class="reply-content">
              <view class="reply-content" @click="replyClick(reply)">
               <view style="display: flex;">
                  <image class="comment-reply-avatar" :src="reply.replyUserAvatar || '/static/default-avatar.png'"></image>
                  <text class="nickname">{{reply.userNickname}}</text>
                  <text v-if="reply.replyUserId && reply.masterCommentId !== reply.replyId" class="reply-to"><text style="margin-right: 10rpx;font-size: 28rpx;" class="iconfont">&#xe666;</text>{{reply.replyUserNickname}}</text>
                  <text class="nickname">{{reply.userNickname}}<text v-if="userId===comment.userId">(我)</text></text>
                  <text v-if="reply.replyUserId && reply.masterCommentId !== reply.replyId" class="reply-to"><text style="margin-right: 10rpx;font-size: 28rpx;" class="iconfont">&#xe666;</text>{{reply.replyUserNickname}}<text v-if="userId===comment.userId">(我)</text></text>
               </view>
               <text class="content">{{reply.commentContent}}</text>
               <view class="reply-footer">
@@ -212,38 +249,75 @@
         </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>
   <ActivityPopup
         :show="activityPopup.show"
         :activityTitle="activityPopup.title"
         :activityDesc="activityPopup.desc"
         :activityImage="activityPopup.image"
         :endTime="activityPopup.endTime"
        :prizeActivityId="activityPopup.prizeActivityId"
         @close="onClosePopup"
       />
  </view>
</template>
<script>
import { getRecommendVideos, savePlayRecord, subscribe, getVideoComments, addVideoComment, thubmsUpComment, cancelThubmsUpComment } from "@/api/video.js";
import {
   getRecommendVideos,
   savePlayRecord,
   subscribe,
   getVideoComments,
   addVideoComment,
   thubmsUpComment,
   cancelThubmsUpComment,
   changeThumbsUp,
   getGoodsSimilarlyVideos,
  removeByIdVideoComment,
} from "@/api/video.js";
import ActivityPopup from '@/pages/ActivityPopup/ActivityPopup.vue'
import { mapState, mapMutations } from 'vuex'
import {setPopupRedisTime,getPopupAcitivty} from '@/api/popup.js'
import { changeCollect } from "@/api/collect.js";
import { saveShare, saveShareClickRecord } from "@/api/share.js";
import { silentLogin } from "@/api/connect.js";
import { getUserInfo } from "@/api/members";
import storage from "@/utils/storage.js";
import TopBar from "@/components/TopBar.vue";
import { nextTick } from "vue";
import {getVideoCover } from "@/api/common.js"
export default {
  components: {TopBar,ActivityPopup},
  computed: {
       hasPlayTime() {
         return this.sliderFormatTime(this.progress > 0 ? this.duration * this.progress / 100 : 0);
       }
       },
      // 错误:没有用 ... 展开,导致 activityPopup 是函数
      ...mapState(['activityPopup'])
  },
  data() {
    return {
      isControls:false,
      on_screen: require('@/pages/tabbar/index/static/on_screen.png'),
      off_screen: require('@/pages/tabbar/index/static/off_screen.png'),
      isFullScreen:false,
      isTouch:false,
      userId :'',
      currentImgIndex: 0, // 播放到第几张图--索引
      currentGoodsIndex: 0, // 播放到第几个商品--索引
      currentTime: 0,
@@ -256,7 +330,7 @@
      barWidth: 0, // 进度条宽度
      isDragging: false, // 是否正在拖动
      processHidenTimer: null, // 进度条隐藏定时器
      showProcess: false, // 是否显示进度条
      showProcess: true, // 是否显示进度条
      videoNoMore: false, // 是否还有更多视频
      commentNoMore: false, // 是否还有更多评论
      commentQuery: {
@@ -295,30 +369,59 @@
         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,
         videoFrom: 'recommend'
      }
      },
      goodsSimilarlyQuery: { // 相似视频查询
         pageNumber: 1,
         pageSize: 10,
         videoFrom: 'goodsSimilarly',
         goodsIds: [],
         currentVideoId: ''
      },
      similarlyVideoList: [], // 相似视频
      similarlyNomore: false, // 是否还有更多相似视频
      similaryVideoIndex: 0, // 相似视频的播放位置
      similarlyLoading: false, // 相似视频加载
      marginBottom: 0 // 底部安全区域
    }
  },
  onShow() {
     this.openActivityPopup()
     if(!this.userId){
        this.getUserId()
     }
     // const token = storage.getAccessToken();
     // if (! token) {
       //  this.wxSilentLogin(() => {
         //   this.loadVideos();
       //  })
     // } else {
     // } else {
     //      this.loadVideos();
     // }
     if (this.videoList.length < 1) {
        this.loading = false;
        this.videoNoMore = false;
        console.log('触发数据加载')
        this.loadVideos();
     }
     // 如果视频按下暂停后切换页面再回到页面时,只算暂停时间(因为暂停时间和离开页面时间是重复的,只算一个)
     if(this.startHidenTime !== 0 && this.currentVideoIsPlaying) {
        const duration = Date.now() - this.startHidenTime
@@ -329,23 +432,45 @@
     this.startHidenTime = Date.now()
  },
  onLoad(option) {
     console.log('-----------分享出的数据---------->',option)
     //处理扫码出来的视频
     this.marginBottom = uni.getSystemInfoSync().safeAreaInsets.bottom
           // 检查是否存在q参数
         let queryParam = this.videoQuery;
           if (option.q) {
             // 双重解码:微信对URL进行了两次编码
             const decodedUrl = decodeURIComponent(decodeURIComponent(option.q));
             console.log('原始URL:', decodedUrl);
             // 解析URL中的查询参数
             const params = this.parseUrlParams(decodedUrl);
             const shareType = params.shareType;
             const videoId = params.videoId;
             queryParam.videoId = videoId
             console.log('解析参数:', { shareType, videoId });
           }
     const token = storage.getAccessToken();
     if (! token) {
        this.wxSilentLogin(() => {
           this.loadVideos();
           // 判断是不是点击分享链接进来的
           if (option.userId && option.videoId) {
              queryParam.videoId = option.videoId
              // 保存分享点击记录
              saveShareClickRecord({refId: option.videoId, shareUserId: option.userId})
           }
           console.log('------------------------>',queryParam)
           this.loadVideos(queryParam);
        })
     } else {
      this.loadVideos();
     } else {
        if (option.userId && option.videoId) {
           queryParam.videoId = option.videoId
                      // 保存分享点击记录
            saveShareClickRecord({refId: option.videoId, shareUserId: option.userId})
        }
        console.log('------------------------2>',queryParam)
      this.loadVideos(queryParam);
     }
  },
  onReady() {
    // 初始化视频上下文
    this.initVideoContexts();
  },
  onShareAppMessage(e) {
   const userInfo = storage.getUserInfo();
@@ -361,13 +486,201 @@
      shareUser: userInfo.id
   }
   saveShare(data)
     return {
        title: videoInfo.title,
        path: `/pages/tabbar/index/home?videoId=${videoInfo.id}&userId=${userInfo.id}`,
        imageUrl: videoInfo.coverUrl
     }
  },
   // getVideoCover(videoInfo.id).then(res =>{
   //    if(res.statusCode === 200){
   //       imageUrl = res.data.data
   //       console.log(imageUrl)
   //       return {
   //          title: videoInfo.title,
   //          path: `/pages/tabbar/index/home?videoId=${videoInfo.id}&userId=${userInfo.id}`,
   //          imageUrl: imageUrl
   //       }
   //    }
   // })
   console.log(videoInfo)
   return {
      title: videoInfo.title,
      path: `/pages/tabbar/index/home?videoId=${videoInfo.id}&userId=${userInfo.id}`,
      imageUrl: videoInfo.coverUrl
      }
   // 保存分享记录
    },
  methods: {
     async openActivityPopup() {
        await getPopupAcitivty().then(res =>{
           if(res.statusCode === 200){
              let obj = res.data.data;
              if(obj.enableStatus === 'ON'){
                 setPopupRedisTime().then(res =>{
                    if(res.statusCode === 200){
                       if(res.data.state){
                          this.showActivityPopup({
                          title: obj.activityName,
                          desc: obj.activityDes,
                          image: obj.activityCoverUrl,
                          endTime:new Date(obj.endTime).getTime(),
                        prizeActivityId:obj.id
                          })
                       }else{
                          this.hideActivityPopup()
                       }
                    }
                 });
              }
           }
        })
         },
     ...mapMutations(['showActivityPopup','hideActivityPopup']), // 引入Vuex的方法
      onClosePopup() {
           this.hideActivityPopup()
         },
     replyClick(reply){
        if(this.userId === reply.userId){
           let that = this;
          uni.showModal({
             title: '提示',
             content: '你确定要删除吗',
             success: function (res) {
                if (res.confirm) {
                console.log('确定');
                  //调用删除的逻辑
                   console.log(reply)
                  removeByIdVideoComment(reply.id).then(res =>{
                     const item = {
                        id:reply.videoId
                     }
                     that.commentQuery.pageNumber = 1;
                     //重新更新评论
                     that.showComments(item);
                  })
                } else if (res.cancel) {
                console.log('取消');
                }
             }
          });
        }
        console.log(reply)
     },
      // 截取视频当前帧
      captureVideoFrame(videoCtx) {
        return new Promise((resolve) => {
          videoCtx.requestFrame(() => {
            wx.canvasToTempFilePath({
              canvasId: 'shareCanvas',
              success: (res) => resolve(res),
              fail: () => resolve({ tempFilePath: '/assets/default-cover.jpg' })
            });
          });
        });
      },
     requestFullScreen(id,item){
        console.log(item)
        const videoContext = uni.createVideoContext(id, this)
        // 根据视频方向决定全屏方向
          const direction = this.shouldUseLandscape(item) ? 90 : 0;
          // 先暂停视频(避免切换时的声音问题)
          // videoContext.pause();
        // 请求全屏
         videoContext.requestFullScreen({
           direction: direction,
         });
     },
     shouldUseLandscape(item) {
      if(item.videoFit === 'cover'){
         return false
      }
       // 默认横屏(根据业务需求调整)
       return true;
     },
     // 全屏状态变化事件
         onFullscreenChange(e) {
           console.log('全屏状态变化:', e.detail.fullScreen)
         if(e.detail.fullScreen){
             this.isControls = true;
         }else{
            this.isControls = false;
         }
         },
     getUserId(){
        const {id} = uni.getStorageSync('user_info_obj_dev')
        this.userId = id;
     },
     // 解析URL参数
    parseUrlParams(url) {
      const params = {};
      // 处理可能存在的hash(如果有的话)
      const cleanUrl = url.split('#')[0];
      const queryStr = cleanUrl.split('?')[1] || '';
      queryStr.split('&').forEach(pair => {
        const [key, value] = pair.split('=');
        if (key) {
          // 如果值存在,则解码,否则设为空字符串
          params[key] = value ? decodeURIComponent(value) : '';
        }
      });
      return params;
    },
     // 查询当前视频的关联视频(挂了同一商品的)
     async getGoodsSimilarly() {
        if (this.similarlyLoading || this.similarlyNomore) return Promise.resolve();;
        const video = this.videoList[this.currentIndex];
        if (video.goodsList && video.goodsList.length > 0) {
           this.goodsSimilarlyQuery.goodsIds = video.goodsList.map(goods => goods.goodsId);
           this.goodsSimilarlyQuery.currentVideoId = video.id;
           if (this.similarlyVideoList.length < 1) {
              this.similarlyVideoList.push(video); // 确保原视频是横向视频的第一个元素
           }
           this.similarlyLoading = true;
           return getGoodsSimilarlyVideos(this.goodsSimilarlyQuery).then(res => {
                      this.similarlyVideoList = [
                        ...this.similarlyVideoList,
                        ...res.data.data.filter(
                         (newItem) => !this.similarlyVideoList.some((oldItem) => oldItem.id === newItem.id)
                        ),
                      ];
                      this.similarlyLoading = false;
                      if(res.data.data.length < this.goodsSimilarlyQuery.pageSize) {
                         this.similarlyNomore = true;
                         return;
                      }
                      this.goodsSimilarlyQuery.pageNumber++;
           })
        }
     },
     // 跳转到搜索页
     jumpToSearch() {
        uni.navigateTo({
           url: '/pages/video/video-search'
        });
     },
     // 切换顶部导航栏
     topBarChange(titleObj) {
      if (titleObj.index === 'home') {
         uni.switchTab({
            url: titleObj.pagePath
         });
      } else {
         uni.redirectTo({
            url: titleObj.pagePath
         });
      }
     },
     // 静默登录
     wxSilentLogin(callback) {
        //获取code
@@ -419,6 +732,7 @@
     },
      // 跳转个人主页
      jumpToHomePage(authorId) {
         console.log("作者id", authorId);
         uni.navigateTo({
            url: "/pages/video/home-page?authorId=" + authorId
         })
@@ -525,11 +839,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 +859,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()) {
@@ -610,6 +924,7 @@
            this.commentQuery.pageNumber++;
         })
      },
       // 显示评论弹窗
       async showComments(item) {
         this.commentForm.videoId = item.id;
@@ -622,6 +937,7 @@
        getVideoComments(this.commentQuery).then(res => {
           this.commentsTotal = res.data.total;
           this.comments = res.data.data;
           console.log('------------------------>',this.comments)
           this.commentQuery.pageNumber += 2;
           this.commentQuery.pageSize /= 2;
        }).catch(() => {
@@ -655,46 +971,73 @@
         }
      })
     },
    // 初始化视频上下文
    initVideoContexts() {
      this.videoContexts = this.videoList.map((_, index) => {
        let videoContent = uni.createVideoContext(`video${index}`, this);
        return videoContent;
      });
    },
    // 加载视频数据
    async loadVideos() {
    async loadVideos(param) {
      if (this.loading || this.videoNoMore) return;
      this.loading = true;
      if(param){
         getRecommendVideos(param).then(res => {
                 // 新增一个字段用于循环时的key
                 const data = res.data.data.map(item => {
                    return {
                       ...item,
                       updateKey: item.id
                    }
                 })
                 if (this.videoQuery.pageNumber === 1) {
                   this.videoList = data;
                 } else {
                  this.videoList = [
                    ...this.videoList,
                    ...data.filter(
                      (newItem) => !this.videoList.some((oldItem) => oldItem.id === newItem.id)
                    ),
                  ];
                 }
                 this.loading = false;
                 if(res.data.data.length < this.videoQuery.pageSize) {
                    this.videoNoMore = true;
                    return;
                 }
                 this.videoQuery.pageNumber++;
         })
      }else{
     getRecommendVideos(this.videoQuery).then(res => {
        console.log(res, "视频数据");
        // 新增一个字段用于循环时的key
        const data = res.data.data.map(item => {
           return {
              ...item,
              updateKey: item.id
           }
        })
        if (this.videoQuery.pageNumber === 1) {
          this.videoList = res.data.data;
          this.videoList = data;
        } else {
         this.videoList = [
           ...this.videoList,
           ...res.data.data.filter(
           ...data.filter(
             (newItem) => !this.videoList.some((oldItem) => oldItem.id === newItem.id)
           ),
         ];
        }
        this.$nextTick(() => {
          this.initVideoContexts();
        });
        this.loading = false;
        if(res.data.data.length < this.videoQuery.pageSize) {
           this.videoNoMore = true;
           return;
        }
        this.videoQuery.pageNumber++;
     })
     }
    },
    // 滑动切换视频
    onSwiperChange(e) {
      this.videoLoading = false
      // 如果视频处于暂停状态往下刷视频,那么需要再计算一次暂停时间
      if(!this.currentVideoIsPlaying) {
         if(this.startPauseTime !== 0) {
@@ -706,22 +1049,108 @@
      this.savePlayRecord()
      const oldIndex = this.currentIndex;
      this.currentIndex = e.detail.current;
      const videoContext = uni.createVideoContext(`video${oldIndex}`, this);
      // 暂停上一个视频
      if (this.videoContexts[oldIndex]) {
         this.videoContexts[oldIndex].pause();
      }
      videoContext.pause();
      this.startPauseTime = 0;
      // 播放当前视频
      if (this.videoContexts[this.currentIndex]) {
         this.videoContexts[this.currentIndex].play();
      }
      // 设置当前播放视频的总时长
      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.similaryVideoIndex !== 0) {
         this.videoList[oldIndex] = this.similarlyVideoList[0]
      }
      this.similarlyVideoList = [];
      this.similaryVideoIndex = 0;
      this.similarlyNomore = false;
      this.similarlyLoading = false;
      this.goodsSimilarlyQuery = {
         pageNumber: 1,
         pageSize: 6,
         videoFrom: 'goodsSimilarly',
         goodsIds: [],
         currentVideoId: ''
      }
      // 如果剩余视频不足,触发请求获取更多视频
      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) {
       this.touchXY.endX = e.touches[0].pageX
       this.touchXY.endY = e.touches[0].pageY
   },
   // 结束触摸
   async handleSwiperEnd(item) {
      // 防止滑动滚动条也触发跳转
      if (this.showProcess) {
         return
      }
       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 (this.similaryVideoIndex !== 0) {
            // 如果滑动了横向视频,那么右滑就做视频切换而不是跳转商品页
            // 切换下一个视频
            const oldIndex = this.similaryVideoIndex;
            this.similaryVideoIndex = Math.max(this.similaryVideoIndex - 1, 0);
            if (this.similaryVideoIndex < oldIndex) {
               // 把竖向视频的当前播放位置替换为横向视频的当前索引元素
               const video = this.similarlyVideoList[this.similaryVideoIndex];
               video["updateKey"] = video.id + this.similaryVideoIndex
               this.videoList.splice(this.currentIndex, 1, video);
               // this.videoList[this.currentIndex] = video
            }
         }
         else if (item.goodsList && item.goodsList.length > 0) {
            this.jumpToPay(item.id)
         }
         } else {
           console.log('左滑')
         if (this.similarlyVideoList.length < 1 || this.similarlyVideoList.length - this.similaryVideoIndex - 1 <= 3) {
            // 相关视频为空或者剩余视频不足,触发加载相关视频
            await this.getGoodsSimilarly()
         }
         // 切换下一个视频
         const oldIndex = this.similaryVideoIndex;
         this.similaryVideoIndex = Math.min(this.similaryVideoIndex + 1, this.similarlyVideoList.length - 1);
         if (this.similaryVideoIndex > oldIndex) {
            // 把竖向视频的当前播放位置替换为横向视频的当前索引元素
            const video = this.similarlyVideoList[this.similaryVideoIndex];
            video["updateKey"] = video.id + this.similaryVideoIndex
            this.videoList.splice(this.currentIndex, 1, video);
         }
         }
       }
       // 重置坐标
       this.touchXY = {
         startX: 0,
         endX: 0,
         startY: 0,
         endY: 0
      }
   },
    // 收藏/取消收藏
    toggleCollect(item, index) {
     let data = {
@@ -744,20 +1173,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, this.videoContexts);
      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
      console.log(id, index, "触发播放");
      if(index === this.currentIndex) {
         this.currentVideoIsPlaying = true;
         if(! this.duration) {
@@ -766,9 +1215,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) {
@@ -778,29 +1229,27 @@
         const duration = Date.now() - this.startPauseTime
         this.totalPauseTime += duration
      }
      this.videoLoading = false
    },
    // 视频暂停事件
    onPause(index) {
      console.log(index, "触发暂停");
      if(index === this.currentIndex) {
         this.currentVideoIsPlaying = false;
      } else {
         this.currentVideoIsPlaying = true;
         return
         this.startPauseTime = Date.now()
      }
     this.startPauseTime = Date.now()
    },
    // 视频结束事件
    onEnded(index) {
      // this.currentVideoIsPlaying = false;
    },
   // 记录播放时长
   onTimeUpdate(e) {
      this.videoLoading = false
      this.playRecord.playAt = e.detail.currentTime;
      this.currentTime = e.detail.currentTime;
      this.progress = (e.detail.currentTime / this.duration) * 100
   },
@@ -808,50 +1257,59 @@
   handleTouchStart(e) {
     this.isDragging = true;
     this.showProcess = true;
     this.isTouch = true;
     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);
   },
   // 触摸移动
   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()
     const videoContext = uni.createVideoContext(`video${this.currentIndex}`, this);
     videoContext.seek(this.duration * this.progress / 100)
     videoContext.play()
     this.processHidenTimer = setTimeout(() => {
        this.showProcess = false;
        // this.showProcess = true;
        this.isTouch = 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;
   },
   // 视频缓冲
   videoWaiting(index) {
      if (index === this.currentIndex) {
         console.log("视频缓冲中。。。");
         this.videoLoading = true;
      }
   },
   // 获取视频总时长
   onLoadedMetadata(e) {
@@ -862,7 +1320,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,
@@ -883,6 +1341,28 @@
</script>
<style scoped>
   .fullscreen-btn {
      position: absolute;
      right: 45rpx;
      bottom: 70rpx;
     width: 60rpx;
     height: 60rpx;
     border-radius: 50%;
     background-color: rgba(0, 0, 0, 0.4);
     display: flex;
     justify-content: center;
     align-items: center;
     z-index: 999;
     backdrop-filter: blur(10rpx);
     border: 1rpx solid rgba(255, 255, 255, 0.2);
   }
   /* 图标样式 */
   .fullscreen-icon {
     width: 36rpx;
     height: 36rpx;
     opacity: 0.9;
   }
   ::v-deep .custom-tabbar {
      border-top: none !important;
   }
@@ -891,16 +1371,16 @@
     height: 100vh;
     background-color: #000;
   }
   .video-swiper {
     width: 100%;
     height: calc(100% - 50px);
   }
   .video-item {
     width: 100%;
     height: 100%;
     object-fit: cover;
     /* object-fit: cover; */
   }
   .play-icon {
     position: absolute;
@@ -912,9 +1392,9 @@
     z-index: 10;
     opacity: 0.6;
   }
   .video-info {
     width: 70%;
     width: 100%;
     position: absolute;
     bottom: 20px;
     left: 20px;
@@ -922,7 +1402,7 @@
     z-index: 10;
     letter-spacing: 1px;
   }
   .action-buttons {
     position: absolute;
     right: 20px;
@@ -932,7 +1412,7 @@
     align-items: center;
     z-index: 10;
   }
   .action-item {
     margin-bottom: 18px;
     display: flex;
@@ -962,7 +1442,7 @@
     bottom: 0;  /* 定位到底部 */
     left: 50%;  /* 水平居中开始位置 */
     transform: translate(-50%, 50%); /* 水平居中并向下移动50% */
     width: 18px;  /* 图标大小 */
     height: 18px;
     background-color: #FF5A5F; /* 图标背景色 */
@@ -997,27 +1477,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 +1510,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 +1672,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 +1693,7 @@
     font-size: 28rpx;
     color: #333;
   }
   .cancel-reply {
     margin-left: 20rpx;
     color: #576b95;
@@ -1262,20 +1742,25 @@
     bottom: 0;
     width: 100%;
   }
   .progress-bar {
     position: relative;
     width: 100%;
     height: 16px;
     background-color: #eee;
     height: 5px;
     background-color: rgba(255, 255, 255, 0.2); /* 半透明背景 */
     overflow: hidden;
     border-radius: 1.5px;
     cursor: pointer;
     transition: height 0.2s;
   }
   .progress-fill {
     position: absolute;
     left: 0;
     top: 0;
     height: 100%;
     border-radius: 2px;
     background-color: lightgray;
     transition: width 0.1s;
   }
@@ -1288,7 +1773,7 @@
   .progress-text {
     margin-top: 10px;
     font-size: 14px;
     color: #666;
     color: #fff;
   }
   .swiper-box {
     width: 100%;
@@ -1312,4 +1797,10 @@
   .custom-share-btn::after {
     border: none;
   }
</style>
   .topBar {
      position: fixed;
      top: 20rpx;
      left: 20rpx;
      z-index: 1000
   }
</style>