messageList.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. <template>
  2. <!-- 聊天虚拟列表 -->
  3. <z-paging ref="pagingRef" v-model="messageList" use-chat-record-mode use-virtual-list cell-height-mode="dynamic"
  4. default-page-size="20" :auto-clean-list-when-reload="false" safe-area-inset-bottom bottom-bg-color="#f8f8f8"
  5. :back-to-top-style="backToTopStyle" :auto-show-back-to-top="showNewMessageTip" @backToTopClick="onBackToTopClick"
  6. @scrolltoupper="onScrollToUpper" @query="queryList">
  7. <template #top>
  8. <!-- 撑一下顶部导航 -->
  9. <view :style="{ height: sys_navBar + 'px' }"></view>
  10. </template>
  11. <!-- style="transform: scaleY(-1)"必须写,否则会导致列表倒置!!! -->
  12. <!-- 注意不要直接在chat-item组件标签上设置style,因为在微信小程序中是无效的,请包一层view -->
  13. <template #cell="{ item, index }">
  14. <view style="transform: scaleY(-1)">
  15. <!-- 消息渲染 -->
  16. <MessageListItem :message="item" :message-index="index" :message-list="messageList"></MessageListItem>
  17. </view>
  18. </template>
  19. <!-- 底部聊天输入框 -->
  20. <template #bottom>
  21. <slot name="bottom"></slot>
  22. </template>
  23. <!-- 查看最新消息 -->
  24. <template #backToTop>
  25. <text>有新消息</text>
  26. </template>
  27. </z-paging>
  28. </template>
  29. <script setup>
  30. import MessageListItem from '@/pages/chat/components/messageListItem.vue';
  31. import { reactive, ref, nextTick } from 'vue';
  32. import KeFuApi from '@/sheep/api/promotion/kefu';
  33. import { isEmpty } from '@/sheep/helper/utils';
  34. import sheep from '@/sheep';
  35. import { formatDate } from '@/sheep/util';
  36. import { onLoad } from '@dcloudio/uni-app';
  37. const props = defineProps({
  38. queryShow: {
  39. type: Boolean,
  40. default: true,
  41. },
  42. });
  43. const sys_navBar = sheep.$platform.navbar;
  44. const messageList = ref([]); // 消息列表
  45. const showNewMessageTip = ref(false); // 显示有新消息提示
  46. const refreshMessage = ref(false); // 更新消息列表
  47. const backToTopStyle = reactive({
  48. width: '100px',
  49. 'background-color': '#fff',
  50. 'border-radius': '30px',
  51. 'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.1)',
  52. display: 'flex',
  53. justifyContent: 'center',
  54. alignItems: 'center',
  55. }); // 返回顶部样式
  56. const queryParams = reactive({
  57. no: 1, // 查询次数,只用于触底计算
  58. limit: 20,
  59. createTime: undefined,
  60. });
  61. onLoad((options) => {
  62. if (options.conversationId) {
  63. queryParams.conversationId = options.conversationId;
  64. } else {
  65. queryParams.conversationId = '1';
  66. }
  67. });
  68. const pagingRef = ref(null); // 虚拟列表
  69. const queryList = async (no, limit) => {
  70. // 组件加载时会自动触发此方法,因此默认页面加载时会自动触发,无需手动调用
  71. queryParams.no = no;
  72. queryParams.limit = limit;
  73. if (props.queryShow) {
  74. await getMessageList();
  75. }
  76. };
  77. // 获得消息分页列表
  78. const getMessageList = async () => {
  79. const { data } = await KeFuApi.getKefuMessageList(queryParams);
  80. if (isEmpty(data)) {
  81. pagingRef.value.completeByNoMore([], true);
  82. return;
  83. }
  84. if (queryParams.no > 1 && refreshMessage.value) {
  85. const newMessageList = [];
  86. for (const message of data) {
  87. if (messageList.value.some((val) => val.id === message.id)) {
  88. continue;
  89. }
  90. newMessageList.push(message);
  91. }
  92. // 新消息追加到开头
  93. messageList.value = [...newMessageList, ...messageList.value];
  94. pagingRef.value.updateCache(); // 更新缓存
  95. refreshMessage.value = false; // 更新好后重置状态
  96. return;
  97. }
  98. if (data.slice(-1).length > 0) {
  99. // 设置最后一次历史查询的最后一条消息的 createTime
  100. queryParams.createTime = formatDate(data.slice(-1)[0].createTime);
  101. }
  102. pagingRef.value.completeByNoMore(data, false);
  103. };
  104. /** 刷新消息列表 */
  105. const refreshMessageList = async (message = undefined) => {
  106. if (typeof message !== 'undefined') {
  107. // 小程序环境下使用 nextTick 更新数据
  108. nextTick(() => {
  109. messageList.value = [message, ...messageList.value];
  110. pagingRef.value.updateCache();
  111. });
  112. } else {
  113. queryParams.createTime = undefined;
  114. refreshMessage.value = true;
  115. await getMessageList();
  116. }
  117. // 若已是第一页则不做处理
  118. if (queryParams.no > 1) {
  119. showNewMessageTip.value = true;
  120. } else {
  121. onScrollToUpper();
  122. }
  123. };
  124. /** 滚动到最新消息 */
  125. const onBackToTopClick = (event) => {
  126. event(false); // 禁用默认操作
  127. pagingRef.value.scrollToBottom();
  128. };
  129. /** 监听滚动到底部事件(因为 scroll 翻转了顶就是底) */
  130. const onScrollToUpper = () => {
  131. // 若已是第一页则不做处理
  132. if (queryParams.no === 1) {
  133. return;
  134. }
  135. showNewMessageTip.value = false;
  136. };
  137. // 暴露方法 修改消息列表指定的消息
  138. const updateMessage = (items, messageId = '') => {
  139. let isShow = true
  140. messageList.value = messageList.value.map((item) => {
  141. if (item.ids === 1) {
  142. delete item.ids;
  143. item.content = items.content;
  144. item.messageId = items.messageId;
  145. item.isAi = false
  146. isShow = false;
  147. return item;
  148. }
  149. return item;
  150. });
  151. console.log("updateMessage", items.event)
  152. if (items.event !== null && items.event !== "message_end" && isShow) {
  153. messageList.value[0].content += items.content;
  154. }
  155. };
  156. // 通过id 获取对应的头像
  157. const getAvatar = (id) => {
  158. return messageList.value[messageList.value.length - 1];
  159. };
  160. defineExpose({ getMessageList, refreshMessageList, updateMessage, messageList, getAvatar });
  161. </script>