绿满眶商城微信小程序-uniapp
zxl
2025-08-25 c1626f0e66bb1301668698c71c490a0bdc1c6a0e
pages/tabbar/index/home.vue
@@ -34,12 +34,18 @@
            :ref="'video'+index"
            :src="item.videoUrl"
            :autoplay="index === currentIndex"
            :controls="false"
            :controls="isControls"
            :loop="true"
            :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)"
@@ -48,6 +54,14 @@
            @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.stop="handleTouchStart"
@@ -56,9 +70,10 @@
            :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 v-show="isTouch" class="progress-text">{{ hasPlayTime }}/{{formartDuration}}</view>
              <view
               class="progress-bar"
               id="progressBar"
@@ -70,6 +85,7 @@
            </view>
           </view>
      </view>
      <view style="width: 100%; height: 100%;" v-else-if="item.videoContentType === 'img'">
        <uni-swiper-dot
         :info="item.imgs"
@@ -122,7 +138,7 @@
        <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: 45px;">&#xe64e;</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>
@@ -186,11 +202,13 @@
         </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 @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>
@@ -202,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">
@@ -245,6 +263,16 @@
   <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>
@@ -258,24 +286,38 @@
   thubmsUpComment, 
   cancelThubmsUpComment,
   changeThumbsUp,
   getGoodsSimilarlyVideos
   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},
  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,
@@ -288,7 +330,7 @@
      barWidth: 0, // 进度条宽度
      isDragging: false, // 是否正在拖动
      processHidenTimer: null, // 进度条隐藏定时器
      showProcess: false, // 是否显示进度条
      showProcess: true, // 是否显示进度条
      videoNoMore: false, // 是否还有更多视频
      commentNoMore: false, // 是否还有更多评论
      commentQuery: {
@@ -359,6 +401,13 @@
    }
  },
  onShow() {
     this.openActivityPopup()
     if(!this.userId){
        this.getUserId()
     }
     // const token = storage.getAccessToken();
     // if (! token) {
       //  this.wxSilentLogin(() => {
@@ -368,6 +417,9 @@
     //      this.loadVideos();
     // }
     if (this.videoList.length < 1) {
        this.loading = false;
        this.videoNoMore = false;
        console.log('触发数据加载')
        this.loadVideos();
     }
     // 如果视频按下暂停后切换页面再回到页面时,只算暂停时间(因为暂停时间和离开页面时间是重复的,只算一个)
@@ -380,6 +432,7 @@
     this.startHidenTime = Date.now()
  },
  onLoad(option) {
     console.log('-----------分享出的数据---------->',option)
     //处理扫码出来的视频
     this.marginBottom = uni.getSystemInfoSync().safeAreaInsets.bottom
@@ -433,12 +486,137 @@
      shareUser: userInfo.id
   }
   saveShare(data)
     return {
        title: videoInfo.title,
        path: `/pages/tabbar/index/home?videoId=${videoInfo.id}&userId=${userInfo.id}`,
     }
  },
  methods: {
   // 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 = {};
@@ -746,6 +924,7 @@
            this.commentQuery.pageNumber++;
         })
      },
       // 显示评论弹窗
       async showComments(item) {
         this.commentForm.videoId = item.id;
@@ -758,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(() => {
@@ -1077,6 +1257,7 @@
   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);
@@ -1100,7 +1281,8 @@
     videoContext.seek(this.duration * this.progress / 100)
     videoContext.play()
     this.processHidenTimer = setTimeout(() => {
        this.showProcess = false;
        // this.showProcess = true;
        this.isTouch = false;
      }, 1000);
   },
@@ -1159,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;
   }
@@ -1542,16 +1746,21 @@
   .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;
   }