index copy.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. <template>
  2. <ul
  3. v-if="!contactSearchingStatus"
  4. :class="['tui-contact-list', !isPC && 'tui-contact-list-h5']"
  5. >
  6. <li
  7. v-for="(contactListObj, key) in contactListMap"
  8. :key="key"
  9. class="tui-contact-list-item"
  10. >
  11. <header
  12. class="tui-contact-list-item-header"
  13. @click="toggleCurrentContactList(key)"
  14. >
  15. <div class="tui-contact-list-item-header-left">
  16. <Icon
  17. :file="currentContactListKey === key ? downSVG : rightSVG"
  18. width="16px"
  19. height="16px"
  20. />
  21. <div>{{ TUITranslateService.t(`TUIContact.${contactListObj.title}`) }}</div>
  22. </div>
  23. <div class="tui-contact-list-item-header-right">
  24. <span
  25. v-if="contactListObj.unreadCount"
  26. class="tui-contact-list-item-header-right-unread"
  27. >
  28. {{ contactListObj.unreadCount }}
  29. </span>
  30. </div>
  31. </header>
  32. <ul :class="['tui-contact-list-item-main', currentContactListKey === key ? '' : 'hidden']">
  33. <li
  34. v-for="contactListItem in contactListObj.list"
  35. :key="contactListItem.renderKey"
  36. class="tui-contact-list-item-main-item"
  37. :class="['selected']"
  38. @click="selectItem(contactListItem)"
  39. >
  40. <ContactListItem
  41. :item="contactListItem"
  42. :displayOnlineStatus="displayOnlineStatus && key === 'friendList'"
  43. />
  44. </li>
  45. </ul>
  46. </li>
  47. </ul>
  48. <ul
  49. v-else
  50. class="tui-contact-list"
  51. >
  52. <li
  53. v-for="(item, key) in contactSearchResult"
  54. :key="key"
  55. class="tui-contact-list-item"
  56. >
  57. <div
  58. v-if="item.list[0]"
  59. class="tui-contact-search-list"
  60. >
  61. <div class="tui-contact-search-list-title">
  62. {{ TUITranslateService.t(`TUIContact.${item.label}`) }}
  63. </div>
  64. <div
  65. v-for="(listItem, index) in item.list"
  66. :key="index"
  67. class="tui-contact-search-list-item"
  68. :class="['selected']"
  69. @click="selectItem(listItem)"
  70. >
  71. <ContactListItem
  72. :item="listItem"
  73. :displayOnlineStatus="false"
  74. />
  75. </div>
  76. </div>
  77. </li>
  78. <div
  79. v-if="isContactSearchNoResult"
  80. class="tui-contact-search-list-default"
  81. >
  82. {{ TUITranslateService.t("TUIContact.无搜索结果") }}
  83. </div>
  84. </ul>
  85. </template>
  86. <script setup lang="ts">
  87. import {
  88. TUITranslateService,
  89. TUIStore,
  90. StoreName,
  91. IGroupModel,
  92. TUIFriendService,
  93. Friend,
  94. FriendApplication,
  95. TUIUserService,
  96. } from '@tencentcloud/chat-uikit-engine';
  97. import TUICore, { TUIConstants } from '@tencentcloud/tui-core';
  98. import { ref, computed, onMounted, onUnmounted, provide } from '../../../adapter-vue';
  99. import Icon from '../../common/Icon.vue';
  100. import downSVG from '../../../assets/icon/down-icon.svg';
  101. import rightSVG from '../../../assets/icon/right-icon.svg';
  102. import {
  103. IContactList,
  104. IContactSearchResult,
  105. IBlackListUserItem,
  106. IUserStatus,
  107. IUserStatusMap,
  108. IContactInfoType,
  109. } from '../../../interface';
  110. import ContactListItem from './contact-list-item/index.vue';
  111. import { isPC } from '../../../utils/env';
  112. const currentContactListKey = ref<keyof IContactList>('');
  113. const currentContactInfo = ref<IContactInfoType>({} as IContactInfoType);
  114. const contactListMap = ref<IContactList>({
  115. friendApplicationList: {
  116. key: 'friendApplicationList',
  117. title: '新的联系人',
  118. list: [] as FriendApplication[],
  119. unreadCount: 0,
  120. },
  121. blackList: {
  122. key: 'blackList',
  123. title: '黑名单',
  124. list: [] as IBlackListUserItem[],
  125. },
  126. groupList: {
  127. key: 'groupList',
  128. title: '我的群聊',
  129. list: [] as IGroupModel[],
  130. },
  131. friendList: {
  132. key: 'friendList',
  133. title: '我的好友',
  134. list: [] as Friend[],
  135. },
  136. });
  137. const contactSearchingStatus = ref<boolean>(false);
  138. const contactSearchResult = ref<IContactSearchResult>();
  139. const displayOnlineStatus = ref<boolean>(false);
  140. const userOnlineStatusMap = ref<IUserStatusMap>();
  141. const isContactSearchNoResult = computed((): boolean => {
  142. return (
  143. !contactSearchResult?.value?.user?.list[0]
  144. && !contactSearchResult?.value?.group?.list[0]
  145. );
  146. });
  147. onMounted(() => {
  148. TUIStore.watch(StoreName.APP, {
  149. enabledCustomerServicePlugin: onCustomerServiceCommercialPluginUpdated,
  150. });
  151. TUIStore.watch(StoreName.GRP, {
  152. groupList: onGroupListUpdated,
  153. });
  154. TUIStore.watch(StoreName.USER, {
  155. userBlacklist: onUserBlacklistUpdated,
  156. displayOnlineStatus: onDisplayOnlineStatusUpdated,
  157. userStatusList: onUserStatusListUpdated,
  158. });
  159. TUIStore.watch(StoreName.FRIEND, {
  160. friendList: onFriendListUpdated,
  161. friendApplicationList: onFriendApplicationListUpdated,
  162. friendApplicationUnreadCount: onFriendApplicationUnreadCountUpdated,
  163. });
  164. TUIStore.watch(StoreName.CUSTOM, {
  165. currentContactSearchingStatus: onCurrentContactSearchingStatusUpdated,
  166. currentContactSearchResult: onCurrentContactSearchResultUpdated,
  167. currentContactListKey: onCurrentContactListKeyUpdated,
  168. currentContactInfo: onCurrentContactInfoUpdated,
  169. });
  170. });
  171. onUnmounted(() => {
  172. TUIStore.unwatch(StoreName.APP, {
  173. enabledCustomerServicePlugin: onCustomerServiceCommercialPluginUpdated,
  174. });
  175. TUIStore.unwatch(StoreName.GRP, {
  176. groupList: onGroupListUpdated,
  177. });
  178. TUIStore.unwatch(StoreName.USER, {
  179. userBlacklist: onUserBlacklistUpdated,
  180. displayOnlineStatus: onDisplayOnlineStatusUpdated,
  181. userStatusList: onUserStatusListUpdated,
  182. });
  183. TUIStore.unwatch(StoreName.FRIEND, {
  184. friendList: onFriendListUpdated,
  185. friendApplicationList: onFriendApplicationListUpdated,
  186. friendApplicationUnreadCount: onFriendApplicationUnreadCountUpdated,
  187. });
  188. TUIStore.unwatch(StoreName.CUSTOM, {
  189. currentContactSearchingStatus: onCurrentContactSearchingStatusUpdated,
  190. currentContactSearchResult: onCurrentContactSearchResultUpdated,
  191. currentContactListKey: onCurrentContactListKeyUpdated,
  192. currentContactInfo: onCurrentContactInfoUpdated,
  193. });
  194. });
  195. function toggleCurrentContactList(key: keyof IContactList) {
  196. if (currentContactListKey.value === key) {
  197. currentContactListKey.value = '';
  198. currentContactInfo.value = {} as IContactInfoType;
  199. TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', '');
  200. TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', {} as IContactInfoType);
  201. } else {
  202. currentContactListKey.value = key;
  203. TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', key);
  204. if (key === 'friendApplicationList') {
  205. TUIFriendService.setFriendApplicationRead();
  206. }
  207. }
  208. }
  209. function selectItem(item: any) {
  210. currentContactInfo.value = item;
  211. // For a result in the search list, before viewing the contactInfo details,
  212. // it is necessary to update the data for the "already in the group list/already in the friend list" situation to obtain more detailed information
  213. if (contactSearchingStatus.value) {
  214. let targetListItem;
  215. if ((currentContactInfo.value as Friend)?.userID) {
  216. targetListItem = contactListMap.value?.friendList?.list?.find(
  217. (item: IContactInfoType) => (item as Friend)?.userID === (currentContactInfo.value as Friend)?.userID,
  218. );
  219. } else if ((currentContactInfo.value as IGroupModel)?.groupID) {
  220. targetListItem = contactListMap.value?.groupList?.list?.find(
  221. (item: IContactInfoType) => (item as IGroupModel)?.groupID === (currentContactInfo.value as IGroupModel)?.groupID,
  222. );
  223. }
  224. if (targetListItem) {
  225. currentContactInfo.value = targetListItem;
  226. }
  227. }
  228. TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', currentContactInfo.value);
  229. }
  230. function onDisplayOnlineStatusUpdated(status: boolean) {
  231. displayOnlineStatus.value = status;
  232. }
  233. function onUserStatusListUpdated(list: Map<string, IUserStatus>) {
  234. if (list?.size > 0) {
  235. userOnlineStatusMap.value = Object.fromEntries(list?.entries());
  236. }
  237. }
  238. function onCustomerServiceCommercialPluginUpdated(isEnabled: boolean) {
  239. if (!isEnabled) {
  240. return;
  241. }
  242. // After the customer purchases the customer service plug-in,
  243. // the engine updates the enabledCustomerServicePlugin to true through the commercial capability bit.
  244. const contactListExtensionID = TUIConstants.TUIContact.EXTENSION.CONTACT_LIST.EXT_ID;
  245. const tuiContactExtensionList = TUICore.getExtensionList(contactListExtensionID);
  246. const customerData = tuiContactExtensionList.find((extension: any) => {
  247. const { name, accountList = [] } = extension.data || {};
  248. return name === 'customer' && accountList.length > 0;
  249. });
  250. if (customerData) {
  251. const { data, text } = customerData;
  252. const { accountList } = (data || {}) as { accountList: string[] };
  253. TUIUserService.getUserProfile({ userIDList: accountList })
  254. .then((res) => {
  255. if (res.data.length > 0) {
  256. const customerList = {
  257. title: text,
  258. list: res.data.map((item: any, index: number) => {
  259. return {
  260. ...item,
  261. renderKey: generateRenderKey('customerList', item, index),
  262. infoKeyList: [],
  263. btnKeyList: ['enterC2CConversation'],
  264. };
  265. }),
  266. key: 'customerList',
  267. };
  268. contactListMap.value = { ...contactListMap.value, customerList };
  269. }
  270. })
  271. .catch(() => { });
  272. }
  273. }
  274. function onGroupListUpdated(groupList: IGroupModel[]) {
  275. updateContactListMap('groupList', groupList);
  276. }
  277. function onUserBlacklistUpdated(userBlacklist: IBlackListUserItem[]) {
  278. updateContactListMap('blackList', userBlacklist);
  279. }
  280. function onFriendApplicationUnreadCountUpdated(friendApplicationUnreadCount: number) {
  281. contactListMap.value.friendApplicationList.unreadCount = friendApplicationUnreadCount;
  282. }
  283. function onFriendListUpdated(friendList: Friend[]) {
  284. updateContactListMap('friendList', friendList);
  285. }
  286. function onFriendApplicationListUpdated(friendApplicationList: FriendApplication[]) {
  287. updateContactListMap('friendApplicationList', friendApplicationList);
  288. }
  289. function updateContactListMap(key: keyof IContactList, list: IContactInfoType[]) {
  290. contactListMap.value[key].list = list;
  291. contactListMap.value[key].list.map((item: IContactInfoType, index: number) => item.renderKey = generateRenderKey(key, item, index));
  292. updateCurrentContactInfoFromList(contactListMap.value[key].list, key);
  293. }
  294. function updateCurrentContactInfoFromList(list: IContactInfoType[], type: keyof IContactList) {
  295. if (
  296. !(currentContactInfo.value as Friend)?.userID
  297. && !(currentContactInfo.value as IGroupModel)?.groupID
  298. ) {
  299. return;
  300. }
  301. if (type === currentContactListKey.value || contactSearchingStatus.value) {
  302. currentContactInfo.value = list?.find(
  303. (item: any) =>
  304. (item?.groupID && item?.groupID === (currentContactInfo.value as IGroupModel)?.groupID) || (item?.userID && item?.userID === (currentContactInfo.value as Friend)?.userID),
  305. ) || {} as IContactInfoType;
  306. TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', currentContactInfo.value);
  307. }
  308. }
  309. function generateRenderKey(contactListMapKey: keyof IContactList, contactInfo: IContactInfoType, index: number) {
  310. return `${contactListMapKey}-${(contactInfo as Friend).userID || (contactInfo as IGroupModel).groupID || ('index' + index)}`;
  311. }
  312. function onCurrentContactSearchResultUpdated(searchResult: IContactSearchResult) {
  313. contactSearchResult.value = searchResult;
  314. }
  315. function onCurrentContactSearchingStatusUpdated(searchingStatus: boolean) {
  316. contactSearchingStatus.value = searchingStatus;
  317. TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', {} as IContactInfoType);
  318. TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', '');
  319. }
  320. function onCurrentContactInfoUpdated(contactInfo: IContactInfoType) {
  321. currentContactInfo.value = contactInfo;
  322. }
  323. function onCurrentContactListKeyUpdated(contactListKey: string) {
  324. currentContactListKey.value = contactListKey;
  325. }
  326. provide('userOnlineStatusMap', userOnlineStatusMap);
  327. </script>
  328. <style lang="scss" scoped src="./style/index.scss"></style>