index.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. <template>
  2. <div
  3. v-show="isShowReadStatus"
  4. :class="{
  5. 'message-label': true,
  6. 'unread': isUseUnreadStyle,
  7. 'finger-point': isHoverFingerPointer,
  8. }"
  9. @click="openReadUserPanel"
  10. >
  11. <span>{{ readStatusText }}</span>
  12. </div>
  13. </template>
  14. <script setup lang="ts">
  15. import { computed, ref, onMounted, onUnmounted } from '../../../../../adapter-vue';
  16. import TUIChatEngine, {
  17. TUIStore,
  18. StoreName,
  19. IMessageModel,
  20. TUITranslateService,
  21. } from '@tencentcloud/chat-uikit-engine';
  22. import TUIChatConfig from '../../../config';
  23. interface IProps {
  24. message: IMessageModel;
  25. }
  26. interface IEmits {
  27. (e: 'openReadUserPanel'): void;
  28. }
  29. const emits = defineEmits<IEmits>();
  30. const props = withDefaults(defineProps<IProps>(), {
  31. message: () => ({}) as IMessageModel,
  32. });
  33. const ReadStatus = TUIChatConfig.getFeatureConfig('ReadStatus');
  34. enum ReadState {
  35. Read,
  36. Unread,
  37. AllRead,
  38. NotShow,
  39. PartiallyRead,
  40. }
  41. const TYPES = TUIChatEngine.TYPES;
  42. // User-level read receipt toggle has the highest priority.
  43. const isDisplayMessageReadReceipt = ref<boolean>(TUIStore.getData(StoreName.USER, 'displayMessageReadReceipt'));
  44. onMounted(() => {
  45. TUIStore.watch(StoreName.USER, {
  46. displayMessageReadReceipt: onDisplayMessageReadReceiptUpdate,
  47. });
  48. });
  49. onUnmounted(() => {
  50. TUIStore.unwatch(StoreName.USER, {
  51. displayMessageReadReceipt: onDisplayMessageReadReceiptUpdate,
  52. });
  53. });
  54. const isShowReadStatus = computed<boolean>(() => {
  55. if (!ReadStatus) {
  56. return false;
  57. }
  58. if (!isDisplayMessageReadReceipt.value) {
  59. return false;
  60. }
  61. const {
  62. ID,
  63. type,
  64. flow,
  65. status,
  66. hasRiskContent,
  67. conversationID,
  68. conversationType,
  69. needReadReceipt = false,
  70. } = props.message;
  71. // Asynchronous message strike: Determine if there is risky content after the message has been sent
  72. if (hasRiskContent) {
  73. return false;
  74. }
  75. const { groupProfile } = TUIStore.getConversationModel(conversationID) || {};
  76. // AVCHATROOM and COMMUNITY chats do not display read status
  77. if (groupProfile?.type === TYPES.GRP_AVCHATROOM || groupProfile?.type === TYPES.GRP_COMMUNITY) {
  78. return false;
  79. }
  80. if (type === TYPES.MSG_CUSTOM) {
  81. const message = TUIStore.getMessageModel(ID);
  82. // If it is a signaling message, do not display the read status
  83. if (message?.getSignalingInfo() !== null) {
  84. return false;
  85. }
  86. }
  87. // Unsuccessful message: Received messages do not display read status
  88. if (flow !== 'out' || status !== 'success') {
  89. return false;
  90. }
  91. if (conversationType === 'GROUP') {
  92. return needReadReceipt;
  93. } else if (conversationType === 'C2C') {
  94. return true;
  95. }
  96. return false;
  97. });
  98. const readState = computed<ReadState>(() => {
  99. const { conversationType, needReadReceipt = false, isPeerRead = false } = props.message;
  100. const { readCount = 0, unreadCount = 0, isPeerRead: isReceiptPeerRead = false } = props.message.readReceiptInfo;
  101. if (conversationType === 'C2C') {
  102. if (needReadReceipt) {
  103. return isReceiptPeerRead ? ReadState.Read : ReadState.Unread;
  104. } else {
  105. return isPeerRead ? ReadState.Read : ReadState.Unread;
  106. }
  107. } else if (conversationType === 'GROUP') {
  108. if (needReadReceipt) {
  109. if (readCount === 0) {
  110. return ReadState.Unread;
  111. } else if (unreadCount === 0) {
  112. return ReadState.AllRead;
  113. } else {
  114. return ReadState.PartiallyRead;
  115. }
  116. } else {
  117. return ReadState.NotShow;
  118. }
  119. }
  120. return ReadState.Unread;
  121. });
  122. const readStatusText = computed(() => {
  123. const { readCount = 0 } = props.message.readReceiptInfo;
  124. switch (readState.value) {
  125. case ReadState.Read:
  126. return TUITranslateService.t('TUIChat.已读');
  127. case ReadState.Unread:
  128. return TUITranslateService.t('TUIChat.未读');
  129. case ReadState.AllRead:
  130. return TUITranslateService.t('TUIChat.全部已读');
  131. case ReadState.PartiallyRead:
  132. return `${readCount}${TUITranslateService.t('TUIChat.人已读')}`;
  133. default:
  134. return '';
  135. }
  136. });
  137. const isUseUnreadStyle = computed(() => {
  138. const { conversationType } = props.message;
  139. if (conversationType === 'C2C') {
  140. return readState.value !== ReadState.Read;
  141. } else if (conversationType === 'GROUP') {
  142. return readState.value !== ReadState.AllRead;
  143. }
  144. return false;
  145. });
  146. const isHoverFingerPointer = computed<boolean>(() => {
  147. return (
  148. props.message.needReadReceipt
  149. && props.message.conversationType === 'GROUP'
  150. && (readState.value === ReadState.PartiallyRead || readState.value === ReadState.Unread)
  151. );
  152. });
  153. function openReadUserPanel() {
  154. if (isHoverFingerPointer.value) {
  155. emits('openReadUserPanel');
  156. }
  157. }
  158. function onDisplayMessageReadReceiptUpdate(isDisplay: boolean) {
  159. isDisplayMessageReadReceipt.value = isDisplay;
  160. }
  161. </script>
  162. <style scoped lang="scss">
  163. .message-label {
  164. align-self: flex-end;
  165. font-size: 12px;
  166. color: #b6b8ba;
  167. word-break: keep-all;
  168. flex: 0 0 auto;
  169. &.unread {
  170. color: #679ce1 !important;
  171. }
  172. }
  173. .finger-point {
  174. cursor: pointer;
  175. -webkit-tap-highlight-color: transparent;
  176. }
  177. </style>