index.vue 12 KB

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