123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- <template>
- <div ref="conversationListInnerDomRef" class="tui-conversation-list">
- <ActionsMenu
- v-if="isShowOverlay"
- :selectedConversation="currentConversation"
- :actionsMenuPosition="actionsMenuPosition"
- :selectedConversationDomRect="currentConversationDomRect"
- @closeConversationActionMenu="closeConversationActionMenu"
- />
- <div
- v-for="(conversation, index) in conversationList"
- :id="`convlistitem-${index}`"
- :key="index"
- :class="[
- 'tui-conversation-content',
- isMobile && 'tui-conversation-content-h5 disable-select',
- ]"
- >
- <div
- :class="[
- isPC && 'isPC',
- 'tui-conversation-item',
- currentConversationID === conversation.conversationID &&
- 'tui-conversation-item-selected',
- conversation.isPinned && 'tui-conversation-item-pinned',
- ]"
- @click="enterConversationChat(conversation.conversationID, conversation)"
- @longpress="showConversationActionMenu($event, conversation, index)"
- @contextmenu="showConversationActionMenu($event, conversation, index, true)"
- >
- <aside class="left">
- <Avatar useSkeletonAnimation :url="conversation.getAvatar()" size="60px" />
- <div
- v-if="userOnlineStatusMap && isShowUserOnlineStatus(conversation)"
- :class="[
- 'online-status',
- Object.keys(userOnlineStatusMap).length > 0 &&
- Object.keys(userOnlineStatusMap).includes(
- conversation.userProfile.userID
- ) &&
- userOnlineStatusMap[conversation.userProfile.userID].statusType === 1
- ? 'online-status-online'
- : 'online-status-offline',
- ]"
- />
- <span v-if="conversation.unreadCount > 0 && !conversation.isMuted" class="num">
- {{ conversation.unreadCount > 99 ? "99+" : conversation.unreadCount }}
- </span>
- <span
- v-if="conversation.unreadCount > 0 && conversation.isMuted"
- class="num-notify"
- />
- </aside>
- <div class="content">
- <div class="content-header">
- <label class="content-header-label">
- <p class="name">{{ conversation.getShowName() }}</p>
- </label>
- <div class="middle-box">
- <span
- v-if="
- conversation.draftText &&
- conversation.conversationID !== currentConversationID
- "
- class="middle-box-draft"
- >{{ TUITranslateService.t("TUIChat.[草稿]") }}</span
- >
- <span
- v-else-if="
- conversation.type === 'GROUP' &&
- conversation.groupAtInfoList &&
- conversation.groupAtInfoList.length > 0
- "
- class="middle-box-at"
- >{{ conversation.getGroupAtInfo() }}</span
- >
- <div class="middle-box-content">
- {{ conversation.getLastMessage("text") }}
- </div>
- </div>
- </div>
- <div class="content-footer">
- <span class="time">{{ conversation.getLastMessage("time") }}</span>
- <Icon v-if="conversation.isMuted" :file="muteIcon" />
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script lang="ts" setup>
- interface IUserStatus {
- statusType: number;
- customStatus: string;
- }
- interface IUserStatusMap {
- [userID: string]: IUserStatus;
- }
- import { ref, onMounted, onUnmounted } from "../../../adapter-vue";
- import TUIChatEngine, {
- TUIStore,
- StoreName,
- TUIConversationService,
- TUITranslateService,
- IConversationModel,
- } from "@tencentcloud/chat-uikit-engine";
- import { TUIGlobal, isIOS, addLongPressListener } from "@tencentcloud/universal-api";
- import Icon from "../../common/Icon.vue";
- import Avatar from "../../common/Avatar/index.vue";
- import ActionsMenu from "../actions-menu/index.vue";
- import muteIcon from "../../../assets/icon/mute.svg";
- import { isPC, isH5, isUniFrameWork, isMobile } from "../../../utils/env";
- const emits = defineEmits(["handleSwitchConversation", "getPassingRef"]);
- const currentConversation = ref<IConversationModel>();
- const currentConversationID = ref<string>();
- const currentConversationDomRect = ref<DOMRect>();
- const isShowOverlay = ref<boolean>(false);
- const conversationList = ref<IConversationModel[]>([]);
- const conversationListDomRef = ref<HTMLElement | undefined>();
- const conversationListInnerDomRef = ref<HTMLElement | undefined>();
- const actionsMenuPosition = ref<{
- top: number;
- left: number | undefined;
- conversationHeight: number | undefined;
- }>({
- top: 0,
- left: undefined,
- conversationHeight: undefined,
- });
- const displayOnlineStatus = ref(false);
- const userOnlineStatusMap = ref<IUserStatusMap>();
- let lastestOpenActionsMenuTime: number | null = null;
- onMounted(() => {
- TUIStore.watch(StoreName.CONV, {
- currentConversationID: onCurrentConversationIDUpdated,
- conversationList: onConversationListUpdated,
- currentConversation: onCurrentConversationUpdated,
- });
- TUIStore.watch(StoreName.USER, {
- displayOnlineStatus: onDisplayOnlineStatusUpdated,
- userStatusList: onUserStatusListUpdated,
- });
- if (!isUniFrameWork && isIOS && !isPC) {
- addLongPressHandler();
- }
- });
- onUnmounted(() => {
- TUIStore.unwatch(StoreName.CONV, {
- currentConversationID: onCurrentConversationIDUpdated,
- conversationList: onConversationListUpdated,
- currentConversation: onCurrentConversationUpdated,
- });
- TUIStore.unwatch(StoreName.USER, {
- displayOnlineStatus: onDisplayOnlineStatusUpdated,
- userStatusList: onUserStatusListUpdated,
- });
- });
- const isShowUserOnlineStatus = (conversation: IConversationModel): boolean => {
- return displayOnlineStatus.value && conversation.type === TUIChatEngine.TYPES.CONV_C2C;
- };
- const showConversationActionMenu = (
- event: Event,
- conversation: IConversationModel,
- index: number,
- isContextMenuEvent?: boolean
- ) => {
- if (isContextMenuEvent) {
- event.preventDefault();
- if (isUniFrameWork) {
- return;
- }
- }
- currentConversation.value = conversation;
- lastestOpenActionsMenuTime = Date.now();
- getActionsMenuPosition(event, index);
- };
- const closeConversationActionMenu = () => {
- // Prevent continuous triggering of overlay tap events
- if (lastestOpenActionsMenuTime && Date.now() - lastestOpenActionsMenuTime > 300) {
- currentConversation.value = undefined;
- isShowOverlay.value = false;
- }
- };
- const getActionsMenuPosition = (event: Event, index: number) => {
- if (isUniFrameWork) {
- if (typeof conversationListDomRef.value === "undefined") {
- emits("getPassingRef", conversationListDomRef);
- }
- const query = TUIGlobal?.createSelectorQuery().in(conversationListDomRef.value);
- query
- .select(`#convlistitem-${index}`)
- .boundingClientRect((data) => {
- if (data) {
- actionsMenuPosition.value = {
- // The uni-page-head of uni-h5 is not considered a member of the viewport, so the height of the head is manually increased.
- top: data.bottom + (isH5 ? 44 : 0),
- // @ts-expect-error in uniapp event has touches property
- left: event.touches[0].pageX,
- conversationHeight: data.height,
- };
- isShowOverlay.value = true;
- }
- })
- .exec();
- } else {
- const rect =
- ((event.currentTarget || event.target) as HTMLElement)?.getBoundingClientRect() ||
- {};
- if (rect) {
- actionsMenuPosition.value = {
- top: rect.bottom,
- left: isPC ? (event as MouseEvent).clientX : undefined,
- conversationHeight: rect.height,
- };
- }
- isShowOverlay.value = true;
- }
- };
- const enterConversationChat = (conversationID: string, a) => {
- emits("handleSwitchConversation", conversationID);
- TUIConversationService.switchConversation(conversationID);
- };
- function addLongPressHandler() {
- if (!conversationListInnerDomRef.value) {
- return;
- }
- addLongPressListener({
- element: conversationListInnerDomRef.value,
- onLongPress: (event, target) => {
- const index = (Array.from(
- conversationListInnerDomRef.value!.children
- ) as HTMLElement[]).indexOf(target!);
- showConversationActionMenu(event, conversationList.value[index], index);
- },
- options: {
- eventDelegation: {
- subSelector: ".tui-conversation-content",
- },
- },
- });
- }
- function onCurrentConversationUpdated(conversation: IConversationModel) {
- currentConversation.value = conversation;
- }
- function onConversationListUpdated(list: IConversationModel[]) {
- conversationList.value = list;
- console.log(conversationList.value);
- }
- function onCurrentConversationIDUpdated(id: string) {
- currentConversationID.value = id;
- }
- function onDisplayOnlineStatusUpdated(status: boolean) {
- displayOnlineStatus.value = status;
- }
- function onUserStatusListUpdated(list: Map<string, IUserStatus>) {
- if (list.size !== 0) {
- userOnlineStatusMap.value = [...list.entries()].reduce((obj, [key, value]) => {
- obj[key] = value;
- return obj;
- }, {} as IUserStatusMap);
- }
- }
- // Expose to the parent component and close actionsMenu when a sliding event is detected
- defineExpose({ closeChildren: closeConversationActionMenu });
- </script>
- <style lang="scss" scoped src="./style/index.scss"></style>
- <style lang="scss" scoped>
- .disable-select {
- -webkit-touch-callout: none;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- }
- </style>
|