绿满眶商城微信小程序-uniapp
xiangpei
2025-05-30 e15dbdbc396f61a645c8d8a504b45476f1fcea08
pages/tabbar/index/home.vue
@@ -13,7 +13,7 @@
      <view 
        class="play-icon" 
        @click="togglePlay(index)"
        v-if="currentVideoIsPlaying != null && !currentVideoIsPlaying"
        v-if="!currentVideoIsPlaying"
      >
        <image src="/static/video/play.png" style="width: 45px;height: 45px" mode="aspectFit"></image>
      </view>
@@ -97,7 +97,11 @@
   <uni-popup ref="commentPopup" type="bottom" :is-mask-click="true" @maskClick="closeCommentPopup">
     <view class="comment-popup">
       <view class="popup-header">
         <text class="popup-title">评论({{comments.length}})</text>
         <text class="popup-title" v-if="!commentForm.replyId">评论({{commentsTotal}})</text>
        <view class="reply-title" v-else>
          <text>回复 @{{commentForm.replyUserNickname}}</text>
          <text class="cancel-reply" @click="cancelReply">取消</text>
        </view>
         <text class="iconfont close-icon" @click="closeCommentPopup">&#xe675;</text>
       </view>
       
@@ -110,25 +114,61 @@
           暂无评论,快来发表第一条评论吧~
         </view>
         
         <view v-else class="comment-item" v-for="comment in comments" :key="comment.id">
           <image class="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>
             <text class="time">{{formatTime(comment.createTime)}}</text>
           </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;">
               <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>
               <text v-else class="thumbs-up time iconfont" @click="cancelThumbsUp(comment.id, index, null)">&#xe607;<text v-show="comment.thumbsUpNum > 0" class="thumbs-num">{{comment.thumbsUpNum}}</text></text>
              </view>
            </view>
         </view>
         <!-- 回复列表 -->
           <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 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>
               </view>
               <text class="content">{{reply.commentContent}}</text>
               <view class="reply-footer">
                 <text class="time">{{formatTime(reply.createTime)}}</text>
                 <text @click="openReply(comment, reply)" class="reply-btu time">回复</text>
                 <text v-if="!reply.hasThumbsUp" class="thumbs-up time iconfont" @click="thubmsUp(reply.id, index, replyIndex)">&#xe614;<text v-show="reply.thumbsUpNum > 0" class="thumbs-num">{{reply.thumbsUpNum}}</text></text>
                 <text v-else class="thumbs-up time iconfont" @click="cancelThumbsUp(reply.id, index, replyIndex)">&#xe607;<text v-show="reply.thumbsUpNum > 0" class="thumbs-num">{{reply.thumbsUpNum}}</text></text>
               </view>
              </view>
            </view>
           </view>
           <view class="view-more-replies" v-if="comment.replyTotalCount > 0 && !comment.expandReply" @click="loadRepliesPage(comment, index)">
            <text class="line">——</text>展开{{comment.replyTotalCount}}条回复 ↓
           </view>
           <view class="reply-op" v-if="comment.replyTotalCount > replyCommentQuery.pageNumber * replyCommentQuery.pageSize && comment.expandReply">
              <view @click="loadNextPageReply(index)" class="reply-op-item"><text class="line">——</text>展开更多<text class="iconfont textSideIcon">&#xeb8d;</text></view>
              <view @click="retractReplyComment(index)" class="reply-op-item" style="margin-left: 50rpx;">收起<text class="iconfont textSideIcon">&#xeb9b;</text></view>
           </view>
           <view class="reply-op" v-else-if="comment.replyTotalCount <= replyCommentQuery.pageNumber * replyCommentQuery.pageSize && comment.expandReply">
              <view @click="retractReplyComment(index)" class="reply-op-item"><text class="line">——</text>收起<text class="iconfont textSideIcon">&#xeb9b;</text></view>
           </view>
         </view>
       </scroll-view>
       <view class="comment-input-area">
         <input
           class="comment-input"
           v-model="commentForm.commentContent"
           placeholder="写下你的评论..."
           placeholder-class="placeholder"
         />
         <button class="submit-btn" @click="submitComment">发送</button>
       </view>
        <input
         ref="commentInput"
         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>
   
@@ -138,24 +178,36 @@
</template>
<script>
import { getRecommendVideos, savePlayRecord, subscribe, getVideoComments, addVideoComment } from "@/api/video.js";
import { getRecommendVideos, savePlayRecord, subscribe, getVideoComments, addVideoComment, thubmsUpComment, cancelThubmsUpComment } from "@/api/video.js";
import { changeCollect } from "@/api/collect.js";
export default {
  data() {
    return {
      commentNoMore: false, // 是否还有更多评论
      commentQuery: {
         pageNumber: 1,
         pageSize: 5,
         videoId: '',
         masterCommentId: ''
      },
      replyCommentQuery: {
         pageNumber: 1,
         pageSize: 5,
         videoId: '',
         masterCommentId: ''
      },
      commentForm: { // 评论表单数据
         id: null,
         videoId: null,
         id: '',
         videoId: '',
         commentContent: '',
         replyId: null
         replyId: '',
         replyUserId: '',
         replyUserNickname: '',
         replyUserAvatar: '',
         masterCommentId: null
      },
      comments: [],            // 评论列表
      commentsTotal: 0,            // 评论总条数
      commentLoading: false,   // 评论加载状态
      startHidenTime: 0, // 记录切换至其它页面的时间,用于计算视频观看时间减去的部分
      totalHidenTime: 0, // 总共隐藏页面的时间
@@ -167,7 +219,7 @@
         playAt: 0 ,// 这个视频播放到哪了
         startPlayTime: 0 // 这个视频从什么时候开始播放的
      },
      currentVideoIsPlaying: null, // 当前视频是否正在播放
      currentVideoIsPlaying: true, // 当前视频是否正在播放
      isFullScreen: false,
      windowHeight: 0,
      currentIndex: 0, // 当前播放的视频索引
@@ -181,6 +233,7 @@
    }
  },
  onShow() {
     this.loadVideos()
     // 如果视频按下暂停后切换页面再回到页面时,只算暂停时间(因为暂停时间和离开页面时间是重复的,只算一个)
     if(this.startHidenTime !== 0 && this.currentVideoIsPlaying) {
        const duration = Date.now() - this.startHidenTime
@@ -198,6 +251,97 @@
    this.initVideoContexts();
  },
  methods: {
      // 取消点赞
      async cancelThumbsUp(id, commentIndex, replyIndex) {
         const data = {
            refId: id,
            thumbsUpType: 'video_comment'
         }
         cancelThubmsUpComment(data).then(res => {
            if(replyIndex != null) {
               this.comments[commentIndex].replies[replyIndex].hasThumbsUp = false;
               this.comments[commentIndex].replies[replyIndex].thumbsUpNum -= 1;
            } else {
               this.comments[commentIndex].hasThumbsUp = false;
               this.comments[commentIndex].thumbsUpNum -= 1;
            }
         })
      },
      // 评论点赞
      async thubmsUp(id, commentIndex, replyIndex) {
         const data = {
            refId: id,
            thumbsUpType: 'video_comment'
         }
         thubmsUpComment(data).then(res => {
            if(replyIndex != null) {
               this.comments[commentIndex].replies[replyIndex].hasThumbsUp = true;
               this.comments[commentIndex].replies[replyIndex].thumbsUpNum += 1;
            } else {
               this.comments[commentIndex].hasThumbsUp = true;
               this.comments[commentIndex].thumbsUpNum += 1;
            }
         })
      },
      // 加载下一页回复
      loadNextPageReply(index) {
         this.replyCommentQuery.pageNumber++;
         getVideoComments(this.replyCommentQuery).then(res => {
            this.comments[index].replies = [
              ...this.comments[index].replies,
              ...res.data.data.filter(
                (newItem) => !this.comments[index].replies.some((oldItem) => oldItem.id === newItem.id)
              ),
            ];
         })
      },
      // 收起回复
      retractReplyComment(index) {
         this.comments[index].expandReply = false;
         this.comments[index].replies = [];
      },
      // 加载回复
      loadRepliesPage(comment, index) {
         this.replyCommentQuery.pageNumber = 1;
         this.replyCommentQuery.masterCommentId = comment.id
          getVideoComments(this.replyCommentQuery).then(res => {
            this.comments[index].replies = res.data.data;
            this.comments[index].expandReply = true;
         })
      },
      resetCommentForm() {
         const videoId = this.commentForm.videoId;
         this.commentForm = { // 评论表单数据
            id: '',
            videoId: '',
            commentContent: '',
            replyId: '',
            replyUserId: '',
            replyUserNickname: '',
            replyUserAvatar: '',
            masterCommentId: null
         }
      },
       // 取消回复
       cancelReply() {
           this.resetCommentForm()
         },
      // 打开回复框
      openReply(comment, reply = null) {
        if(reply) {
         comment = reply
        }
        this.commentForm.masterCommentId = comment.masterCommentId ? comment.masterCommentId : comment.id;
        this.commentForm.replyId = comment.id;
        this.commentForm.replyUserId = comment.userId;
        this.commentForm.replyUserNickname = comment.userNickname;
        this.commentForm.replyUserAvatar = comment.userAvatar;
        // 自动聚焦输入框
        this.$nextTick(() => {
         const input = this.$refs.commentInput;
         if (input) input.focus();
        });
      },
      // 格式化时间
       formatTime(time) {
         const date = new Date(time);
@@ -222,17 +366,26 @@
        // 发表评论
         addVideoComment(this.commentForm).then(res => {
           if(res.data.code === 200) {
              this.commentForm = {
                         id: null,
                         videoId: null,
                         commentContent: '',
                         replyId: null
              this.resetCommentForm()
              // 如果是评论别人的回复,那么就将这个发布到replies里面
              if(res.data.data.replyId) {
                 for (const [index, item] of this.comments.entries()) {
                   if (item.id === res.data.data.replyId) {
                     item.replies.unshift(res.data.data);
                     // this.loadRepliesPage(item, index)
                     break; // 跳出循环
                   }
                 }
              } else {
               this.comments.unshift(res.data.data);
              }
              this.comments.unshift(res.data.data);
              console.log("新增后",this.comments);
              uni.showToast({
                title: '评论成功'
              });
              // 当前视频评论数加一
              this.commentsTotal += 1;
              this.videoList[this.currentIndex].commentNum += 1;
           } else {
              uni.showToast({
@@ -249,21 +402,34 @@
       },
       // 关闭评论弹窗
       closeCommentPopup() {
         console.log("触发了");
        this.$refs.commentPopup.close()
         this.showCommentPopup = false;
         this.comments = [];
         this.commentForm = {
           id: null,
           videoId: null,
           commentContent: '',
           replyId: null
        }
         this.resetCommentForm()
        this.commentQuery.pageNumber = 1;
        this.commentNoMore = false;
       },
      // 下滑评论区加载评论
      async getCommentPage() {
         this.commentQuery.pageNumber += 1;
         if(this.commentNoMore) {
            return;
         }
         getVideoComments(this.commentQuery).then(res => {
            this.comments.push(res.data.data)
            if(this.commentQuery.pageNumber === 1) {
               this.comments = res.data.data
            } else {
               this.comments = [
                 ...this.comments,
                 ...res.data.data.filter(
                   (newItem) => !this.comments.some((oldItem) => oldItem.id === newItem.id)
                 ),
               ];
            }
            if (res.data.data.length < this.commentQuery.pageSize) {
               this.commentNoMore = true;
               return;
            }
            this.commentQuery.pageNumber++;
         })
      },
       // 显示评论弹窗
@@ -272,9 +438,14 @@
         this.$refs.commentPopup.open();
         this.commentLoading = true;
         this.commentQuery.videoId = item.id
         this.replyCommentQuery.videoId = item.id
        // 首次加载评论分页大小增加一倍,以产生滚动条,后续可触发
        this.commentQuery.pageSize *= 2;
        getVideoComments(this.commentQuery).then(res => {
           this.comments = res.data.data
           this.commentsTotal = res.data.total;
           this.comments = res.data.data;
           this.commentQuery.pageNumber += 2;
           this.commentQuery.pageSize /= 2;
        }).catch(() => {
           uni.showToast({
             title: '获取评论失败',
@@ -347,14 +518,13 @@
      // 保存上一个视频的播放记录
      this.savePlayRecord()
      const oldIndex = this.currentIndex;
      console.log("视频上下文",this.videoContexts[oldIndex]);
      this.currentIndex = e.detail.current;
      // 暂停上一个视频
      if (this.videoContexts[oldIndex]) {
         this.videoContexts[oldIndex].pause();
      }
      this.currentVideoIsPlaying = true;
      this.startPauseTime = 0;
      // 播放当前视频
      if (this.videoContexts[this.currentIndex]) {
@@ -394,7 +564,13 @@
   },
    // 视频播放事件
    onPlay(id, index) {
      this.currentVideoIsPlaying = true;
      console.log(id, index, "触发播放");
      if(index === this.currentIndex) {
         this.currentVideoIsPlaying = true;
      } else {
         this.currentVideoIsPlaying = false;
         return
      }
      this.playRecord.videoId = id;
      // 没初始化才赋值,因为一个视频重复播放onPlay会重复触发
      if(this.playRecord.startPlayTime === 0) {
@@ -408,7 +584,13 @@
    
    // 视频暂停事件
    onPause(index) {
      this.currentVideoIsPlaying = false;
      console.log(index, "触发暂停");
      if(index === this.currentIndex) {
         this.currentVideoIsPlaying = false;
      } else {
         this.currentVideoIsPlaying = true;
         return
      }
     this.startPauseTime = Date.now()
    },
    
@@ -604,7 +786,7 @@
   }
   
   .original-price {
     font-size: 24rpx;
     font-size: 28rpx;
     color: #999;
     text-decoration: line-through;
   }
@@ -617,7 +799,7 @@
   .buy-button {
     background: linear-gradient(to right, #ff5a5f, #ff2e4d);
     color: white;
     padding: 10rpx 24rpx;
     padding: 10rpx 28rpx;
     border-radius: 20rpx;
     font-size: 26rpx;
     font-weight: bold;
@@ -659,14 +841,21 @@
   .comment-item {
     display: flex;
     padding: 10rpx 0;
     flex-direction: column;
     padding: 10rpx 0 20rpx 0;
   }
   .avatar {
     width: 80rpx;
     height: 80rpx;
   .comment-avatar {
     width: 70rpx;
     height: 70rpx;
     border-radius: 50%;
     margin-right: 20rpx;
     margin-right: 10rpx;
   }
   .comment-reply-avatar {
      width: 40rpx;
      height: 40rpx;
      border-radius: 50%;
      margin-right: 10rpx;
   }
   .comment-content {
@@ -674,21 +863,21 @@
   }
   .nickname {
     font-size: 24rpx;
     font-size: 28rpx;
     color: #666;
     display: block;
     margin-bottom: 10rpx;
   }
   .content {
     font-size: 24rpx;
     font-size: 28rpx;
     color: #333;
     display: block;
     margin-bottom: 10rpx;
   }
   .time {
     font-size: 24rpx;
     font-size: 28rpx;
     color: #999;
   }
@@ -728,4 +917,82 @@
     text-align: center;
     color: #999;
   }
   .reply-list {
     margin-top: 20rpx;
     padding-left: 80rpx;
   }
   .reply-op {
      margin-top: 10rpx;
      padding-left: 80rpx;
      display: flex;
      font-size: 28rpx;
      color: #333;
   }
   .reply-op-item {
      display: flex;
      align-items: center;
      height: 40rpx;
   }
   .reply-item {
     display: flex;
     margin-bottom: 20rpx;
   }
   .reply-content {
     flex: 1;
   }
   .reply-to {
     color: #576b95;
     margin: 0 10rpx;
     font-size: 28rpx;
   }
   .reply-title {
     display: flex;
     align-items: center;
     font-size: 28rpx;
     color: #333;
   }
   .cancel-reply {
     margin-left: 20rpx;
     color: #576b95;
     font-size: 28rpx;
     padding: 6rpx 12rpx;
     background: #f5f5f5;
     border-radius: 20rpx;
   }
   .view-more-replies {
     color: #576b95;
     font-size: 28rpx;
     padding: 10rpx 0;
     padding-left: 80rpx;
   }
   .comment-footer, .reply-footer {
     display: flex;
     align-items: center;
     font-size: 28rpx;
     color: #999;
   }
   .reply-btu {
      margin-left: 30rpx;
   }
   .thumbs-up {
      position: absolute;
      right: 20rpx;
      font-size: 32rpx;
      width: 120rpx;
   }
   .textSideIcon {
      font-size: 36rpx;
      margin-left: 5rpx;
   }
   .line {
      margin-right: 10rpx;
      color: #cccccc;
   }
   .thumbs-num {
      margin-left: 4rpx;
   }
</style>