index.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. <template>
  2. <BottomPopup :show="showAtList" @onClose="closeAt">
  3. <div
  4. ref="MessageInputAt"
  5. :class="[isPC ? 'message-input-at' : 'message-input-at-h5']"
  6. >
  7. <div ref="dialog" class="member-list">
  8. <header v-if="!isPC" class="member-list-title">
  9. <span class="title">{{ TUITranslateService.t("TUIChat.选择提醒的人") }}</span>
  10. </header>
  11. <ul class="member-list-box">
  12. <li
  13. v-for="(item, index) in showMemberList"
  14. :key="index"
  15. ref="memberListItems"
  16. class="member-list-box-body"
  17. :class="[index === selectedIndex && 'selected']"
  18. @click="selectItem(index)"
  19. >
  20. <img class="member-list-box-body-avatar" :src="handleMemberAvatar(item)" />
  21. <span class="member-list-box-body-name">
  22. {{ handleMemberName(item) }}
  23. </span>
  24. </li>
  25. </ul>
  26. </div>
  27. </div>
  28. </BottomPopup>
  29. </template>
  30. <script lang="ts" setup>
  31. import TUIChatEngine, {
  32. TUIStore,
  33. StoreName,
  34. TUIGroupService,
  35. TUITranslateService,
  36. } from "@tencentcloud/chat-uikit-engine";
  37. import { TUIGlobal } from "@tencentcloud/universal-api";
  38. import { ref, watch } from "../../../../adapter-vue";
  39. import { isPC, isH5 } from "../../../../utils/env";
  40. import BottomPopup from "../../../common/BottomPopup/index.vue";
  41. const emits = defineEmits(["onAtListOpen", "insertAt"]);
  42. const MessageInputAt = ref();
  43. const memberListItems = ref();
  44. const showAtList = ref(false);
  45. const memberList = ref<Array<any>>();
  46. const allMemberList = ref<Array<any>>();
  47. const showMemberList = ref<Array<any>>();
  48. const isGroup = ref(false);
  49. const position = ref({
  50. left: 0,
  51. top: 0,
  52. });
  53. const selectedIndex = ref(0);
  54. const currentConversationID = ref("");
  55. const all = {
  56. userID: TUIChatEngine.TYPES.MSG_AT_ALL,
  57. nick: "所有人",
  58. isAll: true,
  59. avatar: "https://web.sdk.qcloud.com/im/assets/images/at.svg",
  60. };
  61. TUIStore.watch(StoreName.CONV, {
  62. currentConversationID: (id: string) => {
  63. if (id !== currentConversationID.value) {
  64. currentConversationID.value = id;
  65. memberList.value = [];
  66. allMemberList.value = [];
  67. showMemberList.value = [];
  68. isGroup.value = false;
  69. TUIStore.update(StoreName.CUSTOM, "memberList", memberList.value);
  70. if (currentConversationID?.value?.startsWith("GROUP")) {
  71. isGroup.value = true;
  72. const groupID = currentConversationID?.value?.substring(5);
  73. TUIGroupService.switchGroup(groupID);
  74. } else {
  75. TUIGroupService.switchGroup("");
  76. }
  77. }
  78. },
  79. });
  80. TUIStore.watch(StoreName.GRP, {
  81. currentGroupMemberList: (list: Array<any>) => {
  82. memberList.value = list;
  83. allMemberList.value = [all, ...memberList.value];
  84. showMemberList.value = allMemberList.value;
  85. TUIStore.update(StoreName.CUSTOM, "memberList", memberList.value);
  86. },
  87. });
  88. const toggleAtList = (show: boolean) => {
  89. if (!isGroup.value) {
  90. return;
  91. }
  92. showAtList.value = show;
  93. if (showAtList.value) {
  94. emits("onAtListOpen");
  95. }
  96. };
  97. const handleAtListPosition = (positionData: { top: number; left: number }) => {
  98. position.value = positionData;
  99. };
  100. const setCurrentSelectIndex = (index: any) => {
  101. selectedIndex.value = index;
  102. memberListItems.value?.[selectedIndex.value]?.scrollIntoView(false);
  103. };
  104. const setShowMemberList = (list: any) => {
  105. showMemberList.value = list;
  106. };
  107. TUIGlobal.toggleAtList = toggleAtList;
  108. TUIGlobal.handleAtListPosition = handleAtListPosition;
  109. TUIGlobal.setCurrentSelectIndex = setCurrentSelectIndex;
  110. TUIGlobal.setShowMemberList = setShowMemberList;
  111. defineExpose({
  112. toggleAtList,
  113. });
  114. watch(
  115. () => [position.value, MessageInputAt?.value],
  116. () => {
  117. if (isH5 || !MessageInputAt?.value || !MessageInputAt?.value?.style) {
  118. return;
  119. }
  120. MessageInputAt.value.style.left = position.value.left + "px";
  121. MessageInputAt.value.style.top =
  122. position.value.top - MessageInputAt.value.clientHeight + "px";
  123. }
  124. );
  125. const closeAt = () => {
  126. showAtList.value = false;
  127. showMemberList.value = allMemberList.value;
  128. position.value = {
  129. left: 0,
  130. top: 0,
  131. };
  132. };
  133. const selectItem = (index: number) => {
  134. if (isPC && TUIGlobal.selectItem) {
  135. TUIGlobal.selectItem(index);
  136. } else {
  137. if (showMemberList?.value?.length) {
  138. const item = showMemberList?.value[index];
  139. emits("insertAt", {
  140. id: (item as any)?.userID,
  141. label: (item as any)?.nick || (item as any)?.userID,
  142. });
  143. }
  144. }
  145. closeAt();
  146. };
  147. const handleMemberAvatar = (item: any) => {
  148. return (
  149. (item as any)?.avatar ||
  150. "https://bucket.sxdirectpurchase.com/wx/static/img/ImAvatar.png"
  151. );
  152. };
  153. const handleMemberName = (item: any) => {
  154. return (item as any)?.nick ? (item as any)?.nick : (item as any)?.userID;
  155. };
  156. </script>
  157. <style scoped lang="scss">
  158. @import "../../../../assets/styles/common";
  159. .message-input-at {
  160. position: fixed;
  161. max-width: 15rem;
  162. max-height: 10rem;
  163. overflow: hidden auto;
  164. background: #fff;
  165. box-shadow: 0 0.06rem 0.63rem 0 rgba(2, 16, 43, 0.15);
  166. border-radius: 0.13rem;
  167. }
  168. .member-list-box {
  169. &-header {
  170. height: 2.5rem;
  171. padding-top: 5px;
  172. cursor: pointer;
  173. &:hover {
  174. background: rgba(0, 110, 255, 0.1);
  175. }
  176. }
  177. span {
  178. font-family: PingFangSC-Regular;
  179. font-weight: 400;
  180. font-size: 12px;
  181. color: #000;
  182. letter-spacing: 0;
  183. padding: 5px;
  184. }
  185. &-body {
  186. height: 30px;
  187. cursor: pointer;
  188. display: flex;
  189. align-items: center;
  190. .selected,
  191. &:hover {
  192. background: rgba(0, 110, 255, 0.1);
  193. }
  194. &-name {
  195. overflow: hidden;
  196. white-space: nowrap;
  197. word-wrap: break-word;
  198. word-break: break-all;
  199. text-overflow: ellipsis;
  200. }
  201. &-avatar {
  202. width: 20px;
  203. height: 20px;
  204. padding-left: 10px;
  205. }
  206. }
  207. .selected {
  208. background: rgba(0, 110, 255, 0.1);
  209. }
  210. }
  211. .message-input-at-h5 {
  212. .member-list {
  213. height: auto;
  214. max-height: 500px;
  215. width: 100%;
  216. max-width: 100%;
  217. background: white;
  218. border-radius: 12px 12px 0 0;
  219. display: flex;
  220. flex-direction: column;
  221. overflow: hidden;
  222. &-title {
  223. height: fit-content;
  224. width: calc(100% - 30px);
  225. text-align: center;
  226. vertical-align: middle;
  227. padding: 15px;
  228. .title {
  229. vertical-align: middle;
  230. display: inline-block;
  231. font-size: 16px;
  232. }
  233. .close {
  234. vertical-align: middle;
  235. position: absolute;
  236. right: 10px;
  237. display: inline-block;
  238. }
  239. }
  240. &-box {
  241. flex: 1;
  242. overflow-y: scroll;
  243. &-body {
  244. padding: 10px;
  245. img {
  246. width: 26px;
  247. height: 26px;
  248. }
  249. span {
  250. font-size: 14px;
  251. }
  252. }
  253. }
  254. }
  255. }
  256. </style>