Newer
Older
dxCard-admin / src / components / jeecg / comment / CommentList.vue
YFJ on 23 Sep 11 KB 项目推送
<template>
  <div :style="{ position: 'relative', height: allHeight + 'px' }">
    <a-list ref="listRef" class="jeecg-comment-list" header="" item-layout="horizontal" :data-source="dataList" :style="{ height: commentHeight + 'px' }">
      <template #renderItem="{ item }">
        <a-list-item style="padding-left: 10px; flex-direction: column" @click="handleClickItem">
          <a-comment>
            <template #avatar>
              <a-avatar class="tx" :src="getAvatar(item)" :alt="getAvatarText(item)">{{ getAvatarText(item) }}</a-avatar>
            </template>

            <template #author>
              <div class="comment-author">
                <span>{{ item.fromUserId_dictText }}</span>

                <template v-if="item.toUserId">
                  <span>回复</span>
                  <span>{{ item.toUserId_dictText }}</span>
                  <Tooltip class="comment-last-content" @openChange="(v)=>visibleChange(v, item)">
                    <template #title>
                      <div v-html="getHtml(lineFeed(item.commentId_dictText))"></div>
                    </template>
                    <message-outlined />
                  </Tooltip>
                </template>
              </div>
            </template>

            <template #datetime>
              <div>
                <Tooltip :title="item.createTime">
                  <span>{{ getDateDiff(item) }}</span>
                </Tooltip>
              </div>
            </template>

            <template #actions>
              <span @click="showReply(item)">回复</span>

              <Popconfirm title="确定删除吗?" @confirm="deleteComment(item)">
                <span>删除</span>
              </Popconfirm>
            </template>

            <template #content>
              <div class="content" v-html="getHtml(lineFeed(item.commentContent))" style="font-size: 15px">
              </div>

              <div v-if="item.fileList && item.fileList.length > 0">
                <!-- 历史文件 -->
                <history-file-list :dataList="item.fileList" isComment></history-file-list>
              </div>
            </template>
          </a-comment>
          <div v-if="item.commentStatus" class="inner-comment">
            <my-comment inner @cancel="item.commentStatus = false" @comment="(content, fileList) => replyComment(item, content, fileList)" :inputFocus="focusStatus"></my-comment>
          </div>
        </a-list-item>
      </template>
    </a-list>

    <div class="comment-area">
      <a-comment style="margin: 0 10px">
        <template #avatar>
          <a-avatar class="tx" :src="getMyAvatar()" :alt="getMyname()">{{ getMyname() }}</a-avatar>
        </template>
        <template #content>
          <my-comment ref="bottomCommentRef" @comment="sendComment" :inputFocus="focusStatus"></my-comment>
        </template>
      </a-comment>
    </div>
  </div>
</template>

<script>
  /**
   * 评论列表
   */
  import { defineComponent, ref, onMounted, watch, watchEffect ,inject, nextTick } from 'vue';
  import { propTypes } from '/@/utils/propTypes';
  // import dayjs from 'dayjs';
  // import relativeTime from 'dayjs/plugin/relativeTime';
  // import customParseFormat from 'dayjs/plugin/customParseFormat';
  // dayjs.locale('zh');
  // dayjs.extend(relativeTime);
  // dayjs.extend(customParseFormat);
  
  import { MessageOutlined } from '@ant-design/icons-vue';
  import { Comment, Tooltip } from 'ant-design-vue';
  import { useUserStore } from '/@/store/modules/user';
  import MyComment from './MyComment.vue';
  import { list, saveOne, deleteOne, useCommentWithFile, useEmojiHtml, queryById, getGloablEmojiIndex } from './useComment';
  import { useMessage } from '/@/hooks/web/useMessage';
  import HistoryFileList from './HistoryFileList.vue';
  import { Popconfirm } from 'ant-design-vue';
  import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';

  export default defineComponent({
    name: 'CommentList',
    components: {
      MessageOutlined,
      AComment: Comment,
      Tooltip,
      MyComment,
      Popconfirm,
      HistoryFileList,
    },
    props: {
      tableId: propTypes.string.def(''),
      tableName: propTypes.string.def(''),
      dataId: propTypes.string.def(''),
      datetime:  propTypes.number.def(1),
      // 其他需要减去的高度
      otherHeight: propTypes.number.def(0),
    },
    setup(props) {
      const { createMessage } = useMessage();
      const dataList = ref([]);
      const { userInfo } = useUserStore();
      const dayjs = inject('$dayjs')
      const listRef = ref(null);
      /**
       * 获取当前用户名称
       */
      function getMyname() {
        if (userInfo.realname) {
          return userInfo.realname.substr(0, 2);
        }
        return '';
      }
      
      function getMyAvatar(){
        return userInfo.avatar;
      }
      
      // 获取头像
      function getAvatar(item) {
        if (item.fromUserAvatar) {
          return getFileAccessHttpUrl(item.fromUserAvatar)
        }
        return '';
      }

      // 头像没有获取 用户名前两位
      function getAvatarText(item){
        if (item.fromUserId_dictText) {
          return item.fromUserId_dictText.substr(0, 2);
        }
        return '未知';
      }

      function getAuthor(item) {
        if (item.toUser) {
          return item.fromUserId_dictText + ' 回复 ' + item.fromUserId_dictText;
        } else {
          return item.fromUserId_dictText;
        }
      }

      function getDateDiff(item) {
        if (item.createTime) {
          const temp = dayjs(item.createTime, 'YYYY-MM-DD hh:mm:ss');
          return temp.fromNow();
        }
        return '';
      }
      const commentHeight = ref(300);
      const allHeight = ref(300);
      onMounted(() => {
        let otherHeight = props.otherHeight || 0;
        commentHeight.value = window.innerHeight - 57 - 46 - 70 - 160 - otherHeight;
        allHeight.value = window.innerHeight - 57 - 46 - 53 -20 - otherHeight;
      });

      /**
       * 加载数据
       * @returns {Promise<void>}
       */
      async function loadData() {
        const params = {
          tableName: props.tableName,
          tableDataId: props.dataId,
          column: 'createTime',
          order: 'desc',
        };
        const data = await list(params);
        if (!data || !data.records || data.records.length == 0) {
          dataList.value = [];
        } else {
          let array = data.records;
          console.log(123, array);
          dataList.value = array;
          // update-begin--author:liaozhiyang---date:20240521---for:【TV360X-18】评论之后滚动条自动触底
          // Number.MAX_SAFE_INTEGER 火狐不兼容改成 10e4
          nextTick(() => {
            listRef.value && listRef.value.$el && (listRef.value.$el.scrollTop = 10e5);
          });
          // update-end--author:liaozhiyang---date:20240521---for:【TV360X-18】评论之后滚动条自动触底
        }
      }

      const { saveCommentAndFiles } = useCommentWithFile(props);
      // 回复
      async function replyComment(item, content, fileList) {
        console.log(content, item);
        let obj = {
          fromUserId: userInfo.id,
          toUserId: item.fromUserId,
          commentId: item.id,
          commentContent: content
        }
        await saveCommentAndFiles(obj, fileList)
        await loadData();
      }
      
      //评论
      async function sendComment(content, fileList) {
        let obj = {
          fromUserId: userInfo.id,
          commentContent: content
        }
        await saveCommentAndFiles(obj, fileList)
        await loadData();
        focusStatus.value = false;
        setTimeout(()=>{
          focusStatus.value = true;
        },100)
      }

      //删除
      async function deleteComment(item) {
        const params = { id: item.id };
        await deleteOne(params);
        await loadData();
      }

      /**
       * 打开回复时触发
       * @type {Ref<UnwrapRef<boolean>>}
       */
      const focusStatus = ref(false);
      function showReply(item) {
        let arr = dataList.value;
        for (let temp of arr) {
          temp.commentStatus = false;
        }
        item.commentStatus = true;
        focusStatus.value = false;
        focusStatus.value = true;
      }

      // 表单改变 -重新加载评论列表
      watchEffect(() => {
        if(props.datetime){
          if (props.tableName && props.dataId) {
            loadData();
          }
        }
      });

      //const storageEmojiIndex = inject('$globalEmojiIndex')
      const storageEmojiIndex = getGloablEmojiIndex()
      const { getHtml } = useEmojiHtml(storageEmojiIndex);
      const bottomCommentRef = ref()
      function handleClickItem(){
        bottomCommentRef.value.changeActive()
      }


      /**
       * 根据id查询评论信息
       */
      async function visibleChange(v, item){
        if(v==true){
          if(!item.commentId_dictText){
            const data = await queryById(item.commentId);
            if(data.success == true){
              item.commentId_dictText = data.result.commentContent
            }else{
              console.error(data.message)
              item.commentId_dictText='该评论已被删除';
            }
          }
        }
      }
      // update-begin--author:liaozhiyang---date:20240618---for:【TV360X-932】评论加上换行
      const lineFeed = (content) => {
        return content.replace(/\n/g, '<br>');
      };
      // update-end--author:liaozhiyang---date:20240618---for:【TV360X-932】评论加上换行

      return {
        dataList,
        getAvatar,
        getAvatarText,
        getAuthor,
        getDateDiff,
        commentHeight,
        allHeight,
        replyComment,
        sendComment,
        getMyname,
        getMyAvatar,

        focusStatus,
        showReply,
        deleteComment,
        getHtml,
        handleClickItem,
        bottomCommentRef,
        visibleChange,
        listRef,
        lineFeed,
      };
    },
  });
</script>

<style lang="less" scoped>
  .jeecg-comment-list {
    overflow: auto;
    /* border-bottom: 1px solid #eee;*/
    .inner-comment {
      width: 100%;
      padding: 0 10px;
    }
    .ant-comment {
      width: 100%;
    }
    :deep(.ant-comment-avatar) {
      cursor: default;
    }
  }
  .comment-author {
    span {
      margin: 3px;
    }
    .comment-last-content {
      margin-left: 5px;
      &:hover{
        color: @primary-color;
      }
    }
  }
  .ant-list-items{
    .ant-list-item:last-child{
      margin-bottom: 46px;
    }
  }
  .tx{
    margin-top: 4px;
  }
  // update-begin--author:liaozhiyang---date:20240327---for:【QQYUN-8639】暗黑主题适配
  .comment-area {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    border-top: 1px solid #eee;
    background-color: #fff;
  }
  html[data-theme='dark'] {
    .comment-area {
      border-color: rgba(253, 253, 253, 0.12);
      background-color: #1f1f1f;
    }
    .content {
      color:rgba(255, 255, 255, 0.85);
    }
  }
  // update-end--author:liaozhiyang---date:20240327---for:【QQYUN-8639】暗黑主题适配
</style>