index.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <template>
  2. <Overlay
  3. :maskColor="'transparent'"
  4. @onOverlayClick="() => emits('closeConversationActionMenu')"
  5. >
  6. <div
  7. id="conversation-actions-menu"
  8. ref="actionsMenuDomRef"
  9. :class="[
  10. isPC && 'actions-menu-pc',
  11. 'actions-menu',
  12. !isHiddenActionsMenu && 'cancel-hidden',
  13. ]"
  14. :style="{
  15. top: `${_actionsMenuPosition.top}px`,
  16. left: `${_actionsMenuPosition.left}px`,
  17. }"
  18. >
  19. <div
  20. :class="['actions-menu-item']"
  21. @click.stop="deleteConversation()"
  22. >
  23. {{ TUITranslateService.t("TUIConversation.删除会话") }}
  24. </div>
  25. <div
  26. v-if="!(props.selectedConversation && props.selectedConversation.isPinned)"
  27. :class="['actions-menu-item']"
  28. @click.stop="handleItem({ name: CONV_OPERATION.ISPINNED })"
  29. >
  30. {{ TUITranslateService.t("TUIConversation.置顶会话") }}
  31. </div>
  32. <div
  33. v-if="props.selectedConversation && props.selectedConversation.isPinned"
  34. :class="['actions-menu-item']"
  35. @click.stop="handleItem({ name: CONV_OPERATION.DISPINNED })"
  36. >
  37. {{ TUITranslateService.t("TUIConversation.取消置顶") }}
  38. </div>
  39. <div
  40. v-if="!(props.selectedConversation && props.selectedConversation.isMuted)"
  41. :class="['actions-menu-item']"
  42. @click.stop="handleItem({ name: CONV_OPERATION.MUTE })"
  43. >
  44. {{ TUITranslateService.t("TUIConversation.消息免打扰") }}
  45. </div>
  46. <div
  47. v-if="props.selectedConversation && props.selectedConversation.isMuted"
  48. :class="['actions-menu-item']"
  49. @click.stop="handleItem({ name: CONV_OPERATION.NOTMUTE })"
  50. >
  51. {{ TUITranslateService.t("TUIConversation.取消免打扰") }}
  52. </div>
  53. </div>
  54. <Dialog
  55. :show="isShowDeleteConversationDialog"
  56. :center="true"
  57. :isHeaderShow="isPC"
  58. @submit="handleItem({ name: CONV_OPERATION.DELETE })"
  59. @update:show="updateShowDeleteConversationDialog"
  60. >
  61. <p class="delDialog-title">
  62. {{ TUITranslateService.t(deleteConversationDialogTitle) }}
  63. </p>
  64. </Dialog>
  65. </Overlay>
  66. </template>
  67. <script setup lang="ts">
  68. import {
  69. ref,
  70. nextTick,
  71. onMounted,
  72. computed,
  73. getCurrentInstance,
  74. } from '../../../adapter-vue';
  75. import TUIChatEngine, {
  76. IConversationModel,
  77. TUIStore,
  78. TUITranslateService,
  79. } from '@tencentcloud/chat-uikit-engine';
  80. import { TUIGlobal } from '@tencentcloud/universal-api';
  81. import { CONV_OPERATION } from '../../../constant';
  82. import { isPC, isUniFrameWork } from '../../../utils/env';
  83. import Overlay from '../../common/Overlay/index.vue';
  84. import Dialog from '../../common/Dialog/index.vue';
  85. interface IProps {
  86. actionsMenuPosition: {
  87. top: number;
  88. left?: number;
  89. conversationHeight?: number;
  90. };
  91. selectedConversation: IConversationModel | undefined;
  92. selectedConversationDomRect: DOMRect | undefined;
  93. }
  94. const emits = defineEmits(['closeConversationActionMenu']);
  95. const props = defineProps<IProps>();
  96. const thisInstance = getCurrentInstance()?.proxy || getCurrentInstance();
  97. const actionsMenuDomRef = ref<HTMLElement | null>();
  98. const isHiddenActionsMenu = ref(true);
  99. const isShowDeleteConversationDialog = ref<boolean>(false);
  100. const currentConversation = TUIStore.getConversationModel(
  101. props.selectedConversation?.conversationID || '',
  102. );
  103. const _actionsMenuPosition = ref<{
  104. top: number;
  105. left?: number;
  106. conversationHeight?: number;
  107. }>(props.actionsMenuPosition);
  108. onMounted(() => {
  109. checkExceedBounds();
  110. });
  111. const deleteConversationDialogTitle = computed(() => {
  112. return props.selectedConversation?.type === TUIChatEngine.TYPES.CONV_C2C
  113. ? 'TUIConversation.删除后,将清空该聊天的消息记录'
  114. : props.selectedConversation?.type === TUIChatEngine.TYPES.CONV_GROUP ? 'TUIConversation.删除后,将清空该群聊的消息记录' : '';
  115. });
  116. function checkExceedBounds() {
  117. // When the component is initially rendered, it executes and self-checks whether the boundary exceeds the screen, and handles it in nextTick.
  118. nextTick(() => {
  119. if (isUniFrameWork) {
  120. // check exceed bounds
  121. const query = TUIGlobal?.createSelectorQuery().in(thisInstance);
  122. query
  123. .select(`#conversation-actions-menu`)
  124. .boundingClientRect((data) => {
  125. if (data) {
  126. // check if actionsMenu is exceed bottom of the screen
  127. if (data.bottom > TUIGlobal?.getWindowInfo?.().windowHeight) {
  128. _actionsMenuPosition.value = {
  129. ...props.actionsMenuPosition,
  130. top:
  131. props.actionsMenuPosition.top
  132. - (props.actionsMenuPosition.conversationHeight || 0)
  133. - data.height,
  134. };
  135. }
  136. // check if actionsMenu is exceed right of the screen
  137. if (_actionsMenuPosition.value.left + data.width + 5 > TUIGlobal.getWindowInfo().windowWidth) {
  138. _actionsMenuPosition.value.left = TUIGlobal.getWindowInfo().windowWidth - data.width - 5;
  139. }
  140. }
  141. isHiddenActionsMenu.value = false;
  142. })
  143. .exec();
  144. } else {
  145. // Handling the situation where the native Vue menu is lower than the screen
  146. const rect = actionsMenuDomRef.value?.getBoundingClientRect();
  147. // The PC side sets the position of actionsMenu according to the position of the mouse click, otherwise the default value of 167px is used
  148. if (isPC && typeof props.actionsMenuPosition.left !== 'undefined') {
  149. _actionsMenuPosition.value.left = props.actionsMenuPosition.left;
  150. }
  151. if (rect && rect.bottom > window.innerHeight) {
  152. _actionsMenuPosition.value.top
  153. = props.actionsMenuPosition.top
  154. - (props.actionsMenuPosition.conversationHeight || 0)
  155. - rect.height;
  156. }
  157. isHiddenActionsMenu.value = false;
  158. }
  159. });
  160. }
  161. const handleItem = (params: { name: string }) => {
  162. const { name } = params;
  163. const conversationModel = currentConversation;
  164. if (!name || !conversationModel || !conversationModel.conversationID) {
  165. return;
  166. }
  167. switch (name) {
  168. case CONV_OPERATION.DELETE:
  169. conversationModel?.deleteConversation();
  170. break;
  171. case CONV_OPERATION.ISPINNED:
  172. conversationModel?.pinConversation();
  173. break;
  174. case CONV_OPERATION.DISPINNED:
  175. conversationModel?.pinConversation();
  176. break;
  177. case CONV_OPERATION.MUTE:
  178. conversationModel?.muteConversation();
  179. break;
  180. case CONV_OPERATION.NOTMUTE:
  181. conversationModel?.muteConversation();
  182. break;
  183. }
  184. emits('closeConversationActionMenu');
  185. };
  186. const deleteConversation = () => {
  187. isShowDeleteConversationDialog.value = true;
  188. };
  189. const updateShowDeleteConversationDialog = (isShow: boolean) => {
  190. if (!isShow) {
  191. emits('closeConversationActionMenu');
  192. }
  193. isShowDeleteConversationDialog.value = isShow;
  194. };
  195. </script>
  196. <style scoped lang="scss">
  197. .cancel-hidden {
  198. opacity: 1 !important;
  199. }
  200. .actions-menu {
  201. position: absolute;
  202. left: 164px;
  203. border-radius: 8px;
  204. border: 1px solid #e0e0e0;
  205. box-shadow: 0 -4px 12px 0 rgba(0, 0, 0, 0.06);
  206. background-color: #fff;
  207. overflow: hidden;
  208. opacity: 0;
  209. .actions-menu-item {
  210. cursor: pointer;
  211. padding: 10px 20px;
  212. font-size: 12px;
  213. word-break: keep-all;
  214. }
  215. &.actions-menu-pc .actions-menu-item:hover {
  216. background-color: #eee;
  217. }
  218. }
  219. </style>