videoDetailComment.vue 8.93 KB
<template>
  <div class="">
    <van-list v-model:loading="loading" :finished="finished" :finished-text="finishedTextStatus ? '没有更多了' : ''" @load="onLoad" :immediate-check="false">
      <template v-for="(item, key) in commentList" :key="key">
        <div class="comment-wrapper">
          <van-row style="font-size: 0.9rem;">
            <van-col span="4">
              <van-image v-if="item.avatar" round width="3rem" height="3rem" :src="item.avatar" />
              <van-image v-else round width="3rem" height="3rem" :src="icon_avatar" />
            </van-col>
            <van-col span="14">
              <p>{{ item.name }}</p>
              <p>{{ item.kg_name }}</p>
            </van-col>
            <van-col span="6" style="text-align: right;">
              <p @click="setComment(item, 'reply')" style="color: #333333;">回复</p>
              <p>{{ item.comment_time }}</p>
            </van-col>
          </van-row>
          <van-row>
            <van-col offset="4">
              <span style="color: #222222;">{{ item.note }}</span>
            </van-col>
          </van-row>
          <van-row>
            <van-col offset="4" span="20">
              <div v-if="item.total" class="reply-wrapper">
                  <template v-for="(reply, index) in item.reply_list" :key="index">
                    <p v-if="reply.reply_to"><span>{{ reply.name }}</span>&nbsp;回复&nbsp;<span>@{{ reply.reply_to }}:</span><span class="content">{{ reply.note }}</span></p>
                    <p v-else><span>{{ reply.name }}:</span><span class="content">{{ reply.note }}</span></p>
                  </template>
                  <p @click="getReplyList(item)">共{{ item.total }}条回复 ></p>
                </div>
            </van-col>
          </van-row>
        </div>
      </template>
    </van-list>
    <van-empty v-if="emptyStatus"
      class="custom-image"
      :image="no_image"
      description="暂无留言"
    />
    <div style="height: 5rem;"></div>
    <div class="reply-btn" @click="setComment('', 'comment')">
      <div class="text">写下你友善的留言</div>
    </div>
  </div>

  <comment-list :showPopup="showCommentListPopup" :data="commentData" @on-close="closeCommentList"></comment-list>
  <comment-box :showPopup="showCommentBoxPopup" :type="commentType" :replayUser="replayUser" @on-submit="submitCommentBox" @on-close="closeCommentBox"></comment-box>

  <!-- 写评论时,如果没有实名认证提示弹框 -->
  <notice-overlay :show="showNotice" text="前往认证" @on-submit="onSubmit" @on-close="onClose">
    <div style="color: #333333;">
      <p>您还没有实名认证</p>
      <p>请前往个人中心进行实名认证</p>
    </div>
  </notice-overlay>
</template>

<script setup>
import { mainStore } from '@/store'

import CommentList from '@/components/CommentList/index.vue'
import CommentBox from '@/components/CommentBox/index.vue'
import NoticeOverlay from '@/components/NoticeOverlay/index.vue'
import icon_avatar from '@images/que-touxiang@2x.png'
import no_image from '@images/que-shuju@2x.png'

import { ref, reactive, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import axios from '@/utils/axios';
import $ from 'jquery'
import _ from 'lodash'
import { Toast } from 'vant';

// 获取是否实名认证
import { useDefaultPerf } from '@/composables';

const $route = useRoute();
const $router = useRouter();

const { userInfo } = useDefaultPerf($route.query.book_id);

/******** 留言列表相关操作 START *******/
const loading = ref(false);
const finished = ref(false);
const limit = ref(5)
const offset = ref(0)
const reply_limit = ref(3)
const commentList = ref([])

// 因为不能让空图标提前出来的写法
const finishedTextStatus = ref(false);
const emptyStatus = ref(false);

const onLoad = () => {
  // 异步更新数据
  axios.get('/srv/?a=comment_list',{
    params: {
      prod_id: $route.query.prod_id,
      limit: limit.value,
      offset: offset.value,
      reply_limit: reply_limit.value
    }
  })
  .then(res => {
    if (res.data.code === 1) {
      commentList.value = _.concat(commentList.value, res.data.data);
      commentList.value = _.uniqBy(commentList.value, 'id');
      offset.value = commentList.value.length;
      loading.value = false;
      // 数据全部加载完成
      if (!res.data.data.length) {
        // 加载状态结束
        finished.value = true;
      }
      if (!commentList.value.length) {
        finishedTextStatus.value = false;
        emptyStatus.value = true;
      }
    } else {
      console.warn(res);
      if (!res.data.show) return false;
      Toast({
        icon: 'close',
        message: res.data.msg
      });
    }
  })
  .catch(err => {
    console.error(err); 
  })
};

const onReload = () => {
  commentList.value = [];
  offset.value = 0;
  onLoad();
  const store = mainStore()
  // 刷新主路由上面的留言数量
  axios.get('/srv/?a=prod_info', {
    params: {
      prod_id: $route.query.prod_id
    }
  })
  .then(res => {
    if (res.data.code === 1) {
      store.changeCommentNum(res.data.data.comment_num);
    } else {
      console.warn(res);
      if (!res.data.show) return false;
      Toast({
        icon: 'close',
        message: res.data.msg
      });
    }
  })
  .catch(err => {
    console.error(err); 
  })
}
/******** 留言框列表相关操作 END *******/

const showNotice = ref(false)
const onClose = () => { // 关闭提示框回调
  showNotice.value = false;
}
// 跳转个人中心
const onSubmit = () => {
  $router.push({
    path: '/me/verifyUser'
  });
}


/******** 留言框相关操作 START *******/
// 回复评论控件
const showCommentBoxPopup = ref(false);
const commentType = ref('comment'); // 类型 comment 为评论/类型 reply 为回复

/**
 * 回复/评论 功能
 * @param {*} v 单行评论数据
 * @param {*} type 类型 comment 为评论/类型 reply 为回复
 */
const commentId = ref('')
const replayUser = ref('')
const setComment = (v, type) => {
  if (userInfo.value.can_upload) {
    showCommentBoxPopup.value = true;
    commentType.value = type;
    replayUser.value = v.name;
    commentId.value = v.id;
  } else {
    showNotice.value = true;
  }
}

/**
 * 提交留言回调
 * @param {*} note 留言内容
 */
const submitCommentBox = (note) => {
  let url = '';
  let data = {}
  // 判断是留言还是回复 动态调整接口名称
  if (commentType.value === 'comment') {
    url = 'add_comment';
    data = {
      prod_id: $route.query.prod_id,
      note
    }
  } else {
    url = 'add_reply';
    data = {
      comment_id: commentId.value,
      note
    }
  }
  axios.post(`/srv/?a=${url}`, data)
  .then(res => {
    if (res.data.code === 1) {
      showCommentBoxPopup.value = false;
      Toast.success('发布成功')
      // 刷新列表
      if (commentType.value === 'comment') {
        // 留言可以刷新列表
        onReload();
      } else {
        // 初始化列表数据,会使得列表滚到最上面
        // 处理方法只能是插入相应的一条数据,这样就需要后台新增完成后,返回完整数据
        _.each(commentList.value, comment => {
          if (comment.id === commentId.value) {
            comment.reply_list.push(res.data.data)
            comment.total = comment.total + 1
          }
        })
      }
      // 不能刷新,只能插入
    } else {
      console.warn(res);
      if (!res.data.show) return false;
      Toast({
        icon: 'close',
        message: res.data.msg
      });
    }
  })
  .catch(err => {
    console.error(err); 
  })
}

const closeCommentBox = (v) => { // 关闭留言框
  showCommentBoxPopup.value = v;
}
/******** 留言框相关操作 START *******/


/******** 查看回复列表操作 START *******/
const showCommentListPopup = ref(false);
const commentData = ref({})
// 查看更多回复
const getReplyList = (v) => {
  showCommentListPopup.value = true;
  commentData.value = v;
}
const closeCommentList = (v) => { // 查看更多回复
  showCommentListPopup.value = false;
  // 关闭时返回评论列表ID和总数量,用于静态更新回复总数
  _.each(commentList.value, comment => {
    if (v.comment_id === comment.id) {
      comment.total = v.total;
    }
  })
}
/******** 查看回复列表操作 END *******/

onMounted(() => {
  onLoad();
})
</script>

<style lang="less" scoped>
.comment-wrapper {
  color: #999999;
  padding: 1rem;
  line-height: 1.75;

  .reply-wrapper {
    background: #F7F7F7;
    border-radius: 10px;
    padding: 0.5rem;
    margin-top: 0.5rem;
    color: #0B3A72;

    .content {
      color: #222222;
    }
  }
}

.reply-btn {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: white;
  box-shadow: 0px -2px 4px 0px rgba(0, 0, 0, 0.07);
  .text {
    text-align: center;
    padding: 0.8rem;
    margin: 0.8rem;
    font-size: 0.85rem;
    font-weight: bold;
    border-radius: 24px;
    // border: 1px solid F7F7F7;
    color: @base-font-color;
    background-color: @base-color;
    box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.06);
  }
}
</style>