绿满眶商城微信小程序-uniapp
xiangpei
2025-06-03 2e24923ed954974c3b134fae89cc23b64f4406b8
视频主页-视频播放接口适配
4个文件已修改
1个文件已添加
1125 ■■■■■ 已修改文件
api/video.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages.json 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/tabbar/index/home.vue 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/video/home-page.vue 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/video/video-play.vue 1025 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
api/video.js
@@ -22,15 +22,16 @@
}
/**
 * 获取五个推荐视频
 * 获取推荐视频
 * 
 * @param params
 */
 export function getRecommendVideos() {
 export function getRecommendVideos(params) {
  return http.request({
    url: "/lmk/video/recommend",
    method: Method.GET,
    needToken: true
    needToken: true,
    params: params
  });
}
pages.json
@@ -806,6 +806,13 @@
                    "style": {
                        "navigationBarTitleText": "TA的主页"
                    }
                },
                {
                    "path" : "video-play",
                    "style" :
                    {
                        "navigationBarTitleText" : ""
                    }
                }
            ]
        },
pages/tabbar/index/home.vue
@@ -8,7 +8,7 @@
      :current="currentIndex"
      @change="onSwiperChange"
    >
      <swiper-item v-for="(item, index) in videoList" :key="item.id + index">
      <swiper-item v-for="(item, index) in videoList" :key="item.id">
        <!-- 播放按钮(仅当视频暂停时显示) -->
        <view 
          class="play-icon" 
@@ -66,25 +66,25 @@
          </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="item.id + tag.id">#{{tag.tagName}}</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)" :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)">
                 <text class="iconfont">&#xe629;</text>
               </view>
           </view>
          <view class="action-item" @click="() => toggleCollect(item, index)">
          <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)">
         <view class="action-item" @click="showComments(item)">
            <text class="iconfont">&#xe7f7;</text>
            <text style="font-size: 10px;font-weight: lighter;">{{item.commentNum}}</text>
          </view>
@@ -114,7 +114,7 @@
            暂无评论,快来发表第一条评论吧~
          </view>
          
          <view v-else class="comment-item" v-for="(comment, index) in comments" :key="comment.id + index">
          <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">
@@ -130,7 +130,7 @@
            </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 + index">
                <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>
@@ -183,6 +183,7 @@
export default {
  data() {
    return {
        videoNoMore: false, // 是否还有更多视频
        commentNoMore: false, // 是否还有更多评论
        commentQuery: {
            pageNumber: 1,
@@ -228,8 +229,11 @@
        ],   // 视频列表数据
        videoContexts: [], // 视频上下文对象集合
        loading: false,  // 是否正在加载
        page: 1,         // 当前页码
        pageSize: 10     // 每页数量
        videoQuery: {
            pageNumber: 1,
            pageSize: 6,
            videoFrom: 'recommend'
        }
    }
  },
  onShow() {
@@ -493,21 +497,31 @@
    
    // 加载视频数据
    async loadVideos() {
      if (this.loading) return;
      if (this.loading || this.videoNoMore) return;
      this.loading = true;
      getRecommendVideos({pageNumber: this.page, pageSize: this.pageSize}).then(res => {
      getRecommendVideos(this.videoQuery).then(res => {
          console.log(res, "视频数据");
          if (this.page === 1) {
          if (this.videoQuery.pageNumber === 1) {
            this.videoList = res.data.data;
          } else {
            this.videoList = [...this.videoList, ...res.data.data];
            this.videoList = [
              ...this.videoList,
              ...res.data.data.filter(
                (newItem) => !this.videoList.some((oldItem) => oldItem.id === newItem.id)
              ),
            ];
          }
          this.page++;
          this.$nextTick(() => {
            this.initVideoContexts();
          });
          this.loading = false;
          if(res.data.data.length < this.videoQuery.pageSize) {
              this.videoNoMore = true;
              return;
          }
          this.videoQuery.pageNumber++;
      })
    },
    
@@ -561,6 +575,7 @@
    },
    // 单击屏幕:暂停或继续播放
    togglePlay(index) {
        console.log("单击视频", index, this.videoContexts);
        if(this.currentVideoIsPlaying) {
            this.videoContexts[index].pause();
        } else {
pages/video/home-page.vue
@@ -65,7 +65,7 @@
                  class="video-item" 
                  v-for="(item, index) in videoList" 
                  :key="item.id"
                  @click="playVideo(index)"
                  @click="playAuthorVideo(index)"
                >
                  <image class="video-cover" :src="item.coverUrl" mode="aspectFill"></image>
                  <view class="video-info">
@@ -85,7 +85,7 @@
                  class="video-item" 
                  v-for="(item, index) in collectVideoList" 
                  :key="item.id"
                  @click="playVideo(index)"
                  @click="playCollectVideo(index)"
                >
                  <image class="video-cover" :src="item.coverUrl" mode="aspectFill"></image>
                  <view class="video-info">
@@ -212,9 +212,11 @@
        getAuthorVideoPage(this.videoQuery).then(res => {
            this.videoList = res.data.data
            this.videoTotal = res.data.total
            if(res.data.data.length < this.videoQuery.pageSize) {
                this.nomoreVideo = true;
            }
        })
    },
    // 获取作品信息
    // 切换关注状态
    toggleFollow() {
      if(this.userInfo.hasSub) {
@@ -260,16 +262,37 @@
        this.collectVideoQuery.authorId = this.authorId
        getAuthorCollectVideoPage(this.collectVideoQuery).then(res => {
            this.collectVideoList = res.data.data
            if(res.data.data.length < this.collectVideoQuery.pageSize) {
                this.nomoreCollectVideo = true;
            }
        })
    },
    // 播放视频
    playVideo(index) {
      const videoItem = this.videoList[index];
    // 播放作者视频
    playAuthorVideo(index) {
      const playInfo = {
          videoList: this.videoList,
          nomore: this.nomoreVideo,
          pageNumber: this.videoQuery.pageNumber,
          playIndex: index
      }
      uni.setStorageSync("playInfo", playInfo)
      uni.navigateTo({
        url: `/pages/video/play?id=${videoItem.id}`
        url: `/pages/video/video-play?authorId=${this.authorId}&videoFrom=author`
      });
    },
    // 播放收藏视频
    playAuthorVideo(index) {
      const playInfo = {
          videoList: this.collectVideoList,
          nomore: this.nomoreCollectVideo,
          pageNumber: this.collectVideoQuery.pageNumber,
          playIndex: index
      }
      uni.setStorageSync("playInfo", playInfo)
      uni.navigateTo({
        url: `/pages/video/video-play?authorId=${this.authorId}&videoFrom=collect`
      });
    },
    // 编辑个人资料
    editProfile() {
      uni.navigateTo({
pages/video/video-play.vue
New file
@@ -0,0 +1,1025 @@
<template>
  <view class="video-container">
    <!-- 视频列表 -->
    <swiper
      class="video-swiper"
      vertical
      circular
      :current="currentIndex"
      @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>
         <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>
      </swiper-item>
    </swiper>
    <!-- 评论弹窗 -->
    <uni-popup ref="commentPopup" type="bottom" :is-mask-click="true" @maskClick="closeCommentPopup">
      <view class="comment-popup">
        <view class="popup-header">
          <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>
        <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;">
                <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
            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>
  </view>
</template>
<script>
import { getRecommendVideos, savePlayRecord, subscribe, getVideoComments, addVideoComment, thubmsUpComment, cancelThubmsUpComment } from "@/api/video.js";
import { changeCollect } from "@/api/collect.js";
export default {
  data() {
    return {
        videoNoMore: false, // 是否还有更多视频
        commentNoMore: false, // 是否还有更多评论
        commentQuery: {
            pageNumber: 1,
            pageSize: 5,
            videoId: '',
            masterCommentId: ''
        },
        replyCommentQuery: {
            pageNumber: 1,
            pageSize: 5,
            videoId: '',
            masterCommentId: ''
        },
        commentForm: { // 评论表单数据
            id: '',
            videoId: '',
            commentContent: '',
            replyId: '',
            replyUserId: '',
            replyUserNickname: '',
            replyUserAvatar: '',
            masterCommentId: null
        },
        comments: [],            // 评论列表
        commentsTotal: 0,            // 评论总条数
        commentLoading: false,   // 评论加载状态
        startHidenTime: 0, // 记录切换至其它页面的时间,用于计算视频观看时间减去的部分
        totalHidenTime: 0, // 总共隐藏页面的时间
        startPauseTime: 0, // 开始暂停的时间
        totalPauseTime: 0, // 总共暂停的时间
        playRecord: {
            videoId: null,
            viewDuration: 0, // 这个视频总共观看了多久
            playAt: 0 ,// 这个视频播放到哪了
            startPlayTime: 0 // 这个视频从什么时候开始播放的
        },
        currentVideoIsPlaying: true, // 当前视频是否正在播放
        isFullScreen: false,
        windowHeight: 0,
        currentIndex: 0, // 当前播放的视频索引
        videoList: [
        ],   // 视频列表数据
        videoContexts: [], // 视频上下文对象集合
        loading: false,  // 是否正在加载
        videoQuery: {
            pageNumber: 1,
            pageSize: 6,
            authorId: '',
            videoFrom: ''
        }
    }
  },
  onShow() {
      this.loadVideos()
      // 如果视频按下暂停后切换页面再回到页面时,只算暂停时间(因为暂停时间和离开页面时间是重复的,只算一个)
      if(this.startHidenTime !== 0 && this.currentVideoIsPlaying) {
          const duration = Date.now() - this.startHidenTime
          this.totalHidenTime += duration
      }
  },
  onHide() {
      this.startHidenTime = Date.now()
  },
  onLoad(option) {
      const playInfo = uni.getStorageSync("playInfo", playInfo);
      if(playInfo) {
          this.videoList = playInfo.videoList;
          this.videoQuery.pageNumber = playInfo.pageNumber;
          this.videoNoMore = playInfo.nomore;
          this.videoQuery.authorId = option.authorId;
          this.videoQuery.videoFrom = option.videoFrom;
          this.currentIndex = playInfo.playIndex;
      } else {
          this.videoQuery.videoFrom = 'recommend';
          this.loadVideos();
      }
  },
  onReady() {
    // 初始化视频上下文
    this.initVideoContexts();
  },
  methods: {
        // 跳转个人主页
        jumpToHomePage(authorId) {
            uni.navigateTo({
                url: "/pages/video/home-page?authorId=" + authorId
            })
        },
        // 取消点赞
        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);
          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()}日`;
        },
        // 提交评论
        async submitComment() {
          if (!this.commentForm.commentContent.trim()) {
            uni.showToast({
              title: '评论内容不能为空',
              icon: 'none'
            });
            return;
          }
          // 发表评论
          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()) {
                        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);
                  }
                  console.log("新增后",this.comments);
                  uni.showToast({
                    title: '评论成功'
                  });
                  // 当前视频评论数加一
                  this.commentsTotal += 1;
                  this.videoList[this.currentIndex].commentNum += 1;
              } else {
                  uni.showToast({
                          title: res.data.msg,
                          icon: 'none'
                        });
              }
          }).catch(() => {
              uni.showToast({
                      title: '评论失败',
                      icon: 'none'
                    });
          })
        },
        // 关闭评论弹窗
        closeCommentPopup() {
          this.$refs.commentPopup.close()
          this.showCommentPopup = false;
          this.comments = [];
          this.resetCommentForm()
          this.commentQuery.pageNumber = 1;
          this.commentNoMore = false;
        },
        // 下滑评论区加载评论
        async getCommentPage() {
            if(this.commentNoMore) {
                return;
            }
            getVideoComments(this.commentQuery).then(res => {
                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++;
            })
        },
        // 显示评论弹窗
        async showComments(item) {
          this.commentForm.videoId = item.id;
          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.commentsTotal = res.data.total;
              this.comments = res.data.data;
              this.commentQuery.pageNumber += 2;
              this.commentQuery.pageSize /= 2;
          }).catch(() => {
              uni.showToast({
                title: '获取评论失败',
                icon: 'none'
              });
          }).finally(() => {
              this.commentLoading = false;
          })
        },
      // 关注作者
      subscribeAuth(index, authorId) {
        this.videoList.forEach(video => {
            if(video.authorId === authorId) {
                video.subscribeThisAuthor = true
            }
        })
        subscribe(authorId).then(res => {
            if(res.data.code === 200) {
                uni.showToast({
                  title: '关注成功~',
                  icon: 'none'
                });
            } else {
                this.videoList.forEach(video => {
                    if(video.authorId === authorId) {
                        video.subscribeThisAuthor = false
                    }
                })
            }
        })
      },
    // 初始化视频上下文
    initVideoContexts() {
      this.videoContexts = this.videoList.map((_, index) => {
          let videoContent = uni.createVideoContext(`video${index}`, this);
          return videoContent;
      });
    },
    // 加载视频数据
    async loadVideos() {
      if (this.loading || this.videoNoMore) return;
      this.loading = true;
      getRecommendVideos(this.videoQuery).then(res => {
          if (this.videoQuery.pageNumber === 1) {
            this.videoList = res.data.data;
          } else {
            this.videoList = [
              ...this.videoList,
              ...res.data.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) {
        // 如果视频处于暂停状态往下刷视频,那么需要再计算一次暂停时间
        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.startPauseTime = 0;
        // 播放当前视频
        if (this.videoContexts[this.currentIndex]) {
            this.videoContexts[this.currentIndex].play();
        }
    },
    // 收藏/取消收藏
    toggleCollect(item, index) {
      let data = {
          refId: item.id,
          collectType: 'video'
      }
      const beforeCollected = item.collected
      const beforeCollectNum = item.collectNum
      if(item.collected) {
          this.videoList[index].collected = false
          this.videoList[index].collectNum -= 1
      } else {
          this.videoList[index].collected = true
          this.videoList[index].collectNum += 1
      }
      changeCollect(data).then(res => {
          if(res.data.code !== 200) {
              this.videoList[index].collected = beforeCollected
              this.videoList[index].collectNum = beforeCollectNum
          }
      })
    },
    // 单击屏幕:暂停或继续播放
    togglePlay(index) {
        if(this.currentVideoIsPlaying) {
            this.videoContexts[index].pause();
        } else {
            this.videoContexts[index].play();
        }
    },
    // 视频播放事件
    onPlay(id, index) {
        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) {
            this.playRecord.startPlayTime = Date.now();
        }
        if(this.startPauseTime !== 0) {
            const duration = Date.now() - this.startPauseTime
            this.totalPauseTime += duration
        }
    },
    // 视频暂停事件
    onPause(index) {
        console.log(index, "触发暂停");
        if(index === this.currentIndex) {
            this.currentVideoIsPlaying = false;
        } else {
            this.currentVideoIsPlaying = true;
            return
        }
      this.startPauseTime = Date.now()
    },
    // 视频结束事件
    onEnded(index) {
      // this.currentVideoIsPlaying = false;
    },
    // 记录播放时长
    onTimeUpdate(e) {
        this.playRecord.playAt = e.detail.currentTime
    },
    // 保存播放记录
    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,
            playAt: this.playRecord.playAt
        }
        this.playRecord = {
            videoId: null,
            viewDuration: 0, // 这个视频总共观看了多久
            playAt: 0 ,// 这个视频播放到哪了
            startPlayTime: 0 // 这个视频从什么时候开始播放的
        }
        this.totalHidenTime = 0
        this.totalPauseTime = 0
        savePlayRecord(data)
    }
  }
}
</script>
<style scoped>
    ::v-deep .custom-tabbar {
        border-top: none !important;
    }
    .video-container {
      width: 100%;
      height: 100vh;
      background-color: #000;
    }
    .video-swiper {
      width: 100%;
      height: 100%;
    }
    .video-item {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    .play-icon {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 45px;
      height: 45px;
      z-index: 10;
      opacity: 0.6;
    }
    .video-info {
      width: 70%;
      position: absolute;
      bottom: 70px;
      left: 20px;
      color: #f8f8f8;
      z-index: 10;
      letter-spacing: 1px;
    }
    .action-buttons {
      position: absolute;
      right: 20px;
      bottom: 150px;
      display: flex;
      flex-direction: column;
      align-items: center;
      z-index: 10;
    }
    .action-item {
      margin-bottom: 18px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      color: #fff;
    }
    .avatar-container {
      margin-bottom: 27px;
      position: relative;  /* 为绝对定位的子元素提供定位上下文 */
      width: 40px;
      height: 40px;
      display: inline-block; /* 使容器根据内容调整大小 */
    }
    .avatar {
      border: 2px solid #FFFFFF;
      box-sizing: border-box;
      width: 100%;
      height: 100%;
      border-radius: 50%;  /* 关键属性,设置为50%即可实现圆形 */
      overflow: hidden;    /* 确保图片不会超出圆形边界 */
      display: block;
    }
    .follow-icon {
      position: absolute;
      bottom: 0;  /* 定位到底部 */
      left: 50%;  /* 水平居中开始位置 */
      transform: translate(-50%, 50%); /* 水平居中并向下移动50% */
      width: 18px;  /* 图标大小 */
      height: 18px;
      background-color: #FF5A5F; /* 图标背景色 */
      border-radius: 50%;
      display: flex;
      justify-content: center;
      align-items: center;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 轻微阴影 */
    }
    .video-tag {
        margin-left: 5px;
        font-weight: bold;
        color: #eeeeee;
    }
    .video-author {
        font-size: 1.2em;
    }
    /* 商品链接悬挂层样式 */
    .goods-link-warp {
        position: absolute;
        bottom: 160px;
        left: 20px;
        color: #f8f8f8;
        z-index: 10;
    }
    .goods-link {
      position: relative;
      margin: 20rpx 0;
      padding: 12rpx;
      background-color: rgba(255, 255, 255, 0.9);
      border-radius: 12rpx;
      box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
    }
    .goods-container {
      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;
      font-weight: bold;
      display: -webkit-box;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      overflow: hidden;
      margin-bottom: 8rpx;
    }
    .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;
      padding: 10rpx 28rpx;
      border-radius: 20rpx;
      font-size: 26rpx;
      font-weight: bold;
    }
    /* 评论弹窗样式 */
    .comment-popup {
      background-color: #fff;
      border-radius: 20rpx 20rpx 0 0;
      padding-bottom: env(safe-area-inset-bottom);
      height: 60vh;
      display: flex;
      flex-direction: column;
    }
    .popup-header {
      padding: 30rpx;
      display: flex;
      justify-content: space-between;
      align-items: center;
      border-bottom: 1rpx solid #f5f5f5;
    }
    .popup-title {
      font-size: 32rpx;
      font-weight: bold;
    }
    .close-icon {
      /* font-size: 36rpx; */
      color: #999;
    }
    .comment-list {
      flex: 1;
      padding: 0rpx 20rpx 20rpx 20rpx;
      box-sizing: border-box;
      height: calc(60vh - 260rpx);
    }
    .comment-item {
      display: flex;
      flex-direction: column;
      padding: 10rpx 0 20rpx 0;
    }
    .comment-avatar {
      width: 70rpx;
      height: 70rpx;
      border-radius: 50%;
      margin-right: 10rpx;
    }
    .comment-reply-avatar {
        width: 40rpx;
        height: 40rpx;
        border-radius: 50%;
        margin-right: 10rpx;
    }
    .comment-content {
      flex: 1;
    }
    .nickname {
      font-size: 28rpx;
      color: #666;
      display: block;
      margin-bottom: 10rpx;
    }
    .content {
      font-size: 28rpx;
      color: #333;
      display: block;
      margin-bottom: 10rpx;
    }
    .time {
      font-size: 28rpx;
      color: #999;
    }
    .comment-input-area {
      display: flex;
      padding: 20rpx 30rpx;
      align-items: center;
    }
    .comment-input {
      flex: 1;
      background-color: #fff;
      height: 80rpx;
      border: 1px solid #dcdcdc;
      border-radius: 40rpx;
      padding: 0 30rpx;
      font-size: 28rpx;
    }
    .placeholder {
      color: #ccc;
    }
    .submit-btn {
      margin-left: 20rpx;
      background-color: #07c160;
      color: #fff;
      border-radius: 40rpx;
      padding: 0 30rpx;
      height: 80rpx;
      line-height: 80rpx;
      font-size: 28rpx;
    }
    .loading, .empty {
      padding: 40rpx 0;
      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>